blob: 5bf9c6579a4bca01b4a27b5a72680308605e556d [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%
anthony43c49252010-05-18 10:59:50 +0000165% "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%
anthony43c49252010-05-18 10:59:50 +0000172% If a '^' is included the kernel expanded with 90-degree rotations,
173% While a '@' will allow you to expand a 3x3 kernel using 45-degree
174% circular rotates.
175%
anthony29188a82010-01-22 10:12:34 +0000176% "num, num, num, num, ..."
177% list of floating point numbers defining an 'old style' odd sized
178% square kernel. At least 9 values should be provided for a 3x3
179% square kernel, 25 for a 5x5 square kernel, 49 for 7x7, etc.
180% Values can be space or comma separated. This is not recommended.
anthony602ab9b2010-01-05 08:06:50 +0000181%
anthony7a01dcf2010-05-11 12:25:52 +0000182% You can define a 'list of kernels' which can be used by some morphology
183% operators A list is defined as a semi-colon seperated list kernels.
184%
anthonydbc89892010-05-12 07:05:27 +0000185% " kernel ; kernel ; kernel ; "
anthony7a01dcf2010-05-11 12:25:52 +0000186%
anthony43c49252010-05-18 10:59:50 +0000187% Any extra ';' characters (at start, end or between kernel defintions are
188% simply ignored.
189%
190% Note that 'name' kernels will start with an alphabetic character while the
191% new kernel specification has a ':' character in its specification string.
192% If neither is the case, it is assumed an old style of a simple list of
193% numbers generating a odd-sized square kernel has been given.
anthony7a01dcf2010-05-11 12:25:52 +0000194%
anthony602ab9b2010-01-05 08:06:50 +0000195% The format of the AcquireKernal method is:
196%
cristy2be15382010-01-21 02:38:03 +0000197% KernelInfo *AcquireKernelInfo(const char *kernel_string)
anthony602ab9b2010-01-05 08:06:50 +0000198%
199% A description of each parameter follows:
200%
201% o kernel_string: the Morphology/Convolution kernel wanted.
202%
203*/
204
anthonyc84dce52010-05-07 05:42:23 +0000205/* This was separated so that it could be used as a separate
anthony5ef8e942010-05-11 06:51:12 +0000206** array input handling function, such as for -color-matrix
anthonyc84dce52010-05-07 05:42:23 +0000207*/
anthony5ef8e942010-05-11 06:51:12 +0000208static KernelInfo *ParseKernelArray(const char *kernel_string)
anthony602ab9b2010-01-05 08:06:50 +0000209{
cristy2be15382010-01-21 02:38:03 +0000210 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000211 *kernel;
212
213 char
214 token[MaxTextExtent];
215
anthony602ab9b2010-01-05 08:06:50 +0000216 const char
anthony5ef8e942010-05-11 06:51:12 +0000217 *p,
218 *end;
anthony602ab9b2010-01-05 08:06:50 +0000219
anthonyc84dce52010-05-07 05:42:23 +0000220 register long
221 i;
anthony602ab9b2010-01-05 08:06:50 +0000222
anthony29188a82010-01-22 10:12:34 +0000223 double
224 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
225
anthony43c49252010-05-18 10:59:50 +0000226 MagickStatusType
227 flags;
228
229 GeometryInfo
230 args;
231
cristy2be15382010-01-21 02:38:03 +0000232 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
233 if (kernel == (KernelInfo *)NULL)
anthony602ab9b2010-01-05 08:06:50 +0000234 return(kernel);
235 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
anthony43c49252010-05-18 10:59:50 +0000236 kernel->minimum = kernel->maximum = kernel->angle = 0.0;
anthony7a01dcf2010-05-11 12:25:52 +0000237 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000238 kernel->type = UserDefinedKernel;
anthony7a01dcf2010-05-11 12:25:52 +0000239 kernel->next = (KernelInfo *) NULL;
cristyd43a46b2010-01-21 02:13:41 +0000240 kernel->signature = MagickSignature;
anthony602ab9b2010-01-05 08:06:50 +0000241
anthony5ef8e942010-05-11 06:51:12 +0000242 /* find end of this specific kernel definition string */
243 end = strchr(kernel_string, ';');
244 if ( end == (char *) NULL )
245 end = strchr(kernel_string, '\0');
246
anthony43c49252010-05-18 10:59:50 +0000247 /* clear flags - for Expanding kernal lists thorugh rotations */
248 flags = NoValue;
249
anthony602ab9b2010-01-05 08:06:50 +0000250 /* Has a ':' in argument - New user kernel specification */
251 p = strchr(kernel_string, ':');
anthony5ef8e942010-05-11 06:51:12 +0000252 if ( p != (char *) NULL && p < end)
anthony602ab9b2010-01-05 08:06:50 +0000253 {
anthony602ab9b2010-01-05 08:06:50 +0000254 /* ParseGeometry() needs the geometry separated! -- Arrgghh */
cristy150989e2010-02-01 14:59:39 +0000255 memcpy(token, kernel_string, (size_t) (p-kernel_string));
anthony602ab9b2010-01-05 08:06:50 +0000256 token[p-kernel_string] = '\0';
anthonyc84dce52010-05-07 05:42:23 +0000257 SetGeometryInfo(&args);
anthony602ab9b2010-01-05 08:06:50 +0000258 flags = ParseGeometry(token, &args);
anthony602ab9b2010-01-05 08:06:50 +0000259
anthony29188a82010-01-22 10:12:34 +0000260 /* Size handling and checks of geometry settings */
anthony602ab9b2010-01-05 08:06:50 +0000261 if ( (flags & WidthValue) == 0 ) /* if no width then */
262 args.rho = args.sigma; /* then width = height */
263 if ( args.rho < 1.0 ) /* if width too small */
264 args.rho = 1.0; /* then width = 1 */
265 if ( args.sigma < 1.0 ) /* if height too small */
266 args.sigma = args.rho; /* then height = width */
267 kernel->width = (unsigned long)args.rho;
268 kernel->height = (unsigned long)args.sigma;
269
270 /* Offset Handling and Checks */
271 if ( args.xi < 0.0 || args.psi < 0.0 )
anthony83ba99b2010-01-24 08:48:15 +0000272 return(DestroyKernelInfo(kernel));
cristyc99304f2010-02-01 15:26:27 +0000273 kernel->x = ((flags & XValue)!=0) ? (long)args.xi
cristy150989e2010-02-01 14:59:39 +0000274 : (long) (kernel->width-1)/2;
cristyc99304f2010-02-01 15:26:27 +0000275 kernel->y = ((flags & YValue)!=0) ? (long)args.psi
cristy150989e2010-02-01 14:59:39 +0000276 : (long) (kernel->height-1)/2;
cristyc99304f2010-02-01 15:26:27 +0000277 if ( kernel->x >= (long) kernel->width ||
278 kernel->y >= (long) kernel->height )
anthony83ba99b2010-01-24 08:48:15 +0000279 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000280
281 p++; /* advance beyond the ':' */
282 }
283 else
anthonyc84dce52010-05-07 05:42:23 +0000284 { /* ELSE - Old old specification, forming odd-square kernel */
anthony602ab9b2010-01-05 08:06:50 +0000285 /* count up number of values given */
286 p=(const char *) kernel_string;
cristya699b172010-01-06 16:48:49 +0000287 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
anthony29188a82010-01-22 10:12:34 +0000288 p++; /* ignore "'" chars for convolve filter usage - Cristy */
anthony5ef8e942010-05-11 06:51:12 +0000289 for (i=0; p < end; i++)
anthony602ab9b2010-01-05 08:06:50 +0000290 {
291 GetMagickToken(p,&p,token);
292 if (*token == ',')
293 GetMagickToken(p,&p,token);
294 }
295 /* set the size of the kernel - old sized square */
296 kernel->width = kernel->height= (unsigned long) sqrt((double) i+1.0);
cristyc99304f2010-02-01 15:26:27 +0000297 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000298 p=(const char *) kernel_string;
anthony29188a82010-01-22 10:12:34 +0000299 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
300 p++; /* ignore "'" chars for convolve filter usage - Cristy */
anthony602ab9b2010-01-05 08:06:50 +0000301 }
302
303 /* Read in the kernel values from rest of input string argument */
304 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
305 kernel->height*sizeof(double));
306 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000307 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000308
cristyc99304f2010-02-01 15:26:27 +0000309 kernel->minimum = +MagickHuge;
310 kernel->maximum = -MagickHuge;
311 kernel->negative_range = kernel->positive_range = 0.0;
anthonyc84dce52010-05-07 05:42:23 +0000312
anthony5ef8e942010-05-11 06:51:12 +0000313 for (i=0; (i < (long) (kernel->width*kernel->height)) && (p < end); i++)
anthony602ab9b2010-01-05 08:06:50 +0000314 {
315 GetMagickToken(p,&p,token);
316 if (*token == ',')
317 GetMagickToken(p,&p,token);
anthony29188a82010-01-22 10:12:34 +0000318 if ( LocaleCompare("nan",token) == 0
anthonyc84dce52010-05-07 05:42:23 +0000319 || LocaleCompare("-",token) == 0 ) {
anthony29188a82010-01-22 10:12:34 +0000320 kernel->values[i] = nan; /* do not include this value in kernel */
321 }
322 else {
323 kernel->values[i] = StringToDouble(token);
324 ( kernel->values[i] < 0)
cristyc99304f2010-02-01 15:26:27 +0000325 ? ( kernel->negative_range += kernel->values[i] )
326 : ( kernel->positive_range += kernel->values[i] );
327 Minimize(kernel->minimum, kernel->values[i]);
328 Maximize(kernel->maximum, kernel->values[i]);
anthony29188a82010-01-22 10:12:34 +0000329 }
anthony602ab9b2010-01-05 08:06:50 +0000330 }
anthony29188a82010-01-22 10:12:34 +0000331
anthony5ef8e942010-05-11 06:51:12 +0000332 /* sanity check -- no more values in kernel definition */
333 GetMagickToken(p,&p,token);
334 if ( *token != '\0' && *token != ';' && *token != '\'' )
335 return(DestroyKernelInfo(kernel));
336
anthonyc84dce52010-05-07 05:42:23 +0000337#if 0
338 /* this was the old method of handling a incomplete kernel */
cristy150989e2010-02-01 14:59:39 +0000339 if ( i < (long) (kernel->width*kernel->height) ) {
cristyc99304f2010-02-01 15:26:27 +0000340 Minimize(kernel->minimum, kernel->values[i]);
341 Maximize(kernel->maximum, kernel->values[i]);
cristy150989e2010-02-01 14:59:39 +0000342 for ( ; i < (long) (kernel->width*kernel->height); i++)
anthony29188a82010-01-22 10:12:34 +0000343 kernel->values[i]=0.0;
344 }
anthonyc84dce52010-05-07 05:42:23 +0000345#else
346 /* Number of values for kernel was not enough - Report Error */
347 if ( i < (long) (kernel->width*kernel->height) )
348 return(DestroyKernelInfo(kernel));
349#endif
350
351 /* check that we recieved at least one real (non-nan) value! */
352 if ( kernel->minimum == MagickHuge )
353 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000354
anthony43c49252010-05-18 10:59:50 +0000355 if ( (flags & AreaValue) != 0 ) /* '@' symbol in kernel size */
356 ExpandKernelInfo(kernel, 45.0);
357 else if ( (flags & MinimumValue) != 0 ) /* '^' symbol in kernel size */
358 ExpandKernelInfo(kernel, 90.0);
359
anthony602ab9b2010-01-05 08:06:50 +0000360 return(kernel);
361}
anthonyc84dce52010-05-07 05:42:23 +0000362
anthony43c49252010-05-18 10:59:50 +0000363static KernelInfo *ParseKernelName(const char *kernel_string)
anthonyc84dce52010-05-07 05:42:23 +0000364{
365 char
366 token[MaxTextExtent];
367
anthony5ef8e942010-05-11 06:51:12 +0000368 long
369 type;
370
anthonyc84dce52010-05-07 05:42:23 +0000371 const char
anthony7a01dcf2010-05-11 12:25:52 +0000372 *p,
373 *end;
anthonyc84dce52010-05-07 05:42:23 +0000374
375 MagickStatusType
376 flags;
377
378 GeometryInfo
379 args;
380
anthonyc84dce52010-05-07 05:42:23 +0000381 /* Parse special 'named' kernel */
anthony5ef8e942010-05-11 06:51:12 +0000382 GetMagickToken(kernel_string,&p,token);
anthonyc84dce52010-05-07 05:42:23 +0000383 type=ParseMagickOption(MagickKernelOptions,MagickFalse,token);
384 if ( type < 0 || type == UserDefinedKernel )
anthony5ef8e942010-05-11 06:51:12 +0000385 return((KernelInfo *)NULL); /* not a valid named kernel */
anthonyc84dce52010-05-07 05:42:23 +0000386
387 while (((isspace((int) ((unsigned char) *p)) != 0) ||
anthony5ef8e942010-05-11 06:51:12 +0000388 (*p == ',') || (*p == ':' )) && (*p != '\0') && (*p != ';'))
anthonyc84dce52010-05-07 05:42:23 +0000389 p++;
anthony7a01dcf2010-05-11 12:25:52 +0000390
391 end = strchr(p, ';'); /* end of this kernel defintion */
392 if ( end == (char *) NULL )
393 end = strchr(p, '\0');
394
395 /* ParseGeometry() needs the geometry separated! -- Arrgghh */
396 memcpy(token, p, (size_t) (end-p));
397 token[end-p] = '\0';
anthonyc84dce52010-05-07 05:42:23 +0000398 SetGeometryInfo(&args);
anthony7a01dcf2010-05-11 12:25:52 +0000399 flags = ParseGeometry(token, &args);
anthonyc84dce52010-05-07 05:42:23 +0000400
anthony3c10fc82010-05-13 02:40:51 +0000401#if 0
402 /* For Debugging Geometry Input */
403 fprintf(stderr, "Geometry = %04x : %lf x %lf %+lf %+lf\n",
404 flags, args.rho, args.sigma, args.xi, args.psi );
405#endif
406
anthonyc84dce52010-05-07 05:42:23 +0000407 /* special handling of missing values in input string */
408 switch( type ) {
anthony5ef8e942010-05-11 06:51:12 +0000409 case RectangleKernel:
410 if ( (flags & WidthValue) == 0 ) /* if no width then */
411 args.rho = args.sigma; /* then width = height */
412 if ( args.rho < 1.0 ) /* if width too small */
413 args.rho = 3; /* then width = 3 */
414 if ( args.sigma < 1.0 ) /* if height too small */
415 args.sigma = args.rho; /* then height = width */
416 if ( (flags & XValue) == 0 ) /* center offset if not defined */
417 args.xi = (double)(((long)args.rho-1)/2);
418 if ( (flags & YValue) == 0 )
419 args.psi = (double)(((long)args.sigma-1)/2);
420 break;
421 case SquareKernel:
422 case DiamondKernel:
423 case DiskKernel:
424 case PlusKernel:
anthony3dd0f622010-05-13 12:57:32 +0000425 case CrossKernel:
anthony5ef8e942010-05-11 06:51:12 +0000426 /* If no scale given (a 0 scale is valid! - set it to 1.0 */
427 if ( (flags & HeightValue) == 0 )
428 args.sigma = 1.0;
429 break;
anthonyc1061722010-05-14 06:23:49 +0000430 case RingKernel:
431 if ( (flags & XValue) == 0 )
432 args.xi = 1.0;
433 break;
anthony5ef8e942010-05-11 06:51:12 +0000434 case ChebyshevKernel:
435 case ManhattenKernel:
436 case EuclideanKernel:
anthony43c49252010-05-18 10:59:50 +0000437 if ( (flags & HeightValue) == 0 ) /* no distance scale */
438 args.sigma = 100.0; /* default distance scaling */
439 else if ( (flags & AspectValue ) != 0 ) /* '!' flag */
440 args.sigma = QuantumRange/(args.sigma+1); /* maximum pixel distance */
441 else if ( (flags & PercentValue ) != 0 ) /* '%' flag */
442 args.sigma *= QuantumRange/100.0; /* percentage of color range */
anthony5ef8e942010-05-11 06:51:12 +0000443 break;
444 default:
445 break;
anthonyc84dce52010-05-07 05:42:23 +0000446 }
447
448 return(AcquireKernelBuiltIn((KernelInfoType)type, &args));
449}
450
anthony5ef8e942010-05-11 06:51:12 +0000451MagickExport KernelInfo *AcquireKernelInfo(const char *kernel_string)
452{
anthony7a01dcf2010-05-11 12:25:52 +0000453
454 KernelInfo
anthonydbc89892010-05-12 07:05:27 +0000455 *kernel,
anthony43c49252010-05-18 10:59:50 +0000456 *new_kernel;
anthony7a01dcf2010-05-11 12:25:52 +0000457
anthony5ef8e942010-05-11 06:51:12 +0000458 char
459 token[MaxTextExtent];
460
anthony7a01dcf2010-05-11 12:25:52 +0000461 const char
anthonydbc89892010-05-12 07:05:27 +0000462 *p;
anthony7a01dcf2010-05-11 12:25:52 +0000463
anthonye108a3f2010-05-12 07:24:03 +0000464 unsigned long
465 kernel_number;
466
anthonydbc89892010-05-12 07:05:27 +0000467 p = kernel_string;
anthony43c49252010-05-18 10:59:50 +0000468 kernel = NULL;
anthonye108a3f2010-05-12 07:24:03 +0000469 kernel_number = 0;
anthony5ef8e942010-05-11 06:51:12 +0000470
anthonydbc89892010-05-12 07:05:27 +0000471 while ( GetMagickToken(p,NULL,token), *token != '\0' ) {
anthony7a01dcf2010-05-11 12:25:52 +0000472
anthony43c49252010-05-18 10:59:50 +0000473 /* ignore extra or multiple ';' kernel seperators */
anthonydbc89892010-05-12 07:05:27 +0000474 if ( *token != ';' ) {
anthony7a01dcf2010-05-11 12:25:52 +0000475
anthonydbc89892010-05-12 07:05:27 +0000476 /* tokens starting with alpha is a Named kernel */
anthony43c49252010-05-18 10:59:50 +0000477 if (isalpha((int) *token) != 0)
478 new_kernel = ParseKernelName(p);
anthonydbc89892010-05-12 07:05:27 +0000479 else /* otherwise a user defined kernel array */
anthony43c49252010-05-18 10:59:50 +0000480 new_kernel = ParseKernelArray(p);
anthony7a01dcf2010-05-11 12:25:52 +0000481
anthonye108a3f2010-05-12 07:24:03 +0000482 /* Error handling -- this is not proper error handling! */
483 if ( new_kernel == (KernelInfo *) NULL ) {
484 fprintf(stderr, "Failed to parse kernel number #%lu\n", kernel_number);
485 if ( kernel != (KernelInfo *) NULL )
486 kernel=DestroyKernelInfo(kernel);
487 return((KernelInfo *) NULL);
anthonydbc89892010-05-12 07:05:27 +0000488 }
anthonye108a3f2010-05-12 07:24:03 +0000489
490 /* initialise or append the kernel list */
anthony3dd0f622010-05-13 12:57:32 +0000491 if ( kernel == (KernelInfo *) NULL )
492 kernel = new_kernel;
493 else
anthony43c49252010-05-18 10:59:50 +0000494 LastKernelInfo(kernel)->next = new_kernel;
anthonydbc89892010-05-12 07:05:27 +0000495 }
496
497 /* look for the next kernel in list */
498 p = strchr(p, ';');
499 if ( p == (char *) NULL )
500 break;
501 p++;
502
503 }
anthony7a01dcf2010-05-11 12:25:52 +0000504 return(kernel);
anthony5ef8e942010-05-11 06:51:12 +0000505}
506
anthony602ab9b2010-01-05 08:06:50 +0000507
508/*
509%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
510% %
511% %
512% %
513% A c q u i r e K e r n e l B u i l t I n %
514% %
515% %
516% %
517%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
518%
519% AcquireKernelBuiltIn() returned one of the 'named' built-in types of
520% kernels used for special purposes such as gaussian blurring, skeleton
521% pruning, and edge distance determination.
522%
523% They take a KernelType, and a set of geometry style arguments, which were
524% typically decoded from a user supplied string, or from a more complex
525% Morphology Method that was requested.
526%
527% The format of the AcquireKernalBuiltIn method is:
528%
cristy2be15382010-01-21 02:38:03 +0000529% KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000530% const GeometryInfo args)
531%
532% A description of each parameter follows:
533%
534% o type: the pre-defined type of kernel wanted
535%
536% o args: arguments defining or modifying the kernel
537%
538% Convolution Kernels
539%
anthony3c10fc82010-05-13 02:40:51 +0000540% Gaussian:{radius},{sigma}
541% Generate a two-dimentional gaussian kernel, as used by -gaussian.
anthonyc1061722010-05-14 06:23:49 +0000542% The sigma for the curve is required. The resulting kernel is
543% normalized,
544%
545% If 'sigma' is zero, you get a single pixel on a field of zeros.
anthony602ab9b2010-01-05 08:06:50 +0000546%
547% NOTE: that the 'radius' is optional, but if provided can limit (clip)
548% the final size of the resulting kernel to a square 2*radius+1 in size.
549% The radius should be at least 2 times that of the sigma value, or
550% sever clipping and aliasing may result. If not given or set to 0 the
551% radius will be determined so as to produce the best minimal error
552% result, which is usally much larger than is normally needed.
553%
anthonyc1061722010-05-14 06:23:49 +0000554% DOG:{radius},{sigma1},{sigma2}
555% "Difference of Gaussians" Kernel.
556% As "Gaussian" but with a gaussian produced by 'sigma2' subtracted
557% from the gaussian produced by 'sigma1'. Typically sigma2 > sigma1.
558% The result is a zero-summing kernel.
anthony602ab9b2010-01-05 08:06:50 +0000559%
anthony9eb4f742010-05-18 02:45:54 +0000560% LOG:{radius},{sigma}
561% "Laplacian of a Gaussian" or "Mexician Hat" Kernel.
562% The supposed ideal edge detection, zero-summing kernel.
563%
564% An alturnative to this kernel is to use a "DOG" with a sigma ratio of
565% approx 1.6, which can also be applied as a 2 pass "DOB" (see below).
566%
anthonyc1061722010-05-14 06:23:49 +0000567% Blur:{radius},{sigma}[,{angle}]
568% Generates a 1 dimensional or linear gaussian blur, at the angle given
569% (current restricted to orthogonal angles). If a 'radius' is given the
570% kernel is clipped to a width of 2*radius+1. Kernel can be rotated
571% by a 90 degree angle.
572%
573% If 'sigma' is zero, you get a single pixel on a field of zeros.
574%
575% Note that two convolutions with two "Blur" kernels perpendicular to
576% each other, is equivelent to a far larger "Gaussian" kernel with the
577% same sigma value, However it is much faster to apply. This is how the
578% "-blur" operator actually works.
579%
580% DOB:{radius},{sigma1},{sigma2}[,{angle}]
581% "Difference of Blurs" Kernel.
582% As "Blur" but with the 1D gaussian produced by 'sigma2' subtracted
583% from thethe 1D gaussian produced by 'sigma1'.
584% The result is a zero-summing kernel.
585%
586% This can be used to generate a faster "DOG" convolution, in the same
587% way "Blur" can.
anthony602ab9b2010-01-05 08:06:50 +0000588%
anthony3c10fc82010-05-13 02:40:51 +0000589% Comet:{width},{sigma},{angle}
590% Blur in one direction only, much like how a bright object leaves
anthony602ab9b2010-01-05 08:06:50 +0000591% a comet like trail. The Kernel is actually half a gaussian curve,
anthony3c10fc82010-05-13 02:40:51 +0000592% Adding two such blurs in opposite directions produces a Blur Kernel.
593% Angle can be rotated in multiples of 90 degrees.
anthony602ab9b2010-01-05 08:06:50 +0000594%
anthony3c10fc82010-05-13 02:40:51 +0000595% Note that the first argument is the width of the kernel and not the
anthony602ab9b2010-01-05 08:06:50 +0000596% radius of the kernel.
597%
598% # Still to be implemented...
599% #
anthony4fd27e22010-02-07 08:17:18 +0000600% # Filter2D
601% # Filter1D
602% # Set kernel values using a resize filter, and given scale (sigma)
603% # Cylindrical or Linear. Is this posible with an image?
604% #
anthony602ab9b2010-01-05 08:06:50 +0000605%
anthony3c10fc82010-05-13 02:40:51 +0000606% Named Constant Convolution Kernels
607%
anthonyc1061722010-05-14 06:23:49 +0000608% All these are unscaled, zero-summing kernels by default. As such for
609% non-HDRI version of ImageMagick some form of normalization, user scaling,
610% and biasing the results is recommended, to prevent the resulting image
611% being 'clipped'.
612%
613% The 3x3 kernels (most of these) can be circularly rotated in multiples of
614% 45 degrees to generate the 8 angled varients of each of the kernels.
anthony3c10fc82010-05-13 02:40:51 +0000615%
616% Laplacian:{type}
anthony43c49252010-05-18 10:59:50 +0000617% Discrete Lapacian Kernels, (without normalization)
anthonyc1061722010-05-14 06:23:49 +0000618% Type 0 : 3x3 with center:8 surounded by -1 (8 neighbourhood)
619% Type 1 : 3x3 with center:4 edge:-1 corner:0 (4 neighbourhood)
anthony9eb4f742010-05-18 02:45:54 +0000620% Type 2 : 3x3 with center:4 edge:1 corner:-2
621% Type 3 : 3x3 with center:4 edge:-2 corner:1
622% Type 5 : 5x5 laplacian
623% Type 7 : 7x7 laplacian
anthony43c49252010-05-18 10:59:50 +0000624% Type 15 : 5x5 LOG (sigma approx 1.4)
625% Type 19 : 9x9 LOG (sigma approx 1.4)
anthonyc1061722010-05-14 06:23:49 +0000626%
627% Sobel:{angle}
628% Sobel 3x3 'Edge' convolution kernel (3x3)
629% -1, 0, 1
630% -2, 0,-2
631% -1, 0, 1
632% Roberts:{angle}
633% Roberts 3x3 convolution kernel (3x3)
634% 0, 0, 0
635% -1, 1, 0
636% 0, 0, 0
anthonyc1061722010-05-14 06:23:49 +0000637% Prewitt:{angle}
638% Prewitt Edge convolution kernel (3x3)
639% -1, 0, 1
640% -1, 0, 1
641% -1, 0, 1
anthony9eb4f742010-05-18 02:45:54 +0000642% Compass:{angle}
643% Prewitt's "Compass" convolution kernel (3x3)
644% -1, 1, 1
645% -1,-2, 1
646% -1, 1, 1
647% Kirsch:{angle}
648% Kirsch's "Compass" convolution kernel (3x3)
649% -3,-3, 5
650% -3, 0, 5
651% -3,-3, 5
anthony3c10fc82010-05-13 02:40:51 +0000652%
anthony602ab9b2010-01-05 08:06:50 +0000653% Boolean Kernels
654%
anthony3c10fc82010-05-13 02:40:51 +0000655% Diamond:[{radius}[,{scale}]]
anthony1b2bc0a2010-05-12 05:25:22 +0000656% Generate a diamond shaped kernel with given radius to the points.
anthony602ab9b2010-01-05 08:06:50 +0000657% Kernel size will again be radius*2+1 square and defaults to radius 1,
658% generating a 3x3 kernel that is slightly larger than a square.
659%
anthony3c10fc82010-05-13 02:40:51 +0000660% Square:[{radius}[,{scale}]]
anthony602ab9b2010-01-05 08:06:50 +0000661% Generate a square shaped kernel of size radius*2+1, and defaulting
662% to a 3x3 (radius 1).
663%
anthonyc1061722010-05-14 06:23:49 +0000664% Note that using a larger radius for the "Square" or the "Diamond" is
665% also equivelent to iterating the basic morphological method that many
666% times. However iterating with the smaller radius is actually faster
667% than using a larger kernel radius.
668%
669% Rectangle:{geometry}
670% Simply generate a rectangle of 1's with the size given. You can also
671% specify the location of the 'control point', otherwise the closest
672% pixel to the center of the rectangle is selected.
673%
674% Properly centered and odd sized rectangles work the best.
anthony602ab9b2010-01-05 08:06:50 +0000675%
anthony3c10fc82010-05-13 02:40:51 +0000676% Disk:[{radius}[,{scale}]]
anthony602ab9b2010-01-05 08:06:50 +0000677% Generate a binary disk of the radius given, radius may be a float.
678% Kernel size will be ceil(radius)*2+1 square.
679% NOTE: Here are some disk shapes of specific interest
anthonyc1061722010-05-14 06:23:49 +0000680% "Disk:1" => "diamond" or "cross:1"
681% "Disk:1.5" => "square"
682% "Disk:2" => "diamond:2"
683% "Disk:2.5" => a general disk shape of radius 2
684% "Disk:2.9" => "square:2"
685% "Disk:3.5" => default - octagonal/disk shape of radius 3
686% "Disk:4.2" => roughly octagonal shape of radius 4
687% "Disk:4.3" => a general disk shape of radius 4
anthony602ab9b2010-01-05 08:06:50 +0000688% After this all the kernel shape becomes more and more circular.
689%
690% Because a "disk" is more circular when using a larger radius, using a
691% larger radius is preferred over iterating the morphological operation.
692%
anthonyc1061722010-05-14 06:23:49 +0000693% Symbol Dilation Kernels
694%
695% These kernel is not a good general morphological kernel, but is used
696% more for highlighting and marking any single pixels in an image using,
697% a "Dilate" method as appropriate.
698%
699% For the same reasons iterating these kernels does not produce the
700% same result as using a larger radius for the symbol.
701%
anthony3c10fc82010-05-13 02:40:51 +0000702% Plus:[{radius}[,{scale}]]
anthony3dd0f622010-05-13 12:57:32 +0000703% Cross:[{radius}[,{scale}]]
anthonyc1061722010-05-14 06:23:49 +0000704% Generate a kernel in the shape of a 'plus' or a 'cross' with
705% a each arm the length of the given radius (default 2).
anthony3dd0f622010-05-13 12:57:32 +0000706%
707% NOTE: "plus:1" is equivelent to a "Diamond" kernel.
anthony602ab9b2010-01-05 08:06:50 +0000708%
anthonyc1061722010-05-14 06:23:49 +0000709% Ring:{radius1},{radius2}[,{scale}]
710% A ring of the values given that falls between the two radii.
711% Defaults to a ring of approximataly 3 radius in a 7x7 kernel.
712% This is the 'edge' pixels of the default "Disk" kernel,
713% More specifically, "Ring" -> "Ring:2.5,3.5,1.0"
anthony602ab9b2010-01-05 08:06:50 +0000714%
anthony3dd0f622010-05-13 12:57:32 +0000715% Hit and Miss Kernels
716%
717% Peak:radius1,radius2
anthonyc1061722010-05-14 06:23:49 +0000718% Find any peak larger than the pixels the fall between the two radii.
719% The default ring of pixels is as per "Ring".
anthony43c49252010-05-18 10:59:50 +0000720% Edges
721% Find Edges of a binary shape
anthony3dd0f622010-05-13 12:57:32 +0000722% Corners
723% Find corners of a binary shape
724% LineEnds
725% Find end points of lines (for pruning a skeletion)
726% LineJunctions
anthony43c49252010-05-18 10:59:50 +0000727% Find three line junctions (within a skeletion)
anthony3dd0f622010-05-13 12:57:32 +0000728% ConvexHull
729% Octagonal thicken kernel, to generate convex hulls of 45 degrees
730% Skeleton
731% Thinning kernel, which leaves behind a skeletion of a shape
anthony602ab9b2010-01-05 08:06:50 +0000732%
733% Distance Measuring Kernels
734%
anthonyc1061722010-05-14 06:23:49 +0000735% Different types of distance measuring methods, which are used with the
736% a 'Distance' morphology method for generating a gradient based on
737% distance from an edge of a binary shape, though there is a technique
738% for handling a anti-aliased shape.
739%
740% See the 'Distance' Morphological Method, for information of how it is
741% applied.
742%
anthony3dd0f622010-05-13 12:57:32 +0000743% Chebyshev:[{radius}][x{scale}[%!]]
anthonyc94cdb02010-01-06 08:15:29 +0000744% Chebyshev Distance (also known as Tchebychev Distance) is a value of
745% one to any neighbour, orthogonal or diagonal. One why of thinking of
746% it is the number of squares a 'King' or 'Queen' in chess needs to
747% traverse reach any other position on a chess board. It results in a
748% 'square' like distance function, but one where diagonals are closer
749% than expected.
anthony602ab9b2010-01-05 08:06:50 +0000750%
anthonyc1061722010-05-14 06:23:49 +0000751% Manhatten:[{radius}][x{scale}[%!]]
anthonyc94cdb02010-01-06 08:15:29 +0000752% Manhatten Distance (also known as Rectilinear Distance, or the Taxi
753% Cab metric), is the distance needed when you can only travel in
754% orthogonal (horizontal or vertical) only. It is the distance a 'Rook'
755% in chess would travel. It results in a diamond like distances, where
756% diagonals are further than expected.
anthony602ab9b2010-01-05 08:06:50 +0000757%
anthonyc1061722010-05-14 06:23:49 +0000758% Euclidean:[{radius}][x{scale}[%!]]
anthonyc94cdb02010-01-06 08:15:29 +0000759% Euclidean Distance is the 'direct' or 'as the crow flys distance.
760% However by default the kernel size only has a radius of 1, which
761% limits the distance to 'Knight' like moves, with only orthogonal and
762% diagonal measurements being correct. As such for the default kernel
763% you will get octagonal like distance function, which is reasonally
764% accurate.
765%
766% However if you use a larger radius such as "Euclidean:4" you will
767% get a much smoother distance gradient from the edge of the shape.
768% Of course a larger kernel is slower to use, and generally not needed.
769%
770% To allow the use of fractional distances that you get with diagonals
771% the actual distance is scaled by a fixed value which the user can
772% provide. This is not actually nessary for either ""Chebyshev" or
773% "Manhatten" distance kernels, but is done for all three distance
774% kernels. If no scale is provided it is set to a value of 100,
775% allowing for a maximum distance measurement of 655 pixels using a Q16
776% version of IM, from any edge. However for small images this can
777% result in quite a dark gradient.
778%
anthony602ab9b2010-01-05 08:06:50 +0000779*/
780
cristy2be15382010-01-21 02:38:03 +0000781MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000782 const GeometryInfo *args)
783{
cristy2be15382010-01-21 02:38:03 +0000784 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000785 *kernel;
786
cristy150989e2010-02-01 14:59:39 +0000787 register long
anthony602ab9b2010-01-05 08:06:50 +0000788 i;
789
790 register long
791 u,
792 v;
793
794 double
795 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
796
anthonyc1061722010-05-14 06:23:49 +0000797 /* Generate a new empty kernel if needed */
cristye96405a2010-05-19 02:24:31 +0000798 kernel=(KernelInfo *) NULL;
anthonyc1061722010-05-14 06:23:49 +0000799 switch(type) {
anthony9eb4f742010-05-18 02:45:54 +0000800 case UndefinedKernel: /* These should not be used here */
801 case UserDefinedKernel:
802 break;
803 case LaplacianKernel: /* Named Descrete Convolution Kernels */
804 case SobelKernel:
805 case RobertsKernel:
806 case PrewittKernel:
807 case CompassKernel:
808 case KirschKernel:
809 case CornersKernel: /* Hit and Miss kernels */
810 case LineEndsKernel:
811 case LineJunctionsKernel:
anthony9eb4f742010-05-18 02:45:54 +0000812 case ThinningKernel:
813 case ConvexHullKernel:
814 case SkeletonKernel:
815 /* A pre-generated kernel is not needed */
816 break;
817#if 0
anthonyc1061722010-05-14 06:23:49 +0000818 case GaussianKernel:
819 case DOGKernel:
820 case BlurKernel:
821 case DOBKernel:
822 case CometKernel:
823 case DiamondKernel:
824 case SquareKernel:
825 case RectangleKernel:
826 case DiskKernel:
827 case PlusKernel:
828 case CrossKernel:
829 case RingKernel:
830 case PeaksKernel:
831 case ChebyshevKernel:
832 case ManhattenKernel:
833 case EuclideanKernel:
anthony9eb4f742010-05-18 02:45:54 +0000834#endif
835 default:
836 /* Generate the base Kernel Structure */
anthonyc1061722010-05-14 06:23:49 +0000837 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
838 if (kernel == (KernelInfo *) NULL)
839 return(kernel);
840 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
anthony43c49252010-05-18 10:59:50 +0000841 kernel->minimum = kernel->maximum = kernel->angle = 0.0;
anthonyc1061722010-05-14 06:23:49 +0000842 kernel->negative_range = kernel->positive_range = 0.0;
843 kernel->type = type;
844 kernel->next = (KernelInfo *) NULL;
845 kernel->signature = MagickSignature;
anthonyc1061722010-05-14 06:23:49 +0000846 break;
847 }
anthony602ab9b2010-01-05 08:06:50 +0000848
849 switch(type) {
850 /* Convolution Kernels */
851 case GaussianKernel:
anthonyc1061722010-05-14 06:23:49 +0000852 case DOGKernel:
anthony9eb4f742010-05-18 02:45:54 +0000853 case LOGKernel:
anthony602ab9b2010-01-05 08:06:50 +0000854 { double
anthonyc1061722010-05-14 06:23:49 +0000855 sigma = fabs(args->sigma),
856 sigma2 = fabs(args->xi),
anthony9eb4f742010-05-18 02:45:54 +0000857 A, B, R;
anthony602ab9b2010-01-05 08:06:50 +0000858
anthonyc1061722010-05-14 06:23:49 +0000859 if ( args->rho >= 1.0 )
860 kernel->width = (unsigned long)args->rho*2+1;
anthony9eb4f742010-05-18 02:45:54 +0000861 else if ( (type != DOGKernel) || (sigma >= sigma2) )
anthonyc1061722010-05-14 06:23:49 +0000862 kernel->width = GetOptimalKernelWidth2D(args->rho,sigma);
863 else
864 kernel->width = GetOptimalKernelWidth2D(args->rho,sigma2);
865 kernel->height = kernel->width;
cristyc99304f2010-02-01 15:26:27 +0000866 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000867 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
868 kernel->height*sizeof(double));
869 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000870 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000871
anthony9eb4f742010-05-18 02:45:54 +0000872 /* The following generates a 'sampled gaussian' kernel.
873 * What we really want is a 'discrete gaussian' kernel.
874 */
875
876 if ( type == GaussianKernel || type == DOGKernel )
877 { /* Calculate a Gaussian, OR positive half of a DOG */
878 if ( sigma > MagickEpsilon )
879 { A = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
880 B = 1.0/(Magick2PI*sigma*sigma);
881 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
882 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
883 kernel->values[i] = exp(-((double)(u*u+v*v))*A)*B;
884 }
885 else /* limiting case - a unity (normalized Dirac) kernel */
886 { (void) ResetMagickMemory(kernel->values,0, (size_t)
887 kernel->width*kernel->height*sizeof(double));
888 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
889 }
anthonyc1061722010-05-14 06:23:49 +0000890 }
anthony9eb4f742010-05-18 02:45:54 +0000891
anthonyc1061722010-05-14 06:23:49 +0000892 if ( type == DOGKernel )
893 { /* Subtract a Negative Gaussian for "Difference of Gaussian" */
894 if ( sigma2 > MagickEpsilon )
895 { sigma = sigma2; /* simplify loop expressions */
anthony9eb4f742010-05-18 02:45:54 +0000896 A = 1.0/(2.0*sigma*sigma);
897 B = 1.0/(Magick2PI*sigma*sigma);
anthonyc1061722010-05-14 06:23:49 +0000898 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
899 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony9eb4f742010-05-18 02:45:54 +0000900 kernel->values[i] -= exp(-((double)(u*u+v*v))*A)*B;
anthonyc1061722010-05-14 06:23:49 +0000901 }
anthony9eb4f742010-05-18 02:45:54 +0000902 else /* limiting case - a unity (normalized Dirac) kernel */
anthonyc1061722010-05-14 06:23:49 +0000903 kernel->values[kernel->x+kernel->y*kernel->width] -= 1.0;
904 }
anthony9eb4f742010-05-18 02:45:54 +0000905
906 if ( type == LOGKernel )
907 { /* Calculate a Laplacian of a Gaussian - Or Mexician Hat */
908 if ( sigma > MagickEpsilon )
909 { A = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
910 B = 1.0/(MagickPI*sigma*sigma*sigma*sigma);
911 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
912 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
913 { R = ((double)(u*u+v*v))*A;
914 kernel->values[i] = (1-R)*exp(-R)*B;
915 }
916 }
917 else /* special case - generate a unity kernel */
918 { (void) ResetMagickMemory(kernel->values,0, (size_t)
919 kernel->width*kernel->height*sizeof(double));
920 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
921 }
922 }
923
924 /* Note the above kernels may have been 'clipped' by a user defined
anthonyc1061722010-05-14 06:23:49 +0000925 ** radius, producing a smaller (darker) kernel. Also for very small
926 ** sigma's (> 0.1) the central value becomes larger than one, and thus
927 ** producing a very bright kernel.
928 **
929 ** Normalization will still be needed.
930 */
anthony602ab9b2010-01-05 08:06:50 +0000931
anthonyc1061722010-05-14 06:23:49 +0000932 /* Work out the meta-data about kernel */
933 kernel->minimum = kernel->maximum = 0.0;
934 kernel->negative_range = kernel->positive_range = 0.0;
935 u=(long) kernel->width*kernel->height;
936 for ( i=0; i < u; i++)
937 {
938 if ( fabs(kernel->values[i]) < MagickEpsilon )
939 kernel->values[i] = 0.0;
940 ( kernel->values[i] < 0)
941 ? ( kernel->negative_range += kernel->values[i] )
942 : ( kernel->positive_range += kernel->values[i] );
943 Minimize(kernel->minimum, kernel->values[i]);
944 Maximize(kernel->maximum, kernel->values[i]);
945 }
anthony3dd0f622010-05-13 12:57:32 +0000946 /* Normalize the 2D Gaussian Kernel
947 **
anthonyc1061722010-05-14 06:23:49 +0000948 ** NB: a CorrelateNormalize performs a normal Normalize if
949 ** there are no negative values.
anthony3dd0f622010-05-13 12:57:32 +0000950 */
anthonyc1061722010-05-14 06:23:49 +0000951 ScaleKernelInfo(kernel, 1.0, CorrelateNormalizeValue);
anthony602ab9b2010-01-05 08:06:50 +0000952
953 break;
954 }
955 case BlurKernel:
anthonyc1061722010-05-14 06:23:49 +0000956 case DOBKernel:
anthony602ab9b2010-01-05 08:06:50 +0000957 { double
anthonyc1061722010-05-14 06:23:49 +0000958 sigma = fabs(args->sigma),
959 sigma2 = fabs(args->xi),
anthony9eb4f742010-05-18 02:45:54 +0000960 A, B;
anthony602ab9b2010-01-05 08:06:50 +0000961
anthonyc1061722010-05-14 06:23:49 +0000962 if ( args->rho >= 1.0 )
963 kernel->width = (unsigned long)args->rho*2+1;
964 else if ( (type == BlurKernel) || (sigma >= sigma2) )
965 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
966 else
967 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma2);
anthony602ab9b2010-01-05 08:06:50 +0000968 kernel->height = 1;
anthonyc1061722010-05-14 06:23:49 +0000969 kernel->x = (long) (kernel->width-1)/2;
cristyc99304f2010-02-01 15:26:27 +0000970 kernel->y = 0;
971 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000972 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
973 kernel->height*sizeof(double));
974 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000975 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000976
977#if 1
978#define KernelRank 3
979 /* Formula derived from GetBlurKernel() in "effect.c" (plus bug fix).
980 ** It generates a gaussian 3 times the width, and compresses it into
981 ** the expected range. This produces a closer normalization of the
982 ** resulting kernel, especially for very low sigma values.
983 ** As such while wierd it is prefered.
984 **
985 ** I am told this method originally came from Photoshop.
anthony9eb4f742010-05-18 02:45:54 +0000986 **
987 ** A properly normalized curve is generated (apart from edge clipping)
988 ** even though we later normalize the result (for edge clipping)
989 ** to allow the correct generation of a "Difference of Blurs".
anthony602ab9b2010-01-05 08:06:50 +0000990 */
anthonyc1061722010-05-14 06:23:49 +0000991
992 /* initialize */
cristy150989e2010-02-01 14:59:39 +0000993 v = (long) (kernel->width*KernelRank-1)/2; /* start/end points to fit range */
anthony9eb4f742010-05-18 02:45:54 +0000994 (void) ResetMagickMemory(kernel->values,0, (size_t)
995 kernel->width*kernel->height*sizeof(double));
anthonyc1061722010-05-14 06:23:49 +0000996 /* Calculate a Positive 1D Gaussian */
997 if ( sigma > MagickEpsilon )
998 { sigma *= KernelRank; /* simplify loop expressions */
anthony9eb4f742010-05-18 02:45:54 +0000999 A = 1.0/(2.0*sigma*sigma);
1000 B = 1.0/(MagickSQ2PI*sigma );
anthonyc1061722010-05-14 06:23:49 +00001001 for ( u=-v; u <= v; u++) {
anthony9eb4f742010-05-18 02:45:54 +00001002 kernel->values[(u+v)/KernelRank] += exp(-((double)(u*u))*A)*B;
anthonyc1061722010-05-14 06:23:49 +00001003 }
1004 }
1005 else /* special case - generate a unity kernel */
1006 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
anthony9eb4f742010-05-18 02:45:54 +00001007
1008 /* Subtract a Second 1D Gaussian for "Difference of Blur" */
anthonyc1061722010-05-14 06:23:49 +00001009 if ( type == DOBKernel )
anthony9eb4f742010-05-18 02:45:54 +00001010 {
anthonyc1061722010-05-14 06:23:49 +00001011 if ( sigma2 > MagickEpsilon )
1012 { sigma = sigma2*KernelRank; /* simplify loop expressions */
anthony9eb4f742010-05-18 02:45:54 +00001013 A = 1.0/(2.0*sigma*sigma);
1014 B = 1.0/(MagickSQ2PI*sigma);
anthonyc1061722010-05-14 06:23:49 +00001015 for ( u=-v; u <= v; u++)
anthony9eb4f742010-05-18 02:45:54 +00001016 kernel->values[(u+v)/KernelRank] -= exp(-((double)(u*u))*A)*B;
anthonyc1061722010-05-14 06:23:49 +00001017 }
anthony9eb4f742010-05-18 02:45:54 +00001018 else /* limiting case - a unity (normalized Dirac) kernel */
anthonyc1061722010-05-14 06:23:49 +00001019 kernel->values[kernel->x+kernel->y*kernel->width] -= 1.0;
1020 }
anthony602ab9b2010-01-05 08:06:50 +00001021#else
anthonyc1061722010-05-14 06:23:49 +00001022 /* Direct calculation without curve averaging */
1023
1024 /* Calculate a Positive Gaussian */
1025 if ( sigma > MagickEpsilon )
anthony9eb4f742010-05-18 02:45:54 +00001026 { A = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
1027 B = 1.0/(MagickSQ2PI*sigma);
anthonyc1061722010-05-14 06:23:49 +00001028 for ( i=0, u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony9eb4f742010-05-18 02:45:54 +00001029 kernel->values[i] = exp(-((double)(u*u))*A)*B;
anthonyc1061722010-05-14 06:23:49 +00001030 }
1031 else /* special case - generate a unity kernel */
1032 { (void) ResetMagickMemory(kernel->values,0, (size_t)
1033 kernel->width*kernel->height*sizeof(double));
1034 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1035 }
anthony9eb4f742010-05-18 02:45:54 +00001036
1037 /* Subtract a Second 1D Gaussian for "Difference of Blur" */
anthonyc1061722010-05-14 06:23:49 +00001038 if ( type == DOBKernel )
anthony9eb4f742010-05-18 02:45:54 +00001039 {
anthonyc1061722010-05-14 06:23:49 +00001040 if ( sigma2 > MagickEpsilon )
1041 { sigma = sigma2; /* simplify loop expressions */
anthony9eb4f742010-05-18 02:45:54 +00001042 A = 1.0/(2.0*sigma*sigma);
1043 B = 1.0/(MagickSQ2PI*sigma);
anthonyc1061722010-05-14 06:23:49 +00001044 for ( i=0, u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony9eb4f742010-05-18 02:45:54 +00001045 kernel->values[i] -= exp(-((double)(u*u))*A)*B;
anthonyc1061722010-05-14 06:23:49 +00001046 }
anthony9eb4f742010-05-18 02:45:54 +00001047 else /* limiting case - a unity (normalized Dirac) kernel */
anthonyc1061722010-05-14 06:23:49 +00001048 kernel->values[kernel->x+kernel->y*kernel->width] -= 1.0;
1049 }
anthony602ab9b2010-01-05 08:06:50 +00001050#endif
anthonyc1061722010-05-14 06:23:49 +00001051 /* Note the above kernel may have been 'clipped' by a user defined
anthonycc6c8362010-01-25 04:14:01 +00001052 ** radius, producing a smaller (darker) kernel. Also for very small
1053 ** sigma's (> 0.1) the central value becomes larger than one, and thus
1054 ** producing a very bright kernel.
anthonyc1061722010-05-14 06:23:49 +00001055 **
1056 ** Normalization will still be needed.
anthony602ab9b2010-01-05 08:06:50 +00001057 */
anthonycc6c8362010-01-25 04:14:01 +00001058
anthonyc1061722010-05-14 06:23:49 +00001059 /* Work out the meta-data about kernel */
1060 for ( i=0; i < (long) kernel->width; i++)
1061 {
anthonyc1061722010-05-14 06:23:49 +00001062 ( kernel->values[i] < 0)
1063 ? ( kernel->negative_range += kernel->values[i] )
1064 : ( kernel->positive_range += kernel->values[i] );
1065 Minimize(kernel->minimum, kernel->values[i]);
1066 Maximize(kernel->maximum, kernel->values[i]);
1067 }
1068
anthony602ab9b2010-01-05 08:06:50 +00001069 /* Normalize the 1D Gaussian Kernel
1070 **
anthonyc1061722010-05-14 06:23:49 +00001071 ** NB: a CorrelateNormalize performs a normal Normalize if
1072 ** there are no negative values.
anthony602ab9b2010-01-05 08:06:50 +00001073 */
anthony9eb4f742010-05-18 02:45:54 +00001074 //ScaleKernelInfo(kernel, 1.0, CorrelateNormalizeValue);
anthonycc6c8362010-01-25 04:14:01 +00001075
anthonyc1061722010-05-14 06:23:49 +00001076 /* rotate the 1D kernel by given angle */
1077 RotateKernelInfo(kernel, (type == BlurKernel) ? args->xi : args->psi );
anthony602ab9b2010-01-05 08:06:50 +00001078 break;
1079 }
1080 case CometKernel:
1081 { double
anthony9eb4f742010-05-18 02:45:54 +00001082 sigma = fabs(args->sigma),
1083 A;
anthony602ab9b2010-01-05 08:06:50 +00001084
anthony602ab9b2010-01-05 08:06:50 +00001085 if ( args->rho < 1.0 )
1086 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
1087 else
1088 kernel->width = (unsigned long)args->rho;
cristyc99304f2010-02-01 15:26:27 +00001089 kernel->x = kernel->y = 0;
anthony602ab9b2010-01-05 08:06:50 +00001090 kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +00001091 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +00001092 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1093 kernel->height*sizeof(double));
1094 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001095 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001096
anthonyc1061722010-05-14 06:23:49 +00001097 /* A comet blur is half a 1D gaussian curve, so that the object is
anthony602ab9b2010-01-05 08:06:50 +00001098 ** blurred in one direction only. This may not be quite the right
anthony3dd0f622010-05-13 12:57:32 +00001099 ** curve to use so may change in the future. The function must be
1100 ** normalised after generation, which also resolves any clipping.
anthonyc1061722010-05-14 06:23:49 +00001101 **
1102 ** As we are normalizing and not subtracting gaussians,
1103 ** there is no need for a divisor in the gaussian formula
1104 **
anthony43c49252010-05-18 10:59:50 +00001105 ** It is less comples
anthony602ab9b2010-01-05 08:06:50 +00001106 */
anthony9eb4f742010-05-18 02:45:54 +00001107 if ( sigma > MagickEpsilon )
1108 {
anthony602ab9b2010-01-05 08:06:50 +00001109#if 1
1110#define KernelRank 3
anthony9eb4f742010-05-18 02:45:54 +00001111 v = (long) kernel->width*KernelRank; /* start/end points */
1112 (void) ResetMagickMemory(kernel->values,0, (size_t)
1113 kernel->width*sizeof(double));
1114 sigma *= KernelRank; /* simplify the loop expression */
1115 A = 1.0/(2.0*sigma*sigma);
1116 /* B = 1.0/(MagickSQ2PI*sigma); */
1117 for ( u=0; u < v; u++) {
1118 kernel->values[u/KernelRank] +=
1119 exp(-((double)(u*u))*A);
1120 /* exp(-((double)(i*i))/2.0*sigma*sigma)/(MagickSQ2PI*sigma); */
1121 }
1122 for (i=0; i < (long) kernel->width; i++)
1123 kernel->positive_range += kernel->values[i];
anthony602ab9b2010-01-05 08:06:50 +00001124#else
anthony9eb4f742010-05-18 02:45:54 +00001125 A = 1.0/(2.0*sigma*sigma); /* simplify the loop expression */
1126 /* B = 1.0/(MagickSQ2PI*sigma); */
1127 for ( i=0; i < (long) kernel->width; i++)
1128 kernel->positive_range +=
1129 kernel->values[i] =
1130 exp(-((double)(i*i))*A);
1131 /* exp(-((double)(i*i))/2.0*sigma*sigma)/(MagickSQ2PI*sigma); */
anthony602ab9b2010-01-05 08:06:50 +00001132#endif
anthony9eb4f742010-05-18 02:45:54 +00001133 }
1134 else /* special case - generate a unity kernel */
1135 { (void) ResetMagickMemory(kernel->values,0, (size_t)
1136 kernel->width*kernel->height*sizeof(double));
1137 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1138 kernel->positive_range = 1.0;
1139 }
cristyc99304f2010-02-01 15:26:27 +00001140 kernel->minimum = 0;
1141 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001142
anthony999bb2c2010-02-18 12:38:01 +00001143 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
1144 RotateKernelInfo(kernel, args->xi); /* Rotate by angle */
anthony602ab9b2010-01-05 08:06:50 +00001145 break;
1146 }
anthonyc1061722010-05-14 06:23:49 +00001147
anthony3c10fc82010-05-13 02:40:51 +00001148 /* Convolution Kernels - Well Known Constants */
anthony3c10fc82010-05-13 02:40:51 +00001149 case LaplacianKernel:
anthonyc1061722010-05-14 06:23:49 +00001150 {
anthony3c10fc82010-05-13 02:40:51 +00001151 switch ( (int) args->rho ) {
anthony3dd0f622010-05-13 12:57:32 +00001152 case 0:
anthony9eb4f742010-05-18 02:45:54 +00001153 default: /* laplacian square filter -- default */
anthonyc1061722010-05-14 06:23:49 +00001154 kernel=ParseKernelArray("3: -1,-1,-1 -1,8,-1 -1,-1,-1");
anthony3dd0f622010-05-13 12:57:32 +00001155 break;
anthony9eb4f742010-05-18 02:45:54 +00001156 case 1: /* laplacian diamond filter */
anthonyc1061722010-05-14 06:23:49 +00001157 kernel=ParseKernelArray("3: 0,-1,0 -1,4,-1 0,-1,0");
anthony3c10fc82010-05-13 02:40:51 +00001158 break;
1159 case 2:
anthony9eb4f742010-05-18 02:45:54 +00001160 kernel=ParseKernelArray("3: -2,1,-2 1,4,1 -2,1,-2");
1161 break;
1162 case 3:
anthonyc1061722010-05-14 06:23:49 +00001163 kernel=ParseKernelArray("3: 1,-2,1 -2,4,-2 1,-2,1");
anthony3c10fc82010-05-13 02:40:51 +00001164 break;
anthony9eb4f742010-05-18 02:45:54 +00001165 case 5: /* a 5x5 laplacian */
anthony3c10fc82010-05-13 02:40:51 +00001166 kernel=ParseKernelArray(
anthony9eb4f742010-05-18 02:45:54 +00001167 "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 +00001168 break;
anthony9eb4f742010-05-18 02:45:54 +00001169 case 7: /* a 7x7 laplacian */
anthony3c10fc82010-05-13 02:40:51 +00001170 kernel=ParseKernelArray(
anthonyc1061722010-05-14 06:23:49 +00001171 "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 +00001172 break;
anthony43c49252010-05-18 10:59:50 +00001173 case 15: /* a 5x5 LOG (sigma approx 1.4) */
anthony9eb4f742010-05-18 02:45:54 +00001174 kernel=ParseKernelArray(
1175 "5: 0,0,-1,0,0 0,-1,-2,-1,0 -1,-2,16,-2,-1 0,-1,-2,-1,0 0,0,-1,0,0");
1176 break;
anthony43c49252010-05-18 10:59:50 +00001177 case 19: /* a 9x9 LOG (sigma approx 1.4) */
1178 /* http://www.cscjournals.org/csc/manuscript/Journals/IJIP/volume3/Issue1/IJIP-15.pdf */
1179 kernel=ParseKernelArray(
1180 "9: 0,-1,-1,-2,-2,-2,-1,-1,0 -1,-2,-4,-5,-5,-5,-4,-2,-1 -1,-4,-5,-3,-0,-3,-5,-4,-1 -2,-5,-3,@12,@24,@12,-3,-5,-2 -2,-5,-0,@24,@40,@24,-0,-5,-2 -2,-5,-3,@12,@24,@12,-3,-5,-2 -1,-4,-5,-3,-0,-3,-5,-4,-1 -1,-2,-4,-5,-5,-5,-4,-2,-1 0,-1,-1,-2,-2,-2,-1,-1,0");
1181 break;
anthony3c10fc82010-05-13 02:40:51 +00001182 }
1183 if (kernel == (KernelInfo *) NULL)
1184 return(kernel);
1185 kernel->type = type;
1186 break;
1187 }
anthonyc1061722010-05-14 06:23:49 +00001188 case SobelKernel:
anthony602ab9b2010-01-05 08:06:50 +00001189 {
anthonyc1061722010-05-14 06:23:49 +00001190 kernel=ParseKernelArray("3: -1,0,1 -2,0,2 -1,0,1");
1191 if (kernel == (KernelInfo *) NULL)
1192 return(kernel);
1193 kernel->type = type;
1194 RotateKernelInfo(kernel, args->rho); /* Rotate by angle */
1195 break;
1196 }
1197 case RobertsKernel:
1198 {
1199 kernel=ParseKernelArray("3: 0,0,0 -1,1,0 0,0,0");
1200 if (kernel == (KernelInfo *) NULL)
1201 return(kernel);
1202 kernel->type = type;
1203 RotateKernelInfo(kernel, args->rho); /* Rotate by angle */
1204 break;
1205 }
1206 case PrewittKernel:
1207 {
1208 kernel=ParseKernelArray("3: -1,1,1 0,0,0 -1,1,1");
1209 if (kernel == (KernelInfo *) NULL)
1210 return(kernel);
1211 kernel->type = type;
1212 RotateKernelInfo(kernel, args->rho); /* Rotate by angle */
1213 break;
1214 }
1215 case CompassKernel:
1216 {
1217 kernel=ParseKernelArray("3: -1,1,1 -1,-2,1 -1,1,1");
1218 if (kernel == (KernelInfo *) NULL)
1219 return(kernel);
1220 kernel->type = type;
1221 RotateKernelInfo(kernel, args->rho); /* Rotate by angle */
1222 break;
1223 }
anthony9eb4f742010-05-18 02:45:54 +00001224 case KirschKernel:
1225 {
1226 kernel=ParseKernelArray("3: -3,-3,5 -3,0,5 -3,-3,5");
1227 if (kernel == (KernelInfo *) NULL)
1228 return(kernel);
1229 kernel->type = type;
1230 RotateKernelInfo(kernel, args->rho); /* Rotate by angle */
1231 break;
1232 }
anthonyc1061722010-05-14 06:23:49 +00001233 /* Boolean Kernels */
1234 case DiamondKernel:
1235 {
1236 if (args->rho < 1.0)
1237 kernel->width = kernel->height = 3; /* default radius = 1 */
1238 else
1239 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
1240 kernel->x = kernel->y = (long) (kernel->width-1)/2;
1241
1242 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1243 kernel->height*sizeof(double));
1244 if (kernel->values == (double *) NULL)
1245 return(DestroyKernelInfo(kernel));
1246
1247 /* set all kernel values within diamond area to scale given */
1248 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1249 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1250 if ((labs(u)+labs(v)) <= (long)kernel->x)
1251 kernel->positive_range += kernel->values[i] = args->sigma;
1252 else
1253 kernel->values[i] = nan;
1254 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1255 break;
1256 }
1257 case SquareKernel:
1258 case RectangleKernel:
1259 { double
1260 scale;
anthony602ab9b2010-01-05 08:06:50 +00001261 if ( type == SquareKernel )
1262 {
1263 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001264 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001265 else
cristy150989e2010-02-01 14:59:39 +00001266 kernel->width = kernel->height = (unsigned long) (2*args->rho+1);
cristyc99304f2010-02-01 15:26:27 +00001267 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony4fd27e22010-02-07 08:17:18 +00001268 scale = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +00001269 }
1270 else {
cristy2be15382010-01-21 02:38:03 +00001271 /* NOTE: user defaults set in "AcquireKernelInfo()" */
anthony602ab9b2010-01-05 08:06:50 +00001272 if ( args->rho < 1.0 || args->sigma < 1.0 )
anthony83ba99b2010-01-24 08:48:15 +00001273 return(DestroyKernelInfo(kernel)); /* invalid args given */
anthony602ab9b2010-01-05 08:06:50 +00001274 kernel->width = (unsigned long)args->rho;
1275 kernel->height = (unsigned long)args->sigma;
1276 if ( args->xi < 0.0 || args->xi > (double)kernel->width ||
1277 args->psi < 0.0 || args->psi > (double)kernel->height )
anthony83ba99b2010-01-24 08:48:15 +00001278 return(DestroyKernelInfo(kernel)); /* invalid args given */
cristyc99304f2010-02-01 15:26:27 +00001279 kernel->x = (long) args->xi;
1280 kernel->y = (long) args->psi;
anthony4fd27e22010-02-07 08:17:18 +00001281 scale = 1.0;
anthony602ab9b2010-01-05 08:06:50 +00001282 }
1283 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1284 kernel->height*sizeof(double));
1285 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001286 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001287
anthony3dd0f622010-05-13 12:57:32 +00001288 /* set all kernel values to scale given */
cristy150989e2010-02-01 14:59:39 +00001289 u=(long) kernel->width*kernel->height;
1290 for ( i=0; i < u; i++)
anthony4fd27e22010-02-07 08:17:18 +00001291 kernel->values[i] = scale;
1292 kernel->minimum = kernel->maximum = scale; /* a flat shape */
1293 kernel->positive_range = scale*u;
anthonycc6c8362010-01-25 04:14:01 +00001294 break;
anthony602ab9b2010-01-05 08:06:50 +00001295 }
anthony602ab9b2010-01-05 08:06:50 +00001296 case DiskKernel:
1297 {
anthonyc1061722010-05-14 06:23:49 +00001298 long limit = (long)(args->rho*args->rho);
anthony83ba99b2010-01-24 08:48:15 +00001299 if (args->rho < 0.1) /* default radius approx 3.5 */
1300 kernel->width = kernel->height = 7L, limit = 10L;
anthony602ab9b2010-01-05 08:06:50 +00001301 else
1302 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001303 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001304
1305 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1306 kernel->height*sizeof(double));
1307 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001308 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001309
anthony3dd0f622010-05-13 12:57:32 +00001310 /* set all kernel values within disk area to scale given */
1311 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
cristyc99304f2010-02-01 15:26:27 +00001312 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony602ab9b2010-01-05 08:06:50 +00001313 if ((u*u+v*v) <= limit)
anthony4fd27e22010-02-07 08:17:18 +00001314 kernel->positive_range += kernel->values[i] = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +00001315 else
1316 kernel->values[i] = nan;
anthony4fd27e22010-02-07 08:17:18 +00001317 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
anthony602ab9b2010-01-05 08:06:50 +00001318 break;
1319 }
1320 case PlusKernel:
1321 {
1322 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001323 kernel->width = kernel->height = 5; /* default radius 2 */
anthony602ab9b2010-01-05 08:06:50 +00001324 else
1325 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001326 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001327
1328 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1329 kernel->height*sizeof(double));
1330 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001331 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001332
anthony3dd0f622010-05-13 12:57:32 +00001333 /* set all kernel values along axises to given scale */
cristyc99304f2010-02-01 15:26:27 +00001334 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1335 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony4fd27e22010-02-07 08:17:18 +00001336 kernel->values[i] = (u == 0 || v == 0) ? args->sigma : nan;
1337 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1338 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
anthony602ab9b2010-01-05 08:06:50 +00001339 break;
1340 }
anthony3dd0f622010-05-13 12:57:32 +00001341 case CrossKernel:
1342 {
1343 if (args->rho < 1.0)
1344 kernel->width = kernel->height = 5; /* default radius 2 */
1345 else
1346 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
1347 kernel->x = kernel->y = (long) (kernel->width-1)/2;
1348
1349 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1350 kernel->height*sizeof(double));
1351 if (kernel->values == (double *) NULL)
1352 return(DestroyKernelInfo(kernel));
1353
1354 /* set all kernel values along axises to given scale */
1355 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1356 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1357 kernel->values[i] = (u == v || u == -v) ? args->sigma : nan;
1358 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1359 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
1360 break;
1361 }
1362 /* HitAndMiss Kernels */
anthonyc1061722010-05-14 06:23:49 +00001363 case RingKernel:
anthony3dd0f622010-05-13 12:57:32 +00001364 case PeaksKernel:
1365 {
1366 long
1367 limit1,
anthonyc1061722010-05-14 06:23:49 +00001368 limit2,
1369 scale;
anthony3dd0f622010-05-13 12:57:32 +00001370
1371 if (args->rho < args->sigma)
1372 {
1373 kernel->width = ((unsigned long)args->sigma)*2+1;
1374 limit1 = (long)args->rho*args->rho;
1375 limit2 = (long)args->sigma*args->sigma;
1376 }
1377 else
1378 {
1379 kernel->width = ((unsigned long)args->rho)*2+1;
1380 limit1 = (long)args->sigma*args->sigma;
1381 limit2 = (long)args->rho*args->rho;
1382 }
anthonyc1061722010-05-14 06:23:49 +00001383 if ( limit2 <= 0 )
1384 kernel->width = 7L, limit1 = 7L, limit2 = 11L;
1385
anthony3dd0f622010-05-13 12:57:32 +00001386 kernel->height = kernel->width;
1387 kernel->x = kernel->y = (long) (kernel->width-1)/2;
1388 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1389 kernel->height*sizeof(double));
1390 if (kernel->values == (double *) NULL)
1391 return(DestroyKernelInfo(kernel));
1392
anthonyc1061722010-05-14 06:23:49 +00001393 /* set a ring of points of 'scale' ( 0.0 for PeaksKernel ) */
cristye96405a2010-05-19 02:24:31 +00001394 scale = (long) (( type == PeaksKernel) ? 0.0 : args->xi);
anthony3dd0f622010-05-13 12:57:32 +00001395 for ( i=0, v= -kernel->y; v <= (long)kernel->y; v++)
1396 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1397 { long radius=u*u+v*v;
anthonyc1061722010-05-14 06:23:49 +00001398 if (limit1 < radius && radius <= limit2)
cristye96405a2010-05-19 02:24:31 +00001399 kernel->positive_range += kernel->values[i] = (double) scale;
anthony3dd0f622010-05-13 12:57:32 +00001400 else
1401 kernel->values[i] = nan;
1402 }
cristye96405a2010-05-19 02:24:31 +00001403 kernel->minimum = kernel->minimum = (double) scale;
anthonyc1061722010-05-14 06:23:49 +00001404 if ( type == PeaksKernel ) {
1405 /* set the central point in the middle */
1406 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1407 kernel->positive_range = 1.0;
1408 kernel->maximum = 1.0;
1409 }
anthony3dd0f622010-05-13 12:57:32 +00001410 break;
1411 }
anthony43c49252010-05-18 10:59:50 +00001412 case EdgesKernel:
1413 {
1414 kernel=ParseKernelArray("3: 0,0,0 -,1,- 1,1,1");
1415 if (kernel == (KernelInfo *) NULL)
1416 return(kernel);
1417 kernel->type = type;
1418 ExpandKernelInfo(kernel, 90.0); /* Create a list of 4 rotated kernels */
1419 break;
1420 }
anthony3dd0f622010-05-13 12:57:32 +00001421 case CornersKernel:
1422 {
anthony4f1dcb72010-05-14 08:43:10 +00001423 kernel=ParseKernelArray("3: 0,0,- 0,1,1 -,1,-");
anthony3dd0f622010-05-13 12:57:32 +00001424 if (kernel == (KernelInfo *) NULL)
1425 return(kernel);
1426 kernel->type = type;
1427 ExpandKernelInfo(kernel, 90.0); /* Create a list of 4 rotated kernels */
1428 break;
1429 }
1430 case LineEndsKernel:
1431 {
anthony43c49252010-05-18 10:59:50 +00001432 KernelInfo
1433 *new_kernel;
1434 kernel=ParseKernelArray("3: 0,0,0 0,1,0 -,1,-");
anthony3dd0f622010-05-13 12:57:32 +00001435 if (kernel == (KernelInfo *) NULL)
1436 return(kernel);
1437 kernel->type = type;
anthony43c49252010-05-18 10:59:50 +00001438 ExpandKernelInfo(kernel, 90.0);
1439 /* append second set of 4 kernels */
1440 new_kernel=ParseKernelArray("3: 0,0,0 0,1,0 0,0,1");
1441 if (new_kernel == (KernelInfo *) NULL)
1442 return(DestroyKernelInfo(kernel));
1443 new_kernel->type = type;
1444 ExpandKernelInfo(new_kernel, 90.0);
1445 LastKernelInfo(kernel)->next = new_kernel;
anthony3dd0f622010-05-13 12:57:32 +00001446 break;
1447 }
1448 case LineJunctionsKernel:
1449 {
1450 KernelInfo
1451 *new_kernel;
anthony3dd0f622010-05-13 12:57:32 +00001452 /* first set of 4 kernels */
anthony4f1dcb72010-05-14 08:43:10 +00001453 kernel=ParseKernelArray("3: -,1,- -,1,- 1,-,1");
anthony3dd0f622010-05-13 12:57:32 +00001454 if (kernel == (KernelInfo *) NULL)
1455 return(kernel);
1456 kernel->type = type;
anthony43c49252010-05-18 10:59:50 +00001457 ExpandKernelInfo(kernel, 45.0);
anthony3dd0f622010-05-13 12:57:32 +00001458 /* append second set of 4 kernels */
anthony4f1dcb72010-05-14 08:43:10 +00001459 new_kernel=ParseKernelArray("3: 1,-,- -,1,- 1,-,1");
anthony3dd0f622010-05-13 12:57:32 +00001460 if (new_kernel == (KernelInfo *) NULL)
1461 return(DestroyKernelInfo(kernel));
anthony43c49252010-05-18 10:59:50 +00001462 new_kernel->type = type;
anthony3dd0f622010-05-13 12:57:32 +00001463 ExpandKernelInfo(new_kernel, 90.0);
1464 LastKernelInfo(kernel)->next = new_kernel;
anthony4f1dcb72010-05-14 08:43:10 +00001465 break;
1466 }
anthony3dd0f622010-05-13 12:57:32 +00001467 case ConvexHullKernel:
1468 {
1469 KernelInfo
1470 *new_kernel;
anthony3dd0f622010-05-13 12:57:32 +00001471 /* first set of 4 kernels */
anthony4f1dcb72010-05-14 08:43:10 +00001472 kernel=ParseKernelArray("3: 1,1,- 1,0,- 1,-,0");
anthony3dd0f622010-05-13 12:57:32 +00001473 if (kernel == (KernelInfo *) NULL)
1474 return(kernel);
1475 kernel->type = type;
1476 ExpandKernelInfo(kernel, 90.0);
1477 /* append second set of 4 kernels */
anthony43c49252010-05-18 10:59:50 +00001478 new_kernel=ParseKernelArray("3: 1,1,1 1,0,0 -,-,0");
anthony3dd0f622010-05-13 12:57:32 +00001479 if (new_kernel == (KernelInfo *) NULL)
1480 return(DestroyKernelInfo(kernel));
anthony43c49252010-05-18 10:59:50 +00001481 new_kernel->type = type;
anthony3dd0f622010-05-13 12:57:32 +00001482 ExpandKernelInfo(new_kernel, 90.0);
1483 LastKernelInfo(kernel)->next = new_kernel;
1484 break;
1485 }
anthony43c49252010-05-18 10:59:50 +00001486 case ThinningKernel:
1487 { /* Thinning Kernel ?? -- filled corner and edge */
1488 kernel=ParseKernelArray("3: 0,0,- 0,1,1 -,1,1");
anthony3dd0f622010-05-13 12:57:32 +00001489 if (kernel == (KernelInfo *) NULL)
1490 return(kernel);
1491 kernel->type = type;
anthony43c49252010-05-18 10:59:50 +00001492 ExpandKernelInfo(kernel, 45);
1493 break;
1494 }
1495 case SkeletonKernel:
1496 {
1497 kernel=AcquireKernelInfo("Edges;Corners");
anthony3dd0f622010-05-13 12:57:32 +00001498 break;
1499 }
anthony602ab9b2010-01-05 08:06:50 +00001500 /* Distance Measuring Kernels */
1501 case ChebyshevKernel:
1502 {
anthony602ab9b2010-01-05 08:06:50 +00001503 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001504 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001505 else
1506 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001507 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001508
1509 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1510 kernel->height*sizeof(double));
1511 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001512 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001513
cristyc99304f2010-02-01 15:26:27 +00001514 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1515 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1516 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001517 args->sigma*((labs(u)>labs(v)) ? labs(u) : labs(v)) );
cristyc99304f2010-02-01 15:26:27 +00001518 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001519 break;
1520 }
1521 case ManhattenKernel:
1522 {
anthony602ab9b2010-01-05 08:06:50 +00001523 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001524 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001525 else
1526 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001527 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001528
1529 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1530 kernel->height*sizeof(double));
1531 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001532 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001533
cristyc99304f2010-02-01 15:26:27 +00001534 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1535 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1536 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001537 args->sigma*(labs(u)+labs(v)) );
cristyc99304f2010-02-01 15:26:27 +00001538 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001539 break;
1540 }
1541 case EuclideanKernel:
1542 {
anthony602ab9b2010-01-05 08:06:50 +00001543 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001544 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001545 else
1546 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001547 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001548
1549 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1550 kernel->height*sizeof(double));
1551 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001552 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001553
cristyc99304f2010-02-01 15:26:27 +00001554 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1555 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1556 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001557 args->sigma*sqrt((double)(u*u+v*v)) );
cristyc99304f2010-02-01 15:26:27 +00001558 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001559 break;
1560 }
anthony602ab9b2010-01-05 08:06:50 +00001561 default:
anthonyc1061722010-05-14 06:23:49 +00001562 {
1563 /* Generate a No-Op minimal kernel - 1x1 pixel */
1564 kernel=ParseKernelArray("1");
1565 if (kernel == (KernelInfo *) NULL)
1566 return(kernel);
1567 kernel->type = UndefinedKernel;
1568 break;
1569 }
anthony602ab9b2010-01-05 08:06:50 +00001570 break;
1571 }
1572
1573 return(kernel);
1574}
anthonyc94cdb02010-01-06 08:15:29 +00001575
anthony602ab9b2010-01-05 08:06:50 +00001576/*
1577%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1578% %
1579% %
1580% %
cristy6771f1e2010-03-05 19:43:39 +00001581% C l o n e K e r n e l I n f o %
anthony4fd27e22010-02-07 08:17:18 +00001582% %
1583% %
1584% %
1585%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1586%
anthony1b2bc0a2010-05-12 05:25:22 +00001587% CloneKernelInfo() creates a new clone of the given Kernel List so that its
1588% can be modified without effecting the original. The cloned kernel should
1589% be destroyed using DestoryKernelInfo() when no longer needed.
anthony7a01dcf2010-05-11 12:25:52 +00001590%
cristye6365592010-04-02 17:31:23 +00001591% The format of the CloneKernelInfo method is:
anthony4fd27e22010-02-07 08:17:18 +00001592%
anthony930be612010-02-08 04:26:15 +00001593% KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +00001594%
1595% A description of each parameter follows:
1596%
1597% o kernel: the Morphology/Convolution kernel to be cloned
1598%
1599*/
cristyef656912010-03-05 19:54:59 +00001600MagickExport KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +00001601{
1602 register long
1603 i;
1604
cristy19eb6412010-04-23 14:42:29 +00001605 KernelInfo
anthony7a01dcf2010-05-11 12:25:52 +00001606 *new_kernel;
anthony4fd27e22010-02-07 08:17:18 +00001607
1608 assert(kernel != (KernelInfo *) NULL);
anthony7a01dcf2010-05-11 12:25:52 +00001609 new_kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
1610 if (new_kernel == (KernelInfo *) NULL)
1611 return(new_kernel);
1612 *new_kernel=(*kernel); /* copy values in structure */
anthony7a01dcf2010-05-11 12:25:52 +00001613
1614 /* replace the values with a copy of the values */
1615 new_kernel->values=(double *) AcquireQuantumMemory(kernel->width,
cristy19eb6412010-04-23 14:42:29 +00001616 kernel->height*sizeof(double));
anthony7a01dcf2010-05-11 12:25:52 +00001617 if (new_kernel->values == (double *) NULL)
1618 return(DestroyKernelInfo(new_kernel));
anthony4fd27e22010-02-07 08:17:18 +00001619 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthony7a01dcf2010-05-11 12:25:52 +00001620 new_kernel->values[i]=kernel->values[i];
anthony1b2bc0a2010-05-12 05:25:22 +00001621
1622 /* Also clone the next kernel in the kernel list */
1623 if ( kernel->next != (KernelInfo *) NULL ) {
1624 new_kernel->next = CloneKernelInfo(kernel->next);
1625 if ( new_kernel->next == (KernelInfo *) NULL )
1626 return(DestroyKernelInfo(new_kernel));
1627 }
1628
anthony7a01dcf2010-05-11 12:25:52 +00001629 return(new_kernel);
anthony4fd27e22010-02-07 08:17:18 +00001630}
1631
1632/*
1633%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1634% %
1635% %
1636% %
anthony83ba99b2010-01-24 08:48:15 +00001637% D e s t r o y K e r n e l I n f o %
anthony602ab9b2010-01-05 08:06:50 +00001638% %
1639% %
1640% %
1641%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1642%
anthony83ba99b2010-01-24 08:48:15 +00001643% DestroyKernelInfo() frees the memory used by a Convolution/Morphology
1644% kernel.
anthony602ab9b2010-01-05 08:06:50 +00001645%
anthony83ba99b2010-01-24 08:48:15 +00001646% The format of the DestroyKernelInfo method is:
anthony602ab9b2010-01-05 08:06:50 +00001647%
anthony83ba99b2010-01-24 08:48:15 +00001648% KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +00001649%
1650% A description of each parameter follows:
1651%
1652% o kernel: the Morphology/Convolution kernel to be destroyed
1653%
1654*/
1655
anthony83ba99b2010-01-24 08:48:15 +00001656MagickExport KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +00001657{
cristy2be15382010-01-21 02:38:03 +00001658 assert(kernel != (KernelInfo *) NULL);
anthony4fd27e22010-02-07 08:17:18 +00001659
anthony7a01dcf2010-05-11 12:25:52 +00001660 if ( kernel->next != (KernelInfo *) NULL )
1661 kernel->next = DestroyKernelInfo(kernel->next);
1662
1663 kernel->values = (double *)RelinquishMagickMemory(kernel->values);
1664 kernel = (KernelInfo *) RelinquishMagickMemory(kernel);
anthony602ab9b2010-01-05 08:06:50 +00001665 return(kernel);
1666}
anthonyc94cdb02010-01-06 08:15:29 +00001667
1668/*
1669%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1670% %
1671% %
1672% %
anthony3c10fc82010-05-13 02:40:51 +00001673% E x p a n d K e r n e l I n f o %
1674% %
1675% %
1676% %
1677%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1678%
1679% ExpandKernelInfo() takes a single kernel, and expands it into a list
1680% of kernels each incrementally rotated the angle given.
1681%
1682% WARNING: 45 degree rotations only works for 3x3 kernels.
1683% While 90 degree roatations only works for linear and square kernels
1684%
1685% The format of the RotateKernelInfo method is:
1686%
1687% void ExpandKernelInfo(KernelInfo *kernel, double angle)
1688%
1689% A description of each parameter follows:
1690%
1691% o kernel: the Morphology/Convolution kernel
1692%
1693% o angle: angle to rotate in degrees
1694%
1695% This function is only internel to this module, as it is not finalized,
1696% especially with regard to non-orthogonal angles, and rotation of larger
1697% 2D kernels.
1698*/
1699static void ExpandKernelInfo(KernelInfo *kernel, double angle)
1700{
1701 KernelInfo
cristya9a61ad2010-05-13 12:47:41 +00001702 *clone,
anthony3c10fc82010-05-13 02:40:51 +00001703 *last;
cristya9a61ad2010-05-13 12:47:41 +00001704
anthony3c10fc82010-05-13 02:40:51 +00001705 double
1706 a;
1707
1708 last = kernel;
1709 for (a=angle; a<355.0; a+=angle) {
cristya9a61ad2010-05-13 12:47:41 +00001710 clone = CloneKernelInfo(last);
1711 RotateKernelInfo(clone, angle);
1712 last->next = clone;
1713 last = clone;
anthony3c10fc82010-05-13 02:40:51 +00001714 }
1715}
1716
1717/*
1718%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1719% %
1720% %
1721% %
anthony9eb4f742010-05-18 02:45:54 +00001722% M o r p h o l o g y A p p l y %
anthony602ab9b2010-01-05 08:06:50 +00001723% %
1724% %
1725% %
1726%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1727%
anthony9eb4f742010-05-18 02:45:54 +00001728% MorphologyApply() applies a morphological method, multiple times using
1729% a list of multiple kernels.
anthony602ab9b2010-01-05 08:06:50 +00001730%
anthony9eb4f742010-05-18 02:45:54 +00001731% It is basically equivelent to as MorphologyImageChannel() (see below) but
1732% without user controls, that that function extracts and applies to kernels
1733% and morphology methods.
1734%
1735% More specifically kernels are not normalized/scaled/blended by the
1736% 'convolve:scale' Image Artifact (-set setting), and the convolve bias
1737% (-bias setting or image->bias) is passed directly to this function,
1738% and not extracted from an image.
anthony602ab9b2010-01-05 08:06:50 +00001739%
1740% The format of the MorphologyImage method is:
1741%
anthony9eb4f742010-05-18 02:45:54 +00001742% Image *MorphologyApply(const Image *image,MorphologyMethod method,
1743% const long iterations,const KernelInfo *kernel,const double bias,
1744% ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001745%
1746% A description of each parameter follows:
1747%
1748% o image: the image.
1749%
1750% o method: the morphology method to be applied.
1751%
1752% o iterations: apply the operation this many times (or no change).
1753% A value of -1 means loop until no change found.
1754% How this is applied may depend on the morphology method.
1755% Typically this is a value of 1.
1756%
1757% o channel: the channel type.
1758%
1759% o kernel: An array of double representing the morphology kernel.
anthony29188a82010-01-22 10:12:34 +00001760% Warning: kernel may be normalized for the Convolve method.
anthony602ab9b2010-01-05 08:06:50 +00001761%
anthony9eb4f742010-05-18 02:45:54 +00001762% o bias: Convolution Bias to use.
1763%
anthony602ab9b2010-01-05 08:06:50 +00001764% o exception: return any errors or warnings in this structure.
1765%
anthony602ab9b2010-01-05 08:06:50 +00001766*/
1767
anthony930be612010-02-08 04:26:15 +00001768
anthony9eb4f742010-05-18 02:45:54 +00001769/* Apply a Morphology Primative to an image using the given kernel.
1770** Two pre-created images must be provided, no image is created.
1771** Returning the number of pixels that changed.
1772*/
anthony7a01dcf2010-05-11 12:25:52 +00001773static unsigned long MorphologyPrimative(const Image *image, Image
anthony602ab9b2010-01-05 08:06:50 +00001774 *result_image, const MorphologyMethod method, const ChannelType channel,
anthony9eb4f742010-05-18 02:45:54 +00001775 const KernelInfo *kernel,const double bias,ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001776{
cristy2be15382010-01-21 02:38:03 +00001777#define MorphologyTag "Morphology/Image"
anthony602ab9b2010-01-05 08:06:50 +00001778
1779 long
cristy150989e2010-02-01 14:59:39 +00001780 progress,
anthony29188a82010-01-22 10:12:34 +00001781 y, offx, offy,
anthony602ab9b2010-01-05 08:06:50 +00001782 changed;
1783
1784 MagickBooleanType
1785 status;
1786
anthony602ab9b2010-01-05 08:06:50 +00001787 CacheView
1788 *p_view,
1789 *q_view;
1790
anthony602ab9b2010-01-05 08:06:50 +00001791 status=MagickTrue;
1792 changed=0;
1793 progress=0;
1794
anthony602ab9b2010-01-05 08:06:50 +00001795 p_view=AcquireCacheView(image);
1796 q_view=AcquireCacheView(result_image);
anthony29188a82010-01-22 10:12:34 +00001797
anthonycc6c8362010-01-25 04:14:01 +00001798 /* Some methods (including convolve) needs use a reflected kernel.
anthony9eb4f742010-05-18 02:45:54 +00001799 * Adjust 'origin' offsets to loop though kernel as a reflection.
anthony29188a82010-01-22 10:12:34 +00001800 */
cristyc99304f2010-02-01 15:26:27 +00001801 offx = kernel->x;
1802 offy = kernel->y;
anthony29188a82010-01-22 10:12:34 +00001803 switch(method) {
anthony930be612010-02-08 04:26:15 +00001804 case ConvolveMorphology:
1805 case DilateMorphology:
1806 case DilateIntensityMorphology:
1807 case DistanceMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001808 /* kernel needs to used with reflection about origin */
cristy150989e2010-02-01 14:59:39 +00001809 offx = (long) kernel->width-offx-1;
1810 offy = (long) kernel->height-offy-1;
anthony29188a82010-01-22 10:12:34 +00001811 break;
anthony5ef8e942010-05-11 06:51:12 +00001812 case ErodeMorphology:
1813 case ErodeIntensityMorphology:
1814 case HitAndMissMorphology:
1815 case ThinningMorphology:
1816 case ThickenMorphology:
1817 /* kernel is user as is, without reflection */
1818 break;
anthony930be612010-02-08 04:26:15 +00001819 default:
anthony9eb4f742010-05-18 02:45:54 +00001820 assert("Not a Primitive Morphology Method" != (char *) NULL);
anthony930be612010-02-08 04:26:15 +00001821 break;
anthony29188a82010-01-22 10:12:34 +00001822 }
1823
anthony602ab9b2010-01-05 08:06:50 +00001824#if defined(MAGICKCORE_OPENMP_SUPPORT)
1825 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1826#endif
cristy150989e2010-02-01 14:59:39 +00001827 for (y=0; y < (long) image->rows; y++)
anthony602ab9b2010-01-05 08:06:50 +00001828 {
1829 MagickBooleanType
1830 sync;
1831
1832 register const PixelPacket
1833 *restrict p;
1834
1835 register const IndexPacket
1836 *restrict p_indexes;
1837
1838 register PixelPacket
1839 *restrict q;
1840
1841 register IndexPacket
1842 *restrict q_indexes;
1843
cristy150989e2010-02-01 14:59:39 +00001844 register long
anthony602ab9b2010-01-05 08:06:50 +00001845 x;
1846
anthony29188a82010-01-22 10:12:34 +00001847 unsigned long
anthony602ab9b2010-01-05 08:06:50 +00001848 r;
1849
1850 if (status == MagickFalse)
1851 continue;
anthony29188a82010-01-22 10:12:34 +00001852 p=GetCacheViewVirtualPixels(p_view, -offx, y-offy,
1853 image->columns+kernel->width, kernel->height, exception);
anthony602ab9b2010-01-05 08:06:50 +00001854 q=GetCacheViewAuthenticPixels(q_view,0,y,result_image->columns,1,
1855 exception);
1856 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1857 {
1858 status=MagickFalse;
1859 continue;
1860 }
1861 p_indexes=GetCacheViewVirtualIndexQueue(p_view);
1862 q_indexes=GetCacheViewAuthenticIndexQueue(q_view);
anthony29188a82010-01-22 10:12:34 +00001863 r = (image->columns+kernel->width)*offy+offx; /* constant */
1864
cristy150989e2010-02-01 14:59:39 +00001865 for (x=0; x < (long) image->columns; x++)
anthony602ab9b2010-01-05 08:06:50 +00001866 {
cristy150989e2010-02-01 14:59:39 +00001867 long
anthony602ab9b2010-01-05 08:06:50 +00001868 v;
1869
cristy150989e2010-02-01 14:59:39 +00001870 register long
anthony602ab9b2010-01-05 08:06:50 +00001871 u;
1872
1873 register const double
1874 *restrict k;
1875
1876 register const PixelPacket
1877 *restrict k_pixels;
1878
1879 register const IndexPacket
1880 *restrict k_indexes;
1881
1882 MagickPixelPacket
anthony5ef8e942010-05-11 06:51:12 +00001883 result,
1884 min,
1885 max;
anthony602ab9b2010-01-05 08:06:50 +00001886
anthony29188a82010-01-22 10:12:34 +00001887 /* Copy input to ouput image for unused channels
anthony83ba99b2010-01-24 08:48:15 +00001888 * This removes need for 'cloning' a new image every iteration
anthony29188a82010-01-22 10:12:34 +00001889 */
anthony602ab9b2010-01-05 08:06:50 +00001890 *q = p[r];
1891 if (image->colorspace == CMYKColorspace)
1892 q_indexes[x] = p_indexes[r];
1893
anthony5ef8e942010-05-11 06:51:12 +00001894 /* Defaults */
1895 min.red =
1896 min.green =
1897 min.blue =
1898 min.opacity =
1899 min.index = (MagickRealType) QuantumRange;
1900 max.red =
1901 max.green =
1902 max.blue =
1903 max.opacity =
1904 max.index = (MagickRealType) 0;
anthony9eb4f742010-05-18 02:45:54 +00001905 /* default result is the original pixel value */
anthony5ef8e942010-05-11 06:51:12 +00001906 result.red = (MagickRealType) p[r].red;
1907 result.green = (MagickRealType) p[r].green;
1908 result.blue = (MagickRealType) p[r].blue;
1909 result.opacity = QuantumRange - (MagickRealType) p[r].opacity;
cristye96405a2010-05-19 02:24:31 +00001910 result.index = 0.0;
anthony5ef8e942010-05-11 06:51:12 +00001911 if ( image->colorspace == CMYKColorspace)
1912 result.index = (MagickRealType) p_indexes[r];
1913
anthony602ab9b2010-01-05 08:06:50 +00001914 switch (method) {
1915 case ConvolveMorphology:
anthony9eb4f742010-05-18 02:45:54 +00001916 /* Set the user defined bias of the weighted average output */
1917 result.red =
1918 result.green =
1919 result.blue =
1920 result.opacity =
1921 result.index = bias;
anthony930be612010-02-08 04:26:15 +00001922 break;
anthony4fd27e22010-02-07 08:17:18 +00001923 case DilateIntensityMorphology:
1924 case ErodeIntensityMorphology:
anthony9eb4f742010-05-18 02:45:54 +00001925 /* use a boolean flag indicating when first match found */
1926 result.red = 0.0; /* result is not used otherwise */
anthony4fd27e22010-02-07 08:17:18 +00001927 break;
anthony602ab9b2010-01-05 08:06:50 +00001928 default:
anthony602ab9b2010-01-05 08:06:50 +00001929 break;
1930 }
1931
1932 switch ( method ) {
1933 case ConvolveMorphology:
anthony930be612010-02-08 04:26:15 +00001934 /* Weighted Average of pixels using reflected kernel
1935 **
1936 ** NOTE for correct working of this operation for asymetrical
1937 ** kernels, the kernel needs to be applied in its reflected form.
1938 ** That is its values needs to be reversed.
1939 **
1940 ** Correlation is actually the same as this but without reflecting
1941 ** the kernel, and thus 'lower-level' that Convolution. However
1942 ** as Convolution is the more common method used, and it does not
1943 ** really cost us much in terms of processing to use a reflected
anthony5ef8e942010-05-11 06:51:12 +00001944 ** kernel, so it is Convolution that is implemented.
anthony930be612010-02-08 04:26:15 +00001945 **
1946 ** Correlation will have its kernel reflected before calling
1947 ** this function to do a Convolve.
1948 **
1949 ** For more details of Correlation vs Convolution see
1950 ** http://www.cs.umd.edu/~djacobs/CMSC426/Convolution.pdf
1951 */
anthony5ef8e942010-05-11 06:51:12 +00001952 if (((channel & SyncChannels) != 0 ) &&
1953 (image->matte == MagickTrue))
1954 { /* Channel has a 'Sync' Flag, and Alpha Channel enabled.
1955 ** Weight the color channels with Alpha Channel so that
1956 ** transparent pixels are not part of the results.
1957 */
anthony602ab9b2010-01-05 08:06:50 +00001958 MagickRealType
anthony5ef8e942010-05-11 06:51:12 +00001959 alpha, /* color channel weighting : kernel*alpha */
1960 gamma; /* divisor, sum of weighting values */
anthony602ab9b2010-01-05 08:06:50 +00001961
1962 gamma=0.0;
anthony29188a82010-01-22 10:12:34 +00001963 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001964 k_pixels = p;
1965 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001966 for (v=0; v < (long) kernel->height; v++) {
1967 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001968 if ( IsNan(*k) ) continue;
1969 alpha=(*k)*(QuantumScale*(QuantumRange-
1970 k_pixels[u].opacity));
1971 gamma += alpha;
1972 result.red += alpha*k_pixels[u].red;
1973 result.green += alpha*k_pixels[u].green;
1974 result.blue += alpha*k_pixels[u].blue;
anthony83ba99b2010-01-24 08:48:15 +00001975 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00001976 if ( image->colorspace == CMYKColorspace)
1977 result.index += alpha*k_indexes[u];
1978 }
1979 k_pixels += image->columns+kernel->width;
1980 k_indexes += image->columns+kernel->width;
1981 }
1982 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
anthony83ba99b2010-01-24 08:48:15 +00001983 result.red *= gamma;
1984 result.green *= gamma;
1985 result.blue *= gamma;
1986 result.opacity *= gamma;
1987 result.index *= gamma;
anthony602ab9b2010-01-05 08:06:50 +00001988 }
anthony5ef8e942010-05-11 06:51:12 +00001989 else
1990 {
1991 /* No 'Sync' flag, or no Alpha involved.
1992 ** Convolution is simple individual channel weigthed sum.
1993 */
1994 k = &kernel->values[ kernel->width*kernel->height-1 ];
1995 k_pixels = p;
1996 k_indexes = p_indexes;
1997 for (v=0; v < (long) kernel->height; v++) {
1998 for (u=0; u < (long) kernel->width; u++, k--) {
1999 if ( IsNan(*k) ) continue;
2000 result.red += (*k)*k_pixels[u].red;
2001 result.green += (*k)*k_pixels[u].green;
2002 result.blue += (*k)*k_pixels[u].blue;
2003 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
2004 if ( image->colorspace == CMYKColorspace)
2005 result.index += (*k)*k_indexes[u];
2006 }
2007 k_pixels += image->columns+kernel->width;
2008 k_indexes += image->columns+kernel->width;
2009 }
2010 }
anthony602ab9b2010-01-05 08:06:50 +00002011 break;
2012
anthony4fd27e22010-02-07 08:17:18 +00002013 case ErodeMorphology:
anthony5ef8e942010-05-11 06:51:12 +00002014 /* Minimum Value within kernel neighbourhood
anthony930be612010-02-08 04:26:15 +00002015 **
2016 ** NOTE that the kernel is not reflected for this operation!
2017 **
2018 ** NOTE: in normal Greyscale Morphology, the kernel value should
2019 ** be added to the real value, this is currently not done, due to
2020 ** the nature of the boolean kernels being used.
2021 */
anthony4fd27e22010-02-07 08:17:18 +00002022 k = kernel->values;
2023 k_pixels = p;
2024 k_indexes = p_indexes;
2025 for (v=0; v < (long) kernel->height; v++) {
2026 for (u=0; u < (long) kernel->width; u++, k++) {
2027 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony5ef8e942010-05-11 06:51:12 +00002028 Minimize(min.red, (double) k_pixels[u].red);
2029 Minimize(min.green, (double) k_pixels[u].green);
2030 Minimize(min.blue, (double) k_pixels[u].blue);
2031 Minimize(min.opacity,
anthonyd37a5cb2010-05-07 06:37:03 +00002032 QuantumRange-(double) k_pixels[u].opacity);
anthony4fd27e22010-02-07 08:17:18 +00002033 if ( image->colorspace == CMYKColorspace)
anthony5ef8e942010-05-11 06:51:12 +00002034 Minimize(min.index, (double) k_indexes[u]);
anthony4fd27e22010-02-07 08:17:18 +00002035 }
2036 k_pixels += image->columns+kernel->width;
2037 k_indexes += image->columns+kernel->width;
2038 }
2039 break;
2040
anthony5ef8e942010-05-11 06:51:12 +00002041
anthony83ba99b2010-01-24 08:48:15 +00002042 case DilateMorphology:
anthony5ef8e942010-05-11 06:51:12 +00002043 /* Maximum Value within kernel neighbourhood
anthony930be612010-02-08 04:26:15 +00002044 **
2045 ** NOTE for correct working of this operation for asymetrical
2046 ** kernels, the kernel needs to be applied in its reflected form.
2047 ** That is its values needs to be reversed.
2048 **
2049 ** NOTE: in normal Greyscale Morphology, the kernel value should
2050 ** be added to the real value, this is currently not done, due to
2051 ** the nature of the boolean kernels being used.
2052 **
2053 */
anthony29188a82010-01-22 10:12:34 +00002054 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00002055 k_pixels = p;
2056 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002057 for (v=0; v < (long) kernel->height; v++) {
2058 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00002059 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony5ef8e942010-05-11 06:51:12 +00002060 Maximize(max.red, (double) k_pixels[u].red);
2061 Maximize(max.green, (double) k_pixels[u].green);
2062 Maximize(max.blue, (double) k_pixels[u].blue);
2063 Maximize(max.opacity,
anthonyd37a5cb2010-05-07 06:37:03 +00002064 QuantumRange-(double) k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00002065 if ( image->colorspace == CMYKColorspace)
anthony5ef8e942010-05-11 06:51:12 +00002066 Maximize(max.index, (double) k_indexes[u]);
anthony602ab9b2010-01-05 08:06:50 +00002067 }
2068 k_pixels += image->columns+kernel->width;
2069 k_indexes += image->columns+kernel->width;
2070 }
anthony602ab9b2010-01-05 08:06:50 +00002071 break;
2072
anthony5ef8e942010-05-11 06:51:12 +00002073 case HitAndMissMorphology:
2074 case ThinningMorphology:
2075 case ThickenMorphology:
2076 /* Minimum of Foreground Pixel minus Maxumum of Background Pixels
2077 **
2078 ** NOTE that the kernel is not reflected for this operation,
2079 ** and consists of both foreground and background pixel
2080 ** neighbourhoods, 0.0 for background, and 1.0 for foreground
2081 ** with either Nan or 0.5 values for don't care.
2082 **
2083 ** Note that this can produce negative results, though really
2084 ** only a positive match has any real value.
2085 */
2086 k = kernel->values;
2087 k_pixels = p;
2088 k_indexes = p_indexes;
2089 for (v=0; v < (long) kernel->height; v++) {
2090 for (u=0; u < (long) kernel->width; u++, k++) {
2091 if ( IsNan(*k) ) continue;
2092 if ( (*k) > 0.7 )
2093 { /* minimim of foreground pixels */
2094 Minimize(min.red, (double) k_pixels[u].red);
2095 Minimize(min.green, (double) k_pixels[u].green);
2096 Minimize(min.blue, (double) k_pixels[u].blue);
2097 Minimize(min.opacity,
2098 QuantumRange-(double) k_pixels[u].opacity);
2099 if ( image->colorspace == CMYKColorspace)
2100 Minimize(min.index, (double) k_indexes[u]);
2101 }
2102 else if ( (*k) < 0.3 )
2103 { /* maximum of background pixels */
2104 Maximize(max.red, (double) k_pixels[u].red);
2105 Maximize(max.green, (double) k_pixels[u].green);
2106 Maximize(max.blue, (double) k_pixels[u].blue);
2107 Maximize(max.opacity,
2108 QuantumRange-(double) k_pixels[u].opacity);
2109 if ( image->colorspace == CMYKColorspace)
2110 Maximize(max.index, (double) k_indexes[u]);
2111 }
2112 }
2113 k_pixels += image->columns+kernel->width;
2114 k_indexes += image->columns+kernel->width;
2115 }
2116 /* Pattern Match only if min fg larger than min bg pixels */
2117 min.red -= max.red; Maximize( min.red, 0.0 );
2118 min.green -= max.green; Maximize( min.green, 0.0 );
2119 min.blue -= max.blue; Maximize( min.blue, 0.0 );
2120 min.opacity -= max.opacity; Maximize( min.opacity, 0.0 );
2121 min.index -= max.index; Maximize( min.index, 0.0 );
2122 break;
2123
anthony4fd27e22010-02-07 08:17:18 +00002124 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00002125 /* Select Pixel with Minimum Intensity within kernel neighbourhood
2126 **
2127 ** WARNING: the intensity test fails for CMYK and does not
2128 ** take into account the moderating effect of teh alpha channel
2129 ** on the intensity.
2130 **
2131 ** NOTE that the kernel is not reflected for this operation!
2132 */
anthony602ab9b2010-01-05 08:06:50 +00002133 k = kernel->values;
2134 k_pixels = p;
2135 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002136 for (v=0; v < (long) kernel->height; v++) {
2137 for (u=0; u < (long) kernel->width; u++, k++) {
anthony602ab9b2010-01-05 08:06:50 +00002138 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony4fd27e22010-02-07 08:17:18 +00002139 if ( result.red == 0.0 ||
2140 PixelIntensity(&(k_pixels[u])) < PixelIntensity(q) ) {
2141 /* copy the whole pixel - no channel selection */
2142 *q = k_pixels[u];
2143 if ( result.red > 0.0 ) changed++;
2144 result.red = 1.0;
2145 }
anthony602ab9b2010-01-05 08:06:50 +00002146 }
2147 k_pixels += image->columns+kernel->width;
2148 k_indexes += image->columns+kernel->width;
2149 }
anthony602ab9b2010-01-05 08:06:50 +00002150 break;
2151
anthony83ba99b2010-01-24 08:48:15 +00002152 case DilateIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00002153 /* Select Pixel with Maximum Intensity within kernel neighbourhood
2154 **
2155 ** WARNING: the intensity test fails for CMYK and does not
anthony9eb4f742010-05-18 02:45:54 +00002156 ** take into account the moderating effect of the alpha channel
2157 ** on the intensity (yet).
anthony930be612010-02-08 04:26:15 +00002158 **
2159 ** NOTE for correct working of this operation for asymetrical
2160 ** kernels, the kernel needs to be applied in its reflected form.
2161 ** That is its values needs to be reversed.
2162 */
anthony29188a82010-01-22 10:12:34 +00002163 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00002164 k_pixels = p;
2165 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002166 for (v=0; v < (long) kernel->height; v++) {
2167 for (u=0; u < (long) kernel->width; u++, k--) {
anthony29188a82010-01-22 10:12:34 +00002168 if ( IsNan(*k) || (*k) < 0.5 ) continue; /* boolean kernel */
2169 if ( result.red == 0.0 ||
2170 PixelIntensity(&(k_pixels[u])) > PixelIntensity(q) ) {
2171 /* copy the whole pixel - no channel selection */
2172 *q = k_pixels[u];
2173 if ( result.red > 0.0 ) changed++;
2174 result.red = 1.0;
2175 }
anthony602ab9b2010-01-05 08:06:50 +00002176 }
2177 k_pixels += image->columns+kernel->width;
2178 k_indexes += image->columns+kernel->width;
2179 }
anthony602ab9b2010-01-05 08:06:50 +00002180 break;
2181
anthony5ef8e942010-05-11 06:51:12 +00002182
anthony602ab9b2010-01-05 08:06:50 +00002183 case DistanceMorphology:
anthony930be612010-02-08 04:26:15 +00002184 /* Add kernel Value and select the minimum value found.
2185 ** The result is a iterative distance from edge of image shape.
2186 **
2187 ** All Distance Kernels are symetrical, but that may not always
2188 ** be the case. For example how about a distance from left edges?
2189 ** To work correctly with asymetrical kernels the reflected kernel
2190 ** needs to be applied.
anthony5ef8e942010-05-11 06:51:12 +00002191 **
2192 ** Actually this is really a GreyErode with a negative kernel!
2193 **
anthony930be612010-02-08 04:26:15 +00002194 */
anthony29188a82010-01-22 10:12:34 +00002195 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00002196 k_pixels = p;
2197 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002198 for (v=0; v < (long) kernel->height; v++) {
2199 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00002200 if ( IsNan(*k) ) continue;
2201 Minimize(result.red, (*k)+k_pixels[u].red);
2202 Minimize(result.green, (*k)+k_pixels[u].green);
2203 Minimize(result.blue, (*k)+k_pixels[u].blue);
2204 Minimize(result.opacity, (*k)+QuantumRange-k_pixels[u].opacity);
2205 if ( image->colorspace == CMYKColorspace)
2206 Minimize(result.index, (*k)+k_indexes[u]);
2207 }
2208 k_pixels += image->columns+kernel->width;
2209 k_indexes += image->columns+kernel->width;
2210 }
anthony602ab9b2010-01-05 08:06:50 +00002211 break;
2212
2213 case UndefinedMorphology:
2214 default:
2215 break; /* Do nothing */
anthony83ba99b2010-01-24 08:48:15 +00002216 }
anthony5ef8e942010-05-11 06:51:12 +00002217 /* Final mathematics of results (combine with original image?)
2218 **
2219 ** NOTE: Difference Morphology operators Edge* and *Hat could also
2220 ** be done here but works better with iteration as a image difference
2221 ** in the controling function (below). Thicken and Thinning however
2222 ** should be done here so thay can be iterated correctly.
2223 */
2224 switch ( method ) {
2225 case HitAndMissMorphology:
2226 case ErodeMorphology:
2227 result = min; /* minimum of neighbourhood */
2228 break;
2229 case DilateMorphology:
2230 result = max; /* maximum of neighbourhood */
2231 break;
2232 case ThinningMorphology:
2233 /* subtract pattern match from original */
2234 result.red -= min.red;
2235 result.green -= min.green;
2236 result.blue -= min.blue;
2237 result.opacity -= min.opacity;
2238 result.index -= min.index;
2239 break;
2240 case ThickenMorphology:
2241 /* Union with original image (maximize) - or should this be + */
2242 Maximize( result.red, min.red );
2243 Maximize( result.green, min.green );
2244 Maximize( result.blue, min.blue );
2245 Maximize( result.opacity, min.opacity );
2246 Maximize( result.index, min.index );
2247 break;
2248 default:
2249 /* result directly calculated or assigned */
2250 break;
2251 }
2252 /* Assign the resulting pixel values - Clamping Result */
anthony83ba99b2010-01-24 08:48:15 +00002253 switch ( method ) {
2254 case UndefinedMorphology:
2255 case DilateIntensityMorphology:
2256 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00002257 break; /* full pixel was directly assigned - not a channel method */
anthony83ba99b2010-01-24 08:48:15 +00002258 default:
anthony83ba99b2010-01-24 08:48:15 +00002259 if ((channel & RedChannel) != 0)
2260 q->red = ClampToQuantum(result.red);
2261 if ((channel & GreenChannel) != 0)
2262 q->green = ClampToQuantum(result.green);
2263 if ((channel & BlueChannel) != 0)
2264 q->blue = ClampToQuantum(result.blue);
2265 if ((channel & OpacityChannel) != 0
2266 && image->matte == MagickTrue )
2267 q->opacity = ClampToQuantum(QuantumRange-result.opacity);
2268 if ((channel & IndexChannel) != 0
2269 && image->colorspace == CMYKColorspace)
2270 q_indexes[x] = ClampToQuantum(result.index);
2271 break;
2272 }
anthony5ef8e942010-05-11 06:51:12 +00002273 /* Count up changed pixels */
anthony83ba99b2010-01-24 08:48:15 +00002274 if ( ( p[r].red != q->red )
2275 || ( p[r].green != q->green )
2276 || ( p[r].blue != q->blue )
2277 || ( p[r].opacity != q->opacity )
2278 || ( image->colorspace == CMYKColorspace &&
2279 p_indexes[r] != q_indexes[x] ) )
2280 changed++; /* The pixel had some value changed! */
anthony602ab9b2010-01-05 08:06:50 +00002281 p++;
2282 q++;
anthony83ba99b2010-01-24 08:48:15 +00002283 } /* x */
anthony602ab9b2010-01-05 08:06:50 +00002284 sync=SyncCacheViewAuthenticPixels(q_view,exception);
2285 if (sync == MagickFalse)
2286 status=MagickFalse;
2287 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2288 {
2289 MagickBooleanType
2290 proceed;
2291
2292#if defined(MAGICKCORE_OPENMP_SUPPORT)
2293 #pragma omp critical (MagickCore_MorphologyImage)
2294#endif
2295 proceed=SetImageProgress(image,MorphologyTag,progress++,image->rows);
2296 if (proceed == MagickFalse)
2297 status=MagickFalse;
2298 }
anthony83ba99b2010-01-24 08:48:15 +00002299 } /* y */
anthony602ab9b2010-01-05 08:06:50 +00002300 result_image->type=image->type;
2301 q_view=DestroyCacheView(q_view);
2302 p_view=DestroyCacheView(p_view);
cristy150989e2010-02-01 14:59:39 +00002303 return(status ? (unsigned long) changed : 0);
anthony602ab9b2010-01-05 08:06:50 +00002304}
2305
anthony4fd27e22010-02-07 08:17:18 +00002306
anthony9eb4f742010-05-18 02:45:54 +00002307MagickExport Image *MorphologyApply(const Image *image, const ChannelType
2308 channel,const MorphologyMethod method, const long iterations,
2309 const KernelInfo *kernel,const double bias,ExceptionInfo *exception)
cristy2be15382010-01-21 02:38:03 +00002310{
2311 Image
anthony9eb4f742010-05-18 02:45:54 +00002312 *curr_image, /* Image we are working with */
2313 *work_image, /* secondary working image */
2314 *save_image; /* save image for later use */
anthony602ab9b2010-01-05 08:06:50 +00002315
anthony4fd27e22010-02-07 08:17:18 +00002316 KernelInfo
anthony9eb4f742010-05-18 02:45:54 +00002317 *curr_kernel, /* current kernel list to apply */
2318 *this_kernel; /* current individual kernel to apply */
anthony4fd27e22010-02-07 08:17:18 +00002319
2320 MorphologyMethod
cristye96405a2010-05-19 02:24:31 +00002321 primitive; /* the current morphology primitive being applied */
anthony9eb4f742010-05-18 02:45:54 +00002322
2323 MagickBooleanType
2324 verbose; /* verbose output of results */
2325
2326 CompositeOperator
2327 kernel_compose; /* Handling the result of multiple kernels*/
anthony4fd27e22010-02-07 08:17:18 +00002328
anthony1b2bc0a2010-05-12 05:25:22 +00002329 unsigned long
cristye96405a2010-05-19 02:24:31 +00002330 count, /* count of primitive steps applied */
anthony9eb4f742010-05-18 02:45:54 +00002331 loop, /* number of times though kernel list (iterations) */
2332 loop_limit, /* finish looping after this many times */
2333 stage, /* stage number for compound morphology */
cristye96405a2010-05-19 02:24:31 +00002334 changed, /* number pixels changed by one primitive operation */
anthony9eb4f742010-05-18 02:45:54 +00002335 loop_changed, /* changes made over loop though of kernels */
2336 total_changed, /* total count of all changes to image */
2337 kernel_number; /* kernel number being applied */
anthony1b2bc0a2010-05-12 05:25:22 +00002338
anthony602ab9b2010-01-05 08:06:50 +00002339 assert(image != (Image *) NULL);
2340 assert(image->signature == MagickSignature);
anthony4fd27e22010-02-07 08:17:18 +00002341 assert(kernel != (KernelInfo *) NULL);
2342 assert(kernel->signature == MagickSignature);
anthony602ab9b2010-01-05 08:06:50 +00002343 assert(exception != (ExceptionInfo *) NULL);
2344 assert(exception->signature == MagickSignature);
2345
cristye96405a2010-05-19 02:24:31 +00002346 loop_limit = (unsigned long) iterations;
anthony9eb4f742010-05-18 02:45:54 +00002347 if ( iterations < 0 )
2348 loop_limit = image->columns > image->rows ? image->columns : image->rows;
anthony602ab9b2010-01-05 08:06:50 +00002349 if ( iterations == 0 )
2350 return((Image *)NULL); /* null operation - nothing to do! */
2351
2352 /* kernel must be valid at this point
2353 * (except maybe for posible future morphology methods like "Prune"
2354 */
cristy2be15382010-01-21 02:38:03 +00002355 assert(kernel != (KernelInfo *)NULL);
anthony602ab9b2010-01-05 08:06:50 +00002356
cristye96405a2010-05-19 02:24:31 +00002357 verbose = ( GetImageArtifact(image,"verbose") != (const char *) NULL ) ?
2358 MagickTrue : MagickFalse;
anthony4f1dcb72010-05-14 08:43:10 +00002359
anthony9eb4f742010-05-18 02:45:54 +00002360 /* initialise for cleanup */
cristye96405a2010-05-19 02:24:31 +00002361 curr_image = (Image *) image; /* result of morpholgy primitive */
anthony9eb4f742010-05-18 02:45:54 +00002362 work_image = (Image *) NULL; /* secondary working image */
2363 save_image = (Image *) NULL; /* save image for some compound methods */
2364 curr_kernel = (KernelInfo *)kernel; /* allow kernel list to be modified */
anthony4fd27e22010-02-07 08:17:18 +00002365
anthony9eb4f742010-05-18 02:45:54 +00002366 kernel_compose = NoCompositeOp; /* iterated over all kernels */
anthony602ab9b2010-01-05 08:06:50 +00002367
cristye96405a2010-05-19 02:24:31 +00002368 /* Select initial primitive morphology to apply */
2369 primitive = UndefinedMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002370 switch( method ) {
anthony930be612010-02-08 04:26:15 +00002371 case CorrelateMorphology:
2372 /* A Correlation is actually a Convolution with a reflected kernel.
anthony9eb4f742010-05-18 02:45:54 +00002373 ** However a Convolution is a weighted sum using a reflected kernel.
anthony930be612010-02-08 04:26:15 +00002374 ** It may seem stange to convert a Correlation into a Convolution
2375 ** as the Correleation is the simplier method, but Convolution is
2376 ** much more commonly used, and it makes sense to implement it directly
2377 ** so as to avoid the need to duplicate the kernel when it is not
2378 ** required (which is typically the default).
2379 */
anthony9eb4f742010-05-18 02:45:54 +00002380 curr_kernel = CloneKernelInfo(kernel);
anthony1b2bc0a2010-05-12 05:25:22 +00002381 if (curr_kernel == (KernelInfo *) NULL)
2382 goto error_cleanup;
anthony930be612010-02-08 04:26:15 +00002383 RotateKernelInfo(curr_kernel,180);
anthony9eb4f742010-05-18 02:45:54 +00002384 /* FALL THRU to Convolve */
anthonyc94cdb02010-01-06 08:15:29 +00002385 case ConvolveMorphology:
cristye96405a2010-05-19 02:24:31 +00002386 primitive = ConvolveMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002387 kernel_compose = NoCompositeOp;
2388 break;
2389 case ErodeMorphology: /* just erode */
2390 case OpenMorphology: /* erode then dialate */
2391 case EdgeInMorphology: /* erode and image difference */
2392 case TopHatMorphology: /* erode, dilate and image difference */
2393 case SmoothMorphology: /* erode, dilate, dilate, erode */
cristye96405a2010-05-19 02:24:31 +00002394 primitive = ErodeMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002395 break;
2396 case ErodeIntensityMorphology:
2397 case OpenIntensityMorphology:
cristye96405a2010-05-19 02:24:31 +00002398 primitive = ErodeIntensityMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002399 break;
2400 case DilateMorphology: /* just dilate */
2401 case EdgeOutMorphology: /* dilate and image difference */
2402 case EdgeMorphology: /* dilate and erode difference */
cristye96405a2010-05-19 02:24:31 +00002403 primitive = DilateMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002404 break;
2405 case CloseMorphology: /* dilate, then erode */
2406 case BottomHatMorphology: /* dilate and image difference */
2407 curr_kernel = CloneKernelInfo(kernel);
2408 if (curr_kernel == (KernelInfo *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00002409 goto error_cleanup;
anthony9eb4f742010-05-18 02:45:54 +00002410 RotateKernelInfo(curr_kernel,180);
cristye96405a2010-05-19 02:24:31 +00002411 primitive = DilateMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002412 break;
2413 case DilateIntensityMorphology:
2414 case CloseIntensityMorphology:
2415 curr_kernel = CloneKernelInfo(kernel);
2416 if (curr_kernel == (KernelInfo *) NULL)
2417 goto error_cleanup;
2418 RotateKernelInfo(curr_kernel,180);
cristye96405a2010-05-19 02:24:31 +00002419 primitive = DilateIntensityMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002420 break;
2421 case HitAndMissMorphology:
cristye96405a2010-05-19 02:24:31 +00002422 primitive = HitAndMissMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002423 loop_limit = 1; /* iterate only once */
2424 kernel_compose = LightenCompositeOp; /* Union of Hit-And-Miss */
2425 break;
2426 case ThinningMorphology: /* iterative morphology */
2427 case ThickenMorphology:
2428 case DistanceMorphology: /* Distance should never use multple kernels */
2429 case UndefinedMorphology:
cristye96405a2010-05-19 02:24:31 +00002430 primitive = method;
anthony930be612010-02-08 04:26:15 +00002431 break;
anthony602ab9b2010-01-05 08:06:50 +00002432 }
2433
anthony3c1cd552010-05-18 04:33:23 +00002434#if 0
2435 { /* User override of results handling -- Experimental */
anthony9eb4f742010-05-18 02:45:54 +00002436 const char
2437 *artifact = GetImageArtifact(image,"morphology:style");
2438 if ( artifact != (const char *) NULL ) {
2439 if (LocaleCompare("union",artifact) == 0)
2440 kernel_compose = LightenCompositeOp;
2441 if (LocaleCompare("iterate",artifact) == 0)
2442 kernel_compose = NoCompositeOp;
2443 else
2444 kernel_compose = (CompositeOperator) ParseMagickOption(
2445 MagickComposeOptions,MagickFalse,artifact);
2446 if ( kernel_compose == UndefinedCompositeOp )
2447 perror("Invalid \"morphology:compose\" setting\n");
2448 }
2449 }
anthony3c1cd552010-05-18 04:33:23 +00002450#endif
anthony7a01dcf2010-05-11 12:25:52 +00002451
anthony9eb4f742010-05-18 02:45:54 +00002452 /* Initialize compound morphology stages */
cristye96405a2010-05-19 02:24:31 +00002453 count = 0; /* number of low-level morphology primitives performed */
anthony9eb4f742010-05-18 02:45:54 +00002454 total_changed = 0; /* total number of pixels changed thoughout */
2455 stage = 1; /* the compound morphology stage number */
2456
2457 /* compount morphology staging loop */
2458 while ( 1 ) {
2459
2460#if 1
2461 /* Extra information for debugging compound operations */
cristye96405a2010-05-19 02:24:31 +00002462 if ( verbose == MagickTrue && primitive != method )
anthony9eb4f742010-05-18 02:45:54 +00002463 fprintf(stderr, "Morphology %s: Stage %lu %s%s (%s)\n",
2464 MagickOptionToMnemonic(MagickMorphologyOptions, method), stage,
cristye96405a2010-05-19 02:24:31 +00002465 MagickOptionToMnemonic(MagickMorphologyOptions, primitive),
anthony9eb4f742010-05-18 02:45:54 +00002466 ( curr_kernel == kernel) ? "" : "*",
2467 ( kernel_compose == NoCompositeOp ) ? "iterate"
2468 : MagickOptionToMnemonic(MagickComposeOptions, kernel_compose) );
2469#endif
2470
2471 if ( kernel_compose == NoCompositeOp ) {
2472 /******************************
2473 ** Iterate over all Kernels **
2474 ******************************/
2475 loop = 0;
2476 loop_changed = 1;
2477 while ( loop < loop_limit && loop_changed > 0 ) {
2478 loop++; /* the iteration of this kernel */
2479
2480 loop_changed = 0;
2481 this_kernel = curr_kernel;
2482 kernel_number = 0;
2483 while ( this_kernel != NULL ) {
2484
2485 /* Create a destination image, if not yet defined */
2486 if ( work_image == (Image *) NULL )
2487 {
2488 work_image=CloneImage(image,0,0,MagickTrue,exception);
2489 if (work_image == (Image *) NULL)
2490 goto error_cleanup;
2491 if (SetImageStorageClass(work_image,DirectClass) == MagickFalse)
2492 {
2493 InheritException(exception,&work_image->exception);
2494 goto error_cleanup;
2495 }
2496 }
2497
cristye96405a2010-05-19 02:24:31 +00002498 /* morphological primitive curr -> work */
anthony9eb4f742010-05-18 02:45:54 +00002499 count++;
cristye96405a2010-05-19 02:24:31 +00002500 changed = MorphologyPrimative(curr_image, work_image, primitive,
anthony9eb4f742010-05-18 02:45:54 +00002501 channel, this_kernel, bias, exception);
2502 loop_changed += changed;
2503 total_changed += changed;
2504
2505 if ( verbose == MagickTrue )
2506 fprintf(stderr, "Morphology %s:%lu.%lu #%lu => Changed %lu\n",
cristye96405a2010-05-19 02:24:31 +00002507 MagickOptionToMnemonic(MagickMorphologyOptions, primitive),
anthony9eb4f742010-05-18 02:45:54 +00002508 loop, kernel_number, count, changed);
2509
2510 /* prepare next loop */
2511 { Image *tmp = work_image; /* swap images for iteration */
2512 work_image = curr_image;
2513 curr_image = tmp;
2514 }
2515 if ( work_image == image )
2516 work_image = (Image *) NULL; /* never assign image to 'work' */
2517 this_kernel = this_kernel->next; /* prepare next kernel (if any) */
anthony1b2bc0a2010-05-12 05:25:22 +00002518 kernel_number++;
2519 }
anthony7a01dcf2010-05-11 12:25:52 +00002520
anthony9eb4f742010-05-18 02:45:54 +00002521 if ( verbose == MagickTrue && kernel->next != NULL )
2522 fprintf(stderr, "Morphology %s:%lu #%lu ===> Changed %lu Total %lu\n",
cristye96405a2010-05-19 02:24:31 +00002523 MagickOptionToMnemonic(MagickMorphologyOptions, primitive),
anthony9eb4f742010-05-18 02:45:54 +00002524 loop, count, loop_changed, total_changed );
anthony602ab9b2010-01-05 08:06:50 +00002525 }
anthony1b2bc0a2010-05-12 05:25:22 +00002526 }
2527
anthony9eb4f742010-05-18 02:45:54 +00002528 else {
2529 /*************************************
2530 ** Composition of Iterated Kernels **
2531 *************************************/
2532 Image
2533 *input_image, /* starting point for kernels */
2534 *union_image;
2535 input_image = curr_image;
2536 union_image = (Image *) NULL;
2537
2538 this_kernel = curr_kernel;
2539 kernel_number = 0;
2540 while ( this_kernel != NULL ) {
2541
2542 if( curr_image != (Image *) NULL && curr_image != input_image )
2543 curr_image=DestroyImage(curr_image);
2544 curr_image = input_image; /* always start with the original image */
2545
2546 loop = 0;
2547 changed = 1;
2548 loop_changed = 0;
2549 while ( loop < loop_limit && changed > 0 ) {
2550 loop++; /* the iteration of this kernel */
2551
2552 /* Create a destination image, if not defined */
2553 if ( work_image == (Image *) NULL )
2554 {
2555 work_image=CloneImage(image,0,0,MagickTrue,exception);
2556 if (work_image == (Image *) NULL)
2557 goto error_cleanup;
2558 if (SetImageStorageClass(work_image,DirectClass) == MagickFalse)
2559 {
2560 InheritException(exception,&curr_image->exception);
2561 if( union_image != (Image *) NULL )
2562 union_image=DestroyImage(union_image);
2563 if( curr_image != input_image )
2564 curr_image = DestroyImage(curr_image);
2565 curr_image = (Image *) input_image;
2566 goto error_cleanup;
2567 }
2568 }
2569
cristye96405a2010-05-19 02:24:31 +00002570 /* morphological primitive curr -> work */
anthony9eb4f742010-05-18 02:45:54 +00002571 count++;
cristye96405a2010-05-19 02:24:31 +00002572 changed = MorphologyPrimative(curr_image,work_image,primitive,
anthony9eb4f742010-05-18 02:45:54 +00002573 channel, this_kernel, bias, exception);
2574 loop_changed += changed;
2575 total_changed += changed;
2576
2577 if ( verbose == MagickTrue )
anthony4f1dcb72010-05-14 08:43:10 +00002578 fprintf(stderr, "Morphology %s:%lu.%lu #%lu => Changed %lu\n",
cristye96405a2010-05-19 02:24:31 +00002579 MagickOptionToMnemonic(MagickMorphologyOptions, primitive),
anthony9eb4f742010-05-18 02:45:54 +00002580 loop, kernel_number, count, changed);
2581
2582 /* prepare next loop */
2583 { Image *tmp = work_image; /* swap images for iteration */
2584 work_image = curr_image; /* curr_image is now the results */
2585 curr_image = tmp;
2586 }
2587 if ( work_image == input_image )
2588 work_image = (Image *) NULL; /* clear work of the input_image */
2589
2590 } /* end kernel iteration */
2591
2592 /* make a union of the iterated kernel */
2593 if ( union_image == (Image *) NULL) /* start the union? */
2594 union_image = curr_image, curr_image = (Image *)NULL;
2595 else
2596 (void) CompositeImageChannel(union_image,
2597 (ChannelType) (channel & ~SyncChannels), kernel_compose,
2598 curr_image, 0, 0);
2599
2600 this_kernel = this_kernel->next; /* next kernel (if any) */
anthony1b2bc0a2010-05-12 05:25:22 +00002601 kernel_number++;
anthony602ab9b2010-01-05 08:06:50 +00002602 }
anthony9eb4f742010-05-18 02:45:54 +00002603
2604 if ( verbose == MagickTrue && kernel->next != NULL && loop_limit > 1 )
2605 fprintf(stderr, "Morphology %s:%lu #%lu ===> Changed %lu Total %lu\n",
cristye96405a2010-05-19 02:24:31 +00002606 MagickOptionToMnemonic(MagickMorphologyOptions, primitive),
anthony9eb4f742010-05-18 02:45:54 +00002607 loop, count, loop_changed, total_changed );
2608
2609#if 0
2610fprintf(stderr, "--E-- image=0x%lx\n", (unsigned long)image);
2611fprintf(stderr, " input=0x%lx\n", (unsigned long)input_image);
2612fprintf(stderr, " union=0x%lx\n", (unsigned long)union_image);
2613fprintf(stderr, " curr =0x%lx\n", (unsigned long)curr_image);
2614fprintf(stderr, " work =0x%lx\n", (unsigned long)work_image);
2615fprintf(stderr, " save =0x%lx\n", (unsigned long)save_image);
2616#endif
2617
2618 /* Finish up - return the union of results */
2619 if( curr_image != (Image *) NULL && curr_image != input_image )
2620 curr_image=DestroyImage(curr_image);
2621 if( input_image != input_image )
2622 input_image = DestroyImage(input_image);
2623 curr_image = union_image;
anthony1b2bc0a2010-05-12 05:25:22 +00002624 }
anthony9eb4f742010-05-18 02:45:54 +00002625
2626 /* Compound Morphology Operations
cristye96405a2010-05-19 02:24:31 +00002627 * set next 'primitive' iteration, and continue
anthony9eb4f742010-05-18 02:45:54 +00002628 * or break when all operations are complete.
2629 */
2630 stage++; /* what is the next stage number to do */
2631 switch( method ) {
2632 case SmoothMorphology: /* open, close */
2633 switch ( stage ) {
2634 /* case 1: initialized above */
2635 case 2: /* open part 2 */
cristye96405a2010-05-19 02:24:31 +00002636 primitive = DilateMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002637 continue;
2638 case 3: /* close part 1 */
2639 curr_kernel = CloneKernelInfo(kernel);
2640 if (curr_kernel == (KernelInfo *) NULL)
2641 goto error_cleanup;
2642 RotateKernelInfo(curr_kernel,180);
2643 continue;
2644 case 4: /* close part 2 */
cristye96405a2010-05-19 02:24:31 +00002645 primitive = ErodeMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002646 continue;
2647 }
2648 break;
2649 case OpenMorphology: /* erode, dilate */
2650 case TopHatMorphology:
cristye96405a2010-05-19 02:24:31 +00002651 primitive = DilateMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002652 if ( stage <= 2 ) continue;
2653 break;
2654 case OpenIntensityMorphology:
cristye96405a2010-05-19 02:24:31 +00002655 primitive = DilateIntensityMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002656 if ( stage <= 2 ) continue;
2657 break;
2658 case CloseMorphology: /* dilate, erode */
2659 case BottomHatMorphology:
cristye96405a2010-05-19 02:24:31 +00002660 primitive = ErodeMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002661 if ( stage <= 2 ) continue;
2662 break;
2663 case CloseIntensityMorphology:
cristye96405a2010-05-19 02:24:31 +00002664 primitive = ErodeIntensityMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002665 if ( stage <= 2 ) continue;
2666 break;
2667 case EdgeMorphology: /* dilate and erode difference */
2668 if (stage <= 2) {
2669 save_image = curr_image;
2670 curr_image = (Image *) image;
cristye96405a2010-05-19 02:24:31 +00002671 primitive = ErodeMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002672 continue;
2673 }
2674 break;
2675 default: /* Primitive Morphology is just finished! */
2676 break;
2677 }
2678
2679 if ( verbose == MagickTrue && count > 1 )
2680 fprintf(stderr, "Morphology %s: ======> Total %lu\n",
2681 MagickOptionToMnemonic(MagickMorphologyOptions, method),
2682 total_changed );
2683
2684 /* If we reach this point we are finished! - Break the Loop */
2685 break;
anthony602ab9b2010-01-05 08:06:50 +00002686 }
anthony930be612010-02-08 04:26:15 +00002687
anthony9eb4f742010-05-18 02:45:54 +00002688 /* Final Post-processing for some Compound Methods
anthony7d10d742010-05-06 07:05:29 +00002689 **
2690 ** The removal of any 'Sync' channel flag in the Image Compositon below
2691 ** ensures the compose method is applied in a purely mathematical way, only
2692 ** the selected channels, without any normal 'alpha blending' normally
2693 ** associated with the compose method.
2694 **
2695 ** Note "method" here is the 'original' morphological method, and not the
2696 ** 'current' morphological method used above to generate "new_image".
2697 */
anthony4fd27e22010-02-07 08:17:18 +00002698 switch( method ) {
2699 case EdgeOutMorphology:
2700 case EdgeInMorphology:
2701 case TopHatMorphology:
2702 case BottomHatMorphology:
anthony9eb4f742010-05-18 02:45:54 +00002703 (void) CompositeImageChannel(curr_image,
2704 (ChannelType) (channel & ~SyncChannels), DifferenceCompositeOp,
2705 image, 0, 0);
anthony4fd27e22010-02-07 08:17:18 +00002706 break;
anthony7d10d742010-05-06 07:05:29 +00002707 case EdgeMorphology:
anthony9eb4f742010-05-18 02:45:54 +00002708 /* Difference the Eroded Image with a Dilate image */
2709 (void) CompositeImageChannel(curr_image,
2710 (ChannelType) (channel & ~SyncChannels), DifferenceCompositeOp,
2711 save_image, 0, 0);
anthony4fd27e22010-02-07 08:17:18 +00002712 break;
2713 default:
2714 break;
2715 }
anthony602ab9b2010-01-05 08:06:50 +00002716
anthony9eb4f742010-05-18 02:45:54 +00002717 goto exit_cleanup;
anthony1b2bc0a2010-05-12 05:25:22 +00002718
2719 /* Yes goto's are bad, but in this case it makes cleanup lot more efficient */
2720error_cleanup:
anthony9eb4f742010-05-18 02:45:54 +00002721 if ( curr_image != (Image *) NULL && curr_image != image )
cristye96405a2010-05-19 02:24:31 +00002722 (void) DestroyImage(curr_image);
anthony1b2bc0a2010-05-12 05:25:22 +00002723exit_cleanup:
anthony9eb4f742010-05-18 02:45:54 +00002724 if ( work_image != (Image *) NULL )
cristye96405a2010-05-19 02:24:31 +00002725 (void) DestroyImage(work_image);
anthony9eb4f742010-05-18 02:45:54 +00002726 if ( save_image != (Image *) NULL )
cristye96405a2010-05-19 02:24:31 +00002727 (void) DestroyImage(save_image);
anthony9eb4f742010-05-18 02:45:54 +00002728 return(curr_image);
2729}
2730
2731/*
2732%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2733% %
2734% %
2735% %
2736% M o r p h o l o g y I m a g e C h a n n e l %
2737% %
2738% %
2739% %
2740%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2741%
2742% MorphologyImageChannel() applies a user supplied kernel to the image
2743% according to the given mophology method.
2744%
2745% This function applies any and all user defined settings before calling
2746% the above internal function MorphologyApply().
2747%
2748% User defined settings include...
2749% * convolution/correlation output bias (as per "-bias")
2750% * kernel normalization/scaling settings ("-set 'option:convolve:scale'")
2751% * kernel printing (after modification) ("-set option:showkernel 1")
2752%
2753%
2754% The format of the MorphologyImage method is:
2755%
2756% Image *MorphologyImage(const Image *image,MorphologyMethod method,
2757% const long iterations,KernelInfo *kernel,ExceptionInfo *exception)
2758%
2759% Image *MorphologyImageChannel(const Image *image, const ChannelType
2760% channel,MorphologyMethod method,const long iterations,
2761% KernelInfo *kernel,ExceptionInfo *exception)
2762%
2763% A description of each parameter follows:
2764%
2765% o image: the image.
2766%
2767% o method: the morphology method to be applied.
2768%
2769% o iterations: apply the operation this many times (or no change).
2770% A value of -1 means loop until no change found.
2771% How this is applied may depend on the morphology method.
2772% Typically this is a value of 1.
2773%
2774% o channel: the channel type.
2775%
2776% o kernel: An array of double representing the morphology kernel.
2777% Warning: kernel may be normalized for the Convolve method.
2778%
2779% o exception: return any errors or warnings in this structure.
2780%
2781*/
2782
2783MagickExport Image *MorphologyImageChannel(const Image *image,
2784 const ChannelType channel,const MorphologyMethod method,
2785 const long iterations,const KernelInfo *kernel,ExceptionInfo *exception)
2786{
2787 const char
2788 *artifact;
2789
2790 KernelInfo
2791 *curr_kernel;
2792
2793 Image
2794 *morphology_image;
2795
2796
2797 /* Apply Convolve/Correlate Normalization and Scaling Factors
2798 this is done BEFORE the ShowKernelInfo() function is called
2799 so that users can see the results of the 'convolve:scale' option.
2800 */
2801 curr_kernel = (KernelInfo *) kernel;
2802 if ( method == ConvolveMorphology )
2803 {
2804 artifact = GetImageArtifact(image,"convolve:scale");
2805 if ( artifact != (char *)NULL ) {
2806 GeometryFlags
2807 flags;
2808 GeometryInfo
2809 args;
2810
2811 if ( curr_kernel == kernel )
2812 curr_kernel = CloneKernelInfo(kernel);
2813 if (curr_kernel == (KernelInfo *) NULL) {
2814 curr_kernel=DestroyKernelInfo(curr_kernel);
2815 return((Image *) NULL);
2816 }
anthony43c49252010-05-18 10:59:50 +00002817 SetGeometryInfo(&args);
anthony9eb4f742010-05-18 02:45:54 +00002818 args.rho = 1.0;
2819 flags = (GeometryFlags) ParseGeometry(artifact, &args);
anthony43c49252010-05-18 10:59:50 +00002820
2821 /* normalize and/or scale kernel values */
anthony9eb4f742010-05-18 02:45:54 +00002822 ScaleKernelInfo(curr_kernel, args.rho, flags);
anthony43c49252010-05-18 10:59:50 +00002823
2824 /* Add percentage of Unity Kernel, for blending with original */
2825 if ( (flags & SigmaValue) != 0 )
2826 {
2827 if ( (flags & PercentValue) != 0 )
2828 args.sigma = args.sigma/100.0;
2829 UnityAddKernelInfo(curr_kernel, args.sigma);
2830 }
anthony9eb4f742010-05-18 02:45:54 +00002831 }
2832 }
2833
2834 /* display the (normalized) kernel via stderr */
2835 artifact = GetImageArtifact(image,"showkernel");
2836 if ( artifact != (const char *) NULL)
2837 ShowKernelInfo(curr_kernel);
2838
2839 /* Apply the Morphology */
2840 morphology_image = MorphologyApply(image, channel, method, iterations,
2841 curr_kernel, image->bias, exception);
2842
2843 /* Cleanup and Exit */
2844 if ( curr_kernel != kernel )
anthony1b2bc0a2010-05-12 05:25:22 +00002845 curr_kernel=DestroyKernelInfo(curr_kernel);
anthony9eb4f742010-05-18 02:45:54 +00002846 return(morphology_image);
2847}
2848
2849MagickExport Image *MorphologyImage(const Image *image, const MorphologyMethod
2850 method, const long iterations,const KernelInfo *kernel, ExceptionInfo
2851 *exception)
2852{
2853 Image
2854 *morphology_image;
2855
2856 morphology_image=MorphologyImageChannel(image,DefaultChannels,method,
2857 iterations,kernel,exception);
2858 return(morphology_image);
anthony602ab9b2010-01-05 08:06:50 +00002859}
anthony83ba99b2010-01-24 08:48:15 +00002860
2861/*
2862%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2863% %
2864% %
2865% %
anthony4fd27e22010-02-07 08:17:18 +00002866+ R o t a t e K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00002867% %
2868% %
2869% %
2870%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2871%
anthony4fd27e22010-02-07 08:17:18 +00002872% RotateKernelInfo() rotates the kernel by the angle given. Currently it is
anthony83ba99b2010-01-24 08:48:15 +00002873% restricted to 90 degree angles, but this may be improved in the future.
2874%
anthony4fd27e22010-02-07 08:17:18 +00002875% The format of the RotateKernelInfo method is:
anthony83ba99b2010-01-24 08:48:15 +00002876%
anthony4fd27e22010-02-07 08:17:18 +00002877% void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00002878%
2879% A description of each parameter follows:
2880%
2881% o kernel: the Morphology/Convolution kernel
2882%
2883% o angle: angle to rotate in degrees
2884%
anthonyc4c86e02010-01-27 09:30:32 +00002885% This function is only internel to this module, as it is not finalized,
2886% especially with regard to non-orthogonal angles, and rotation of larger
2887% 2D kernels.
anthony83ba99b2010-01-24 08:48:15 +00002888*/
anthony4fd27e22010-02-07 08:17:18 +00002889static void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00002890{
anthony1b2bc0a2010-05-12 05:25:22 +00002891 /* angle the lower kernels first */
2892 if ( kernel->next != (KernelInfo *) NULL)
2893 RotateKernelInfo(kernel->next, angle);
2894
anthony83ba99b2010-01-24 08:48:15 +00002895 /* WARNING: Currently assumes the kernel (rightly) is horizontally symetrical
2896 **
2897 ** TODO: expand beyond simple 90 degree rotates, flips and flops
2898 */
2899
2900 /* Modulus the angle */
2901 angle = fmod(angle, 360.0);
2902 if ( angle < 0 )
2903 angle += 360.0;
2904
anthony3c10fc82010-05-13 02:40:51 +00002905 if ( 337.5 < angle || angle <= 22.5 )
anthony43c49252010-05-18 10:59:50 +00002906 return; /* Near zero angle - no change! - At least not at this time */
anthony83ba99b2010-01-24 08:48:15 +00002907
anthony3dd0f622010-05-13 12:57:32 +00002908 /* Handle special cases */
anthony83ba99b2010-01-24 08:48:15 +00002909 switch (kernel->type) {
2910 /* These built-in kernels are cylindrical kernels, rotating is useless */
2911 case GaussianKernel:
anthony83ba99b2010-01-24 08:48:15 +00002912 case DOGKernel:
2913 case DiskKernel:
anthony3dd0f622010-05-13 12:57:32 +00002914 case PeaksKernel:
2915 case LaplacianKernel:
anthony83ba99b2010-01-24 08:48:15 +00002916 case ChebyshevKernel:
2917 case ManhattenKernel:
2918 case EuclideanKernel:
2919 return;
2920
2921 /* These may be rotatable at non-90 angles in the future */
2922 /* but simply rotating them in multiples of 90 degrees is useless */
2923 case SquareKernel:
2924 case DiamondKernel:
2925 case PlusKernel:
anthony3dd0f622010-05-13 12:57:32 +00002926 case CrossKernel:
anthony83ba99b2010-01-24 08:48:15 +00002927 return;
2928
2929 /* These only allows a +/-90 degree rotation (by transpose) */
2930 /* A 180 degree rotation is useless */
2931 case BlurKernel:
2932 case RectangleKernel:
2933 if ( 135.0 < angle && angle <= 225.0 )
2934 return;
2935 if ( 225.0 < angle && angle <= 315.0 )
2936 angle -= 180;
2937 break;
2938
anthony3dd0f622010-05-13 12:57:32 +00002939 default:
anthony83ba99b2010-01-24 08:48:15 +00002940 break;
2941 }
anthony3c10fc82010-05-13 02:40:51 +00002942 /* Attempt rotations by 45 degrees */
2943 if ( 22.5 < fmod(angle,90.0) && fmod(angle,90.0) <= 67.5 )
2944 {
2945 if ( kernel->width == 3 && kernel->height == 3 )
2946 { /* Rotate a 3x3 square by 45 degree angle */
2947 MagickRealType t = kernel->values[0];
anthony43c49252010-05-18 10:59:50 +00002948 kernel->values[0] = kernel->values[3];
2949 kernel->values[3] = kernel->values[6];
2950 kernel->values[6] = kernel->values[7];
2951 kernel->values[7] = kernel->values[8];
2952 kernel->values[8] = kernel->values[5];
2953 kernel->values[5] = kernel->values[2];
2954 kernel->values[2] = kernel->values[1];
2955 kernel->values[1] = t;
2956 /* NOT DONE - rotate a off-centered origin as well! */
2957 angle = fmod(angle+315.0, 360.0); /* angle reduced 45 degrees */
2958 kernel->angle = fmod(kernel->angle+45.0, 360.0);
anthony3c10fc82010-05-13 02:40:51 +00002959 }
2960 else
2961 perror("Unable to rotate non-3x3 kernel by 45 degrees");
2962 }
2963 if ( 45.0 < fmod(angle, 180.0) && fmod(angle,180.0) <= 135.0 )
2964 {
2965 if ( kernel->width == 1 || kernel->height == 1 )
2966 { /* Do a transpose of the image, which results in a 90
2967 ** degree rotation of a 1 dimentional kernel
2968 */
2969 long
2970 t;
2971 t = (long) kernel->width;
2972 kernel->width = kernel->height;
2973 kernel->height = (unsigned long) t;
2974 t = kernel->x;
2975 kernel->x = kernel->y;
2976 kernel->y = t;
anthony43c49252010-05-18 10:59:50 +00002977 if ( kernel->width == 1 ) {
2978 angle = fmod(angle+270.0, 360.0); /* angle reduced 90 degrees */
2979 kernel->angle = fmod(kernel->angle+90.0, 360.0);
2980 } else {
2981 angle = fmod(angle+90.0, 360.0); /* angle increased 90 degrees */
2982 kernel->angle = fmod(kernel->angle+270.0, 360.0);
2983 }
anthony3c10fc82010-05-13 02:40:51 +00002984 }
2985 else if ( kernel->width == kernel->height )
2986 { /* Rotate a square array of values by 90 degrees */
2987 register unsigned long
2988 i,j,x,y;
2989 register MagickRealType
2990 *k,t;
2991 k=kernel->values;
2992 for( i=0, x=kernel->width-1; i<=x; i++, x--)
2993 for( j=0, y=kernel->height-1; j<y; j++, y--)
2994 { t = k[i+j*kernel->width];
anthony43c49252010-05-18 10:59:50 +00002995 k[i+j*kernel->width] = k[j+x*kernel->width];
2996 k[j+x*kernel->width] = k[x+y*kernel->width];
2997 k[x+y*kernel->width] = k[y+i*kernel->width];
2998 k[y+i*kernel->width] = t;
anthony3c10fc82010-05-13 02:40:51 +00002999 }
anthony43c49252010-05-18 10:59:50 +00003000 /* NOT DONE - rotate a off-centered origin as well! */
3001 angle = fmod(angle+270.0, 360.0); /* angle reduced 90 degrees */
3002 kernel->angle = fmod(kernel->angle+90.0, 360.0);
anthony3c10fc82010-05-13 02:40:51 +00003003 }
3004 else
3005 perror("Unable to rotate a non-square, non-linear kernel 90 degrees");
3006 }
anthony83ba99b2010-01-24 08:48:15 +00003007 if ( 135.0 < angle && angle <= 225.0 )
3008 {
anthony43c49252010-05-18 10:59:50 +00003009 /* For a 180 degree rotation - also know as a reflection
3010 * This is actually a very very common operation!
3011 * Basically all that is needed is a reversal of the kernel data!
3012 * And a reflection of the origon
3013 */
anthony83ba99b2010-01-24 08:48:15 +00003014 unsigned long
3015 i,j;
3016 register double
3017 *k,t;
3018
3019 k=kernel->values;
3020 for ( i=0, j=kernel->width*kernel->height-1; i<j; i++, j--)
3021 t=k[i], k[i]=k[j], k[j]=t;
3022
anthony930be612010-02-08 04:26:15 +00003023 kernel->x = (long) kernel->width - kernel->x - 1;
3024 kernel->y = (long) kernel->height - kernel->y - 1;
anthony43c49252010-05-18 10:59:50 +00003025 angle = fmod(angle-180.0, 360.0); /* angle+180 degrees */
3026 kernel->angle = fmod(kernel->angle+180.0, 360.0);
anthony83ba99b2010-01-24 08:48:15 +00003027 }
anthony3c10fc82010-05-13 02:40:51 +00003028 /* At this point angle should at least between -45 (315) and +45 degrees
anthony83ba99b2010-01-24 08:48:15 +00003029 * In the future some form of non-orthogonal angled rotates could be
3030 * performed here, posibily with a linear kernel restriction.
3031 */
3032
3033#if 0
anthony3c10fc82010-05-13 02:40:51 +00003034 { /* Do a Flop by reversing each row.
anthony83ba99b2010-01-24 08:48:15 +00003035 */
3036 unsigned long
3037 y;
cristy150989e2010-02-01 14:59:39 +00003038 register long
anthony83ba99b2010-01-24 08:48:15 +00003039 x,r;
3040 register double
3041 *k,t;
3042
3043 for ( y=0, k=kernel->values; y < kernel->height; y++, k+=kernel->width)
3044 for ( x=0, r=kernel->width-1; x<kernel->width/2; x++, r--)
3045 t=k[x], k[x]=k[r], k[r]=t;
3046
cristyc99304f2010-02-01 15:26:27 +00003047 kernel->x = kernel->width - kernel->x - 1;
anthony83ba99b2010-01-24 08:48:15 +00003048 angle = fmod(angle+180.0, 360.0);
3049 }
3050#endif
3051 return;
3052}
3053
3054/*
3055%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3056% %
3057% %
3058% %
cristy6771f1e2010-03-05 19:43:39 +00003059% S c a l e K e r n e l I n f o %
anthonycc6c8362010-01-25 04:14:01 +00003060% %
3061% %
3062% %
3063%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3064%
anthony1b2bc0a2010-05-12 05:25:22 +00003065% ScaleKernelInfo() scales the given kernel list by the given amount, with or
3066% without normalization of the sum of the kernel values (as per given flags).
anthonycc6c8362010-01-25 04:14:01 +00003067%
anthony999bb2c2010-02-18 12:38:01 +00003068% By default (no flags given) the values within the kernel is scaled
anthony1b2bc0a2010-05-12 05:25:22 +00003069% directly using given scaling factor without change.
anthonycc6c8362010-01-25 04:14:01 +00003070%
anthony1b2bc0a2010-05-12 05:25:22 +00003071% If any 'normalize_flags' are given the kernel will first be normalized and
3072% then further scaled by the scaling factor value given. A 'PercentValue'
3073% flag will cause the given scaling factor to be divided by one hundred
3074% percent.
anthony999bb2c2010-02-18 12:38:01 +00003075%
3076% Kernel normalization ('normalize_flags' given) is designed to ensure that
3077% any use of the kernel scaling factor with 'Convolve' or 'Correlate'
anthony1b2bc0a2010-05-12 05:25:22 +00003078% morphology methods will fall into -1.0 to +1.0 range. Note that for
3079% non-HDRI versions of IM this may cause images to have any negative results
3080% clipped, unless some 'bias' is used.
anthony999bb2c2010-02-18 12:38:01 +00003081%
3082% More specifically. Kernels which only contain positive values (such as a
3083% 'Gaussian' kernel) will be scaled so that those values sum to +1.0,
anthony1b2bc0a2010-05-12 05:25:22 +00003084% ensuring a 0.0 to +1.0 output range for non-HDRI images.
anthony999bb2c2010-02-18 12:38:01 +00003085%
3086% For Kernels that contain some negative values, (such as 'Sharpen' kernels)
3087% the kernel will be scaled by the absolute of the sum of kernel values, so
3088% that it will generally fall within the +/- 1.0 range.
3089%
3090% For kernels whose values sum to zero, (such as 'Laplician' kernels) kernel
3091% will be scaled by just the sum of the postive values, so that its output
3092% range will again fall into the +/- 1.0 range.
3093%
3094% For special kernels designed for locating shapes using 'Correlate', (often
3095% only containing +1 and -1 values, representing foreground/brackground
3096% matching) a special normalization method is provided to scale the positive
3097% values seperatally to those of the negative values, so the kernel will be
3098% forced to become a zero-sum kernel better suited to such searches.
3099%
anthony1b2bc0a2010-05-12 05:25:22 +00003100% WARNING: Correct normalization of the kernel assumes that the '*_range'
anthony999bb2c2010-02-18 12:38:01 +00003101% attributes within the kernel structure have been correctly set during the
3102% kernels creation.
3103%
3104% NOTE: The values used for 'normalize_flags' have been selected specifically
anthony1b2bc0a2010-05-12 05:25:22 +00003105% to match the use of geometry options, so that '!' means NormalizeValue,
3106% '^' means CorrelateNormalizeValue, and '%' means PercentValue. All other
anthony999bb2c2010-02-18 12:38:01 +00003107% GeometryFlags values are ignored.
anthonycc6c8362010-01-25 04:14:01 +00003108%
anthony4fd27e22010-02-07 08:17:18 +00003109% The format of the ScaleKernelInfo method is:
anthonycc6c8362010-01-25 04:14:01 +00003110%
anthony999bb2c2010-02-18 12:38:01 +00003111% void ScaleKernelInfo(KernelInfo *kernel, const double scaling_factor,
3112% const MagickStatusType normalize_flags )
anthonycc6c8362010-01-25 04:14:01 +00003113%
3114% A description of each parameter follows:
3115%
3116% o kernel: the Morphology/Convolution kernel
3117%
anthony999bb2c2010-02-18 12:38:01 +00003118% o scaling_factor:
3119% multiply all values (after normalization) by this factor if not
3120% zero. If the kernel is normalized regardless of any flags.
3121%
3122% o normalize_flags:
3123% GeometryFlags defining normalization method to use.
3124% specifically: NormalizeValue, CorrelateNormalizeValue,
3125% and/or PercentValue
anthonycc6c8362010-01-25 04:14:01 +00003126%
anthonyc4c86e02010-01-27 09:30:32 +00003127% This function is internal to this module only at this time, but can be
3128% exported to other modules if needed.
anthonycc6c8362010-01-25 04:14:01 +00003129*/
cristy6771f1e2010-03-05 19:43:39 +00003130MagickExport void ScaleKernelInfo(KernelInfo *kernel,
3131 const double scaling_factor,const GeometryFlags normalize_flags)
anthonycc6c8362010-01-25 04:14:01 +00003132{
cristy150989e2010-02-01 14:59:39 +00003133 register long
anthonycc6c8362010-01-25 04:14:01 +00003134 i;
3135
anthony999bb2c2010-02-18 12:38:01 +00003136 register double
3137 pos_scale,
3138 neg_scale;
3139
anthony1b2bc0a2010-05-12 05:25:22 +00003140 /* scale the lower kernels first */
3141 if ( kernel->next != (KernelInfo *) NULL)
3142 ScaleKernelInfo(kernel->next, scaling_factor, normalize_flags);
3143
anthony999bb2c2010-02-18 12:38:01 +00003144 pos_scale = 1.0;
3145 if ( (normalize_flags&NormalizeValue) != 0 ) {
3146 /* normalize kernel appropriately */
3147 if ( fabs(kernel->positive_range + kernel->negative_range) > MagickEpsilon )
3148 pos_scale = fabs(kernel->positive_range + kernel->negative_range);
anthonycc6c8362010-01-25 04:14:01 +00003149 else
anthony999bb2c2010-02-18 12:38:01 +00003150 pos_scale = kernel->positive_range; /* special zero-summing kernel */
3151 }
3152 /* force kernel into being a normalized zero-summing kernel */
3153 if ( (normalize_flags&CorrelateNormalizeValue) != 0 ) {
3154 pos_scale = ( fabs(kernel->positive_range) > MagickEpsilon )
3155 ? kernel->positive_range : 1.0;
3156 neg_scale = ( fabs(kernel->negative_range) > MagickEpsilon )
3157 ? -kernel->negative_range : 1.0;
3158 }
3159 else
3160 neg_scale = pos_scale;
3161
3162 /* finialize scaling_factor for positive and negative components */
3163 pos_scale = scaling_factor/pos_scale;
3164 neg_scale = scaling_factor/neg_scale;
3165 if ( (normalize_flags&PercentValue) != 0 ) {
3166 pos_scale /= 100.0;
3167 neg_scale /= 100.0;
anthonycc6c8362010-01-25 04:14:01 +00003168 }
3169
cristy150989e2010-02-01 14:59:39 +00003170 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00003171 if ( ! IsNan(kernel->values[i]) )
anthony999bb2c2010-02-18 12:38:01 +00003172 kernel->values[i] *= (kernel->values[i] >= 0) ? pos_scale : neg_scale;
anthonycc6c8362010-01-25 04:14:01 +00003173
anthony999bb2c2010-02-18 12:38:01 +00003174 /* convolution output range */
3175 kernel->positive_range *= pos_scale;
3176 kernel->negative_range *= neg_scale;
3177 /* maximum and minimum values in kernel */
3178 kernel->maximum *= (kernel->maximum >= 0.0) ? pos_scale : neg_scale;
3179 kernel->minimum *= (kernel->minimum >= 0.0) ? pos_scale : neg_scale;
3180
3181 /* swap kernel settings if user scaling factor is negative */
3182 if ( scaling_factor < MagickEpsilon ) {
3183 double t;
3184 t = kernel->positive_range;
3185 kernel->positive_range = kernel->negative_range;
3186 kernel->negative_range = t;
3187 t = kernel->maximum;
3188 kernel->maximum = kernel->minimum;
3189 kernel->minimum = 1;
3190 }
anthonycc6c8362010-01-25 04:14:01 +00003191
3192 return;
3193}
3194
3195/*
3196%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3197% %
3198% %
3199% %
anthony4fd27e22010-02-07 08:17:18 +00003200+ S h o w K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00003201% %
3202% %
3203% %
3204%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3205%
anthony4fd27e22010-02-07 08:17:18 +00003206% ShowKernelInfo() outputs the details of the given kernel defination to
3207% standard error, generally due to a users 'showkernel' option request.
anthony83ba99b2010-01-24 08:48:15 +00003208%
3209% The format of the ShowKernel method is:
3210%
anthony4fd27e22010-02-07 08:17:18 +00003211% void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00003212%
3213% A description of each parameter follows:
3214%
3215% o kernel: the Morphology/Convolution kernel
3216%
anthonyc4c86e02010-01-27 09:30:32 +00003217% This function is internal to this module only at this time. That may change
3218% in the future.
anthony83ba99b2010-01-24 08:48:15 +00003219*/
anthony4fd27e22010-02-07 08:17:18 +00003220MagickExport void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00003221{
anthony7a01dcf2010-05-11 12:25:52 +00003222 KernelInfo
3223 *k;
anthony83ba99b2010-01-24 08:48:15 +00003224
anthony43c49252010-05-18 10:59:50 +00003225 unsigned long
anthony7a01dcf2010-05-11 12:25:52 +00003226 c, i, u, v;
3227
3228 for (c=0, k=kernel; k != (KernelInfo *) NULL; c++, k=k->next ) {
3229
3230 fprintf(stderr, "Kernel ");
3231 if ( kernel->next != (KernelInfo *) NULL )
cristye96405a2010-05-19 02:24:31 +00003232 fprintf(stderr, " #%lu", c );
anthony43c49252010-05-18 10:59:50 +00003233 fprintf(stderr, " \"%s",
3234 MagickOptionToMnemonic(MagickKernelOptions, k->type) );
3235 if ( fabs(k->angle) > MagickEpsilon )
3236 fprintf(stderr, "@%lg", k->angle);
3237 fprintf(stderr, "\" of size %lux%lu%+ld%+ld ",
3238 k->width, k->height,
3239 k->x, k->y );
anthony7a01dcf2010-05-11 12:25:52 +00003240 fprintf(stderr,
3241 " with values from %.*lg to %.*lg\n",
3242 GetMagickPrecision(), k->minimum,
3243 GetMagickPrecision(), k->maximum);
3244 fprintf(stderr, "Forming convolution output range from %.*lg to %.*lg%s\n",
3245 GetMagickPrecision(), k->negative_range,
3246 GetMagickPrecision(), k->positive_range,
3247 /*kernel->normalized == MagickTrue ? " (normalized)" : */ "" );
anthony43c49252010-05-18 10:59:50 +00003248 for (i=v=0; v < k->height; v++) {
cristye96405a2010-05-19 02:24:31 +00003249 fprintf(stderr,"%2lu:",v);
anthony43c49252010-05-18 10:59:50 +00003250 for (u=0; u < k->width; u++, i++)
anthony7a01dcf2010-05-11 12:25:52 +00003251 if ( IsNan(k->values[i]) )
3252 fprintf(stderr," %*s", GetMagickPrecision()+2, "nan");
3253 else
3254 fprintf(stderr," %*.*lg", GetMagickPrecision()+2,
3255 GetMagickPrecision(), k->values[i]);
3256 fprintf(stderr,"\n");
3257 }
anthony83ba99b2010-01-24 08:48:15 +00003258 }
3259}
anthonycc6c8362010-01-25 04:14:01 +00003260
3261/*
3262%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3263% %
3264% %
3265% %
anthony43c49252010-05-18 10:59:50 +00003266% U n i t y A d d K e r n a l I n f o %
3267% %
3268% %
3269% %
3270%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3271%
3272% UnityAddKernelInfo() Adds a given amount of the 'Unity' Convolution Kernel
3273% to the given pre-scaled and normalized Kernel. This in effect adds that
3274% amount of the original image into the resulting convolution kernel. This
3275% value is usually provided by the user as a percentage value in the
3276% 'convolve:scale' setting.
3277%
3278% The resulting effect is to either convert a 'zero-summing' edge detection
3279% kernel (such as a "Laplacian", "DOG" or a "LOG") into a 'sharpening'
3280% kernel.
3281%
3282% Alternativally by using a purely positive kernel, and using a negative
3283% post-normalizing scaling factor, you can convert a 'blurring' kernel (such
3284% as a "Gaussian") into a 'unsharp' kernel.
3285%
3286% The format of the ScaleKernelInfo method is:
3287%
3288% void UnityAdditionKernelInfo(KernelInfo *kernel, const double scale )
3289%
3290% A description of each parameter follows:
3291%
3292% o kernel: the Morphology/Convolution kernel
3293%
3294% o scale:
3295% scaling factor for the unity kernel to be added to
3296% the given kernel.
3297%
3298% This function is currently internal to this module only at this time, but
3299% can be exported to other modules if needed.
3300%
3301*/
3302MagickExport void UnityAddKernelInfo(KernelInfo *kernel,
3303 const double scale)
3304{
3305 register unsigned long
3306 i;
3307
3308 kernel->values[kernel->x+kernel->y*kernel->width] += scale;
3309
3310 /* make sure kernel meta-data is now correct */
3311 kernel->minimum = kernel->maximum = 0.0;
3312 kernel->negative_range = kernel->positive_range = 0.0;
3313 for (i=0; i < (kernel->width*kernel->height); i++)
3314 {
3315 if ( fabs(kernel->values[i]) < MagickEpsilon )
3316 kernel->values[i] = 0.0;
3317 ( kernel->values[i] < 0)
3318 ? ( kernel->negative_range += kernel->values[i] )
3319 : ( kernel->positive_range += kernel->values[i] );
3320 Minimize(kernel->minimum, kernel->values[i]);
3321 Maximize(kernel->maximum, kernel->values[i]);
3322 }
3323
3324 return;
3325}
3326
3327/*
3328%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3329% %
3330% %
3331% %
3332% Z e r o K e r n e l N a n s %
anthonycc6c8362010-01-25 04:14:01 +00003333% %
3334% %
3335% %
3336%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3337%
3338% ZeroKernelNans() replaces any special 'nan' value that may be present in
3339% the kernel with a zero value. This is typically done when the kernel will
3340% be used in special hardware (GPU) convolution processors, to simply
3341% matters.
3342%
3343% The format of the ZeroKernelNans method is:
3344%
3345% voidZeroKernelNans (KernelInfo *kernel)
3346%
3347% A description of each parameter follows:
3348%
3349% o kernel: the Morphology/Convolution kernel
3350%
anthonycc6c8362010-01-25 04:14:01 +00003351*/
anthonyc4c86e02010-01-27 09:30:32 +00003352MagickExport void ZeroKernelNans(KernelInfo *kernel)
anthonycc6c8362010-01-25 04:14:01 +00003353{
anthony43c49252010-05-18 10:59:50 +00003354 register unsigned long
anthonycc6c8362010-01-25 04:14:01 +00003355 i;
3356
anthony1b2bc0a2010-05-12 05:25:22 +00003357 /* scale the lower kernels first */
3358 if ( kernel->next != (KernelInfo *) NULL)
3359 ZeroKernelNans(kernel->next);
3360
anthony43c49252010-05-18 10:59:50 +00003361 for (i=0; i < (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00003362 if ( IsNan(kernel->values[i]) )
3363 kernel->values[i] = 0.0;
3364
3365 return;
3366}