blob: 4179cb4e0b60fc9691b9056f3e5132b448a4cc11 [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 */
798 switch(type) {
anthony9eb4f742010-05-18 02:45:54 +0000799 case UndefinedKernel: /* These should not be used here */
800 case UserDefinedKernel:
801 break;
802 case LaplacianKernel: /* Named Descrete Convolution Kernels */
803 case SobelKernel:
804 case RobertsKernel:
805 case PrewittKernel:
806 case CompassKernel:
807 case KirschKernel:
808 case CornersKernel: /* Hit and Miss kernels */
809 case LineEndsKernel:
810 case LineJunctionsKernel:
anthony9eb4f742010-05-18 02:45:54 +0000811 case ThinningKernel:
812 case ConvexHullKernel:
813 case SkeletonKernel:
814 /* A pre-generated kernel is not needed */
815 break;
816#if 0
anthonyc1061722010-05-14 06:23:49 +0000817 case GaussianKernel:
818 case DOGKernel:
819 case BlurKernel:
820 case DOBKernel:
821 case CometKernel:
822 case DiamondKernel:
823 case SquareKernel:
824 case RectangleKernel:
825 case DiskKernel:
826 case PlusKernel:
827 case CrossKernel:
828 case RingKernel:
829 case PeaksKernel:
830 case ChebyshevKernel:
831 case ManhattenKernel:
832 case EuclideanKernel:
anthony9eb4f742010-05-18 02:45:54 +0000833#endif
834 default:
835 /* Generate the base Kernel Structure */
anthonyc1061722010-05-14 06:23:49 +0000836 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
837 if (kernel == (KernelInfo *) NULL)
838 return(kernel);
839 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
anthony43c49252010-05-18 10:59:50 +0000840 kernel->minimum = kernel->maximum = kernel->angle = 0.0;
anthonyc1061722010-05-14 06:23:49 +0000841 kernel->negative_range = kernel->positive_range = 0.0;
842 kernel->type = type;
843 kernel->next = (KernelInfo *) NULL;
844 kernel->signature = MagickSignature;
anthonyc1061722010-05-14 06:23:49 +0000845 break;
846 }
anthony602ab9b2010-01-05 08:06:50 +0000847
848 switch(type) {
849 /* Convolution Kernels */
850 case GaussianKernel:
anthonyc1061722010-05-14 06:23:49 +0000851 case DOGKernel:
anthony9eb4f742010-05-18 02:45:54 +0000852 case LOGKernel:
anthony602ab9b2010-01-05 08:06:50 +0000853 { double
anthonyc1061722010-05-14 06:23:49 +0000854 sigma = fabs(args->sigma),
855 sigma2 = fabs(args->xi),
anthony9eb4f742010-05-18 02:45:54 +0000856 A, B, R;
anthony602ab9b2010-01-05 08:06:50 +0000857
anthonyc1061722010-05-14 06:23:49 +0000858 if ( args->rho >= 1.0 )
859 kernel->width = (unsigned long)args->rho*2+1;
anthony9eb4f742010-05-18 02:45:54 +0000860 else if ( (type != DOGKernel) || (sigma >= sigma2) )
anthonyc1061722010-05-14 06:23:49 +0000861 kernel->width = GetOptimalKernelWidth2D(args->rho,sigma);
862 else
863 kernel->width = GetOptimalKernelWidth2D(args->rho,sigma2);
864 kernel->height = kernel->width;
cristyc99304f2010-02-01 15:26:27 +0000865 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000866 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
867 kernel->height*sizeof(double));
868 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000869 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000870
anthony9eb4f742010-05-18 02:45:54 +0000871 /* The following generates a 'sampled gaussian' kernel.
872 * What we really want is a 'discrete gaussian' kernel.
873 */
874
875 if ( type == GaussianKernel || type == DOGKernel )
876 { /* Calculate a Gaussian, OR positive half of a DOG */
877 if ( sigma > MagickEpsilon )
878 { A = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
879 B = 1.0/(Magick2PI*sigma*sigma);
880 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
881 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
882 kernel->values[i] = exp(-((double)(u*u+v*v))*A)*B;
883 }
884 else /* limiting case - a unity (normalized Dirac) kernel */
885 { (void) ResetMagickMemory(kernel->values,0, (size_t)
886 kernel->width*kernel->height*sizeof(double));
887 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
888 }
anthonyc1061722010-05-14 06:23:49 +0000889 }
anthony9eb4f742010-05-18 02:45:54 +0000890
anthonyc1061722010-05-14 06:23:49 +0000891 if ( type == DOGKernel )
892 { /* Subtract a Negative Gaussian for "Difference of Gaussian" */
893 if ( sigma2 > MagickEpsilon )
894 { sigma = sigma2; /* simplify loop expressions */
anthony9eb4f742010-05-18 02:45:54 +0000895 A = 1.0/(2.0*sigma*sigma);
896 B = 1.0/(Magick2PI*sigma*sigma);
anthonyc1061722010-05-14 06:23:49 +0000897 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
898 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony9eb4f742010-05-18 02:45:54 +0000899 kernel->values[i] -= exp(-((double)(u*u+v*v))*A)*B;
anthonyc1061722010-05-14 06:23:49 +0000900 }
anthony9eb4f742010-05-18 02:45:54 +0000901 else /* limiting case - a unity (normalized Dirac) kernel */
anthonyc1061722010-05-14 06:23:49 +0000902 kernel->values[kernel->x+kernel->y*kernel->width] -= 1.0;
903 }
anthony9eb4f742010-05-18 02:45:54 +0000904
905 if ( type == LOGKernel )
906 { /* Calculate a Laplacian of a Gaussian - Or Mexician Hat */
907 if ( sigma > MagickEpsilon )
908 { A = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
909 B = 1.0/(MagickPI*sigma*sigma*sigma*sigma);
910 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
911 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
912 { R = ((double)(u*u+v*v))*A;
913 kernel->values[i] = (1-R)*exp(-R)*B;
914 }
915 }
916 else /* special case - generate a unity kernel */
917 { (void) ResetMagickMemory(kernel->values,0, (size_t)
918 kernel->width*kernel->height*sizeof(double));
919 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
920 }
921 }
922
923 /* Note the above kernels may have been 'clipped' by a user defined
anthonyc1061722010-05-14 06:23:49 +0000924 ** radius, producing a smaller (darker) kernel. Also for very small
925 ** sigma's (> 0.1) the central value becomes larger than one, and thus
926 ** producing a very bright kernel.
927 **
928 ** Normalization will still be needed.
929 */
anthony602ab9b2010-01-05 08:06:50 +0000930
anthonyc1061722010-05-14 06:23:49 +0000931 /* Work out the meta-data about kernel */
932 kernel->minimum = kernel->maximum = 0.0;
933 kernel->negative_range = kernel->positive_range = 0.0;
934 u=(long) kernel->width*kernel->height;
935 for ( i=0; i < u; i++)
936 {
937 if ( fabs(kernel->values[i]) < MagickEpsilon )
938 kernel->values[i] = 0.0;
939 ( kernel->values[i] < 0)
940 ? ( kernel->negative_range += kernel->values[i] )
941 : ( kernel->positive_range += kernel->values[i] );
942 Minimize(kernel->minimum, kernel->values[i]);
943 Maximize(kernel->maximum, kernel->values[i]);
944 }
anthony3dd0f622010-05-13 12:57:32 +0000945 /* Normalize the 2D Gaussian Kernel
946 **
anthonyc1061722010-05-14 06:23:49 +0000947 ** NB: a CorrelateNormalize performs a normal Normalize if
948 ** there are no negative values.
anthony3dd0f622010-05-13 12:57:32 +0000949 */
anthonyc1061722010-05-14 06:23:49 +0000950 ScaleKernelInfo(kernel, 1.0, CorrelateNormalizeValue);
anthony602ab9b2010-01-05 08:06:50 +0000951
952 break;
953 }
954 case BlurKernel:
anthonyc1061722010-05-14 06:23:49 +0000955 case DOBKernel:
anthony602ab9b2010-01-05 08:06:50 +0000956 { double
anthonyc1061722010-05-14 06:23:49 +0000957 sigma = fabs(args->sigma),
958 sigma2 = fabs(args->xi),
anthony9eb4f742010-05-18 02:45:54 +0000959 A, B;
anthony602ab9b2010-01-05 08:06:50 +0000960
anthonyc1061722010-05-14 06:23:49 +0000961 if ( args->rho >= 1.0 )
962 kernel->width = (unsigned long)args->rho*2+1;
963 else if ( (type == BlurKernel) || (sigma >= sigma2) )
964 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
965 else
966 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma2);
anthony602ab9b2010-01-05 08:06:50 +0000967 kernel->height = 1;
anthonyc1061722010-05-14 06:23:49 +0000968 kernel->x = (long) (kernel->width-1)/2;
cristyc99304f2010-02-01 15:26:27 +0000969 kernel->y = 0;
970 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000971 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
972 kernel->height*sizeof(double));
973 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000974 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000975
976#if 1
977#define KernelRank 3
978 /* Formula derived from GetBlurKernel() in "effect.c" (plus bug fix).
979 ** It generates a gaussian 3 times the width, and compresses it into
980 ** the expected range. This produces a closer normalization of the
981 ** resulting kernel, especially for very low sigma values.
982 ** As such while wierd it is prefered.
983 **
984 ** I am told this method originally came from Photoshop.
anthony9eb4f742010-05-18 02:45:54 +0000985 **
986 ** A properly normalized curve is generated (apart from edge clipping)
987 ** even though we later normalize the result (for edge clipping)
988 ** to allow the correct generation of a "Difference of Blurs".
anthony602ab9b2010-01-05 08:06:50 +0000989 */
anthonyc1061722010-05-14 06:23:49 +0000990
991 /* initialize */
cristy150989e2010-02-01 14:59:39 +0000992 v = (long) (kernel->width*KernelRank-1)/2; /* start/end points to fit range */
anthony9eb4f742010-05-18 02:45:54 +0000993 (void) ResetMagickMemory(kernel->values,0, (size_t)
994 kernel->width*kernel->height*sizeof(double));
anthonyc1061722010-05-14 06:23:49 +0000995 /* Calculate a Positive 1D Gaussian */
996 if ( sigma > MagickEpsilon )
997 { sigma *= KernelRank; /* simplify loop expressions */
anthony9eb4f742010-05-18 02:45:54 +0000998 A = 1.0/(2.0*sigma*sigma);
999 B = 1.0/(MagickSQ2PI*sigma );
anthonyc1061722010-05-14 06:23:49 +00001000 for ( u=-v; u <= v; u++) {
anthony9eb4f742010-05-18 02:45:54 +00001001 kernel->values[(u+v)/KernelRank] += exp(-((double)(u*u))*A)*B;
anthonyc1061722010-05-14 06:23:49 +00001002 }
1003 }
1004 else /* special case - generate a unity kernel */
1005 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
anthony9eb4f742010-05-18 02:45:54 +00001006
1007 /* Subtract a Second 1D Gaussian for "Difference of Blur" */
anthonyc1061722010-05-14 06:23:49 +00001008 if ( type == DOBKernel )
anthony9eb4f742010-05-18 02:45:54 +00001009 {
anthonyc1061722010-05-14 06:23:49 +00001010 if ( sigma2 > MagickEpsilon )
1011 { sigma = sigma2*KernelRank; /* simplify loop expressions */
anthony9eb4f742010-05-18 02:45:54 +00001012 A = 1.0/(2.0*sigma*sigma);
1013 B = 1.0/(MagickSQ2PI*sigma);
anthonyc1061722010-05-14 06:23:49 +00001014 for ( u=-v; u <= v; u++)
anthony9eb4f742010-05-18 02:45:54 +00001015 kernel->values[(u+v)/KernelRank] -= exp(-((double)(u*u))*A)*B;
anthonyc1061722010-05-14 06:23:49 +00001016 }
anthony9eb4f742010-05-18 02:45:54 +00001017 else /* limiting case - a unity (normalized Dirac) kernel */
anthonyc1061722010-05-14 06:23:49 +00001018 kernel->values[kernel->x+kernel->y*kernel->width] -= 1.0;
1019 }
anthony602ab9b2010-01-05 08:06:50 +00001020#else
anthonyc1061722010-05-14 06:23:49 +00001021 /* Direct calculation without curve averaging */
1022
1023 /* Calculate a Positive Gaussian */
1024 if ( sigma > MagickEpsilon )
anthony9eb4f742010-05-18 02:45:54 +00001025 { A = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
1026 B = 1.0/(MagickSQ2PI*sigma);
anthonyc1061722010-05-14 06:23:49 +00001027 for ( i=0, u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony9eb4f742010-05-18 02:45:54 +00001028 kernel->values[i] = exp(-((double)(u*u))*A)*B;
anthonyc1061722010-05-14 06:23:49 +00001029 }
1030 else /* special case - generate a unity kernel */
1031 { (void) ResetMagickMemory(kernel->values,0, (size_t)
1032 kernel->width*kernel->height*sizeof(double));
1033 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1034 }
anthony9eb4f742010-05-18 02:45:54 +00001035
1036 /* Subtract a Second 1D Gaussian for "Difference of Blur" */
anthonyc1061722010-05-14 06:23:49 +00001037 if ( type == DOBKernel )
anthony9eb4f742010-05-18 02:45:54 +00001038 {
anthonyc1061722010-05-14 06:23:49 +00001039 if ( sigma2 > MagickEpsilon )
1040 { sigma = sigma2; /* simplify loop expressions */
anthony9eb4f742010-05-18 02:45:54 +00001041 A = 1.0/(2.0*sigma*sigma);
1042 B = 1.0/(MagickSQ2PI*sigma);
anthonyc1061722010-05-14 06:23:49 +00001043 for ( i=0, u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony9eb4f742010-05-18 02:45:54 +00001044 kernel->values[i] -= exp(-((double)(u*u))*A)*B;
anthonyc1061722010-05-14 06:23:49 +00001045 }
anthony9eb4f742010-05-18 02:45:54 +00001046 else /* limiting case - a unity (normalized Dirac) kernel */
anthonyc1061722010-05-14 06:23:49 +00001047 kernel->values[kernel->x+kernel->y*kernel->width] -= 1.0;
1048 }
anthony602ab9b2010-01-05 08:06:50 +00001049#endif
anthonyc1061722010-05-14 06:23:49 +00001050 /* Note the above kernel may have been 'clipped' by a user defined
anthonycc6c8362010-01-25 04:14:01 +00001051 ** radius, producing a smaller (darker) kernel. Also for very small
1052 ** sigma's (> 0.1) the central value becomes larger than one, and thus
1053 ** producing a very bright kernel.
anthonyc1061722010-05-14 06:23:49 +00001054 **
1055 ** Normalization will still be needed.
anthony602ab9b2010-01-05 08:06:50 +00001056 */
anthonycc6c8362010-01-25 04:14:01 +00001057
anthonyc1061722010-05-14 06:23:49 +00001058 /* Work out the meta-data about kernel */
1059 for ( i=0; i < (long) kernel->width; i++)
1060 {
anthonyc1061722010-05-14 06:23:49 +00001061 ( kernel->values[i] < 0)
1062 ? ( kernel->negative_range += kernel->values[i] )
1063 : ( kernel->positive_range += kernel->values[i] );
1064 Minimize(kernel->minimum, kernel->values[i]);
1065 Maximize(kernel->maximum, kernel->values[i]);
1066 }
1067
anthony602ab9b2010-01-05 08:06:50 +00001068 /* Normalize the 1D Gaussian Kernel
1069 **
anthonyc1061722010-05-14 06:23:49 +00001070 ** NB: a CorrelateNormalize performs a normal Normalize if
1071 ** there are no negative values.
anthony602ab9b2010-01-05 08:06:50 +00001072 */
anthony9eb4f742010-05-18 02:45:54 +00001073 //ScaleKernelInfo(kernel, 1.0, CorrelateNormalizeValue);
anthonycc6c8362010-01-25 04:14:01 +00001074
anthonyc1061722010-05-14 06:23:49 +00001075 /* rotate the 1D kernel by given angle */
1076 RotateKernelInfo(kernel, (type == BlurKernel) ? args->xi : args->psi );
anthony602ab9b2010-01-05 08:06:50 +00001077 break;
1078 }
1079 case CometKernel:
1080 { double
anthony9eb4f742010-05-18 02:45:54 +00001081 sigma = fabs(args->sigma),
1082 A;
anthony602ab9b2010-01-05 08:06:50 +00001083
anthony602ab9b2010-01-05 08:06:50 +00001084 if ( args->rho < 1.0 )
1085 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
1086 else
1087 kernel->width = (unsigned long)args->rho;
cristyc99304f2010-02-01 15:26:27 +00001088 kernel->x = kernel->y = 0;
anthony602ab9b2010-01-05 08:06:50 +00001089 kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +00001090 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +00001091 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1092 kernel->height*sizeof(double));
1093 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001094 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001095
anthonyc1061722010-05-14 06:23:49 +00001096 /* A comet blur is half a 1D gaussian curve, so that the object is
anthony602ab9b2010-01-05 08:06:50 +00001097 ** blurred in one direction only. This may not be quite the right
anthony3dd0f622010-05-13 12:57:32 +00001098 ** curve to use so may change in the future. The function must be
1099 ** normalised after generation, which also resolves any clipping.
anthonyc1061722010-05-14 06:23:49 +00001100 **
1101 ** As we are normalizing and not subtracting gaussians,
1102 ** there is no need for a divisor in the gaussian formula
1103 **
anthony43c49252010-05-18 10:59:50 +00001104 ** It is less comples
anthony602ab9b2010-01-05 08:06:50 +00001105 */
anthony9eb4f742010-05-18 02:45:54 +00001106 if ( sigma > MagickEpsilon )
1107 {
anthony602ab9b2010-01-05 08:06:50 +00001108#if 1
1109#define KernelRank 3
anthony9eb4f742010-05-18 02:45:54 +00001110 v = (long) kernel->width*KernelRank; /* start/end points */
1111 (void) ResetMagickMemory(kernel->values,0, (size_t)
1112 kernel->width*sizeof(double));
1113 sigma *= KernelRank; /* simplify the loop expression */
1114 A = 1.0/(2.0*sigma*sigma);
1115 /* B = 1.0/(MagickSQ2PI*sigma); */
1116 for ( u=0; u < v; u++) {
1117 kernel->values[u/KernelRank] +=
1118 exp(-((double)(u*u))*A);
1119 /* exp(-((double)(i*i))/2.0*sigma*sigma)/(MagickSQ2PI*sigma); */
1120 }
1121 for (i=0; i < (long) kernel->width; i++)
1122 kernel->positive_range += kernel->values[i];
anthony602ab9b2010-01-05 08:06:50 +00001123#else
anthony9eb4f742010-05-18 02:45:54 +00001124 A = 1.0/(2.0*sigma*sigma); /* simplify the loop expression */
1125 /* B = 1.0/(MagickSQ2PI*sigma); */
1126 for ( i=0; i < (long) kernel->width; i++)
1127 kernel->positive_range +=
1128 kernel->values[i] =
1129 exp(-((double)(i*i))*A);
1130 /* exp(-((double)(i*i))/2.0*sigma*sigma)/(MagickSQ2PI*sigma); */
anthony602ab9b2010-01-05 08:06:50 +00001131#endif
anthony9eb4f742010-05-18 02:45:54 +00001132 }
1133 else /* special case - generate a unity kernel */
1134 { (void) ResetMagickMemory(kernel->values,0, (size_t)
1135 kernel->width*kernel->height*sizeof(double));
1136 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1137 kernel->positive_range = 1.0;
1138 }
cristyc99304f2010-02-01 15:26:27 +00001139 kernel->minimum = 0;
1140 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001141
anthony999bb2c2010-02-18 12:38:01 +00001142 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
1143 RotateKernelInfo(kernel, args->xi); /* Rotate by angle */
anthony602ab9b2010-01-05 08:06:50 +00001144 break;
1145 }
anthonyc1061722010-05-14 06:23:49 +00001146
anthony3c10fc82010-05-13 02:40:51 +00001147 /* Convolution Kernels - Well Known Constants */
anthony3c10fc82010-05-13 02:40:51 +00001148 case LaplacianKernel:
anthonyc1061722010-05-14 06:23:49 +00001149 {
anthony3c10fc82010-05-13 02:40:51 +00001150 switch ( (int) args->rho ) {
anthony3dd0f622010-05-13 12:57:32 +00001151 case 0:
anthony9eb4f742010-05-18 02:45:54 +00001152 default: /* laplacian square filter -- default */
anthonyc1061722010-05-14 06:23:49 +00001153 kernel=ParseKernelArray("3: -1,-1,-1 -1,8,-1 -1,-1,-1");
anthony3dd0f622010-05-13 12:57:32 +00001154 break;
anthony9eb4f742010-05-18 02:45:54 +00001155 case 1: /* laplacian diamond filter */
anthonyc1061722010-05-14 06:23:49 +00001156 kernel=ParseKernelArray("3: 0,-1,0 -1,4,-1 0,-1,0");
anthony3c10fc82010-05-13 02:40:51 +00001157 break;
1158 case 2:
anthony9eb4f742010-05-18 02:45:54 +00001159 kernel=ParseKernelArray("3: -2,1,-2 1,4,1 -2,1,-2");
1160 break;
1161 case 3:
anthonyc1061722010-05-14 06:23:49 +00001162 kernel=ParseKernelArray("3: 1,-2,1 -2,4,-2 1,-2,1");
anthony3c10fc82010-05-13 02:40:51 +00001163 break;
anthony9eb4f742010-05-18 02:45:54 +00001164 case 5: /* a 5x5 laplacian */
anthony3c10fc82010-05-13 02:40:51 +00001165 kernel=ParseKernelArray(
anthony9eb4f742010-05-18 02:45:54 +00001166 "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 +00001167 break;
anthony9eb4f742010-05-18 02:45:54 +00001168 case 7: /* a 7x7 laplacian */
anthony3c10fc82010-05-13 02:40:51 +00001169 kernel=ParseKernelArray(
anthonyc1061722010-05-14 06:23:49 +00001170 "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 +00001171 break;
anthony43c49252010-05-18 10:59:50 +00001172 case 15: /* a 5x5 LOG (sigma approx 1.4) */
anthony9eb4f742010-05-18 02:45:54 +00001173 kernel=ParseKernelArray(
1174 "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");
1175 break;
anthony43c49252010-05-18 10:59:50 +00001176 case 19: /* a 9x9 LOG (sigma approx 1.4) */
1177 /* http://www.cscjournals.org/csc/manuscript/Journals/IJIP/volume3/Issue1/IJIP-15.pdf */
1178 kernel=ParseKernelArray(
1179 "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");
1180 break;
anthony3c10fc82010-05-13 02:40:51 +00001181 }
1182 if (kernel == (KernelInfo *) NULL)
1183 return(kernel);
1184 kernel->type = type;
1185 break;
1186 }
anthonyc1061722010-05-14 06:23:49 +00001187 case SobelKernel:
anthony602ab9b2010-01-05 08:06:50 +00001188 {
anthonyc1061722010-05-14 06:23:49 +00001189 kernel=ParseKernelArray("3: -1,0,1 -2,0,2 -1,0,1");
1190 if (kernel == (KernelInfo *) NULL)
1191 return(kernel);
1192 kernel->type = type;
1193 RotateKernelInfo(kernel, args->rho); /* Rotate by angle */
1194 break;
1195 }
1196 case RobertsKernel:
1197 {
1198 kernel=ParseKernelArray("3: 0,0,0 -1,1,0 0,0,0");
1199 if (kernel == (KernelInfo *) NULL)
1200 return(kernel);
1201 kernel->type = type;
1202 RotateKernelInfo(kernel, args->rho); /* Rotate by angle */
1203 break;
1204 }
1205 case PrewittKernel:
1206 {
1207 kernel=ParseKernelArray("3: -1,1,1 0,0,0 -1,1,1");
1208 if (kernel == (KernelInfo *) NULL)
1209 return(kernel);
1210 kernel->type = type;
1211 RotateKernelInfo(kernel, args->rho); /* Rotate by angle */
1212 break;
1213 }
1214 case CompassKernel:
1215 {
1216 kernel=ParseKernelArray("3: -1,1,1 -1,-2,1 -1,1,1");
1217 if (kernel == (KernelInfo *) NULL)
1218 return(kernel);
1219 kernel->type = type;
1220 RotateKernelInfo(kernel, args->rho); /* Rotate by angle */
1221 break;
1222 }
anthony9eb4f742010-05-18 02:45:54 +00001223 case KirschKernel:
1224 {
1225 kernel=ParseKernelArray("3: -3,-3,5 -3,0,5 -3,-3,5");
1226 if (kernel == (KernelInfo *) NULL)
1227 return(kernel);
1228 kernel->type = type;
1229 RotateKernelInfo(kernel, args->rho); /* Rotate by angle */
1230 break;
1231 }
anthonyc1061722010-05-14 06:23:49 +00001232 /* Boolean Kernels */
1233 case DiamondKernel:
1234 {
1235 if (args->rho < 1.0)
1236 kernel->width = kernel->height = 3; /* default radius = 1 */
1237 else
1238 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
1239 kernel->x = kernel->y = (long) (kernel->width-1)/2;
1240
1241 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1242 kernel->height*sizeof(double));
1243 if (kernel->values == (double *) NULL)
1244 return(DestroyKernelInfo(kernel));
1245
1246 /* set all kernel values within diamond area to scale given */
1247 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1248 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1249 if ((labs(u)+labs(v)) <= (long)kernel->x)
1250 kernel->positive_range += kernel->values[i] = args->sigma;
1251 else
1252 kernel->values[i] = nan;
1253 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1254 break;
1255 }
1256 case SquareKernel:
1257 case RectangleKernel:
1258 { double
1259 scale;
anthony602ab9b2010-01-05 08:06:50 +00001260 if ( type == SquareKernel )
1261 {
1262 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001263 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001264 else
cristy150989e2010-02-01 14:59:39 +00001265 kernel->width = kernel->height = (unsigned long) (2*args->rho+1);
cristyc99304f2010-02-01 15:26:27 +00001266 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony4fd27e22010-02-07 08:17:18 +00001267 scale = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +00001268 }
1269 else {
cristy2be15382010-01-21 02:38:03 +00001270 /* NOTE: user defaults set in "AcquireKernelInfo()" */
anthony602ab9b2010-01-05 08:06:50 +00001271 if ( args->rho < 1.0 || args->sigma < 1.0 )
anthony83ba99b2010-01-24 08:48:15 +00001272 return(DestroyKernelInfo(kernel)); /* invalid args given */
anthony602ab9b2010-01-05 08:06:50 +00001273 kernel->width = (unsigned long)args->rho;
1274 kernel->height = (unsigned long)args->sigma;
1275 if ( args->xi < 0.0 || args->xi > (double)kernel->width ||
1276 args->psi < 0.0 || args->psi > (double)kernel->height )
anthony83ba99b2010-01-24 08:48:15 +00001277 return(DestroyKernelInfo(kernel)); /* invalid args given */
cristyc99304f2010-02-01 15:26:27 +00001278 kernel->x = (long) args->xi;
1279 kernel->y = (long) args->psi;
anthony4fd27e22010-02-07 08:17:18 +00001280 scale = 1.0;
anthony602ab9b2010-01-05 08:06:50 +00001281 }
1282 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1283 kernel->height*sizeof(double));
1284 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001285 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001286
anthony3dd0f622010-05-13 12:57:32 +00001287 /* set all kernel values to scale given */
cristy150989e2010-02-01 14:59:39 +00001288 u=(long) kernel->width*kernel->height;
1289 for ( i=0; i < u; i++)
anthony4fd27e22010-02-07 08:17:18 +00001290 kernel->values[i] = scale;
1291 kernel->minimum = kernel->maximum = scale; /* a flat shape */
1292 kernel->positive_range = scale*u;
anthonycc6c8362010-01-25 04:14:01 +00001293 break;
anthony602ab9b2010-01-05 08:06:50 +00001294 }
anthony602ab9b2010-01-05 08:06:50 +00001295 case DiskKernel:
1296 {
anthonyc1061722010-05-14 06:23:49 +00001297 long limit = (long)(args->rho*args->rho);
anthony83ba99b2010-01-24 08:48:15 +00001298 if (args->rho < 0.1) /* default radius approx 3.5 */
1299 kernel->width = kernel->height = 7L, limit = 10L;
anthony602ab9b2010-01-05 08:06:50 +00001300 else
1301 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001302 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001303
1304 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1305 kernel->height*sizeof(double));
1306 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001307 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001308
anthony3dd0f622010-05-13 12:57:32 +00001309 /* set all kernel values within disk area to scale given */
1310 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
cristyc99304f2010-02-01 15:26:27 +00001311 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony602ab9b2010-01-05 08:06:50 +00001312 if ((u*u+v*v) <= limit)
anthony4fd27e22010-02-07 08:17:18 +00001313 kernel->positive_range += kernel->values[i] = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +00001314 else
1315 kernel->values[i] = nan;
anthony4fd27e22010-02-07 08:17:18 +00001316 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
anthony602ab9b2010-01-05 08:06:50 +00001317 break;
1318 }
1319 case PlusKernel:
1320 {
1321 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001322 kernel->width = kernel->height = 5; /* default radius 2 */
anthony602ab9b2010-01-05 08:06:50 +00001323 else
1324 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001325 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001326
1327 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1328 kernel->height*sizeof(double));
1329 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001330 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001331
anthony3dd0f622010-05-13 12:57:32 +00001332 /* set all kernel values along axises to given scale */
cristyc99304f2010-02-01 15:26:27 +00001333 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1334 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony4fd27e22010-02-07 08:17:18 +00001335 kernel->values[i] = (u == 0 || v == 0) ? args->sigma : nan;
1336 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1337 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
anthony602ab9b2010-01-05 08:06:50 +00001338 break;
1339 }
anthony3dd0f622010-05-13 12:57:32 +00001340 case CrossKernel:
1341 {
1342 if (args->rho < 1.0)
1343 kernel->width = kernel->height = 5; /* default radius 2 */
1344 else
1345 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
1346 kernel->x = kernel->y = (long) (kernel->width-1)/2;
1347
1348 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1349 kernel->height*sizeof(double));
1350 if (kernel->values == (double *) NULL)
1351 return(DestroyKernelInfo(kernel));
1352
1353 /* set all kernel values along axises to given scale */
1354 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1355 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1356 kernel->values[i] = (u == v || u == -v) ? args->sigma : nan;
1357 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1358 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
1359 break;
1360 }
1361 /* HitAndMiss Kernels */
anthonyc1061722010-05-14 06:23:49 +00001362 case RingKernel:
anthony3dd0f622010-05-13 12:57:32 +00001363 case PeaksKernel:
1364 {
1365 long
1366 limit1,
anthonyc1061722010-05-14 06:23:49 +00001367 limit2,
1368 scale;
anthony3dd0f622010-05-13 12:57:32 +00001369
1370 if (args->rho < args->sigma)
1371 {
1372 kernel->width = ((unsigned long)args->sigma)*2+1;
1373 limit1 = (long)args->rho*args->rho;
1374 limit2 = (long)args->sigma*args->sigma;
1375 }
1376 else
1377 {
1378 kernel->width = ((unsigned long)args->rho)*2+1;
1379 limit1 = (long)args->sigma*args->sigma;
1380 limit2 = (long)args->rho*args->rho;
1381 }
anthonyc1061722010-05-14 06:23:49 +00001382 if ( limit2 <= 0 )
1383 kernel->width = 7L, limit1 = 7L, limit2 = 11L;
1384
anthony3dd0f622010-05-13 12:57:32 +00001385 kernel->height = kernel->width;
1386 kernel->x = kernel->y = (long) (kernel->width-1)/2;
1387 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1388 kernel->height*sizeof(double));
1389 if (kernel->values == (double *) NULL)
1390 return(DestroyKernelInfo(kernel));
1391
anthonyc1061722010-05-14 06:23:49 +00001392 /* set a ring of points of 'scale' ( 0.0 for PeaksKernel ) */
1393 scale = ( type == PeaksKernel) ? 0.0 : args->xi;
anthony3dd0f622010-05-13 12:57:32 +00001394 for ( i=0, v= -kernel->y; v <= (long)kernel->y; v++)
1395 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1396 { long radius=u*u+v*v;
anthonyc1061722010-05-14 06:23:49 +00001397 if (limit1 < radius && radius <= limit2)
1398 kernel->positive_range += kernel->values[i] = scale;
anthony3dd0f622010-05-13 12:57:32 +00001399 else
1400 kernel->values[i] = nan;
1401 }
anthonyc1061722010-05-14 06:23:49 +00001402 kernel->minimum = kernel->minimum = scale;
1403 if ( type == PeaksKernel ) {
1404 /* set the central point in the middle */
1405 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1406 kernel->positive_range = 1.0;
1407 kernel->maximum = 1.0;
1408 }
anthony3dd0f622010-05-13 12:57:32 +00001409 break;
1410 }
anthony43c49252010-05-18 10:59:50 +00001411 case EdgesKernel:
1412 {
1413 kernel=ParseKernelArray("3: 0,0,0 -,1,- 1,1,1");
1414 if (kernel == (KernelInfo *) NULL)
1415 return(kernel);
1416 kernel->type = type;
1417 ExpandKernelInfo(kernel, 90.0); /* Create a list of 4 rotated kernels */
1418 break;
1419 }
anthony3dd0f622010-05-13 12:57:32 +00001420 case CornersKernel:
1421 {
anthony4f1dcb72010-05-14 08:43:10 +00001422 kernel=ParseKernelArray("3: 0,0,- 0,1,1 -,1,-");
anthony3dd0f622010-05-13 12:57:32 +00001423 if (kernel == (KernelInfo *) NULL)
1424 return(kernel);
1425 kernel->type = type;
1426 ExpandKernelInfo(kernel, 90.0); /* Create a list of 4 rotated kernels */
1427 break;
1428 }
1429 case LineEndsKernel:
1430 {
anthony43c49252010-05-18 10:59:50 +00001431 KernelInfo
1432 *new_kernel;
1433 kernel=ParseKernelArray("3: 0,0,0 0,1,0 -,1,-");
anthony3dd0f622010-05-13 12:57:32 +00001434 if (kernel == (KernelInfo *) NULL)
1435 return(kernel);
1436 kernel->type = type;
anthony43c49252010-05-18 10:59:50 +00001437 ExpandKernelInfo(kernel, 90.0);
1438 /* append second set of 4 kernels */
1439 new_kernel=ParseKernelArray("3: 0,0,0 0,1,0 0,0,1");
1440 if (new_kernel == (KernelInfo *) NULL)
1441 return(DestroyKernelInfo(kernel));
1442 new_kernel->type = type;
1443 ExpandKernelInfo(new_kernel, 90.0);
1444 LastKernelInfo(kernel)->next = new_kernel;
anthony3dd0f622010-05-13 12:57:32 +00001445 break;
1446 }
1447 case LineJunctionsKernel:
1448 {
1449 KernelInfo
1450 *new_kernel;
anthony3dd0f622010-05-13 12:57:32 +00001451 /* first set of 4 kernels */
anthony4f1dcb72010-05-14 08:43:10 +00001452 kernel=ParseKernelArray("3: -,1,- -,1,- 1,-,1");
anthony3dd0f622010-05-13 12:57:32 +00001453 if (kernel == (KernelInfo *) NULL)
1454 return(kernel);
1455 kernel->type = type;
anthony43c49252010-05-18 10:59:50 +00001456 ExpandKernelInfo(kernel, 45.0);
anthony3dd0f622010-05-13 12:57:32 +00001457 /* append second set of 4 kernels */
anthony4f1dcb72010-05-14 08:43:10 +00001458 new_kernel=ParseKernelArray("3: 1,-,- -,1,- 1,-,1");
anthony3dd0f622010-05-13 12:57:32 +00001459 if (new_kernel == (KernelInfo *) NULL)
1460 return(DestroyKernelInfo(kernel));
anthony43c49252010-05-18 10:59:50 +00001461 new_kernel->type = type;
anthony3dd0f622010-05-13 12:57:32 +00001462 ExpandKernelInfo(new_kernel, 90.0);
1463 LastKernelInfo(kernel)->next = new_kernel;
anthony4f1dcb72010-05-14 08:43:10 +00001464 break;
1465 }
anthony3dd0f622010-05-13 12:57:32 +00001466 case ConvexHullKernel:
1467 {
1468 KernelInfo
1469 *new_kernel;
anthony3dd0f622010-05-13 12:57:32 +00001470 /* first set of 4 kernels */
anthony4f1dcb72010-05-14 08:43:10 +00001471 kernel=ParseKernelArray("3: 1,1,- 1,0,- 1,-,0");
anthony3dd0f622010-05-13 12:57:32 +00001472 if (kernel == (KernelInfo *) NULL)
1473 return(kernel);
1474 kernel->type = type;
1475 ExpandKernelInfo(kernel, 90.0);
1476 /* append second set of 4 kernels */
anthony43c49252010-05-18 10:59:50 +00001477 new_kernel=ParseKernelArray("3: 1,1,1 1,0,0 -,-,0");
anthony3dd0f622010-05-13 12:57:32 +00001478 if (new_kernel == (KernelInfo *) NULL)
1479 return(DestroyKernelInfo(kernel));
anthony43c49252010-05-18 10:59:50 +00001480 new_kernel->type = type;
anthony3dd0f622010-05-13 12:57:32 +00001481 ExpandKernelInfo(new_kernel, 90.0);
1482 LastKernelInfo(kernel)->next = new_kernel;
1483 break;
1484 }
anthony43c49252010-05-18 10:59:50 +00001485 case ThinningKernel:
1486 { /* Thinning Kernel ?? -- filled corner and edge */
1487 kernel=ParseKernelArray("3: 0,0,- 0,1,1 -,1,1");
anthony3dd0f622010-05-13 12:57:32 +00001488 if (kernel == (KernelInfo *) NULL)
1489 return(kernel);
1490 kernel->type = type;
anthony43c49252010-05-18 10:59:50 +00001491 ExpandKernelInfo(kernel, 45);
1492 break;
1493 }
1494 case SkeletonKernel:
1495 {
1496 kernel=AcquireKernelInfo("Edges;Corners");
anthony3dd0f622010-05-13 12:57:32 +00001497 break;
1498 }
anthony602ab9b2010-01-05 08:06:50 +00001499 /* Distance Measuring Kernels */
1500 case ChebyshevKernel:
1501 {
anthony602ab9b2010-01-05 08:06:50 +00001502 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001503 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001504 else
1505 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001506 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001507
1508 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1509 kernel->height*sizeof(double));
1510 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001511 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001512
cristyc99304f2010-02-01 15:26:27 +00001513 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1514 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1515 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001516 args->sigma*((labs(u)>labs(v)) ? labs(u) : labs(v)) );
cristyc99304f2010-02-01 15:26:27 +00001517 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001518 break;
1519 }
1520 case ManhattenKernel:
1521 {
anthony602ab9b2010-01-05 08:06:50 +00001522 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001523 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001524 else
1525 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001526 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001527
1528 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1529 kernel->height*sizeof(double));
1530 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001531 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001532
cristyc99304f2010-02-01 15:26:27 +00001533 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1534 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1535 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001536 args->sigma*(labs(u)+labs(v)) );
cristyc99304f2010-02-01 15:26:27 +00001537 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001538 break;
1539 }
1540 case EuclideanKernel:
1541 {
anthony602ab9b2010-01-05 08:06:50 +00001542 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001543 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001544 else
1545 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001546 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001547
1548 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1549 kernel->height*sizeof(double));
1550 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001551 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001552
cristyc99304f2010-02-01 15:26:27 +00001553 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1554 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1555 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001556 args->sigma*sqrt((double)(u*u+v*v)) );
cristyc99304f2010-02-01 15:26:27 +00001557 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001558 break;
1559 }
anthony602ab9b2010-01-05 08:06:50 +00001560 default:
anthonyc1061722010-05-14 06:23:49 +00001561 {
1562 /* Generate a No-Op minimal kernel - 1x1 pixel */
1563 kernel=ParseKernelArray("1");
1564 if (kernel == (KernelInfo *) NULL)
1565 return(kernel);
1566 kernel->type = UndefinedKernel;
1567 break;
1568 }
anthony602ab9b2010-01-05 08:06:50 +00001569 break;
1570 }
1571
1572 return(kernel);
1573}
anthonyc94cdb02010-01-06 08:15:29 +00001574
anthony602ab9b2010-01-05 08:06:50 +00001575/*
1576%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1577% %
1578% %
1579% %
cristy6771f1e2010-03-05 19:43:39 +00001580% C l o n e K e r n e l I n f o %
anthony4fd27e22010-02-07 08:17:18 +00001581% %
1582% %
1583% %
1584%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1585%
anthony1b2bc0a2010-05-12 05:25:22 +00001586% CloneKernelInfo() creates a new clone of the given Kernel List so that its
1587% can be modified without effecting the original. The cloned kernel should
1588% be destroyed using DestoryKernelInfo() when no longer needed.
anthony7a01dcf2010-05-11 12:25:52 +00001589%
cristye6365592010-04-02 17:31:23 +00001590% The format of the CloneKernelInfo method is:
anthony4fd27e22010-02-07 08:17:18 +00001591%
anthony930be612010-02-08 04:26:15 +00001592% KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +00001593%
1594% A description of each parameter follows:
1595%
1596% o kernel: the Morphology/Convolution kernel to be cloned
1597%
1598*/
cristyef656912010-03-05 19:54:59 +00001599MagickExport KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +00001600{
1601 register long
1602 i;
1603
cristy19eb6412010-04-23 14:42:29 +00001604 KernelInfo
anthony7a01dcf2010-05-11 12:25:52 +00001605 *new_kernel;
anthony4fd27e22010-02-07 08:17:18 +00001606
1607 assert(kernel != (KernelInfo *) NULL);
anthony7a01dcf2010-05-11 12:25:52 +00001608 new_kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
1609 if (new_kernel == (KernelInfo *) NULL)
1610 return(new_kernel);
1611 *new_kernel=(*kernel); /* copy values in structure */
anthony7a01dcf2010-05-11 12:25:52 +00001612
1613 /* replace the values with a copy of the values */
1614 new_kernel->values=(double *) AcquireQuantumMemory(kernel->width,
cristy19eb6412010-04-23 14:42:29 +00001615 kernel->height*sizeof(double));
anthony7a01dcf2010-05-11 12:25:52 +00001616 if (new_kernel->values == (double *) NULL)
1617 return(DestroyKernelInfo(new_kernel));
anthony4fd27e22010-02-07 08:17:18 +00001618 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthony7a01dcf2010-05-11 12:25:52 +00001619 new_kernel->values[i]=kernel->values[i];
anthony1b2bc0a2010-05-12 05:25:22 +00001620
1621 /* Also clone the next kernel in the kernel list */
1622 if ( kernel->next != (KernelInfo *) NULL ) {
1623 new_kernel->next = CloneKernelInfo(kernel->next);
1624 if ( new_kernel->next == (KernelInfo *) NULL )
1625 return(DestroyKernelInfo(new_kernel));
1626 }
1627
anthony7a01dcf2010-05-11 12:25:52 +00001628 return(new_kernel);
anthony4fd27e22010-02-07 08:17:18 +00001629}
1630
1631/*
1632%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1633% %
1634% %
1635% %
anthony83ba99b2010-01-24 08:48:15 +00001636% D e s t r o y K e r n e l I n f o %
anthony602ab9b2010-01-05 08:06:50 +00001637% %
1638% %
1639% %
1640%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1641%
anthony83ba99b2010-01-24 08:48:15 +00001642% DestroyKernelInfo() frees the memory used by a Convolution/Morphology
1643% kernel.
anthony602ab9b2010-01-05 08:06:50 +00001644%
anthony83ba99b2010-01-24 08:48:15 +00001645% The format of the DestroyKernelInfo method is:
anthony602ab9b2010-01-05 08:06:50 +00001646%
anthony83ba99b2010-01-24 08:48:15 +00001647% KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +00001648%
1649% A description of each parameter follows:
1650%
1651% o kernel: the Morphology/Convolution kernel to be destroyed
1652%
1653*/
1654
anthony83ba99b2010-01-24 08:48:15 +00001655MagickExport KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +00001656{
cristy2be15382010-01-21 02:38:03 +00001657 assert(kernel != (KernelInfo *) NULL);
anthony4fd27e22010-02-07 08:17:18 +00001658
anthony7a01dcf2010-05-11 12:25:52 +00001659 if ( kernel->next != (KernelInfo *) NULL )
1660 kernel->next = DestroyKernelInfo(kernel->next);
1661
1662 kernel->values = (double *)RelinquishMagickMemory(kernel->values);
1663 kernel = (KernelInfo *) RelinquishMagickMemory(kernel);
anthony602ab9b2010-01-05 08:06:50 +00001664 return(kernel);
1665}
anthonyc94cdb02010-01-06 08:15:29 +00001666
1667/*
1668%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1669% %
1670% %
1671% %
anthony3c10fc82010-05-13 02:40:51 +00001672% E x p a n d K e r n e l I n f o %
1673% %
1674% %
1675% %
1676%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1677%
1678% ExpandKernelInfo() takes a single kernel, and expands it into a list
1679% of kernels each incrementally rotated the angle given.
1680%
1681% WARNING: 45 degree rotations only works for 3x3 kernels.
1682% While 90 degree roatations only works for linear and square kernels
1683%
1684% The format of the RotateKernelInfo method is:
1685%
1686% void ExpandKernelInfo(KernelInfo *kernel, double angle)
1687%
1688% A description of each parameter follows:
1689%
1690% o kernel: the Morphology/Convolution kernel
1691%
1692% o angle: angle to rotate in degrees
1693%
1694% This function is only internel to this module, as it is not finalized,
1695% especially with regard to non-orthogonal angles, and rotation of larger
1696% 2D kernels.
1697*/
1698static void ExpandKernelInfo(KernelInfo *kernel, double angle)
1699{
1700 KernelInfo
cristya9a61ad2010-05-13 12:47:41 +00001701 *clone,
anthony3c10fc82010-05-13 02:40:51 +00001702 *last;
cristya9a61ad2010-05-13 12:47:41 +00001703
anthony3c10fc82010-05-13 02:40:51 +00001704 double
1705 a;
1706
1707 last = kernel;
1708 for (a=angle; a<355.0; a+=angle) {
cristya9a61ad2010-05-13 12:47:41 +00001709 clone = CloneKernelInfo(last);
1710 RotateKernelInfo(clone, angle);
1711 last->next = clone;
1712 last = clone;
anthony3c10fc82010-05-13 02:40:51 +00001713 }
1714}
1715
1716/*
1717%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1718% %
1719% %
1720% %
anthony9eb4f742010-05-18 02:45:54 +00001721% M o r p h o l o g y A p p l y %
anthony602ab9b2010-01-05 08:06:50 +00001722% %
1723% %
1724% %
1725%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1726%
anthony9eb4f742010-05-18 02:45:54 +00001727% MorphologyApply() applies a morphological method, multiple times using
1728% a list of multiple kernels.
anthony602ab9b2010-01-05 08:06:50 +00001729%
anthony9eb4f742010-05-18 02:45:54 +00001730% It is basically equivelent to as MorphologyImageChannel() (see below) but
1731% without user controls, that that function extracts and applies to kernels
1732% and morphology methods.
1733%
1734% More specifically kernels are not normalized/scaled/blended by the
1735% 'convolve:scale' Image Artifact (-set setting), and the convolve bias
1736% (-bias setting or image->bias) is passed directly to this function,
1737% and not extracted from an image.
anthony602ab9b2010-01-05 08:06:50 +00001738%
1739% The format of the MorphologyImage method is:
1740%
anthony9eb4f742010-05-18 02:45:54 +00001741% Image *MorphologyApply(const Image *image,MorphologyMethod method,
1742% const long iterations,const KernelInfo *kernel,const double bias,
1743% ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001744%
1745% A description of each parameter follows:
1746%
1747% o image: the image.
1748%
1749% o method: the morphology method to be applied.
1750%
1751% o iterations: apply the operation this many times (or no change).
1752% A value of -1 means loop until no change found.
1753% How this is applied may depend on the morphology method.
1754% Typically this is a value of 1.
1755%
1756% o channel: the channel type.
1757%
1758% o kernel: An array of double representing the morphology kernel.
anthony29188a82010-01-22 10:12:34 +00001759% Warning: kernel may be normalized for the Convolve method.
anthony602ab9b2010-01-05 08:06:50 +00001760%
anthony9eb4f742010-05-18 02:45:54 +00001761% o bias: Convolution Bias to use.
1762%
anthony602ab9b2010-01-05 08:06:50 +00001763% o exception: return any errors or warnings in this structure.
1764%
anthony602ab9b2010-01-05 08:06:50 +00001765*/
1766
anthony930be612010-02-08 04:26:15 +00001767
anthony9eb4f742010-05-18 02:45:54 +00001768/* Apply a Morphology Primative to an image using the given kernel.
1769** Two pre-created images must be provided, no image is created.
1770** Returning the number of pixels that changed.
1771*/
anthony7a01dcf2010-05-11 12:25:52 +00001772static unsigned long MorphologyPrimative(const Image *image, Image
anthony602ab9b2010-01-05 08:06:50 +00001773 *result_image, const MorphologyMethod method, const ChannelType channel,
anthony9eb4f742010-05-18 02:45:54 +00001774 const KernelInfo *kernel,const double bias,ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001775{
cristy2be15382010-01-21 02:38:03 +00001776#define MorphologyTag "Morphology/Image"
anthony602ab9b2010-01-05 08:06:50 +00001777
1778 long
cristy150989e2010-02-01 14:59:39 +00001779 progress,
anthony29188a82010-01-22 10:12:34 +00001780 y, offx, offy,
anthony602ab9b2010-01-05 08:06:50 +00001781 changed;
1782
1783 MagickBooleanType
1784 status;
1785
anthony602ab9b2010-01-05 08:06:50 +00001786 CacheView
1787 *p_view,
1788 *q_view;
1789
anthony602ab9b2010-01-05 08:06:50 +00001790 status=MagickTrue;
1791 changed=0;
1792 progress=0;
1793
anthony602ab9b2010-01-05 08:06:50 +00001794 p_view=AcquireCacheView(image);
1795 q_view=AcquireCacheView(result_image);
anthony29188a82010-01-22 10:12:34 +00001796
anthonycc6c8362010-01-25 04:14:01 +00001797 /* Some methods (including convolve) needs use a reflected kernel.
anthony9eb4f742010-05-18 02:45:54 +00001798 * Adjust 'origin' offsets to loop though kernel as a reflection.
anthony29188a82010-01-22 10:12:34 +00001799 */
cristyc99304f2010-02-01 15:26:27 +00001800 offx = kernel->x;
1801 offy = kernel->y;
anthony29188a82010-01-22 10:12:34 +00001802 switch(method) {
anthony930be612010-02-08 04:26:15 +00001803 case ConvolveMorphology:
1804 case DilateMorphology:
1805 case DilateIntensityMorphology:
1806 case DistanceMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001807 /* kernel needs to used with reflection about origin */
cristy150989e2010-02-01 14:59:39 +00001808 offx = (long) kernel->width-offx-1;
1809 offy = (long) kernel->height-offy-1;
anthony29188a82010-01-22 10:12:34 +00001810 break;
anthony5ef8e942010-05-11 06:51:12 +00001811 case ErodeMorphology:
1812 case ErodeIntensityMorphology:
1813 case HitAndMissMorphology:
1814 case ThinningMorphology:
1815 case ThickenMorphology:
1816 /* kernel is user as is, without reflection */
1817 break;
anthony930be612010-02-08 04:26:15 +00001818 default:
anthony9eb4f742010-05-18 02:45:54 +00001819 assert("Not a Primitive Morphology Method" != (char *) NULL);
anthony930be612010-02-08 04:26:15 +00001820 break;
anthony29188a82010-01-22 10:12:34 +00001821 }
1822
anthony602ab9b2010-01-05 08:06:50 +00001823#if defined(MAGICKCORE_OPENMP_SUPPORT)
1824 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1825#endif
cristy150989e2010-02-01 14:59:39 +00001826 for (y=0; y < (long) image->rows; y++)
anthony602ab9b2010-01-05 08:06:50 +00001827 {
1828 MagickBooleanType
1829 sync;
1830
1831 register const PixelPacket
1832 *restrict p;
1833
1834 register const IndexPacket
1835 *restrict p_indexes;
1836
1837 register PixelPacket
1838 *restrict q;
1839
1840 register IndexPacket
1841 *restrict q_indexes;
1842
cristy150989e2010-02-01 14:59:39 +00001843 register long
anthony602ab9b2010-01-05 08:06:50 +00001844 x;
1845
anthony29188a82010-01-22 10:12:34 +00001846 unsigned long
anthony602ab9b2010-01-05 08:06:50 +00001847 r;
1848
1849 if (status == MagickFalse)
1850 continue;
anthony29188a82010-01-22 10:12:34 +00001851 p=GetCacheViewVirtualPixels(p_view, -offx, y-offy,
1852 image->columns+kernel->width, kernel->height, exception);
anthony602ab9b2010-01-05 08:06:50 +00001853 q=GetCacheViewAuthenticPixels(q_view,0,y,result_image->columns,1,
1854 exception);
1855 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1856 {
1857 status=MagickFalse;
1858 continue;
1859 }
1860 p_indexes=GetCacheViewVirtualIndexQueue(p_view);
1861 q_indexes=GetCacheViewAuthenticIndexQueue(q_view);
anthony29188a82010-01-22 10:12:34 +00001862 r = (image->columns+kernel->width)*offy+offx; /* constant */
1863
cristy150989e2010-02-01 14:59:39 +00001864 for (x=0; x < (long) image->columns; x++)
anthony602ab9b2010-01-05 08:06:50 +00001865 {
cristy150989e2010-02-01 14:59:39 +00001866 long
anthony602ab9b2010-01-05 08:06:50 +00001867 v;
1868
cristy150989e2010-02-01 14:59:39 +00001869 register long
anthony602ab9b2010-01-05 08:06:50 +00001870 u;
1871
1872 register const double
1873 *restrict k;
1874
1875 register const PixelPacket
1876 *restrict k_pixels;
1877
1878 register const IndexPacket
1879 *restrict k_indexes;
1880
1881 MagickPixelPacket
anthony5ef8e942010-05-11 06:51:12 +00001882 result,
1883 min,
1884 max;
anthony602ab9b2010-01-05 08:06:50 +00001885
anthony29188a82010-01-22 10:12:34 +00001886 /* Copy input to ouput image for unused channels
anthony83ba99b2010-01-24 08:48:15 +00001887 * This removes need for 'cloning' a new image every iteration
anthony29188a82010-01-22 10:12:34 +00001888 */
anthony602ab9b2010-01-05 08:06:50 +00001889 *q = p[r];
1890 if (image->colorspace == CMYKColorspace)
1891 q_indexes[x] = p_indexes[r];
1892
anthony5ef8e942010-05-11 06:51:12 +00001893 /* Defaults */
1894 min.red =
1895 min.green =
1896 min.blue =
1897 min.opacity =
1898 min.index = (MagickRealType) QuantumRange;
1899 max.red =
1900 max.green =
1901 max.blue =
1902 max.opacity =
1903 max.index = (MagickRealType) 0;
anthony9eb4f742010-05-18 02:45:54 +00001904 /* default result is the original pixel value */
anthony5ef8e942010-05-11 06:51:12 +00001905 result.red = (MagickRealType) p[r].red;
1906 result.green = (MagickRealType) p[r].green;
1907 result.blue = (MagickRealType) p[r].blue;
1908 result.opacity = QuantumRange - (MagickRealType) p[r].opacity;
cristy9b1a5442010-05-13 12:35:40 +00001909 result.index = 0;
anthony5ef8e942010-05-11 06:51:12 +00001910 if ( image->colorspace == CMYKColorspace)
1911 result.index = (MagickRealType) p_indexes[r];
1912
anthony602ab9b2010-01-05 08:06:50 +00001913 switch (method) {
1914 case ConvolveMorphology:
anthony9eb4f742010-05-18 02:45:54 +00001915 /* Set the user defined bias of the weighted average output */
1916 result.red =
1917 result.green =
1918 result.blue =
1919 result.opacity =
1920 result.index = bias;
anthony930be612010-02-08 04:26:15 +00001921 break;
anthony4fd27e22010-02-07 08:17:18 +00001922 case DilateIntensityMorphology:
1923 case ErodeIntensityMorphology:
anthony9eb4f742010-05-18 02:45:54 +00001924 /* use a boolean flag indicating when first match found */
1925 result.red = 0.0; /* result is not used otherwise */
anthony4fd27e22010-02-07 08:17:18 +00001926 break;
anthony602ab9b2010-01-05 08:06:50 +00001927 default:
anthony602ab9b2010-01-05 08:06:50 +00001928 break;
1929 }
1930
1931 switch ( method ) {
1932 case ConvolveMorphology:
anthony930be612010-02-08 04:26:15 +00001933 /* Weighted Average of pixels using reflected kernel
1934 **
1935 ** NOTE for correct working of this operation for asymetrical
1936 ** kernels, the kernel needs to be applied in its reflected form.
1937 ** That is its values needs to be reversed.
1938 **
1939 ** Correlation is actually the same as this but without reflecting
1940 ** the kernel, and thus 'lower-level' that Convolution. However
1941 ** as Convolution is the more common method used, and it does not
1942 ** really cost us much in terms of processing to use a reflected
anthony5ef8e942010-05-11 06:51:12 +00001943 ** kernel, so it is Convolution that is implemented.
anthony930be612010-02-08 04:26:15 +00001944 **
1945 ** Correlation will have its kernel reflected before calling
1946 ** this function to do a Convolve.
1947 **
1948 ** For more details of Correlation vs Convolution see
1949 ** http://www.cs.umd.edu/~djacobs/CMSC426/Convolution.pdf
1950 */
anthony5ef8e942010-05-11 06:51:12 +00001951 if (((channel & SyncChannels) != 0 ) &&
1952 (image->matte == MagickTrue))
1953 { /* Channel has a 'Sync' Flag, and Alpha Channel enabled.
1954 ** Weight the color channels with Alpha Channel so that
1955 ** transparent pixels are not part of the results.
1956 */
anthony602ab9b2010-01-05 08:06:50 +00001957 MagickRealType
anthony5ef8e942010-05-11 06:51:12 +00001958 alpha, /* color channel weighting : kernel*alpha */
1959 gamma; /* divisor, sum of weighting values */
anthony602ab9b2010-01-05 08:06:50 +00001960
1961 gamma=0.0;
anthony29188a82010-01-22 10:12:34 +00001962 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001963 k_pixels = p;
1964 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001965 for (v=0; v < (long) kernel->height; v++) {
1966 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001967 if ( IsNan(*k) ) continue;
1968 alpha=(*k)*(QuantumScale*(QuantumRange-
1969 k_pixels[u].opacity));
1970 gamma += alpha;
1971 result.red += alpha*k_pixels[u].red;
1972 result.green += alpha*k_pixels[u].green;
1973 result.blue += alpha*k_pixels[u].blue;
anthony83ba99b2010-01-24 08:48:15 +00001974 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00001975 if ( image->colorspace == CMYKColorspace)
1976 result.index += alpha*k_indexes[u];
1977 }
1978 k_pixels += image->columns+kernel->width;
1979 k_indexes += image->columns+kernel->width;
1980 }
1981 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
anthony83ba99b2010-01-24 08:48:15 +00001982 result.red *= gamma;
1983 result.green *= gamma;
1984 result.blue *= gamma;
1985 result.opacity *= gamma;
1986 result.index *= gamma;
anthony602ab9b2010-01-05 08:06:50 +00001987 }
anthony5ef8e942010-05-11 06:51:12 +00001988 else
1989 {
1990 /* No 'Sync' flag, or no Alpha involved.
1991 ** Convolution is simple individual channel weigthed sum.
1992 */
1993 k = &kernel->values[ kernel->width*kernel->height-1 ];
1994 k_pixels = p;
1995 k_indexes = p_indexes;
1996 for (v=0; v < (long) kernel->height; v++) {
1997 for (u=0; u < (long) kernel->width; u++, k--) {
1998 if ( IsNan(*k) ) continue;
1999 result.red += (*k)*k_pixels[u].red;
2000 result.green += (*k)*k_pixels[u].green;
2001 result.blue += (*k)*k_pixels[u].blue;
2002 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
2003 if ( image->colorspace == CMYKColorspace)
2004 result.index += (*k)*k_indexes[u];
2005 }
2006 k_pixels += image->columns+kernel->width;
2007 k_indexes += image->columns+kernel->width;
2008 }
2009 }
anthony602ab9b2010-01-05 08:06:50 +00002010 break;
2011
anthony4fd27e22010-02-07 08:17:18 +00002012 case ErodeMorphology:
anthony5ef8e942010-05-11 06:51:12 +00002013 /* Minimum Value within kernel neighbourhood
anthony930be612010-02-08 04:26:15 +00002014 **
2015 ** NOTE that the kernel is not reflected for this operation!
2016 **
2017 ** NOTE: in normal Greyscale Morphology, the kernel value should
2018 ** be added to the real value, this is currently not done, due to
2019 ** the nature of the boolean kernels being used.
2020 */
anthony4fd27e22010-02-07 08:17:18 +00002021 k = kernel->values;
2022 k_pixels = p;
2023 k_indexes = p_indexes;
2024 for (v=0; v < (long) kernel->height; v++) {
2025 for (u=0; u < (long) kernel->width; u++, k++) {
2026 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony5ef8e942010-05-11 06:51:12 +00002027 Minimize(min.red, (double) k_pixels[u].red);
2028 Minimize(min.green, (double) k_pixels[u].green);
2029 Minimize(min.blue, (double) k_pixels[u].blue);
2030 Minimize(min.opacity,
anthonyd37a5cb2010-05-07 06:37:03 +00002031 QuantumRange-(double) k_pixels[u].opacity);
anthony4fd27e22010-02-07 08:17:18 +00002032 if ( image->colorspace == CMYKColorspace)
anthony5ef8e942010-05-11 06:51:12 +00002033 Minimize(min.index, (double) k_indexes[u]);
anthony4fd27e22010-02-07 08:17:18 +00002034 }
2035 k_pixels += image->columns+kernel->width;
2036 k_indexes += image->columns+kernel->width;
2037 }
2038 break;
2039
anthony5ef8e942010-05-11 06:51:12 +00002040
anthony83ba99b2010-01-24 08:48:15 +00002041 case DilateMorphology:
anthony5ef8e942010-05-11 06:51:12 +00002042 /* Maximum Value within kernel neighbourhood
anthony930be612010-02-08 04:26:15 +00002043 **
2044 ** NOTE for correct working of this operation for asymetrical
2045 ** kernels, the kernel needs to be applied in its reflected form.
2046 ** That is its values needs to be reversed.
2047 **
2048 ** NOTE: in normal Greyscale Morphology, the kernel value should
2049 ** be added to the real value, this is currently not done, due to
2050 ** the nature of the boolean kernels being used.
2051 **
2052 */
anthony29188a82010-01-22 10:12:34 +00002053 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00002054 k_pixels = p;
2055 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002056 for (v=0; v < (long) kernel->height; v++) {
2057 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00002058 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony5ef8e942010-05-11 06:51:12 +00002059 Maximize(max.red, (double) k_pixels[u].red);
2060 Maximize(max.green, (double) k_pixels[u].green);
2061 Maximize(max.blue, (double) k_pixels[u].blue);
2062 Maximize(max.opacity,
anthonyd37a5cb2010-05-07 06:37:03 +00002063 QuantumRange-(double) k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00002064 if ( image->colorspace == CMYKColorspace)
anthony5ef8e942010-05-11 06:51:12 +00002065 Maximize(max.index, (double) k_indexes[u]);
anthony602ab9b2010-01-05 08:06:50 +00002066 }
2067 k_pixels += image->columns+kernel->width;
2068 k_indexes += image->columns+kernel->width;
2069 }
anthony602ab9b2010-01-05 08:06:50 +00002070 break;
2071
anthony5ef8e942010-05-11 06:51:12 +00002072 case HitAndMissMorphology:
2073 case ThinningMorphology:
2074 case ThickenMorphology:
2075 /* Minimum of Foreground Pixel minus Maxumum of Background Pixels
2076 **
2077 ** NOTE that the kernel is not reflected for this operation,
2078 ** and consists of both foreground and background pixel
2079 ** neighbourhoods, 0.0 for background, and 1.0 for foreground
2080 ** with either Nan or 0.5 values for don't care.
2081 **
2082 ** Note that this can produce negative results, though really
2083 ** only a positive match has any real value.
2084 */
2085 k = kernel->values;
2086 k_pixels = p;
2087 k_indexes = p_indexes;
2088 for (v=0; v < (long) kernel->height; v++) {
2089 for (u=0; u < (long) kernel->width; u++, k++) {
2090 if ( IsNan(*k) ) continue;
2091 if ( (*k) > 0.7 )
2092 { /* minimim of foreground pixels */
2093 Minimize(min.red, (double) k_pixels[u].red);
2094 Minimize(min.green, (double) k_pixels[u].green);
2095 Minimize(min.blue, (double) k_pixels[u].blue);
2096 Minimize(min.opacity,
2097 QuantumRange-(double) k_pixels[u].opacity);
2098 if ( image->colorspace == CMYKColorspace)
2099 Minimize(min.index, (double) k_indexes[u]);
2100 }
2101 else if ( (*k) < 0.3 )
2102 { /* maximum of background pixels */
2103 Maximize(max.red, (double) k_pixels[u].red);
2104 Maximize(max.green, (double) k_pixels[u].green);
2105 Maximize(max.blue, (double) k_pixels[u].blue);
2106 Maximize(max.opacity,
2107 QuantumRange-(double) k_pixels[u].opacity);
2108 if ( image->colorspace == CMYKColorspace)
2109 Maximize(max.index, (double) k_indexes[u]);
2110 }
2111 }
2112 k_pixels += image->columns+kernel->width;
2113 k_indexes += image->columns+kernel->width;
2114 }
2115 /* Pattern Match only if min fg larger than min bg pixels */
2116 min.red -= max.red; Maximize( min.red, 0.0 );
2117 min.green -= max.green; Maximize( min.green, 0.0 );
2118 min.blue -= max.blue; Maximize( min.blue, 0.0 );
2119 min.opacity -= max.opacity; Maximize( min.opacity, 0.0 );
2120 min.index -= max.index; Maximize( min.index, 0.0 );
2121 break;
2122
anthony4fd27e22010-02-07 08:17:18 +00002123 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00002124 /* Select Pixel with Minimum Intensity within kernel neighbourhood
2125 **
2126 ** WARNING: the intensity test fails for CMYK and does not
2127 ** take into account the moderating effect of teh alpha channel
2128 ** on the intensity.
2129 **
2130 ** NOTE that the kernel is not reflected for this operation!
2131 */
anthony602ab9b2010-01-05 08:06:50 +00002132 k = kernel->values;
2133 k_pixels = p;
2134 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002135 for (v=0; v < (long) kernel->height; v++) {
2136 for (u=0; u < (long) kernel->width; u++, k++) {
anthony602ab9b2010-01-05 08:06:50 +00002137 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony4fd27e22010-02-07 08:17:18 +00002138 if ( result.red == 0.0 ||
2139 PixelIntensity(&(k_pixels[u])) < PixelIntensity(q) ) {
2140 /* copy the whole pixel - no channel selection */
2141 *q = k_pixels[u];
2142 if ( result.red > 0.0 ) changed++;
2143 result.red = 1.0;
2144 }
anthony602ab9b2010-01-05 08:06:50 +00002145 }
2146 k_pixels += image->columns+kernel->width;
2147 k_indexes += image->columns+kernel->width;
2148 }
anthony602ab9b2010-01-05 08:06:50 +00002149 break;
2150
anthony83ba99b2010-01-24 08:48:15 +00002151 case DilateIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00002152 /* Select Pixel with Maximum Intensity within kernel neighbourhood
2153 **
2154 ** WARNING: the intensity test fails for CMYK and does not
anthony9eb4f742010-05-18 02:45:54 +00002155 ** take into account the moderating effect of the alpha channel
2156 ** on the intensity (yet).
anthony930be612010-02-08 04:26:15 +00002157 **
2158 ** NOTE for correct working of this operation for asymetrical
2159 ** kernels, the kernel needs to be applied in its reflected form.
2160 ** That is its values needs to be reversed.
2161 */
anthony29188a82010-01-22 10:12:34 +00002162 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00002163 k_pixels = p;
2164 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002165 for (v=0; v < (long) kernel->height; v++) {
2166 for (u=0; u < (long) kernel->width; u++, k--) {
anthony29188a82010-01-22 10:12:34 +00002167 if ( IsNan(*k) || (*k) < 0.5 ) continue; /* boolean kernel */
2168 if ( result.red == 0.0 ||
2169 PixelIntensity(&(k_pixels[u])) > PixelIntensity(q) ) {
2170 /* copy the whole pixel - no channel selection */
2171 *q = k_pixels[u];
2172 if ( result.red > 0.0 ) changed++;
2173 result.red = 1.0;
2174 }
anthony602ab9b2010-01-05 08:06:50 +00002175 }
2176 k_pixels += image->columns+kernel->width;
2177 k_indexes += image->columns+kernel->width;
2178 }
anthony602ab9b2010-01-05 08:06:50 +00002179 break;
2180
anthony5ef8e942010-05-11 06:51:12 +00002181
anthony602ab9b2010-01-05 08:06:50 +00002182 case DistanceMorphology:
anthony930be612010-02-08 04:26:15 +00002183 /* Add kernel Value and select the minimum value found.
2184 ** The result is a iterative distance from edge of image shape.
2185 **
2186 ** All Distance Kernels are symetrical, but that may not always
2187 ** be the case. For example how about a distance from left edges?
2188 ** To work correctly with asymetrical kernels the reflected kernel
2189 ** needs to be applied.
anthony5ef8e942010-05-11 06:51:12 +00002190 **
2191 ** Actually this is really a GreyErode with a negative kernel!
2192 **
anthony930be612010-02-08 04:26:15 +00002193 */
anthony29188a82010-01-22 10:12:34 +00002194 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00002195 k_pixels = p;
2196 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002197 for (v=0; v < (long) kernel->height; v++) {
2198 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00002199 if ( IsNan(*k) ) continue;
2200 Minimize(result.red, (*k)+k_pixels[u].red);
2201 Minimize(result.green, (*k)+k_pixels[u].green);
2202 Minimize(result.blue, (*k)+k_pixels[u].blue);
2203 Minimize(result.opacity, (*k)+QuantumRange-k_pixels[u].opacity);
2204 if ( image->colorspace == CMYKColorspace)
2205 Minimize(result.index, (*k)+k_indexes[u]);
2206 }
2207 k_pixels += image->columns+kernel->width;
2208 k_indexes += image->columns+kernel->width;
2209 }
anthony602ab9b2010-01-05 08:06:50 +00002210 break;
2211
2212 case UndefinedMorphology:
2213 default:
2214 break; /* Do nothing */
anthony83ba99b2010-01-24 08:48:15 +00002215 }
anthony5ef8e942010-05-11 06:51:12 +00002216 /* Final mathematics of results (combine with original image?)
2217 **
2218 ** NOTE: Difference Morphology operators Edge* and *Hat could also
2219 ** be done here but works better with iteration as a image difference
2220 ** in the controling function (below). Thicken and Thinning however
2221 ** should be done here so thay can be iterated correctly.
2222 */
2223 switch ( method ) {
2224 case HitAndMissMorphology:
2225 case ErodeMorphology:
2226 result = min; /* minimum of neighbourhood */
2227 break;
2228 case DilateMorphology:
2229 result = max; /* maximum of neighbourhood */
2230 break;
2231 case ThinningMorphology:
2232 /* subtract pattern match from original */
2233 result.red -= min.red;
2234 result.green -= min.green;
2235 result.blue -= min.blue;
2236 result.opacity -= min.opacity;
2237 result.index -= min.index;
2238 break;
2239 case ThickenMorphology:
2240 /* Union with original image (maximize) - or should this be + */
2241 Maximize( result.red, min.red );
2242 Maximize( result.green, min.green );
2243 Maximize( result.blue, min.blue );
2244 Maximize( result.opacity, min.opacity );
2245 Maximize( result.index, min.index );
2246 break;
2247 default:
2248 /* result directly calculated or assigned */
2249 break;
2250 }
2251 /* Assign the resulting pixel values - Clamping Result */
anthony83ba99b2010-01-24 08:48:15 +00002252 switch ( method ) {
2253 case UndefinedMorphology:
2254 case DilateIntensityMorphology:
2255 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00002256 break; /* full pixel was directly assigned - not a channel method */
anthony83ba99b2010-01-24 08:48:15 +00002257 default:
anthony83ba99b2010-01-24 08:48:15 +00002258 if ((channel & RedChannel) != 0)
2259 q->red = ClampToQuantum(result.red);
2260 if ((channel & GreenChannel) != 0)
2261 q->green = ClampToQuantum(result.green);
2262 if ((channel & BlueChannel) != 0)
2263 q->blue = ClampToQuantum(result.blue);
2264 if ((channel & OpacityChannel) != 0
2265 && image->matte == MagickTrue )
2266 q->opacity = ClampToQuantum(QuantumRange-result.opacity);
2267 if ((channel & IndexChannel) != 0
2268 && image->colorspace == CMYKColorspace)
2269 q_indexes[x] = ClampToQuantum(result.index);
2270 break;
2271 }
anthony5ef8e942010-05-11 06:51:12 +00002272 /* Count up changed pixels */
anthony83ba99b2010-01-24 08:48:15 +00002273 if ( ( p[r].red != q->red )
2274 || ( p[r].green != q->green )
2275 || ( p[r].blue != q->blue )
2276 || ( p[r].opacity != q->opacity )
2277 || ( image->colorspace == CMYKColorspace &&
2278 p_indexes[r] != q_indexes[x] ) )
2279 changed++; /* The pixel had some value changed! */
anthony602ab9b2010-01-05 08:06:50 +00002280 p++;
2281 q++;
anthony83ba99b2010-01-24 08:48:15 +00002282 } /* x */
anthony602ab9b2010-01-05 08:06:50 +00002283 sync=SyncCacheViewAuthenticPixels(q_view,exception);
2284 if (sync == MagickFalse)
2285 status=MagickFalse;
2286 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2287 {
2288 MagickBooleanType
2289 proceed;
2290
2291#if defined(MAGICKCORE_OPENMP_SUPPORT)
2292 #pragma omp critical (MagickCore_MorphologyImage)
2293#endif
2294 proceed=SetImageProgress(image,MorphologyTag,progress++,image->rows);
2295 if (proceed == MagickFalse)
2296 status=MagickFalse;
2297 }
anthony83ba99b2010-01-24 08:48:15 +00002298 } /* y */
anthony602ab9b2010-01-05 08:06:50 +00002299 result_image->type=image->type;
2300 q_view=DestroyCacheView(q_view);
2301 p_view=DestroyCacheView(p_view);
cristy150989e2010-02-01 14:59:39 +00002302 return(status ? (unsigned long) changed : 0);
anthony602ab9b2010-01-05 08:06:50 +00002303}
2304
anthony4fd27e22010-02-07 08:17:18 +00002305
anthony9eb4f742010-05-18 02:45:54 +00002306MagickExport Image *MorphologyApply(const Image *image, const ChannelType
2307 channel,const MorphologyMethod method, const long iterations,
2308 const KernelInfo *kernel,const double bias,ExceptionInfo *exception)
cristy2be15382010-01-21 02:38:03 +00002309{
2310 Image
anthony9eb4f742010-05-18 02:45:54 +00002311 *curr_image, /* Image we are working with */
2312 *work_image, /* secondary working image */
2313 *save_image; /* save image for later use */
anthony602ab9b2010-01-05 08:06:50 +00002314
anthony4fd27e22010-02-07 08:17:18 +00002315 KernelInfo
anthony9eb4f742010-05-18 02:45:54 +00002316 *curr_kernel, /* current kernel list to apply */
2317 *this_kernel; /* current individual kernel to apply */
anthony4fd27e22010-02-07 08:17:18 +00002318
2319 MorphologyMethod
anthony9eb4f742010-05-18 02:45:54 +00002320 primative; /* the current morphology primative being applied */
2321
2322 MagickBooleanType
2323 verbose; /* verbose output of results */
2324
2325 CompositeOperator
2326 kernel_compose; /* Handling the result of multiple kernels*/
anthony4fd27e22010-02-07 08:17:18 +00002327
anthony1b2bc0a2010-05-12 05:25:22 +00002328 unsigned long
anthony9eb4f742010-05-18 02:45:54 +00002329 count, /* count of primative steps applied */
2330 loop, /* number of times though kernel list (iterations) */
2331 loop_limit, /* finish looping after this many times */
2332 stage, /* stage number for compound morphology */
2333 changed, /* number pixels changed by one primative operation */
2334 loop_changed, /* changes made over loop though of kernels */
2335 total_changed, /* total count of all changes to image */
2336 kernel_number; /* kernel number being applied */
anthony1b2bc0a2010-05-12 05:25:22 +00002337
anthony602ab9b2010-01-05 08:06:50 +00002338 assert(image != (Image *) NULL);
2339 assert(image->signature == MagickSignature);
anthony4fd27e22010-02-07 08:17:18 +00002340 assert(kernel != (KernelInfo *) NULL);
2341 assert(kernel->signature == MagickSignature);
anthony602ab9b2010-01-05 08:06:50 +00002342 assert(exception != (ExceptionInfo *) NULL);
2343 assert(exception->signature == MagickSignature);
2344
anthony9eb4f742010-05-18 02:45:54 +00002345 loop_limit = iterations;
2346 if ( iterations < 0 )
2347 loop_limit = image->columns > image->rows ? image->columns : image->rows;
anthony602ab9b2010-01-05 08:06:50 +00002348 if ( iterations == 0 )
2349 return((Image *)NULL); /* null operation - nothing to do! */
2350
2351 /* kernel must be valid at this point
2352 * (except maybe for posible future morphology methods like "Prune"
2353 */
cristy2be15382010-01-21 02:38:03 +00002354 assert(kernel != (KernelInfo *)NULL);
anthony602ab9b2010-01-05 08:06:50 +00002355
anthony9eb4f742010-05-18 02:45:54 +00002356 verbose = ( GetImageArtifact(image,"verbose") != (const char *) NULL );
anthony4f1dcb72010-05-14 08:43:10 +00002357
anthony9eb4f742010-05-18 02:45:54 +00002358 /* initialise for cleanup */
2359 curr_image = (Image *) image; /* result of morpholgy primative */
2360 work_image = (Image *) NULL; /* secondary working image */
2361 save_image = (Image *) NULL; /* save image for some compound methods */
2362 curr_kernel = (KernelInfo *)kernel; /* allow kernel list to be modified */
anthony4fd27e22010-02-07 08:17:18 +00002363
anthony9eb4f742010-05-18 02:45:54 +00002364 kernel_compose = NoCompositeOp; /* iterated over all kernels */
anthony602ab9b2010-01-05 08:06:50 +00002365
anthony9eb4f742010-05-18 02:45:54 +00002366 /* Select initial primative morphology to apply */
2367 switch( method ) {
anthony930be612010-02-08 04:26:15 +00002368 case CorrelateMorphology:
2369 /* A Correlation is actually a Convolution with a reflected kernel.
anthony9eb4f742010-05-18 02:45:54 +00002370 ** However a Convolution is a weighted sum using a reflected kernel.
anthony930be612010-02-08 04:26:15 +00002371 ** It may seem stange to convert a Correlation into a Convolution
2372 ** as the Correleation is the simplier method, but Convolution is
2373 ** much more commonly used, and it makes sense to implement it directly
2374 ** so as to avoid the need to duplicate the kernel when it is not
2375 ** required (which is typically the default).
2376 */
anthony9eb4f742010-05-18 02:45:54 +00002377 curr_kernel = CloneKernelInfo(kernel);
anthony1b2bc0a2010-05-12 05:25:22 +00002378 if (curr_kernel == (KernelInfo *) NULL)
2379 goto error_cleanup;
anthony930be612010-02-08 04:26:15 +00002380 RotateKernelInfo(curr_kernel,180);
anthony9eb4f742010-05-18 02:45:54 +00002381 /* FALL THRU to Convolve */
anthonyc94cdb02010-01-06 08:15:29 +00002382 case ConvolveMorphology:
anthony9eb4f742010-05-18 02:45:54 +00002383 primative = ConvolveMorphology;
2384 kernel_compose = NoCompositeOp;
2385 break;
2386 case ErodeMorphology: /* just erode */
2387 case OpenMorphology: /* erode then dialate */
2388 case EdgeInMorphology: /* erode and image difference */
2389 case TopHatMorphology: /* erode, dilate and image difference */
2390 case SmoothMorphology: /* erode, dilate, dilate, erode */
2391 primative = ErodeMorphology;
2392 break;
2393 case ErodeIntensityMorphology:
2394 case OpenIntensityMorphology:
2395 primative = ErodeIntensityMorphology;
2396 break;
2397 case DilateMorphology: /* just dilate */
2398 case EdgeOutMorphology: /* dilate and image difference */
2399 case EdgeMorphology: /* dilate and erode difference */
2400 primative = DilateMorphology;
2401 break;
2402 case CloseMorphology: /* dilate, then erode */
2403 case BottomHatMorphology: /* dilate and image difference */
2404 curr_kernel = CloneKernelInfo(kernel);
2405 if (curr_kernel == (KernelInfo *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00002406 goto error_cleanup;
anthony9eb4f742010-05-18 02:45:54 +00002407 RotateKernelInfo(curr_kernel,180);
2408 primative = DilateMorphology;
2409 break;
2410 case DilateIntensityMorphology:
2411 case CloseIntensityMorphology:
2412 curr_kernel = CloneKernelInfo(kernel);
2413 if (curr_kernel == (KernelInfo *) NULL)
2414 goto error_cleanup;
2415 RotateKernelInfo(curr_kernel,180);
2416 primative = DilateIntensityMorphology;
2417 break;
2418 case HitAndMissMorphology:
2419 primative = HitAndMissMorphology;
2420 loop_limit = 1; /* iterate only once */
2421 kernel_compose = LightenCompositeOp; /* Union of Hit-And-Miss */
2422 break;
2423 case ThinningMorphology: /* iterative morphology */
2424 case ThickenMorphology:
2425 case DistanceMorphology: /* Distance should never use multple kernels */
2426 case UndefinedMorphology:
anthony3c1cd552010-05-18 04:33:23 +00002427 primative = method;
anthony930be612010-02-08 04:26:15 +00002428 break;
anthony602ab9b2010-01-05 08:06:50 +00002429 }
2430
anthony3c1cd552010-05-18 04:33:23 +00002431#if 0
2432 { /* User override of results handling -- Experimental */
anthony9eb4f742010-05-18 02:45:54 +00002433 const char
2434 *artifact = GetImageArtifact(image,"morphology:style");
2435 if ( artifact != (const char *) NULL ) {
2436 if (LocaleCompare("union",artifact) == 0)
2437 kernel_compose = LightenCompositeOp;
2438 if (LocaleCompare("iterate",artifact) == 0)
2439 kernel_compose = NoCompositeOp;
2440 else
2441 kernel_compose = (CompositeOperator) ParseMagickOption(
2442 MagickComposeOptions,MagickFalse,artifact);
2443 if ( kernel_compose == UndefinedCompositeOp )
2444 perror("Invalid \"morphology:compose\" setting\n");
2445 }
2446 }
anthony3c1cd552010-05-18 04:33:23 +00002447#endif
anthony7a01dcf2010-05-11 12:25:52 +00002448
anthony9eb4f742010-05-18 02:45:54 +00002449 /* Initialize compound morphology stages */
2450 count = 0; /* number of low-level morphology primatives performed */
2451 total_changed = 0; /* total number of pixels changed thoughout */
2452 stage = 1; /* the compound morphology stage number */
2453
2454 /* compount morphology staging loop */
2455 while ( 1 ) {
2456
2457#if 1
2458 /* Extra information for debugging compound operations */
2459 if ( verbose == MagickTrue && primative != method )
2460 fprintf(stderr, "Morphology %s: Stage %lu %s%s (%s)\n",
2461 MagickOptionToMnemonic(MagickMorphologyOptions, method), stage,
2462 MagickOptionToMnemonic(MagickMorphologyOptions, primative),
2463 ( curr_kernel == kernel) ? "" : "*",
2464 ( kernel_compose == NoCompositeOp ) ? "iterate"
2465 : MagickOptionToMnemonic(MagickComposeOptions, kernel_compose) );
2466#endif
2467
2468 if ( kernel_compose == NoCompositeOp ) {
2469 /******************************
2470 ** Iterate over all Kernels **
2471 ******************************/
2472 loop = 0;
2473 loop_changed = 1;
2474 while ( loop < loop_limit && loop_changed > 0 ) {
2475 loop++; /* the iteration of this kernel */
2476
2477 loop_changed = 0;
2478 this_kernel = curr_kernel;
2479 kernel_number = 0;
2480 while ( this_kernel != NULL ) {
2481
2482 /* Create a destination image, if not yet defined */
2483 if ( work_image == (Image *) NULL )
2484 {
2485 work_image=CloneImage(image,0,0,MagickTrue,exception);
2486 if (work_image == (Image *) NULL)
2487 goto error_cleanup;
2488 if (SetImageStorageClass(work_image,DirectClass) == MagickFalse)
2489 {
2490 InheritException(exception,&work_image->exception);
2491 goto error_cleanup;
2492 }
2493 }
2494
2495 /* morphological primative curr -> work */
2496 count++;
2497 changed = MorphologyPrimative(curr_image, work_image, primative,
2498 channel, this_kernel, bias, exception);
2499 loop_changed += changed;
2500 total_changed += changed;
2501
2502 if ( verbose == MagickTrue )
2503 fprintf(stderr, "Morphology %s:%lu.%lu #%lu => Changed %lu\n",
2504 MagickOptionToMnemonic(MagickMorphologyOptions, primative),
2505 loop, kernel_number, count, changed);
2506
2507 /* prepare next loop */
2508 { Image *tmp = work_image; /* swap images for iteration */
2509 work_image = curr_image;
2510 curr_image = tmp;
2511 }
2512 if ( work_image == image )
2513 work_image = (Image *) NULL; /* never assign image to 'work' */
2514 this_kernel = this_kernel->next; /* prepare next kernel (if any) */
anthony1b2bc0a2010-05-12 05:25:22 +00002515 kernel_number++;
2516 }
anthony7a01dcf2010-05-11 12:25:52 +00002517
anthony9eb4f742010-05-18 02:45:54 +00002518 if ( verbose == MagickTrue && kernel->next != NULL )
2519 fprintf(stderr, "Morphology %s:%lu #%lu ===> Changed %lu Total %lu\n",
2520 MagickOptionToMnemonic(MagickMorphologyOptions, primative),
2521 loop, count, loop_changed, total_changed );
anthony602ab9b2010-01-05 08:06:50 +00002522 }
anthony1b2bc0a2010-05-12 05:25:22 +00002523 }
2524
anthony9eb4f742010-05-18 02:45:54 +00002525 else {
2526 /*************************************
2527 ** Composition of Iterated Kernels **
2528 *************************************/
2529 Image
2530 *input_image, /* starting point for kernels */
2531 *union_image;
2532 input_image = curr_image;
2533 union_image = (Image *) NULL;
2534
2535 this_kernel = curr_kernel;
2536 kernel_number = 0;
2537 while ( this_kernel != NULL ) {
2538
2539 if( curr_image != (Image *) NULL && curr_image != input_image )
2540 curr_image=DestroyImage(curr_image);
2541 curr_image = input_image; /* always start with the original image */
2542
2543 loop = 0;
2544 changed = 1;
2545 loop_changed = 0;
2546 while ( loop < loop_limit && changed > 0 ) {
2547 loop++; /* the iteration of this kernel */
2548
2549 /* Create a destination image, if not defined */
2550 if ( work_image == (Image *) NULL )
2551 {
2552 work_image=CloneImage(image,0,0,MagickTrue,exception);
2553 if (work_image == (Image *) NULL)
2554 goto error_cleanup;
2555 if (SetImageStorageClass(work_image,DirectClass) == MagickFalse)
2556 {
2557 InheritException(exception,&curr_image->exception);
2558 if( union_image != (Image *) NULL )
2559 union_image=DestroyImage(union_image);
2560 if( curr_image != input_image )
2561 curr_image = DestroyImage(curr_image);
2562 curr_image = (Image *) input_image;
2563 goto error_cleanup;
2564 }
2565 }
2566
2567 /* morphological primative curr -> work */
2568 count++;
2569 changed = MorphologyPrimative(curr_image,work_image,primative,
2570 channel, this_kernel, bias, exception);
2571 loop_changed += changed;
2572 total_changed += changed;
2573
2574 if ( verbose == MagickTrue )
anthony4f1dcb72010-05-14 08:43:10 +00002575 fprintf(stderr, "Morphology %s:%lu.%lu #%lu => Changed %lu\n",
anthony9eb4f742010-05-18 02:45:54 +00002576 MagickOptionToMnemonic(MagickMorphologyOptions, primative),
2577 loop, kernel_number, count, changed);
2578
2579 /* prepare next loop */
2580 { Image *tmp = work_image; /* swap images for iteration */
2581 work_image = curr_image; /* curr_image is now the results */
2582 curr_image = tmp;
2583 }
2584 if ( work_image == input_image )
2585 work_image = (Image *) NULL; /* clear work of the input_image */
2586
2587 } /* end kernel iteration */
2588
2589 /* make a union of the iterated kernel */
2590 if ( union_image == (Image *) NULL) /* start the union? */
2591 union_image = curr_image, curr_image = (Image *)NULL;
2592 else
2593 (void) CompositeImageChannel(union_image,
2594 (ChannelType) (channel & ~SyncChannels), kernel_compose,
2595 curr_image, 0, 0);
2596
2597 this_kernel = this_kernel->next; /* next kernel (if any) */
anthony1b2bc0a2010-05-12 05:25:22 +00002598 kernel_number++;
anthony602ab9b2010-01-05 08:06:50 +00002599 }
anthony9eb4f742010-05-18 02:45:54 +00002600
2601 if ( verbose == MagickTrue && kernel->next != NULL && loop_limit > 1 )
2602 fprintf(stderr, "Morphology %s:%lu #%lu ===> Changed %lu Total %lu\n",
2603 MagickOptionToMnemonic(MagickMorphologyOptions, primative),
2604 loop, count, loop_changed, total_changed );
2605
2606#if 0
2607fprintf(stderr, "--E-- image=0x%lx\n", (unsigned long)image);
2608fprintf(stderr, " input=0x%lx\n", (unsigned long)input_image);
2609fprintf(stderr, " union=0x%lx\n", (unsigned long)union_image);
2610fprintf(stderr, " curr =0x%lx\n", (unsigned long)curr_image);
2611fprintf(stderr, " work =0x%lx\n", (unsigned long)work_image);
2612fprintf(stderr, " save =0x%lx\n", (unsigned long)save_image);
2613#endif
2614
2615 /* Finish up - return the union of results */
2616 if( curr_image != (Image *) NULL && curr_image != input_image )
2617 curr_image=DestroyImage(curr_image);
2618 if( input_image != input_image )
2619 input_image = DestroyImage(input_image);
2620 curr_image = union_image;
anthony1b2bc0a2010-05-12 05:25:22 +00002621 }
anthony9eb4f742010-05-18 02:45:54 +00002622
2623 /* Compound Morphology Operations
2624 * set next 'primative' iteration, and continue
2625 * or break when all operations are complete.
2626 */
2627 stage++; /* what is the next stage number to do */
2628 switch( method ) {
2629 case SmoothMorphology: /* open, close */
2630 switch ( stage ) {
2631 /* case 1: initialized above */
2632 case 2: /* open part 2 */
2633 primative = DilateMorphology;
2634 continue;
2635 case 3: /* close part 1 */
2636 curr_kernel = CloneKernelInfo(kernel);
2637 if (curr_kernel == (KernelInfo *) NULL)
2638 goto error_cleanup;
2639 RotateKernelInfo(curr_kernel,180);
2640 continue;
2641 case 4: /* close part 2 */
2642 primative = ErodeMorphology;
2643 continue;
2644 }
2645 break;
2646 case OpenMorphology: /* erode, dilate */
2647 case TopHatMorphology:
2648 primative = DilateMorphology;
2649 if ( stage <= 2 ) continue;
2650 break;
2651 case OpenIntensityMorphology:
2652 primative = DilateIntensityMorphology;
2653 if ( stage <= 2 ) continue;
2654 break;
2655 case CloseMorphology: /* dilate, erode */
2656 case BottomHatMorphology:
2657 primative = ErodeMorphology;
2658 if ( stage <= 2 ) continue;
2659 break;
2660 case CloseIntensityMorphology:
2661 primative = ErodeIntensityMorphology;
2662 if ( stage <= 2 ) continue;
2663 break;
2664 case EdgeMorphology: /* dilate and erode difference */
2665 if (stage <= 2) {
2666 save_image = curr_image;
2667 curr_image = (Image *) image;
2668 primative = ErodeMorphology;
2669 continue;
2670 }
2671 break;
2672 default: /* Primitive Morphology is just finished! */
2673 break;
2674 }
2675
2676 if ( verbose == MagickTrue && count > 1 )
2677 fprintf(stderr, "Morphology %s: ======> Total %lu\n",
2678 MagickOptionToMnemonic(MagickMorphologyOptions, method),
2679 total_changed );
2680
2681 /* If we reach this point we are finished! - Break the Loop */
2682 break;
anthony602ab9b2010-01-05 08:06:50 +00002683 }
anthony930be612010-02-08 04:26:15 +00002684
anthony9eb4f742010-05-18 02:45:54 +00002685 /* Final Post-processing for some Compound Methods
anthony7d10d742010-05-06 07:05:29 +00002686 **
2687 ** The removal of any 'Sync' channel flag in the Image Compositon below
2688 ** ensures the compose method is applied in a purely mathematical way, only
2689 ** the selected channels, without any normal 'alpha blending' normally
2690 ** associated with the compose method.
2691 **
2692 ** Note "method" here is the 'original' morphological method, and not the
2693 ** 'current' morphological method used above to generate "new_image".
2694 */
anthony4fd27e22010-02-07 08:17:18 +00002695 switch( method ) {
2696 case EdgeOutMorphology:
2697 case EdgeInMorphology:
2698 case TopHatMorphology:
2699 case BottomHatMorphology:
anthony9eb4f742010-05-18 02:45:54 +00002700 (void) CompositeImageChannel(curr_image,
2701 (ChannelType) (channel & ~SyncChannels), DifferenceCompositeOp,
2702 image, 0, 0);
anthony4fd27e22010-02-07 08:17:18 +00002703 break;
anthony7d10d742010-05-06 07:05:29 +00002704 case EdgeMorphology:
anthony9eb4f742010-05-18 02:45:54 +00002705 /* Difference the Eroded Image with a Dilate image */
2706 (void) CompositeImageChannel(curr_image,
2707 (ChannelType) (channel & ~SyncChannels), DifferenceCompositeOp,
2708 save_image, 0, 0);
anthony4fd27e22010-02-07 08:17:18 +00002709 break;
2710 default:
2711 break;
2712 }
anthony602ab9b2010-01-05 08:06:50 +00002713
anthony9eb4f742010-05-18 02:45:54 +00002714 goto exit_cleanup;
anthony1b2bc0a2010-05-12 05:25:22 +00002715
2716 /* Yes goto's are bad, but in this case it makes cleanup lot more efficient */
2717error_cleanup:
anthony9eb4f742010-05-18 02:45:54 +00002718 if ( curr_image != (Image *) NULL && curr_image != image )
2719 DestroyImage(curr_image);
anthony1b2bc0a2010-05-12 05:25:22 +00002720exit_cleanup:
anthony9eb4f742010-05-18 02:45:54 +00002721 if ( work_image != (Image *) NULL )
2722 DestroyImage(work_image);
2723 if ( save_image != (Image *) NULL )
2724 DestroyImage(save_image);
2725 return(curr_image);
2726}
2727
2728/*
2729%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2730% %
2731% %
2732% %
2733% M o r p h o l o g y I m a g e C h a n n e l %
2734% %
2735% %
2736% %
2737%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2738%
2739% MorphologyImageChannel() applies a user supplied kernel to the image
2740% according to the given mophology method.
2741%
2742% This function applies any and all user defined settings before calling
2743% the above internal function MorphologyApply().
2744%
2745% User defined settings include...
2746% * convolution/correlation output bias (as per "-bias")
2747% * kernel normalization/scaling settings ("-set 'option:convolve:scale'")
2748% * kernel printing (after modification) ("-set option:showkernel 1")
2749%
2750%
2751% The format of the MorphologyImage method is:
2752%
2753% Image *MorphologyImage(const Image *image,MorphologyMethod method,
2754% const long iterations,KernelInfo *kernel,ExceptionInfo *exception)
2755%
2756% Image *MorphologyImageChannel(const Image *image, const ChannelType
2757% channel,MorphologyMethod method,const long iterations,
2758% KernelInfo *kernel,ExceptionInfo *exception)
2759%
2760% A description of each parameter follows:
2761%
2762% o image: the image.
2763%
2764% o method: the morphology method to be applied.
2765%
2766% o iterations: apply the operation this many times (or no change).
2767% A value of -1 means loop until no change found.
2768% How this is applied may depend on the morphology method.
2769% Typically this is a value of 1.
2770%
2771% o channel: the channel type.
2772%
2773% o kernel: An array of double representing the morphology kernel.
2774% Warning: kernel may be normalized for the Convolve method.
2775%
2776% o exception: return any errors or warnings in this structure.
2777%
2778*/
2779
2780MagickExport Image *MorphologyImageChannel(const Image *image,
2781 const ChannelType channel,const MorphologyMethod method,
2782 const long iterations,const KernelInfo *kernel,ExceptionInfo *exception)
2783{
2784 const char
2785 *artifact;
2786
2787 KernelInfo
2788 *curr_kernel;
2789
2790 Image
2791 *morphology_image;
2792
2793
2794 /* Apply Convolve/Correlate Normalization and Scaling Factors
2795 this is done BEFORE the ShowKernelInfo() function is called
2796 so that users can see the results of the 'convolve:scale' option.
2797 */
2798 curr_kernel = (KernelInfo *) kernel;
2799 if ( method == ConvolveMorphology )
2800 {
2801 artifact = GetImageArtifact(image,"convolve:scale");
2802 if ( artifact != (char *)NULL ) {
2803 GeometryFlags
2804 flags;
2805 GeometryInfo
2806 args;
2807
2808 if ( curr_kernel == kernel )
2809 curr_kernel = CloneKernelInfo(kernel);
2810 if (curr_kernel == (KernelInfo *) NULL) {
2811 curr_kernel=DestroyKernelInfo(curr_kernel);
2812 return((Image *) NULL);
2813 }
anthony43c49252010-05-18 10:59:50 +00002814 SetGeometryInfo(&args);
anthony9eb4f742010-05-18 02:45:54 +00002815 args.rho = 1.0;
2816 flags = (GeometryFlags) ParseGeometry(artifact, &args);
anthony43c49252010-05-18 10:59:50 +00002817
2818 /* normalize and/or scale kernel values */
anthony9eb4f742010-05-18 02:45:54 +00002819 ScaleKernelInfo(curr_kernel, args.rho, flags);
anthony43c49252010-05-18 10:59:50 +00002820
2821 /* Add percentage of Unity Kernel, for blending with original */
2822 if ( (flags & SigmaValue) != 0 )
2823 {
2824 if ( (flags & PercentValue) != 0 )
2825 args.sigma = args.sigma/100.0;
2826 UnityAddKernelInfo(curr_kernel, args.sigma);
2827 }
anthony9eb4f742010-05-18 02:45:54 +00002828 }
2829 }
2830
2831 /* display the (normalized) kernel via stderr */
2832 artifact = GetImageArtifact(image,"showkernel");
2833 if ( artifact != (const char *) NULL)
2834 ShowKernelInfo(curr_kernel);
2835
2836 /* Apply the Morphology */
2837 morphology_image = MorphologyApply(image, channel, method, iterations,
2838 curr_kernel, image->bias, exception);
2839
2840 /* Cleanup and Exit */
2841 if ( curr_kernel != kernel )
anthony1b2bc0a2010-05-12 05:25:22 +00002842 curr_kernel=DestroyKernelInfo(curr_kernel);
anthony9eb4f742010-05-18 02:45:54 +00002843 return(morphology_image);
2844}
2845
2846MagickExport Image *MorphologyImage(const Image *image, const MorphologyMethod
2847 method, const long iterations,const KernelInfo *kernel, ExceptionInfo
2848 *exception)
2849{
2850 Image
2851 *morphology_image;
2852
2853 morphology_image=MorphologyImageChannel(image,DefaultChannels,method,
2854 iterations,kernel,exception);
2855 return(morphology_image);
anthony602ab9b2010-01-05 08:06:50 +00002856}
anthony83ba99b2010-01-24 08:48:15 +00002857
2858/*
2859%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2860% %
2861% %
2862% %
anthony4fd27e22010-02-07 08:17:18 +00002863+ R o t a t e K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00002864% %
2865% %
2866% %
2867%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2868%
anthony4fd27e22010-02-07 08:17:18 +00002869% RotateKernelInfo() rotates the kernel by the angle given. Currently it is
anthony83ba99b2010-01-24 08:48:15 +00002870% restricted to 90 degree angles, but this may be improved in the future.
2871%
anthony4fd27e22010-02-07 08:17:18 +00002872% The format of the RotateKernelInfo method is:
anthony83ba99b2010-01-24 08:48:15 +00002873%
anthony4fd27e22010-02-07 08:17:18 +00002874% void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00002875%
2876% A description of each parameter follows:
2877%
2878% o kernel: the Morphology/Convolution kernel
2879%
2880% o angle: angle to rotate in degrees
2881%
anthonyc4c86e02010-01-27 09:30:32 +00002882% This function is only internel to this module, as it is not finalized,
2883% especially with regard to non-orthogonal angles, and rotation of larger
2884% 2D kernels.
anthony83ba99b2010-01-24 08:48:15 +00002885*/
anthony4fd27e22010-02-07 08:17:18 +00002886static void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00002887{
anthony1b2bc0a2010-05-12 05:25:22 +00002888 /* angle the lower kernels first */
2889 if ( kernel->next != (KernelInfo *) NULL)
2890 RotateKernelInfo(kernel->next, angle);
2891
anthony83ba99b2010-01-24 08:48:15 +00002892 /* WARNING: Currently assumes the kernel (rightly) is horizontally symetrical
2893 **
2894 ** TODO: expand beyond simple 90 degree rotates, flips and flops
2895 */
2896
2897 /* Modulus the angle */
2898 angle = fmod(angle, 360.0);
2899 if ( angle < 0 )
2900 angle += 360.0;
2901
anthony3c10fc82010-05-13 02:40:51 +00002902 if ( 337.5 < angle || angle <= 22.5 )
anthony43c49252010-05-18 10:59:50 +00002903 return; /* Near zero angle - no change! - At least not at this time */
anthony83ba99b2010-01-24 08:48:15 +00002904
anthony3dd0f622010-05-13 12:57:32 +00002905 /* Handle special cases */
anthony83ba99b2010-01-24 08:48:15 +00002906 switch (kernel->type) {
2907 /* These built-in kernels are cylindrical kernels, rotating is useless */
2908 case GaussianKernel:
anthony83ba99b2010-01-24 08:48:15 +00002909 case DOGKernel:
2910 case DiskKernel:
anthony3dd0f622010-05-13 12:57:32 +00002911 case PeaksKernel:
2912 case LaplacianKernel:
anthony83ba99b2010-01-24 08:48:15 +00002913 case ChebyshevKernel:
2914 case ManhattenKernel:
2915 case EuclideanKernel:
2916 return;
2917
2918 /* These may be rotatable at non-90 angles in the future */
2919 /* but simply rotating them in multiples of 90 degrees is useless */
2920 case SquareKernel:
2921 case DiamondKernel:
2922 case PlusKernel:
anthony3dd0f622010-05-13 12:57:32 +00002923 case CrossKernel:
anthony83ba99b2010-01-24 08:48:15 +00002924 return;
2925
2926 /* These only allows a +/-90 degree rotation (by transpose) */
2927 /* A 180 degree rotation is useless */
2928 case BlurKernel:
2929 case RectangleKernel:
2930 if ( 135.0 < angle && angle <= 225.0 )
2931 return;
2932 if ( 225.0 < angle && angle <= 315.0 )
2933 angle -= 180;
2934 break;
2935
anthony3dd0f622010-05-13 12:57:32 +00002936 default:
anthony83ba99b2010-01-24 08:48:15 +00002937 break;
2938 }
anthony3c10fc82010-05-13 02:40:51 +00002939 /* Attempt rotations by 45 degrees */
2940 if ( 22.5 < fmod(angle,90.0) && fmod(angle,90.0) <= 67.5 )
2941 {
2942 if ( kernel->width == 3 && kernel->height == 3 )
2943 { /* Rotate a 3x3 square by 45 degree angle */
2944 MagickRealType t = kernel->values[0];
anthony43c49252010-05-18 10:59:50 +00002945 kernel->values[0] = kernel->values[3];
2946 kernel->values[3] = kernel->values[6];
2947 kernel->values[6] = kernel->values[7];
2948 kernel->values[7] = kernel->values[8];
2949 kernel->values[8] = kernel->values[5];
2950 kernel->values[5] = kernel->values[2];
2951 kernel->values[2] = kernel->values[1];
2952 kernel->values[1] = t;
2953 /* NOT DONE - rotate a off-centered origin as well! */
2954 angle = fmod(angle+315.0, 360.0); /* angle reduced 45 degrees */
2955 kernel->angle = fmod(kernel->angle+45.0, 360.0);
anthony3c10fc82010-05-13 02:40:51 +00002956 }
2957 else
2958 perror("Unable to rotate non-3x3 kernel by 45 degrees");
2959 }
2960 if ( 45.0 < fmod(angle, 180.0) && fmod(angle,180.0) <= 135.0 )
2961 {
2962 if ( kernel->width == 1 || kernel->height == 1 )
2963 { /* Do a transpose of the image, which results in a 90
2964 ** degree rotation of a 1 dimentional kernel
2965 */
2966 long
2967 t;
2968 t = (long) kernel->width;
2969 kernel->width = kernel->height;
2970 kernel->height = (unsigned long) t;
2971 t = kernel->x;
2972 kernel->x = kernel->y;
2973 kernel->y = t;
anthony43c49252010-05-18 10:59:50 +00002974 if ( kernel->width == 1 ) {
2975 angle = fmod(angle+270.0, 360.0); /* angle reduced 90 degrees */
2976 kernel->angle = fmod(kernel->angle+90.0, 360.0);
2977 } else {
2978 angle = fmod(angle+90.0, 360.0); /* angle increased 90 degrees */
2979 kernel->angle = fmod(kernel->angle+270.0, 360.0);
2980 }
anthony3c10fc82010-05-13 02:40:51 +00002981 }
2982 else if ( kernel->width == kernel->height )
2983 { /* Rotate a square array of values by 90 degrees */
2984 register unsigned long
2985 i,j,x,y;
2986 register MagickRealType
2987 *k,t;
2988 k=kernel->values;
2989 for( i=0, x=kernel->width-1; i<=x; i++, x--)
2990 for( j=0, y=kernel->height-1; j<y; j++, y--)
2991 { t = k[i+j*kernel->width];
anthony43c49252010-05-18 10:59:50 +00002992 k[i+j*kernel->width] = k[j+x*kernel->width];
2993 k[j+x*kernel->width] = k[x+y*kernel->width];
2994 k[x+y*kernel->width] = k[y+i*kernel->width];
2995 k[y+i*kernel->width] = t;
anthony3c10fc82010-05-13 02:40:51 +00002996 }
anthony43c49252010-05-18 10:59:50 +00002997 /* NOT DONE - rotate a off-centered origin as well! */
2998 angle = fmod(angle+270.0, 360.0); /* angle reduced 90 degrees */
2999 kernel->angle = fmod(kernel->angle+90.0, 360.0);
anthony3c10fc82010-05-13 02:40:51 +00003000 }
3001 else
3002 perror("Unable to rotate a non-square, non-linear kernel 90 degrees");
3003 }
anthony83ba99b2010-01-24 08:48:15 +00003004 if ( 135.0 < angle && angle <= 225.0 )
3005 {
anthony43c49252010-05-18 10:59:50 +00003006 /* For a 180 degree rotation - also know as a reflection
3007 * This is actually a very very common operation!
3008 * Basically all that is needed is a reversal of the kernel data!
3009 * And a reflection of the origon
3010 */
anthony83ba99b2010-01-24 08:48:15 +00003011 unsigned long
3012 i,j;
3013 register double
3014 *k,t;
3015
3016 k=kernel->values;
3017 for ( i=0, j=kernel->width*kernel->height-1; i<j; i++, j--)
3018 t=k[i], k[i]=k[j], k[j]=t;
3019
anthony930be612010-02-08 04:26:15 +00003020 kernel->x = (long) kernel->width - kernel->x - 1;
3021 kernel->y = (long) kernel->height - kernel->y - 1;
anthony43c49252010-05-18 10:59:50 +00003022 angle = fmod(angle-180.0, 360.0); /* angle+180 degrees */
3023 kernel->angle = fmod(kernel->angle+180.0, 360.0);
anthony83ba99b2010-01-24 08:48:15 +00003024 }
anthony3c10fc82010-05-13 02:40:51 +00003025 /* At this point angle should at least between -45 (315) and +45 degrees
anthony83ba99b2010-01-24 08:48:15 +00003026 * In the future some form of non-orthogonal angled rotates could be
3027 * performed here, posibily with a linear kernel restriction.
3028 */
3029
3030#if 0
anthony3c10fc82010-05-13 02:40:51 +00003031 { /* Do a Flop by reversing each row.
anthony83ba99b2010-01-24 08:48:15 +00003032 */
3033 unsigned long
3034 y;
cristy150989e2010-02-01 14:59:39 +00003035 register long
anthony83ba99b2010-01-24 08:48:15 +00003036 x,r;
3037 register double
3038 *k,t;
3039
3040 for ( y=0, k=kernel->values; y < kernel->height; y++, k+=kernel->width)
3041 for ( x=0, r=kernel->width-1; x<kernel->width/2; x++, r--)
3042 t=k[x], k[x]=k[r], k[r]=t;
3043
cristyc99304f2010-02-01 15:26:27 +00003044 kernel->x = kernel->width - kernel->x - 1;
anthony83ba99b2010-01-24 08:48:15 +00003045 angle = fmod(angle+180.0, 360.0);
3046 }
3047#endif
3048 return;
3049}
3050
3051/*
3052%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3053% %
3054% %
3055% %
cristy6771f1e2010-03-05 19:43:39 +00003056% S c a l e K e r n e l I n f o %
anthonycc6c8362010-01-25 04:14:01 +00003057% %
3058% %
3059% %
3060%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3061%
anthony1b2bc0a2010-05-12 05:25:22 +00003062% ScaleKernelInfo() scales the given kernel list by the given amount, with or
3063% without normalization of the sum of the kernel values (as per given flags).
anthonycc6c8362010-01-25 04:14:01 +00003064%
anthony999bb2c2010-02-18 12:38:01 +00003065% By default (no flags given) the values within the kernel is scaled
anthony1b2bc0a2010-05-12 05:25:22 +00003066% directly using given scaling factor without change.
anthonycc6c8362010-01-25 04:14:01 +00003067%
anthony1b2bc0a2010-05-12 05:25:22 +00003068% If any 'normalize_flags' are given the kernel will first be normalized and
3069% then further scaled by the scaling factor value given. A 'PercentValue'
3070% flag will cause the given scaling factor to be divided by one hundred
3071% percent.
anthony999bb2c2010-02-18 12:38:01 +00003072%
3073% Kernel normalization ('normalize_flags' given) is designed to ensure that
3074% any use of the kernel scaling factor with 'Convolve' or 'Correlate'
anthony1b2bc0a2010-05-12 05:25:22 +00003075% morphology methods will fall into -1.0 to +1.0 range. Note that for
3076% non-HDRI versions of IM this may cause images to have any negative results
3077% clipped, unless some 'bias' is used.
anthony999bb2c2010-02-18 12:38:01 +00003078%
3079% More specifically. Kernels which only contain positive values (such as a
3080% 'Gaussian' kernel) will be scaled so that those values sum to +1.0,
anthony1b2bc0a2010-05-12 05:25:22 +00003081% ensuring a 0.0 to +1.0 output range for non-HDRI images.
anthony999bb2c2010-02-18 12:38:01 +00003082%
3083% For Kernels that contain some negative values, (such as 'Sharpen' kernels)
3084% the kernel will be scaled by the absolute of the sum of kernel values, so
3085% that it will generally fall within the +/- 1.0 range.
3086%
3087% For kernels whose values sum to zero, (such as 'Laplician' kernels) kernel
3088% will be scaled by just the sum of the postive values, so that its output
3089% range will again fall into the +/- 1.0 range.
3090%
3091% For special kernels designed for locating shapes using 'Correlate', (often
3092% only containing +1 and -1 values, representing foreground/brackground
3093% matching) a special normalization method is provided to scale the positive
3094% values seperatally to those of the negative values, so the kernel will be
3095% forced to become a zero-sum kernel better suited to such searches.
3096%
anthony1b2bc0a2010-05-12 05:25:22 +00003097% WARNING: Correct normalization of the kernel assumes that the '*_range'
anthony999bb2c2010-02-18 12:38:01 +00003098% attributes within the kernel structure have been correctly set during the
3099% kernels creation.
3100%
3101% NOTE: The values used for 'normalize_flags' have been selected specifically
anthony1b2bc0a2010-05-12 05:25:22 +00003102% to match the use of geometry options, so that '!' means NormalizeValue,
3103% '^' means CorrelateNormalizeValue, and '%' means PercentValue. All other
anthony999bb2c2010-02-18 12:38:01 +00003104% GeometryFlags values are ignored.
anthonycc6c8362010-01-25 04:14:01 +00003105%
anthony4fd27e22010-02-07 08:17:18 +00003106% The format of the ScaleKernelInfo method is:
anthonycc6c8362010-01-25 04:14:01 +00003107%
anthony999bb2c2010-02-18 12:38:01 +00003108% void ScaleKernelInfo(KernelInfo *kernel, const double scaling_factor,
3109% const MagickStatusType normalize_flags )
anthonycc6c8362010-01-25 04:14:01 +00003110%
3111% A description of each parameter follows:
3112%
3113% o kernel: the Morphology/Convolution kernel
3114%
anthony999bb2c2010-02-18 12:38:01 +00003115% o scaling_factor:
3116% multiply all values (after normalization) by this factor if not
3117% zero. If the kernel is normalized regardless of any flags.
3118%
3119% o normalize_flags:
3120% GeometryFlags defining normalization method to use.
3121% specifically: NormalizeValue, CorrelateNormalizeValue,
3122% and/or PercentValue
anthonycc6c8362010-01-25 04:14:01 +00003123%
anthonyc4c86e02010-01-27 09:30:32 +00003124% This function is internal to this module only at this time, but can be
3125% exported to other modules if needed.
anthonycc6c8362010-01-25 04:14:01 +00003126*/
cristy6771f1e2010-03-05 19:43:39 +00003127MagickExport void ScaleKernelInfo(KernelInfo *kernel,
3128 const double scaling_factor,const GeometryFlags normalize_flags)
anthonycc6c8362010-01-25 04:14:01 +00003129{
cristy150989e2010-02-01 14:59:39 +00003130 register long
anthonycc6c8362010-01-25 04:14:01 +00003131 i;
3132
anthony999bb2c2010-02-18 12:38:01 +00003133 register double
3134 pos_scale,
3135 neg_scale;
3136
anthony1b2bc0a2010-05-12 05:25:22 +00003137 /* scale the lower kernels first */
3138 if ( kernel->next != (KernelInfo *) NULL)
3139 ScaleKernelInfo(kernel->next, scaling_factor, normalize_flags);
3140
anthony999bb2c2010-02-18 12:38:01 +00003141 pos_scale = 1.0;
3142 if ( (normalize_flags&NormalizeValue) != 0 ) {
3143 /* normalize kernel appropriately */
3144 if ( fabs(kernel->positive_range + kernel->negative_range) > MagickEpsilon )
3145 pos_scale = fabs(kernel->positive_range + kernel->negative_range);
anthonycc6c8362010-01-25 04:14:01 +00003146 else
anthony999bb2c2010-02-18 12:38:01 +00003147 pos_scale = kernel->positive_range; /* special zero-summing kernel */
3148 }
3149 /* force kernel into being a normalized zero-summing kernel */
3150 if ( (normalize_flags&CorrelateNormalizeValue) != 0 ) {
3151 pos_scale = ( fabs(kernel->positive_range) > MagickEpsilon )
3152 ? kernel->positive_range : 1.0;
3153 neg_scale = ( fabs(kernel->negative_range) > MagickEpsilon )
3154 ? -kernel->negative_range : 1.0;
3155 }
3156 else
3157 neg_scale = pos_scale;
3158
3159 /* finialize scaling_factor for positive and negative components */
3160 pos_scale = scaling_factor/pos_scale;
3161 neg_scale = scaling_factor/neg_scale;
3162 if ( (normalize_flags&PercentValue) != 0 ) {
3163 pos_scale /= 100.0;
3164 neg_scale /= 100.0;
anthonycc6c8362010-01-25 04:14:01 +00003165 }
3166
cristy150989e2010-02-01 14:59:39 +00003167 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00003168 if ( ! IsNan(kernel->values[i]) )
anthony999bb2c2010-02-18 12:38:01 +00003169 kernel->values[i] *= (kernel->values[i] >= 0) ? pos_scale : neg_scale;
anthonycc6c8362010-01-25 04:14:01 +00003170
anthony999bb2c2010-02-18 12:38:01 +00003171 /* convolution output range */
3172 kernel->positive_range *= pos_scale;
3173 kernel->negative_range *= neg_scale;
3174 /* maximum and minimum values in kernel */
3175 kernel->maximum *= (kernel->maximum >= 0.0) ? pos_scale : neg_scale;
3176 kernel->minimum *= (kernel->minimum >= 0.0) ? pos_scale : neg_scale;
3177
3178 /* swap kernel settings if user scaling factor is negative */
3179 if ( scaling_factor < MagickEpsilon ) {
3180 double t;
3181 t = kernel->positive_range;
3182 kernel->positive_range = kernel->negative_range;
3183 kernel->negative_range = t;
3184 t = kernel->maximum;
3185 kernel->maximum = kernel->minimum;
3186 kernel->minimum = 1;
3187 }
anthonycc6c8362010-01-25 04:14:01 +00003188
3189 return;
3190}
3191
3192/*
3193%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3194% %
3195% %
3196% %
anthony4fd27e22010-02-07 08:17:18 +00003197+ S h o w K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00003198% %
3199% %
3200% %
3201%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3202%
anthony4fd27e22010-02-07 08:17:18 +00003203% ShowKernelInfo() outputs the details of the given kernel defination to
3204% standard error, generally due to a users 'showkernel' option request.
anthony83ba99b2010-01-24 08:48:15 +00003205%
3206% The format of the ShowKernel method is:
3207%
anthony4fd27e22010-02-07 08:17:18 +00003208% void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00003209%
3210% A description of each parameter follows:
3211%
3212% o kernel: the Morphology/Convolution kernel
3213%
anthonyc4c86e02010-01-27 09:30:32 +00003214% This function is internal to this module only at this time. That may change
3215% in the future.
anthony83ba99b2010-01-24 08:48:15 +00003216*/
anthony4fd27e22010-02-07 08:17:18 +00003217MagickExport void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00003218{
anthony7a01dcf2010-05-11 12:25:52 +00003219 KernelInfo
3220 *k;
anthony83ba99b2010-01-24 08:48:15 +00003221
anthony43c49252010-05-18 10:59:50 +00003222 unsigned long
anthony7a01dcf2010-05-11 12:25:52 +00003223 c, i, u, v;
3224
3225 for (c=0, k=kernel; k != (KernelInfo *) NULL; c++, k=k->next ) {
3226
3227 fprintf(stderr, "Kernel ");
3228 if ( kernel->next != (KernelInfo *) NULL )
3229 fprintf(stderr, " #%ld", c );
anthony43c49252010-05-18 10:59:50 +00003230 fprintf(stderr, " \"%s",
3231 MagickOptionToMnemonic(MagickKernelOptions, k->type) );
3232 if ( fabs(k->angle) > MagickEpsilon )
3233 fprintf(stderr, "@%lg", k->angle);
3234 fprintf(stderr, "\" of size %lux%lu%+ld%+ld ",
3235 k->width, k->height,
3236 k->x, k->y );
anthony7a01dcf2010-05-11 12:25:52 +00003237 fprintf(stderr,
3238 " with values from %.*lg to %.*lg\n",
3239 GetMagickPrecision(), k->minimum,
3240 GetMagickPrecision(), k->maximum);
3241 fprintf(stderr, "Forming convolution output range from %.*lg to %.*lg%s\n",
3242 GetMagickPrecision(), k->negative_range,
3243 GetMagickPrecision(), k->positive_range,
3244 /*kernel->normalized == MagickTrue ? " (normalized)" : */ "" );
anthony43c49252010-05-18 10:59:50 +00003245 for (i=v=0; v < k->height; v++) {
anthony7a01dcf2010-05-11 12:25:52 +00003246 fprintf(stderr,"%2ld:",v);
anthony43c49252010-05-18 10:59:50 +00003247 for (u=0; u < k->width; u++, i++)
anthony7a01dcf2010-05-11 12:25:52 +00003248 if ( IsNan(k->values[i]) )
3249 fprintf(stderr," %*s", GetMagickPrecision()+2, "nan");
3250 else
3251 fprintf(stderr," %*.*lg", GetMagickPrecision()+2,
3252 GetMagickPrecision(), k->values[i]);
3253 fprintf(stderr,"\n");
3254 }
anthony83ba99b2010-01-24 08:48:15 +00003255 }
3256}
anthonycc6c8362010-01-25 04:14:01 +00003257
3258/*
3259%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3260% %
3261% %
3262% %
anthony43c49252010-05-18 10:59:50 +00003263% U n i t y A d d K e r n a l I n f o %
3264% %
3265% %
3266% %
3267%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3268%
3269% UnityAddKernelInfo() Adds a given amount of the 'Unity' Convolution Kernel
3270% to the given pre-scaled and normalized Kernel. This in effect adds that
3271% amount of the original image into the resulting convolution kernel. This
3272% value is usually provided by the user as a percentage value in the
3273% 'convolve:scale' setting.
3274%
3275% The resulting effect is to either convert a 'zero-summing' edge detection
3276% kernel (such as a "Laplacian", "DOG" or a "LOG") into a 'sharpening'
3277% kernel.
3278%
3279% Alternativally by using a purely positive kernel, and using a negative
3280% post-normalizing scaling factor, you can convert a 'blurring' kernel (such
3281% as a "Gaussian") into a 'unsharp' kernel.
3282%
3283% The format of the ScaleKernelInfo method is:
3284%
3285% void UnityAdditionKernelInfo(KernelInfo *kernel, const double scale )
3286%
3287% A description of each parameter follows:
3288%
3289% o kernel: the Morphology/Convolution kernel
3290%
3291% o scale:
3292% scaling factor for the unity kernel to be added to
3293% the given kernel.
3294%
3295% This function is currently internal to this module only at this time, but
3296% can be exported to other modules if needed.
3297%
3298*/
3299MagickExport void UnityAddKernelInfo(KernelInfo *kernel,
3300 const double scale)
3301{
3302 register unsigned long
3303 i;
3304
3305 kernel->values[kernel->x+kernel->y*kernel->width] += scale;
3306
3307 /* make sure kernel meta-data is now correct */
3308 kernel->minimum = kernel->maximum = 0.0;
3309 kernel->negative_range = kernel->positive_range = 0.0;
3310 for (i=0; i < (kernel->width*kernel->height); i++)
3311 {
3312 if ( fabs(kernel->values[i]) < MagickEpsilon )
3313 kernel->values[i] = 0.0;
3314 ( kernel->values[i] < 0)
3315 ? ( kernel->negative_range += kernel->values[i] )
3316 : ( kernel->positive_range += kernel->values[i] );
3317 Minimize(kernel->minimum, kernel->values[i]);
3318 Maximize(kernel->maximum, kernel->values[i]);
3319 }
3320
3321 return;
3322}
3323
3324/*
3325%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3326% %
3327% %
3328% %
3329% Z e r o K e r n e l N a n s %
anthonycc6c8362010-01-25 04:14:01 +00003330% %
3331% %
3332% %
3333%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3334%
3335% ZeroKernelNans() replaces any special 'nan' value that may be present in
3336% the kernel with a zero value. This is typically done when the kernel will
3337% be used in special hardware (GPU) convolution processors, to simply
3338% matters.
3339%
3340% The format of the ZeroKernelNans method is:
3341%
3342% voidZeroKernelNans (KernelInfo *kernel)
3343%
3344% A description of each parameter follows:
3345%
3346% o kernel: the Morphology/Convolution kernel
3347%
anthonycc6c8362010-01-25 04:14:01 +00003348*/
anthonyc4c86e02010-01-27 09:30:32 +00003349MagickExport void ZeroKernelNans(KernelInfo *kernel)
anthonycc6c8362010-01-25 04:14:01 +00003350{
anthony43c49252010-05-18 10:59:50 +00003351 register unsigned long
anthonycc6c8362010-01-25 04:14:01 +00003352 i;
3353
anthony1b2bc0a2010-05-12 05:25:22 +00003354 /* scale the lower kernels first */
3355 if ( kernel->next != (KernelInfo *) NULL)
3356 ZeroKernelNans(kernel->next);
3357
anthony43c49252010-05-18 10:59:50 +00003358 for (i=0; i < (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00003359 if ( IsNan(kernel->values[i]) )
3360 kernel->values[i] = 0.0;
3361
3362 return;
3363}