blob: 008dea0d98aa18bc897518edf39187d4051887ee [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"
anthony46a369d2010-05-19 02:41:48 +000068#include "magick/morphology-private.h"
anthony602ab9b2010-01-05 08:06:50 +000069#include "magick/option.h"
cristy701db312009-11-20 03:14:08 +000070#include "magick/pixel-private.h"
71#include "magick/prepress.h"
72#include "magick/quantize.h"
73#include "magick/registry.h"
74#include "magick/semaphore.h"
75#include "magick/splay-tree.h"
76#include "magick/statistic.h"
77#include "magick/string_.h"
anthony602ab9b2010-01-05 08:06:50 +000078#include "magick/string-private.h"
79#include "magick/token.h"
cristya29d45f2010-03-05 21:14:54 +000080
anthony602ab9b2010-01-05 08:06:50 +000081/*
cristya29d45f2010-03-05 21:14:54 +000082 The following test is for special floating point numbers of value NaN (not
83 a number), that may be used within a Kernel Definition. NaN's are defined
84 as part of the IEEE standard for floating point number representation.
85
anthony1b2bc0a2010-05-12 05:25:22 +000086 These are used a Kernel value of NaN means that that kernel position is not
cristya29d45f2010-03-05 21:14:54 +000087 part of the normal convolution or morphology process, and thus allowing the
88 use of 'shaped' kernels.
89
90 Special properities two NaN's are never equal, even if they are from the
91 same variable That is the IsNaN() macro is only true if the value is NaN.
92*/
anthony602ab9b2010-01-05 08:06:50 +000093#define IsNan(a) ((a)!=(a))
94
anthony29188a82010-01-22 10:12:34 +000095/*
cristya29d45f2010-03-05 21:14:54 +000096 Other global definitions used by module.
97*/
anthony29188a82010-01-22 10:12:34 +000098static inline double MagickMin(const double x,const double y)
99{
100 return( x < y ? x : y);
101}
102static inline double MagickMax(const double x,const double y)
103{
104 return( x > y ? x : y);
105}
106#define Minimize(assign,value) assign=MagickMin(assign,value)
107#define Maximize(assign,value) assign=MagickMax(assign,value)
108
anthonyc4c86e02010-01-27 09:30:32 +0000109/* Currently these are only internal to this module */
110static void
anthony46a369d2010-05-19 02:41:48 +0000111 CalcKernelMetaData(KernelInfo *),
cristyeb8db6d2010-05-24 18:34:11 +0000112 ExpandKernelInfo(KernelInfo *, const double),
cristyef656912010-03-05 19:54:59 +0000113 RotateKernelInfo(KernelInfo *, double);
anthony602ab9b2010-01-05 08:06:50 +0000114
anthony3dd0f622010-05-13 12:57:32 +0000115
116/* Quick function to find last kernel in a kernel list */
117static inline KernelInfo *LastKernelInfo(KernelInfo *kernel)
118{
119 while (kernel->next != (KernelInfo *) NULL)
120 kernel = kernel->next;
121 return(kernel);
122}
123
124
anthony602ab9b2010-01-05 08:06:50 +0000125/*
126%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
127% %
128% %
129% %
anthony83ba99b2010-01-24 08:48:15 +0000130% A c q u i r e K e r n e l I n f o %
anthony602ab9b2010-01-05 08:06:50 +0000131% %
132% %
133% %
134%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
135%
cristy2be15382010-01-21 02:38:03 +0000136% AcquireKernelInfo() takes the given string (generally supplied by the
anthony602ab9b2010-01-05 08:06:50 +0000137% user) and converts it into a Morphology/Convolution Kernel. This allows
138% users to specify a kernel from a number of pre-defined kernels, or to fully
139% specify their own kernel for a specific Convolution or Morphology
140% Operation.
141%
142% The kernel so generated can be any rectangular array of floating point
143% values (doubles) with the 'control point' or 'pixel being affected'
144% anywhere within that array of values.
145%
anthony83ba99b2010-01-24 08:48:15 +0000146% Previously IM was restricted to a square of odd size using the exact
147% center as origin, this is no longer the case, and any rectangular kernel
148% with any value being declared the origin. This in turn allows the use of
149% highly asymmetrical kernels.
anthony602ab9b2010-01-05 08:06:50 +0000150%
151% The floating point values in the kernel can also include a special value
anthony83ba99b2010-01-24 08:48:15 +0000152% known as 'nan' or 'not a number' to indicate that this value is not part
153% of the kernel array. This allows you to shaped the kernel within its
154% rectangular area. That is 'nan' values provide a 'mask' for the kernel
155% shape. However at least one non-nan value must be provided for correct
156% working of a kernel.
anthony602ab9b2010-01-05 08:06:50 +0000157%
anthony7a01dcf2010-05-11 12:25:52 +0000158% The returned kernel should be freed using the DestroyKernelInfo() when you
159% are finished with it. Do not free this memory yourself.
anthony602ab9b2010-01-05 08:06:50 +0000160%
161% Input kernel defintion strings can consist of any of three types.
162%
anthony29188a82010-01-22 10:12:34 +0000163% "name:args"
164% Select from one of the built in kernels, using the name and
165% geometry arguments supplied. See AcquireKernelBuiltIn()
anthony602ab9b2010-01-05 08:06:50 +0000166%
anthony43c49252010-05-18 10:59:50 +0000167% "WxH[+X+Y][^@]:num, num, num ..."
anthony1b2bc0a2010-05-12 05:25:22 +0000168% a kernel of size W by H, with W*H floating point numbers following.
anthony602ab9b2010-01-05 08:06:50 +0000169% the 'center' can be optionally be defined at +X+Y (such that +0+0
anthony29188a82010-01-22 10:12:34 +0000170% is top left corner). If not defined the pixel in the center, for
171% odd sizes, or to the immediate top or left of center for even sizes
172% is automatically selected.
anthony602ab9b2010-01-05 08:06:50 +0000173%
anthony43c49252010-05-18 10:59:50 +0000174% If a '^' is included the kernel expanded with 90-degree rotations,
175% While a '@' will allow you to expand a 3x3 kernel using 45-degree
176% circular rotates.
177%
anthony29188a82010-01-22 10:12:34 +0000178% "num, num, num, num, ..."
179% list of floating point numbers defining an 'old style' odd sized
180% square kernel. At least 9 values should be provided for a 3x3
181% square kernel, 25 for a 5x5 square kernel, 49 for 7x7, etc.
182% Values can be space or comma separated. This is not recommended.
anthony602ab9b2010-01-05 08:06:50 +0000183%
anthony7a01dcf2010-05-11 12:25:52 +0000184% You can define a 'list of kernels' which can be used by some morphology
185% operators A list is defined as a semi-colon seperated list kernels.
186%
anthonydbc89892010-05-12 07:05:27 +0000187% " kernel ; kernel ; kernel ; "
anthony7a01dcf2010-05-11 12:25:52 +0000188%
anthony43c49252010-05-18 10:59:50 +0000189% Any extra ';' characters (at start, end or between kernel defintions are
190% simply ignored.
191%
192% Note that 'name' kernels will start with an alphabetic character while the
193% new kernel specification has a ':' character in its specification string.
194% If neither is the case, it is assumed an old style of a simple list of
195% numbers generating a odd-sized square kernel has been given.
anthony7a01dcf2010-05-11 12:25:52 +0000196%
anthony602ab9b2010-01-05 08:06:50 +0000197% The format of the AcquireKernal method is:
198%
cristy2be15382010-01-21 02:38:03 +0000199% KernelInfo *AcquireKernelInfo(const char *kernel_string)
anthony602ab9b2010-01-05 08:06:50 +0000200%
201% A description of each parameter follows:
202%
203% o kernel_string: the Morphology/Convolution kernel wanted.
204%
205*/
206
anthonyc84dce52010-05-07 05:42:23 +0000207/* This was separated so that it could be used as a separate
anthony5ef8e942010-05-11 06:51:12 +0000208** array input handling function, such as for -color-matrix
anthonyc84dce52010-05-07 05:42:23 +0000209*/
anthony5ef8e942010-05-11 06:51:12 +0000210static KernelInfo *ParseKernelArray(const char *kernel_string)
anthony602ab9b2010-01-05 08:06:50 +0000211{
cristy2be15382010-01-21 02:38:03 +0000212 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000213 *kernel;
214
215 char
216 token[MaxTextExtent];
217
anthony602ab9b2010-01-05 08:06:50 +0000218 const char
anthony5ef8e942010-05-11 06:51:12 +0000219 *p,
220 *end;
anthony602ab9b2010-01-05 08:06:50 +0000221
anthonyc84dce52010-05-07 05:42:23 +0000222 register long
223 i;
anthony602ab9b2010-01-05 08:06:50 +0000224
anthony29188a82010-01-22 10:12:34 +0000225 double
226 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
227
anthony43c49252010-05-18 10:59:50 +0000228 MagickStatusType
229 flags;
230
231 GeometryInfo
232 args;
233
cristy2be15382010-01-21 02:38:03 +0000234 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
235 if (kernel == (KernelInfo *)NULL)
anthony602ab9b2010-01-05 08:06:50 +0000236 return(kernel);
237 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
anthony43c49252010-05-18 10:59:50 +0000238 kernel->minimum = kernel->maximum = kernel->angle = 0.0;
anthony7a01dcf2010-05-11 12:25:52 +0000239 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000240 kernel->type = UserDefinedKernel;
anthony7a01dcf2010-05-11 12:25:52 +0000241 kernel->next = (KernelInfo *) NULL;
cristyd43a46b2010-01-21 02:13:41 +0000242 kernel->signature = MagickSignature;
anthony602ab9b2010-01-05 08:06:50 +0000243
anthony5ef8e942010-05-11 06:51:12 +0000244 /* find end of this specific kernel definition string */
245 end = strchr(kernel_string, ';');
246 if ( end == (char *) NULL )
247 end = strchr(kernel_string, '\0');
248
anthony43c49252010-05-18 10:59:50 +0000249 /* clear flags - for Expanding kernal lists thorugh rotations */
250 flags = NoValue;
251
anthony602ab9b2010-01-05 08:06:50 +0000252 /* Has a ':' in argument - New user kernel specification */
253 p = strchr(kernel_string, ':');
anthony5ef8e942010-05-11 06:51:12 +0000254 if ( p != (char *) NULL && p < end)
anthony602ab9b2010-01-05 08:06:50 +0000255 {
anthony602ab9b2010-01-05 08:06:50 +0000256 /* ParseGeometry() needs the geometry separated! -- Arrgghh */
cristy150989e2010-02-01 14:59:39 +0000257 memcpy(token, kernel_string, (size_t) (p-kernel_string));
anthony602ab9b2010-01-05 08:06:50 +0000258 token[p-kernel_string] = '\0';
anthonyc84dce52010-05-07 05:42:23 +0000259 SetGeometryInfo(&args);
anthony602ab9b2010-01-05 08:06:50 +0000260 flags = ParseGeometry(token, &args);
anthony602ab9b2010-01-05 08:06:50 +0000261
anthony29188a82010-01-22 10:12:34 +0000262 /* Size handling and checks of geometry settings */
anthony602ab9b2010-01-05 08:06:50 +0000263 if ( (flags & WidthValue) == 0 ) /* if no width then */
264 args.rho = args.sigma; /* then width = height */
265 if ( args.rho < 1.0 ) /* if width too small */
266 args.rho = 1.0; /* then width = 1 */
267 if ( args.sigma < 1.0 ) /* if height too small */
268 args.sigma = args.rho; /* then height = width */
269 kernel->width = (unsigned long)args.rho;
270 kernel->height = (unsigned long)args.sigma;
271
272 /* Offset Handling and Checks */
273 if ( args.xi < 0.0 || args.psi < 0.0 )
anthony83ba99b2010-01-24 08:48:15 +0000274 return(DestroyKernelInfo(kernel));
cristyc99304f2010-02-01 15:26:27 +0000275 kernel->x = ((flags & XValue)!=0) ? (long)args.xi
cristy150989e2010-02-01 14:59:39 +0000276 : (long) (kernel->width-1)/2;
cristyc99304f2010-02-01 15:26:27 +0000277 kernel->y = ((flags & YValue)!=0) ? (long)args.psi
cristy150989e2010-02-01 14:59:39 +0000278 : (long) (kernel->height-1)/2;
cristyc99304f2010-02-01 15:26:27 +0000279 if ( kernel->x >= (long) kernel->width ||
280 kernel->y >= (long) kernel->height )
anthony83ba99b2010-01-24 08:48:15 +0000281 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000282
283 p++; /* advance beyond the ':' */
284 }
285 else
anthonyc84dce52010-05-07 05:42:23 +0000286 { /* ELSE - Old old specification, forming odd-square kernel */
anthony602ab9b2010-01-05 08:06:50 +0000287 /* count up number of values given */
288 p=(const char *) kernel_string;
cristya699b172010-01-06 16:48:49 +0000289 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
anthony29188a82010-01-22 10:12:34 +0000290 p++; /* ignore "'" chars for convolve filter usage - Cristy */
anthony5ef8e942010-05-11 06:51:12 +0000291 for (i=0; p < end; i++)
anthony602ab9b2010-01-05 08:06:50 +0000292 {
293 GetMagickToken(p,&p,token);
294 if (*token == ',')
295 GetMagickToken(p,&p,token);
296 }
297 /* set the size of the kernel - old sized square */
298 kernel->width = kernel->height= (unsigned long) sqrt((double) i+1.0);
cristyc99304f2010-02-01 15:26:27 +0000299 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000300 p=(const char *) kernel_string;
anthony29188a82010-01-22 10:12:34 +0000301 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
302 p++; /* ignore "'" chars for convolve filter usage - Cristy */
anthony602ab9b2010-01-05 08:06:50 +0000303 }
304
305 /* Read in the kernel values from rest of input string argument */
306 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
307 kernel->height*sizeof(double));
308 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000309 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000310
cristyc99304f2010-02-01 15:26:27 +0000311 kernel->minimum = +MagickHuge;
312 kernel->maximum = -MagickHuge;
313 kernel->negative_range = kernel->positive_range = 0.0;
anthonyc84dce52010-05-07 05:42:23 +0000314
anthony5ef8e942010-05-11 06:51:12 +0000315 for (i=0; (i < (long) (kernel->width*kernel->height)) && (p < end); i++)
anthony602ab9b2010-01-05 08:06:50 +0000316 {
317 GetMagickToken(p,&p,token);
318 if (*token == ',')
319 GetMagickToken(p,&p,token);
anthony29188a82010-01-22 10:12:34 +0000320 if ( LocaleCompare("nan",token) == 0
anthonyc84dce52010-05-07 05:42:23 +0000321 || LocaleCompare("-",token) == 0 ) {
anthony29188a82010-01-22 10:12:34 +0000322 kernel->values[i] = nan; /* do not include this value in kernel */
323 }
324 else {
325 kernel->values[i] = StringToDouble(token);
326 ( kernel->values[i] < 0)
cristyc99304f2010-02-01 15:26:27 +0000327 ? ( kernel->negative_range += kernel->values[i] )
328 : ( kernel->positive_range += kernel->values[i] );
329 Minimize(kernel->minimum, kernel->values[i]);
330 Maximize(kernel->maximum, kernel->values[i]);
anthony29188a82010-01-22 10:12:34 +0000331 }
anthony602ab9b2010-01-05 08:06:50 +0000332 }
anthony29188a82010-01-22 10:12:34 +0000333
anthony5ef8e942010-05-11 06:51:12 +0000334 /* sanity check -- no more values in kernel definition */
335 GetMagickToken(p,&p,token);
336 if ( *token != '\0' && *token != ';' && *token != '\'' )
337 return(DestroyKernelInfo(kernel));
338
anthonyc84dce52010-05-07 05:42:23 +0000339#if 0
340 /* this was the old method of handling a incomplete kernel */
cristy150989e2010-02-01 14:59:39 +0000341 if ( i < (long) (kernel->width*kernel->height) ) {
cristyc99304f2010-02-01 15:26:27 +0000342 Minimize(kernel->minimum, kernel->values[i]);
343 Maximize(kernel->maximum, kernel->values[i]);
cristy150989e2010-02-01 14:59:39 +0000344 for ( ; i < (long) (kernel->width*kernel->height); i++)
anthony29188a82010-01-22 10:12:34 +0000345 kernel->values[i]=0.0;
346 }
anthonyc84dce52010-05-07 05:42:23 +0000347#else
348 /* Number of values for kernel was not enough - Report Error */
349 if ( i < (long) (kernel->width*kernel->height) )
350 return(DestroyKernelInfo(kernel));
351#endif
352
353 /* check that we recieved at least one real (non-nan) value! */
354 if ( kernel->minimum == MagickHuge )
355 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000356
anthony43c49252010-05-18 10:59:50 +0000357 if ( (flags & AreaValue) != 0 ) /* '@' symbol in kernel size */
358 ExpandKernelInfo(kernel, 45.0);
359 else if ( (flags & MinimumValue) != 0 ) /* '^' symbol in kernel size */
360 ExpandKernelInfo(kernel, 90.0);
361
anthony602ab9b2010-01-05 08:06:50 +0000362 return(kernel);
363}
anthonyc84dce52010-05-07 05:42:23 +0000364
anthony43c49252010-05-18 10:59:50 +0000365static KernelInfo *ParseKernelName(const char *kernel_string)
anthonyc84dce52010-05-07 05:42:23 +0000366{
anthonyf0176c32010-05-23 23:08:57 +0000367 KernelInfo
368 *kernel;
369
anthonyc84dce52010-05-07 05:42:23 +0000370 char
371 token[MaxTextExtent];
372
anthony5ef8e942010-05-11 06:51:12 +0000373 long
374 type;
375
anthonyc84dce52010-05-07 05:42:23 +0000376 const char
anthony7a01dcf2010-05-11 12:25:52 +0000377 *p,
378 *end;
anthonyc84dce52010-05-07 05:42:23 +0000379
380 MagickStatusType
381 flags;
382
383 GeometryInfo
384 args;
385
anthonyc84dce52010-05-07 05:42:23 +0000386 /* Parse special 'named' kernel */
anthony5ef8e942010-05-11 06:51:12 +0000387 GetMagickToken(kernel_string,&p,token);
anthonyc84dce52010-05-07 05:42:23 +0000388 type=ParseMagickOption(MagickKernelOptions,MagickFalse,token);
389 if ( type < 0 || type == UserDefinedKernel )
anthony5ef8e942010-05-11 06:51:12 +0000390 return((KernelInfo *)NULL); /* not a valid named kernel */
anthonyc84dce52010-05-07 05:42:23 +0000391
392 while (((isspace((int) ((unsigned char) *p)) != 0) ||
anthony5ef8e942010-05-11 06:51:12 +0000393 (*p == ',') || (*p == ':' )) && (*p != '\0') && (*p != ';'))
anthonyc84dce52010-05-07 05:42:23 +0000394 p++;
anthony7a01dcf2010-05-11 12:25:52 +0000395
396 end = strchr(p, ';'); /* end of this kernel defintion */
397 if ( end == (char *) NULL )
398 end = strchr(p, '\0');
399
400 /* ParseGeometry() needs the geometry separated! -- Arrgghh */
401 memcpy(token, p, (size_t) (end-p));
402 token[end-p] = '\0';
anthonyc84dce52010-05-07 05:42:23 +0000403 SetGeometryInfo(&args);
anthony7a01dcf2010-05-11 12:25:52 +0000404 flags = ParseGeometry(token, &args);
anthonyc84dce52010-05-07 05:42:23 +0000405
anthony3c10fc82010-05-13 02:40:51 +0000406#if 0
407 /* For Debugging Geometry Input */
anthony46a369d2010-05-19 02:41:48 +0000408 fprintf(stderr, "Geometry = 0x%04X : %lg x %lg %+lg %+lg\n",
anthony3c10fc82010-05-13 02:40:51 +0000409 flags, args.rho, args.sigma, args.xi, args.psi );
410#endif
411
anthonyc84dce52010-05-07 05:42:23 +0000412 /* special handling of missing values in input string */
413 switch( type ) {
anthony5ef8e942010-05-11 06:51:12 +0000414 case RectangleKernel:
415 if ( (flags & WidthValue) == 0 ) /* if no width then */
416 args.rho = args.sigma; /* then width = height */
417 if ( args.rho < 1.0 ) /* if width too small */
418 args.rho = 3; /* then width = 3 */
419 if ( args.sigma < 1.0 ) /* if height too small */
420 args.sigma = args.rho; /* then height = width */
421 if ( (flags & XValue) == 0 ) /* center offset if not defined */
422 args.xi = (double)(((long)args.rho-1)/2);
423 if ( (flags & YValue) == 0 )
424 args.psi = (double)(((long)args.sigma-1)/2);
425 break;
426 case SquareKernel:
427 case DiamondKernel:
428 case DiskKernel:
429 case PlusKernel:
anthony3dd0f622010-05-13 12:57:32 +0000430 case CrossKernel:
anthony5ef8e942010-05-11 06:51:12 +0000431 /* If no scale given (a 0 scale is valid! - set it to 1.0 */
432 if ( (flags & HeightValue) == 0 )
433 args.sigma = 1.0;
434 break;
anthonyc1061722010-05-14 06:23:49 +0000435 case RingKernel:
436 if ( (flags & XValue) == 0 )
437 args.xi = 1.0;
438 break;
anthony5ef8e942010-05-11 06:51:12 +0000439 case ChebyshevKernel:
440 case ManhattenKernel:
441 case EuclideanKernel:
anthony43c49252010-05-18 10:59:50 +0000442 if ( (flags & HeightValue) == 0 ) /* no distance scale */
443 args.sigma = 100.0; /* default distance scaling */
444 else if ( (flags & AspectValue ) != 0 ) /* '!' flag */
445 args.sigma = QuantumRange/(args.sigma+1); /* maximum pixel distance */
446 else if ( (flags & PercentValue ) != 0 ) /* '%' flag */
447 args.sigma *= QuantumRange/100.0; /* percentage of color range */
anthony5ef8e942010-05-11 06:51:12 +0000448 break;
449 default:
450 break;
anthonyc84dce52010-05-07 05:42:23 +0000451 }
452
anthonyf0176c32010-05-23 23:08:57 +0000453 kernel = AcquireKernelBuiltIn((KernelInfoType)type, &args);
454
455 /* global expand to rotated kernel list - only for single kernels */
456 if ( kernel->next == (KernelInfo *) NULL ) {
457 if ( (flags & AreaValue) != 0 ) /* '@' symbol in kernel args */
458 ExpandKernelInfo(kernel, 45.0);
459 else if ( (flags & MinimumValue) != 0 ) /* '^' symbol in kernel args */
460 ExpandKernelInfo(kernel, 90.0);
461 }
462
463 return(kernel);
anthonyc84dce52010-05-07 05:42:23 +0000464}
465
anthony5ef8e942010-05-11 06:51:12 +0000466MagickExport KernelInfo *AcquireKernelInfo(const char *kernel_string)
467{
anthony7a01dcf2010-05-11 12:25:52 +0000468
469 KernelInfo
anthonydbc89892010-05-12 07:05:27 +0000470 *kernel,
anthony43c49252010-05-18 10:59:50 +0000471 *new_kernel;
anthony7a01dcf2010-05-11 12:25:52 +0000472
anthony5ef8e942010-05-11 06:51:12 +0000473 char
474 token[MaxTextExtent];
475
anthony7a01dcf2010-05-11 12:25:52 +0000476 const char
anthonydbc89892010-05-12 07:05:27 +0000477 *p;
anthony7a01dcf2010-05-11 12:25:52 +0000478
anthonye108a3f2010-05-12 07:24:03 +0000479 unsigned long
480 kernel_number;
481
anthonydbc89892010-05-12 07:05:27 +0000482 p = kernel_string;
anthony43c49252010-05-18 10:59:50 +0000483 kernel = NULL;
anthonye108a3f2010-05-12 07:24:03 +0000484 kernel_number = 0;
anthony5ef8e942010-05-11 06:51:12 +0000485
anthonydbc89892010-05-12 07:05:27 +0000486 while ( GetMagickToken(p,NULL,token), *token != '\0' ) {
anthony7a01dcf2010-05-11 12:25:52 +0000487
anthony43c49252010-05-18 10:59:50 +0000488 /* ignore extra or multiple ';' kernel seperators */
anthonydbc89892010-05-12 07:05:27 +0000489 if ( *token != ';' ) {
anthony7a01dcf2010-05-11 12:25:52 +0000490
anthonydbc89892010-05-12 07:05:27 +0000491 /* tokens starting with alpha is a Named kernel */
anthony43c49252010-05-18 10:59:50 +0000492 if (isalpha((int) *token) != 0)
493 new_kernel = ParseKernelName(p);
anthonydbc89892010-05-12 07:05:27 +0000494 else /* otherwise a user defined kernel array */
anthony43c49252010-05-18 10:59:50 +0000495 new_kernel = ParseKernelArray(p);
anthony7a01dcf2010-05-11 12:25:52 +0000496
anthonye108a3f2010-05-12 07:24:03 +0000497 /* Error handling -- this is not proper error handling! */
498 if ( new_kernel == (KernelInfo *) NULL ) {
499 fprintf(stderr, "Failed to parse kernel number #%lu\n", kernel_number);
500 if ( kernel != (KernelInfo *) NULL )
501 kernel=DestroyKernelInfo(kernel);
502 return((KernelInfo *) NULL);
anthonydbc89892010-05-12 07:05:27 +0000503 }
anthonye108a3f2010-05-12 07:24:03 +0000504
505 /* initialise or append the kernel list */
anthony3dd0f622010-05-13 12:57:32 +0000506 if ( kernel == (KernelInfo *) NULL )
507 kernel = new_kernel;
508 else
anthony43c49252010-05-18 10:59:50 +0000509 LastKernelInfo(kernel)->next = new_kernel;
anthonydbc89892010-05-12 07:05:27 +0000510 }
511
512 /* look for the next kernel in list */
513 p = strchr(p, ';');
514 if ( p == (char *) NULL )
515 break;
516 p++;
517
518 }
anthony7a01dcf2010-05-11 12:25:52 +0000519 return(kernel);
anthony5ef8e942010-05-11 06:51:12 +0000520}
521
anthony602ab9b2010-01-05 08:06:50 +0000522
523/*
524%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
525% %
526% %
527% %
528% A c q u i r e K e r n e l B u i l t I n %
529% %
530% %
531% %
532%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
533%
534% AcquireKernelBuiltIn() returned one of the 'named' built-in types of
535% kernels used for special purposes such as gaussian blurring, skeleton
536% pruning, and edge distance determination.
537%
538% They take a KernelType, and a set of geometry style arguments, which were
539% typically decoded from a user supplied string, or from a more complex
540% Morphology Method that was requested.
541%
542% The format of the AcquireKernalBuiltIn method is:
543%
cristy2be15382010-01-21 02:38:03 +0000544% KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000545% const GeometryInfo args)
546%
547% A description of each parameter follows:
548%
549% o type: the pre-defined type of kernel wanted
550%
551% o args: arguments defining or modifying the kernel
552%
553% Convolution Kernels
554%
anthony46a369d2010-05-19 02:41:48 +0000555% Unity
556% the No-Op kernel, also requivelent to Gaussian of sigma zero.
557% Basically a 3x3 kernel of a 1 surrounded by zeros.
558%
anthony3c10fc82010-05-13 02:40:51 +0000559% Gaussian:{radius},{sigma}
560% Generate a two-dimentional gaussian kernel, as used by -gaussian.
anthonyc1061722010-05-14 06:23:49 +0000561% The sigma for the curve is required. The resulting kernel is
562% normalized,
563%
564% If 'sigma' is zero, you get a single pixel on a field of zeros.
anthony602ab9b2010-01-05 08:06:50 +0000565%
566% NOTE: that the 'radius' is optional, but if provided can limit (clip)
567% the final size of the resulting kernel to a square 2*radius+1 in size.
568% The radius should be at least 2 times that of the sigma value, or
569% sever clipping and aliasing may result. If not given or set to 0 the
570% radius will be determined so as to produce the best minimal error
571% result, which is usally much larger than is normally needed.
572%
anthonyc1061722010-05-14 06:23:49 +0000573% DOG:{radius},{sigma1},{sigma2}
574% "Difference of Gaussians" Kernel.
575% As "Gaussian" but with a gaussian produced by 'sigma2' subtracted
576% from the gaussian produced by 'sigma1'. Typically sigma2 > sigma1.
577% The result is a zero-summing kernel.
anthony602ab9b2010-01-05 08:06:50 +0000578%
anthony9eb4f742010-05-18 02:45:54 +0000579% LOG:{radius},{sigma}
580% "Laplacian of a Gaussian" or "Mexician Hat" Kernel.
581% The supposed ideal edge detection, zero-summing kernel.
582%
583% An alturnative to this kernel is to use a "DOG" with a sigma ratio of
584% approx 1.6, which can also be applied as a 2 pass "DOB" (see below).
585%
anthonyc1061722010-05-14 06:23:49 +0000586% Blur:{radius},{sigma}[,{angle}]
587% Generates a 1 dimensional or linear gaussian blur, at the angle given
588% (current restricted to orthogonal angles). If a 'radius' is given the
589% kernel is clipped to a width of 2*radius+1. Kernel can be rotated
590% by a 90 degree angle.
591%
592% If 'sigma' is zero, you get a single pixel on a field of zeros.
593%
594% Note that two convolutions with two "Blur" kernels perpendicular to
595% each other, is equivelent to a far larger "Gaussian" kernel with the
596% same sigma value, However it is much faster to apply. This is how the
597% "-blur" operator actually works.
598%
599% DOB:{radius},{sigma1},{sigma2}[,{angle}]
600% "Difference of Blurs" Kernel.
601% As "Blur" but with the 1D gaussian produced by 'sigma2' subtracted
602% from thethe 1D gaussian produced by 'sigma1'.
603% The result is a zero-summing kernel.
604%
605% This can be used to generate a faster "DOG" convolution, in the same
606% way "Blur" can.
anthony602ab9b2010-01-05 08:06:50 +0000607%
anthony3c10fc82010-05-13 02:40:51 +0000608% Comet:{width},{sigma},{angle}
609% Blur in one direction only, much like how a bright object leaves
anthony602ab9b2010-01-05 08:06:50 +0000610% a comet like trail. The Kernel is actually half a gaussian curve,
anthony3c10fc82010-05-13 02:40:51 +0000611% Adding two such blurs in opposite directions produces a Blur Kernel.
612% Angle can be rotated in multiples of 90 degrees.
anthony602ab9b2010-01-05 08:06:50 +0000613%
anthony3c10fc82010-05-13 02:40:51 +0000614% Note that the first argument is the width of the kernel and not the
anthony602ab9b2010-01-05 08:06:50 +0000615% radius of the kernel.
616%
617% # Still to be implemented...
618% #
anthony4fd27e22010-02-07 08:17:18 +0000619% # Filter2D
620% # Filter1D
621% # Set kernel values using a resize filter, and given scale (sigma)
622% # Cylindrical or Linear. Is this posible with an image?
623% #
anthony602ab9b2010-01-05 08:06:50 +0000624%
anthony3c10fc82010-05-13 02:40:51 +0000625% Named Constant Convolution Kernels
626%
anthonyc1061722010-05-14 06:23:49 +0000627% All these are unscaled, zero-summing kernels by default. As such for
628% non-HDRI version of ImageMagick some form of normalization, user scaling,
629% and biasing the results is recommended, to prevent the resulting image
630% being 'clipped'.
631%
632% The 3x3 kernels (most of these) can be circularly rotated in multiples of
633% 45 degrees to generate the 8 angled varients of each of the kernels.
anthony3c10fc82010-05-13 02:40:51 +0000634%
635% Laplacian:{type}
anthony43c49252010-05-18 10:59:50 +0000636% Discrete Lapacian Kernels, (without normalization)
anthonyc1061722010-05-14 06:23:49 +0000637% Type 0 : 3x3 with center:8 surounded by -1 (8 neighbourhood)
638% Type 1 : 3x3 with center:4 edge:-1 corner:0 (4 neighbourhood)
anthony9eb4f742010-05-18 02:45:54 +0000639% Type 2 : 3x3 with center:4 edge:1 corner:-2
640% Type 3 : 3x3 with center:4 edge:-2 corner:1
641% Type 5 : 5x5 laplacian
642% Type 7 : 7x7 laplacian
anthony43c49252010-05-18 10:59:50 +0000643% Type 15 : 5x5 LOG (sigma approx 1.4)
644% Type 19 : 9x9 LOG (sigma approx 1.4)
anthonyc1061722010-05-14 06:23:49 +0000645%
646% Sobel:{angle}
anthony46a369d2010-05-19 02:41:48 +0000647% Sobel 'Edge' convolution kernel (3x3)
anthonyc1061722010-05-14 06:23:49 +0000648% -1, 0, 1
649% -2, 0,-2
650% -1, 0, 1
anthonye2a60ce2010-05-19 12:30:40 +0000651%
anthonyc1061722010-05-14 06:23:49 +0000652% Roberts:{angle}
anthony46a369d2010-05-19 02:41:48 +0000653% Roberts convolution kernel (3x3)
anthonyc1061722010-05-14 06:23:49 +0000654% 0, 0, 0
655% -1, 1, 0
656% 0, 0, 0
anthonyc1061722010-05-14 06:23:49 +0000657% Prewitt:{angle}
658% Prewitt Edge convolution kernel (3x3)
659% -1, 0, 1
660% -1, 0, 1
661% -1, 0, 1
anthony9eb4f742010-05-18 02:45:54 +0000662% Compass:{angle}
663% Prewitt's "Compass" convolution kernel (3x3)
664% -1, 1, 1
665% -1,-2, 1
666% -1, 1, 1
667% Kirsch:{angle}
668% Kirsch's "Compass" convolution kernel (3x3)
669% -3,-3, 5
670% -3, 0, 5
671% -3,-3, 5
anthony3c10fc82010-05-13 02:40:51 +0000672%
anthonye2a60ce2010-05-19 12:30:40 +0000673% FreiChen:{type},{angle}
674% Frei-Chen Edge Detector is a set of 9 unique convolution kernels that
anthony6915d062010-05-19 12:45:51 +0000675% are specially weighted. They should not be normalized. After applying
676% each to the original image, the results is then added together. The
677% square root of the resulting image is the cosine of the edge, and the
678% direction of the feature detection.
anthonye2a60ce2010-05-19 12:30:40 +0000679%
680% Type 1: | 1, sqrt(2), 1 |
681% | 0, 0, 0 | / 2*sqrt(2)
682% | -1, -sqrt(2), -1 |
683%
684% Type 2: | 1, 0, 1 |
685% | sqrt(2), 0, sqrt(2) | / 2*sqrt(2)
686% | 1, 0, 1 |
687%
688% Type 3: | 0, -1, sqrt(2) |
689% | 1, 0, -1 | / 2*sqrt(2)
690% | -sqrt(2), 1, 0 |
691%
anthony6915d062010-05-19 12:45:51 +0000692% Type 4: | sqrt(2), -1, 0 |
anthonye2a60ce2010-05-19 12:30:40 +0000693% | -1, 0, 1 | / 2*sqrt(2)
694% | 0, 1, -sqrt(2) |
695%
696% Type 5: | 0, 1, 0 |
697% | -1, 0, -1 | / 2
698% | 0, 1, 0 |
699%
700% Type 6: | -1, 0, 1 |
701% | 0, 0, 0 | / 2
702% | 1, 0, -1 |
703%
anthonyf4e00312010-05-20 12:06:35 +0000704% Type 7: | 1, -2, 1 |
anthonye2a60ce2010-05-19 12:30:40 +0000705% | -2, 4, -2 | / 6
706% | 1, -2, 1 |
707%
anthonyf4e00312010-05-20 12:06:35 +0000708% Type 8: | -2, 1, -2 |
709% | 1, 4, 1 | / 6
710% | -2, 1, -2 |
anthonye2a60ce2010-05-19 12:30:40 +0000711%
anthonyf4e00312010-05-20 12:06:35 +0000712% Type 9: | 1, 1, 1 |
713% | 1, 1, 1 | / 3
714% | 1, 1, 1 |
anthonye2a60ce2010-05-19 12:30:40 +0000715%
716% The first 4 are for edge detection, the next 4 are for line detection
717% and the last is to add a average component to the results.
718%
anthonye2a60ce2010-05-19 12:30:40 +0000719%
anthony602ab9b2010-01-05 08:06:50 +0000720% Boolean Kernels
721%
anthony3c10fc82010-05-13 02:40:51 +0000722% Diamond:[{radius}[,{scale}]]
anthony1b2bc0a2010-05-12 05:25:22 +0000723% Generate a diamond shaped kernel with given radius to the points.
anthony602ab9b2010-01-05 08:06:50 +0000724% Kernel size will again be radius*2+1 square and defaults to radius 1,
725% generating a 3x3 kernel that is slightly larger than a square.
726%
anthony3c10fc82010-05-13 02:40:51 +0000727% Square:[{radius}[,{scale}]]
anthony602ab9b2010-01-05 08:06:50 +0000728% Generate a square shaped kernel of size radius*2+1, and defaulting
729% to a 3x3 (radius 1).
730%
anthonyc1061722010-05-14 06:23:49 +0000731% Note that using a larger radius for the "Square" or the "Diamond" is
732% also equivelent to iterating the basic morphological method that many
733% times. However iterating with the smaller radius is actually faster
734% than using a larger kernel radius.
735%
736% Rectangle:{geometry}
737% Simply generate a rectangle of 1's with the size given. You can also
738% specify the location of the 'control point', otherwise the closest
739% pixel to the center of the rectangle is selected.
740%
741% Properly centered and odd sized rectangles work the best.
anthony602ab9b2010-01-05 08:06:50 +0000742%
anthony3c10fc82010-05-13 02:40:51 +0000743% Disk:[{radius}[,{scale}]]
anthony602ab9b2010-01-05 08:06:50 +0000744% Generate a binary disk of the radius given, radius may be a float.
745% Kernel size will be ceil(radius)*2+1 square.
746% NOTE: Here are some disk shapes of specific interest
anthonyc1061722010-05-14 06:23:49 +0000747% "Disk:1" => "diamond" or "cross:1"
748% "Disk:1.5" => "square"
749% "Disk:2" => "diamond:2"
750% "Disk:2.5" => a general disk shape of radius 2
751% "Disk:2.9" => "square:2"
752% "Disk:3.5" => default - octagonal/disk shape of radius 3
753% "Disk:4.2" => roughly octagonal shape of radius 4
754% "Disk:4.3" => a general disk shape of radius 4
anthony602ab9b2010-01-05 08:06:50 +0000755% After this all the kernel shape becomes more and more circular.
756%
757% Because a "disk" is more circular when using a larger radius, using a
758% larger radius is preferred over iterating the morphological operation.
759%
anthonyc1061722010-05-14 06:23:49 +0000760% Symbol Dilation Kernels
761%
762% These kernel is not a good general morphological kernel, but is used
763% more for highlighting and marking any single pixels in an image using,
764% a "Dilate" method as appropriate.
765%
766% For the same reasons iterating these kernels does not produce the
767% same result as using a larger radius for the symbol.
768%
anthony3c10fc82010-05-13 02:40:51 +0000769% Plus:[{radius}[,{scale}]]
anthony3dd0f622010-05-13 12:57:32 +0000770% Cross:[{radius}[,{scale}]]
anthonyc1061722010-05-14 06:23:49 +0000771% Generate a kernel in the shape of a 'plus' or a 'cross' with
772% a each arm the length of the given radius (default 2).
anthony3dd0f622010-05-13 12:57:32 +0000773%
774% NOTE: "plus:1" is equivelent to a "Diamond" kernel.
anthony602ab9b2010-01-05 08:06:50 +0000775%
anthonyc1061722010-05-14 06:23:49 +0000776% Ring:{radius1},{radius2}[,{scale}]
777% A ring of the values given that falls between the two radii.
778% Defaults to a ring of approximataly 3 radius in a 7x7 kernel.
779% This is the 'edge' pixels of the default "Disk" kernel,
780% More specifically, "Ring" -> "Ring:2.5,3.5,1.0"
anthony602ab9b2010-01-05 08:06:50 +0000781%
anthony3dd0f622010-05-13 12:57:32 +0000782% Hit and Miss Kernels
783%
784% Peak:radius1,radius2
anthonyc1061722010-05-14 06:23:49 +0000785% Find any peak larger than the pixels the fall between the two radii.
786% The default ring of pixels is as per "Ring".
anthony43c49252010-05-18 10:59:50 +0000787% Edges
anthony1d45eb92010-05-25 11:13:23 +0000788% Find edges of a binary shape
anthony3dd0f622010-05-13 12:57:32 +0000789% Corners
790% Find corners of a binary shape
anthony47f5d062010-05-23 07:47:50 +0000791% Ridges
anthony1d45eb92010-05-25 11:13:23 +0000792% Find single pixel ridges or thin lines
793% Ridges2
794% Find 2 pixel thick ridges or lines
anthonya648a302010-05-27 02:14:36 +0000795% Ridges3
796% Find 2 pixel thick diagonal ridges (experimental)
anthony3dd0f622010-05-13 12:57:32 +0000797% LineEnds
798% Find end points of lines (for pruning a skeletion)
799% LineJunctions
anthony43c49252010-05-18 10:59:50 +0000800% Find three line junctions (within a skeletion)
anthony3dd0f622010-05-13 12:57:32 +0000801% ConvexHull
802% Octagonal thicken kernel, to generate convex hulls of 45 degrees
803% Skeleton
804% Thinning kernel, which leaves behind a skeletion of a shape
anthony602ab9b2010-01-05 08:06:50 +0000805%
806% Distance Measuring Kernels
807%
anthonyc1061722010-05-14 06:23:49 +0000808% Different types of distance measuring methods, which are used with the
809% a 'Distance' morphology method for generating a gradient based on
810% distance from an edge of a binary shape, though there is a technique
811% for handling a anti-aliased shape.
812%
813% See the 'Distance' Morphological Method, for information of how it is
814% applied.
815%
anthony3dd0f622010-05-13 12:57:32 +0000816% Chebyshev:[{radius}][x{scale}[%!]]
anthonyc94cdb02010-01-06 08:15:29 +0000817% Chebyshev Distance (also known as Tchebychev Distance) is a value of
818% one to any neighbour, orthogonal or diagonal. One why of thinking of
819% it is the number of squares a 'King' or 'Queen' in chess needs to
820% traverse reach any other position on a chess board. It results in a
821% 'square' like distance function, but one where diagonals are closer
822% than expected.
anthony602ab9b2010-01-05 08:06:50 +0000823%
anthonyc1061722010-05-14 06:23:49 +0000824% Manhatten:[{radius}][x{scale}[%!]]
anthonyc94cdb02010-01-06 08:15:29 +0000825% Manhatten Distance (also known as Rectilinear Distance, or the Taxi
826% Cab metric), is the distance needed when you can only travel in
827% orthogonal (horizontal or vertical) only. It is the distance a 'Rook'
828% in chess would travel. It results in a diamond like distances, where
829% diagonals are further than expected.
anthony602ab9b2010-01-05 08:06:50 +0000830%
anthonyc1061722010-05-14 06:23:49 +0000831% Euclidean:[{radius}][x{scale}[%!]]
anthonyc94cdb02010-01-06 08:15:29 +0000832% Euclidean Distance is the 'direct' or 'as the crow flys distance.
833% However by default the kernel size only has a radius of 1, which
834% limits the distance to 'Knight' like moves, with only orthogonal and
835% diagonal measurements being correct. As such for the default kernel
836% you will get octagonal like distance function, which is reasonally
837% accurate.
838%
839% However if you use a larger radius such as "Euclidean:4" you will
840% get a much smoother distance gradient from the edge of the shape.
841% Of course a larger kernel is slower to use, and generally not needed.
842%
843% To allow the use of fractional distances that you get with diagonals
844% the actual distance is scaled by a fixed value which the user can
845% provide. This is not actually nessary for either ""Chebyshev" or
846% "Manhatten" distance kernels, but is done for all three distance
847% kernels. If no scale is provided it is set to a value of 100,
848% allowing for a maximum distance measurement of 655 pixels using a Q16
849% version of IM, from any edge. However for small images this can
850% result in quite a dark gradient.
851%
anthony602ab9b2010-01-05 08:06:50 +0000852*/
853
cristy2be15382010-01-21 02:38:03 +0000854MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000855 const GeometryInfo *args)
856{
cristy2be15382010-01-21 02:38:03 +0000857 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000858 *kernel;
859
cristy150989e2010-02-01 14:59:39 +0000860 register long
anthony602ab9b2010-01-05 08:06:50 +0000861 i;
862
863 register long
864 u,
865 v;
866
867 double
868 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
869
anthonyc1061722010-05-14 06:23:49 +0000870 /* Generate a new empty kernel if needed */
cristye96405a2010-05-19 02:24:31 +0000871 kernel=(KernelInfo *) NULL;
anthonyc1061722010-05-14 06:23:49 +0000872 switch(type) {
anthony9eb4f742010-05-18 02:45:54 +0000873 case UndefinedKernel: /* These should not be used here */
874 case UserDefinedKernel:
875 break;
876 case LaplacianKernel: /* Named Descrete Convolution Kernels */
877 case SobelKernel:
878 case RobertsKernel:
879 case PrewittKernel:
880 case CompassKernel:
881 case KirschKernel:
882 case CornersKernel: /* Hit and Miss kernels */
883 case LineEndsKernel:
884 case LineJunctionsKernel:
anthony9eb4f742010-05-18 02:45:54 +0000885 case ConvexHullKernel:
886 case SkeletonKernel:
887 /* A pre-generated kernel is not needed */
888 break;
889#if 0
anthonyc1061722010-05-14 06:23:49 +0000890 case GaussianKernel:
891 case DOGKernel:
892 case BlurKernel:
893 case DOBKernel:
894 case CometKernel:
895 case DiamondKernel:
896 case SquareKernel:
897 case RectangleKernel:
898 case DiskKernel:
899 case PlusKernel:
900 case CrossKernel:
901 case RingKernel:
902 case PeaksKernel:
903 case ChebyshevKernel:
904 case ManhattenKernel:
905 case EuclideanKernel:
anthony9eb4f742010-05-18 02:45:54 +0000906#endif
907 default:
908 /* Generate the base Kernel Structure */
anthonyc1061722010-05-14 06:23:49 +0000909 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
910 if (kernel == (KernelInfo *) NULL)
911 return(kernel);
912 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
anthony43c49252010-05-18 10:59:50 +0000913 kernel->minimum = kernel->maximum = kernel->angle = 0.0;
anthonyc1061722010-05-14 06:23:49 +0000914 kernel->negative_range = kernel->positive_range = 0.0;
915 kernel->type = type;
916 kernel->next = (KernelInfo *) NULL;
917 kernel->signature = MagickSignature;
anthonyc1061722010-05-14 06:23:49 +0000918 break;
919 }
anthony602ab9b2010-01-05 08:06:50 +0000920
921 switch(type) {
922 /* Convolution Kernels */
923 case GaussianKernel:
anthonyc1061722010-05-14 06:23:49 +0000924 case DOGKernel:
anthony9eb4f742010-05-18 02:45:54 +0000925 case LOGKernel:
anthony602ab9b2010-01-05 08:06:50 +0000926 { double
anthonyc1061722010-05-14 06:23:49 +0000927 sigma = fabs(args->sigma),
928 sigma2 = fabs(args->xi),
anthony9eb4f742010-05-18 02:45:54 +0000929 A, B, R;
anthony602ab9b2010-01-05 08:06:50 +0000930
anthonyc1061722010-05-14 06:23:49 +0000931 if ( args->rho >= 1.0 )
932 kernel->width = (unsigned long)args->rho*2+1;
anthony9eb4f742010-05-18 02:45:54 +0000933 else if ( (type != DOGKernel) || (sigma >= sigma2) )
anthonyc1061722010-05-14 06:23:49 +0000934 kernel->width = GetOptimalKernelWidth2D(args->rho,sigma);
935 else
936 kernel->width = GetOptimalKernelWidth2D(args->rho,sigma2);
937 kernel->height = kernel->width;
cristyc99304f2010-02-01 15:26:27 +0000938 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000939 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
940 kernel->height*sizeof(double));
941 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000942 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000943
anthony46a369d2010-05-19 02:41:48 +0000944 /* WARNING: The following generates a 'sampled gaussian' kernel.
anthony9eb4f742010-05-18 02:45:54 +0000945 * What we really want is a 'discrete gaussian' kernel.
anthony46a369d2010-05-19 02:41:48 +0000946 *
947 * How to do this is currently not known, but appears to be
948 * basied on the Error Function 'erf()' (intergral of a gaussian)
anthony9eb4f742010-05-18 02:45:54 +0000949 */
950
951 if ( type == GaussianKernel || type == DOGKernel )
952 { /* Calculate a Gaussian, OR positive half of a DOG */
953 if ( sigma > MagickEpsilon )
954 { A = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
955 B = 1.0/(Magick2PI*sigma*sigma);
956 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
957 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
958 kernel->values[i] = exp(-((double)(u*u+v*v))*A)*B;
959 }
960 else /* limiting case - a unity (normalized Dirac) kernel */
961 { (void) ResetMagickMemory(kernel->values,0, (size_t)
962 kernel->width*kernel->height*sizeof(double));
963 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
964 }
anthonyc1061722010-05-14 06:23:49 +0000965 }
anthony9eb4f742010-05-18 02:45:54 +0000966
anthonyc1061722010-05-14 06:23:49 +0000967 if ( type == DOGKernel )
968 { /* Subtract a Negative Gaussian for "Difference of Gaussian" */
969 if ( sigma2 > MagickEpsilon )
970 { sigma = sigma2; /* simplify loop expressions */
anthony9eb4f742010-05-18 02:45:54 +0000971 A = 1.0/(2.0*sigma*sigma);
972 B = 1.0/(Magick2PI*sigma*sigma);
anthonyc1061722010-05-14 06:23:49 +0000973 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
974 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony9eb4f742010-05-18 02:45:54 +0000975 kernel->values[i] -= exp(-((double)(u*u+v*v))*A)*B;
anthonyc1061722010-05-14 06:23:49 +0000976 }
anthony9eb4f742010-05-18 02:45:54 +0000977 else /* limiting case - a unity (normalized Dirac) kernel */
anthonyc1061722010-05-14 06:23:49 +0000978 kernel->values[kernel->x+kernel->y*kernel->width] -= 1.0;
979 }
anthony9eb4f742010-05-18 02:45:54 +0000980
981 if ( type == LOGKernel )
982 { /* Calculate a Laplacian of a Gaussian - Or Mexician Hat */
983 if ( sigma > MagickEpsilon )
984 { A = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
985 B = 1.0/(MagickPI*sigma*sigma*sigma*sigma);
986 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
987 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
988 { R = ((double)(u*u+v*v))*A;
989 kernel->values[i] = (1-R)*exp(-R)*B;
990 }
991 }
992 else /* special case - generate a unity kernel */
993 { (void) ResetMagickMemory(kernel->values,0, (size_t)
994 kernel->width*kernel->height*sizeof(double));
995 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
996 }
997 }
998
999 /* Note the above kernels may have been 'clipped' by a user defined
anthonyc1061722010-05-14 06:23:49 +00001000 ** radius, producing a smaller (darker) kernel. Also for very small
1001 ** sigma's (> 0.1) the central value becomes larger than one, and thus
1002 ** producing a very bright kernel.
1003 **
1004 ** Normalization will still be needed.
1005 */
anthony602ab9b2010-01-05 08:06:50 +00001006
anthony3dd0f622010-05-13 12:57:32 +00001007 /* Normalize the 2D Gaussian Kernel
1008 **
anthonyc1061722010-05-14 06:23:49 +00001009 ** NB: a CorrelateNormalize performs a normal Normalize if
1010 ** there are no negative values.
anthony3dd0f622010-05-13 12:57:32 +00001011 */
anthony46a369d2010-05-19 02:41:48 +00001012 CalcKernelMetaData(kernel); /* the other kernel meta-data */
anthonyc1061722010-05-14 06:23:49 +00001013 ScaleKernelInfo(kernel, 1.0, CorrelateNormalizeValue);
anthony602ab9b2010-01-05 08:06:50 +00001014
1015 break;
1016 }
1017 case BlurKernel:
anthonyc1061722010-05-14 06:23:49 +00001018 case DOBKernel:
anthony602ab9b2010-01-05 08:06:50 +00001019 { double
anthonyc1061722010-05-14 06:23:49 +00001020 sigma = fabs(args->sigma),
1021 sigma2 = fabs(args->xi),
anthony9eb4f742010-05-18 02:45:54 +00001022 A, B;
anthony602ab9b2010-01-05 08:06:50 +00001023
anthonyc1061722010-05-14 06:23:49 +00001024 if ( args->rho >= 1.0 )
1025 kernel->width = (unsigned long)args->rho*2+1;
1026 else if ( (type == BlurKernel) || (sigma >= sigma2) )
1027 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
1028 else
1029 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma2);
anthony602ab9b2010-01-05 08:06:50 +00001030 kernel->height = 1;
anthonyc1061722010-05-14 06:23:49 +00001031 kernel->x = (long) (kernel->width-1)/2;
cristyc99304f2010-02-01 15:26:27 +00001032 kernel->y = 0;
1033 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +00001034 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1035 kernel->height*sizeof(double));
1036 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001037 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001038
1039#if 1
1040#define KernelRank 3
1041 /* Formula derived from GetBlurKernel() in "effect.c" (plus bug fix).
1042 ** It generates a gaussian 3 times the width, and compresses it into
1043 ** the expected range. This produces a closer normalization of the
1044 ** resulting kernel, especially for very low sigma values.
1045 ** As such while wierd it is prefered.
1046 **
1047 ** I am told this method originally came from Photoshop.
anthony9eb4f742010-05-18 02:45:54 +00001048 **
1049 ** A properly normalized curve is generated (apart from edge clipping)
1050 ** even though we later normalize the result (for edge clipping)
1051 ** to allow the correct generation of a "Difference of Blurs".
anthony602ab9b2010-01-05 08:06:50 +00001052 */
anthonyc1061722010-05-14 06:23:49 +00001053
1054 /* initialize */
cristy150989e2010-02-01 14:59:39 +00001055 v = (long) (kernel->width*KernelRank-1)/2; /* start/end points to fit range */
anthony9eb4f742010-05-18 02:45:54 +00001056 (void) ResetMagickMemory(kernel->values,0, (size_t)
1057 kernel->width*kernel->height*sizeof(double));
anthonyc1061722010-05-14 06:23:49 +00001058 /* Calculate a Positive 1D Gaussian */
1059 if ( sigma > MagickEpsilon )
1060 { sigma *= KernelRank; /* simplify loop expressions */
anthony9eb4f742010-05-18 02:45:54 +00001061 A = 1.0/(2.0*sigma*sigma);
1062 B = 1.0/(MagickSQ2PI*sigma );
anthonyc1061722010-05-14 06:23:49 +00001063 for ( u=-v; u <= v; u++) {
anthony9eb4f742010-05-18 02:45:54 +00001064 kernel->values[(u+v)/KernelRank] += exp(-((double)(u*u))*A)*B;
anthonyc1061722010-05-14 06:23:49 +00001065 }
1066 }
1067 else /* special case - generate a unity kernel */
1068 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
anthony9eb4f742010-05-18 02:45:54 +00001069
1070 /* Subtract a Second 1D Gaussian for "Difference of Blur" */
anthonyc1061722010-05-14 06:23:49 +00001071 if ( type == DOBKernel )
anthony9eb4f742010-05-18 02:45:54 +00001072 {
anthonyc1061722010-05-14 06:23:49 +00001073 if ( sigma2 > MagickEpsilon )
1074 { sigma = sigma2*KernelRank; /* simplify loop expressions */
anthony9eb4f742010-05-18 02:45:54 +00001075 A = 1.0/(2.0*sigma*sigma);
1076 B = 1.0/(MagickSQ2PI*sigma);
anthonyc1061722010-05-14 06:23:49 +00001077 for ( u=-v; u <= v; u++)
anthony9eb4f742010-05-18 02:45:54 +00001078 kernel->values[(u+v)/KernelRank] -= exp(-((double)(u*u))*A)*B;
anthonyc1061722010-05-14 06:23:49 +00001079 }
anthony9eb4f742010-05-18 02:45:54 +00001080 else /* limiting case - a unity (normalized Dirac) kernel */
anthonyc1061722010-05-14 06:23:49 +00001081 kernel->values[kernel->x+kernel->y*kernel->width] -= 1.0;
1082 }
anthony602ab9b2010-01-05 08:06:50 +00001083#else
anthonyc1061722010-05-14 06:23:49 +00001084 /* Direct calculation without curve averaging */
1085
1086 /* Calculate a Positive Gaussian */
1087 if ( sigma > MagickEpsilon )
anthony9eb4f742010-05-18 02:45:54 +00001088 { A = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
1089 B = 1.0/(MagickSQ2PI*sigma);
anthonyc1061722010-05-14 06:23:49 +00001090 for ( i=0, u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony9eb4f742010-05-18 02:45:54 +00001091 kernel->values[i] = exp(-((double)(u*u))*A)*B;
anthonyc1061722010-05-14 06:23:49 +00001092 }
1093 else /* special case - generate a unity kernel */
1094 { (void) ResetMagickMemory(kernel->values,0, (size_t)
1095 kernel->width*kernel->height*sizeof(double));
1096 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1097 }
anthony9eb4f742010-05-18 02:45:54 +00001098
1099 /* Subtract a Second 1D Gaussian for "Difference of Blur" */
anthonyc1061722010-05-14 06:23:49 +00001100 if ( type == DOBKernel )
anthony9eb4f742010-05-18 02:45:54 +00001101 {
anthonyc1061722010-05-14 06:23:49 +00001102 if ( sigma2 > MagickEpsilon )
1103 { sigma = sigma2; /* simplify loop expressions */
anthony9eb4f742010-05-18 02:45:54 +00001104 A = 1.0/(2.0*sigma*sigma);
1105 B = 1.0/(MagickSQ2PI*sigma);
anthonyc1061722010-05-14 06:23:49 +00001106 for ( i=0, u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony9eb4f742010-05-18 02:45:54 +00001107 kernel->values[i] -= exp(-((double)(u*u))*A)*B;
anthonyc1061722010-05-14 06:23:49 +00001108 }
anthony9eb4f742010-05-18 02:45:54 +00001109 else /* limiting case - a unity (normalized Dirac) kernel */
anthonyc1061722010-05-14 06:23:49 +00001110 kernel->values[kernel->x+kernel->y*kernel->width] -= 1.0;
1111 }
anthony602ab9b2010-01-05 08:06:50 +00001112#endif
anthonyc1061722010-05-14 06:23:49 +00001113 /* Note the above kernel may have been 'clipped' by a user defined
anthonycc6c8362010-01-25 04:14:01 +00001114 ** radius, producing a smaller (darker) kernel. Also for very small
1115 ** sigma's (> 0.1) the central value becomes larger than one, and thus
1116 ** producing a very bright kernel.
anthonyc1061722010-05-14 06:23:49 +00001117 **
1118 ** Normalization will still be needed.
anthony602ab9b2010-01-05 08:06:50 +00001119 */
anthonycc6c8362010-01-25 04:14:01 +00001120
anthony602ab9b2010-01-05 08:06:50 +00001121 /* Normalize the 1D Gaussian Kernel
1122 **
anthonyc1061722010-05-14 06:23:49 +00001123 ** NB: a CorrelateNormalize performs a normal Normalize if
1124 ** there are no negative values.
anthony602ab9b2010-01-05 08:06:50 +00001125 */
anthony46a369d2010-05-19 02:41:48 +00001126 CalcKernelMetaData(kernel); /* the other kernel meta-data */
1127 ScaleKernelInfo(kernel, 1.0, CorrelateNormalizeValue);
anthonycc6c8362010-01-25 04:14:01 +00001128
anthonyc1061722010-05-14 06:23:49 +00001129 /* rotate the 1D kernel by given angle */
1130 RotateKernelInfo(kernel, (type == BlurKernel) ? args->xi : args->psi );
anthony602ab9b2010-01-05 08:06:50 +00001131 break;
1132 }
1133 case CometKernel:
1134 { double
anthony9eb4f742010-05-18 02:45:54 +00001135 sigma = fabs(args->sigma),
1136 A;
anthony602ab9b2010-01-05 08:06:50 +00001137
anthony602ab9b2010-01-05 08:06:50 +00001138 if ( args->rho < 1.0 )
anthonye1cf9462010-05-19 03:50:26 +00001139 kernel->width = (GetOptimalKernelWidth1D(args->rho,sigma)-1)/2+1;
anthony602ab9b2010-01-05 08:06:50 +00001140 else
1141 kernel->width = (unsigned long)args->rho;
cristyc99304f2010-02-01 15:26:27 +00001142 kernel->x = kernel->y = 0;
anthony602ab9b2010-01-05 08:06:50 +00001143 kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +00001144 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +00001145 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1146 kernel->height*sizeof(double));
1147 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001148 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001149
anthonyc1061722010-05-14 06:23:49 +00001150 /* A comet blur is half a 1D gaussian curve, so that the object is
anthony602ab9b2010-01-05 08:06:50 +00001151 ** blurred in one direction only. This may not be quite the right
anthony3dd0f622010-05-13 12:57:32 +00001152 ** curve to use so may change in the future. The function must be
1153 ** normalised after generation, which also resolves any clipping.
anthonyc1061722010-05-14 06:23:49 +00001154 **
1155 ** As we are normalizing and not subtracting gaussians,
1156 ** there is no need for a divisor in the gaussian formula
1157 **
anthony43c49252010-05-18 10:59:50 +00001158 ** It is less comples
anthony602ab9b2010-01-05 08:06:50 +00001159 */
anthony9eb4f742010-05-18 02:45:54 +00001160 if ( sigma > MagickEpsilon )
1161 {
anthony602ab9b2010-01-05 08:06:50 +00001162#if 1
1163#define KernelRank 3
anthony9eb4f742010-05-18 02:45:54 +00001164 v = (long) kernel->width*KernelRank; /* start/end points */
1165 (void) ResetMagickMemory(kernel->values,0, (size_t)
1166 kernel->width*sizeof(double));
1167 sigma *= KernelRank; /* simplify the loop expression */
1168 A = 1.0/(2.0*sigma*sigma);
1169 /* B = 1.0/(MagickSQ2PI*sigma); */
1170 for ( u=0; u < v; u++) {
1171 kernel->values[u/KernelRank] +=
1172 exp(-((double)(u*u))*A);
1173 /* exp(-((double)(i*i))/2.0*sigma*sigma)/(MagickSQ2PI*sigma); */
1174 }
1175 for (i=0; i < (long) kernel->width; i++)
1176 kernel->positive_range += kernel->values[i];
anthony602ab9b2010-01-05 08:06:50 +00001177#else
anthony9eb4f742010-05-18 02:45:54 +00001178 A = 1.0/(2.0*sigma*sigma); /* simplify the loop expression */
1179 /* B = 1.0/(MagickSQ2PI*sigma); */
1180 for ( i=0; i < (long) kernel->width; i++)
1181 kernel->positive_range +=
1182 kernel->values[i] =
1183 exp(-((double)(i*i))*A);
1184 /* exp(-((double)(i*i))/2.0*sigma*sigma)/(MagickSQ2PI*sigma); */
anthony602ab9b2010-01-05 08:06:50 +00001185#endif
anthony9eb4f742010-05-18 02:45:54 +00001186 }
1187 else /* special case - generate a unity kernel */
1188 { (void) ResetMagickMemory(kernel->values,0, (size_t)
1189 kernel->width*kernel->height*sizeof(double));
1190 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1191 kernel->positive_range = 1.0;
1192 }
anthony46a369d2010-05-19 02:41:48 +00001193
1194 kernel->minimum = 0.0;
cristyc99304f2010-02-01 15:26:27 +00001195 kernel->maximum = kernel->values[0];
anthony46a369d2010-05-19 02:41:48 +00001196 kernel->negative_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +00001197
anthony999bb2c2010-02-18 12:38:01 +00001198 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
1199 RotateKernelInfo(kernel, args->xi); /* Rotate by angle */
anthony602ab9b2010-01-05 08:06:50 +00001200 break;
1201 }
anthonyc1061722010-05-14 06:23:49 +00001202
anthony3c10fc82010-05-13 02:40:51 +00001203 /* Convolution Kernels - Well Known Constants */
anthony3c10fc82010-05-13 02:40:51 +00001204 case LaplacianKernel:
anthonye2a60ce2010-05-19 12:30:40 +00001205 { switch ( (int) args->rho ) {
anthony3dd0f622010-05-13 12:57:32 +00001206 case 0:
anthony9eb4f742010-05-18 02:45:54 +00001207 default: /* laplacian square filter -- default */
anthonyc1061722010-05-14 06:23:49 +00001208 kernel=ParseKernelArray("3: -1,-1,-1 -1,8,-1 -1,-1,-1");
anthony3dd0f622010-05-13 12:57:32 +00001209 break;
anthony9eb4f742010-05-18 02:45:54 +00001210 case 1: /* laplacian diamond filter */
anthonyc1061722010-05-14 06:23:49 +00001211 kernel=ParseKernelArray("3: 0,-1,0 -1,4,-1 0,-1,0");
anthony3c10fc82010-05-13 02:40:51 +00001212 break;
1213 case 2:
anthony9eb4f742010-05-18 02:45:54 +00001214 kernel=ParseKernelArray("3: -2,1,-2 1,4,1 -2,1,-2");
1215 break;
1216 case 3:
anthonyc1061722010-05-14 06:23:49 +00001217 kernel=ParseKernelArray("3: 1,-2,1 -2,4,-2 1,-2,1");
anthony3c10fc82010-05-13 02:40:51 +00001218 break;
anthony9eb4f742010-05-18 02:45:54 +00001219 case 5: /* a 5x5 laplacian */
anthony3c10fc82010-05-13 02:40:51 +00001220 kernel=ParseKernelArray(
anthony9eb4f742010-05-18 02:45:54 +00001221 "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 +00001222 break;
anthony9eb4f742010-05-18 02:45:54 +00001223 case 7: /* a 7x7 laplacian */
anthony3c10fc82010-05-13 02:40:51 +00001224 kernel=ParseKernelArray(
anthonyc1061722010-05-14 06:23:49 +00001225 "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 +00001226 break;
anthony43c49252010-05-18 10:59:50 +00001227 case 15: /* a 5x5 LOG (sigma approx 1.4) */
anthony9eb4f742010-05-18 02:45:54 +00001228 kernel=ParseKernelArray(
1229 "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");
1230 break;
anthony43c49252010-05-18 10:59:50 +00001231 case 19: /* a 9x9 LOG (sigma approx 1.4) */
1232 /* http://www.cscjournals.org/csc/manuscript/Journals/IJIP/volume3/Issue1/IJIP-15.pdf */
1233 kernel=ParseKernelArray(
1234 "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");
1235 break;
anthony3c10fc82010-05-13 02:40:51 +00001236 }
1237 if (kernel == (KernelInfo *) NULL)
1238 return(kernel);
1239 kernel->type = type;
1240 break;
1241 }
anthonyc1061722010-05-14 06:23:49 +00001242 case SobelKernel:
anthony602ab9b2010-01-05 08:06:50 +00001243 {
anthonyc1061722010-05-14 06:23:49 +00001244 kernel=ParseKernelArray("3: -1,0,1 -2,0,2 -1,0,1");
1245 if (kernel == (KernelInfo *) NULL)
1246 return(kernel);
1247 kernel->type = type;
1248 RotateKernelInfo(kernel, args->rho); /* Rotate by angle */
1249 break;
1250 }
1251 case RobertsKernel:
1252 {
1253 kernel=ParseKernelArray("3: 0,0,0 -1,1,0 0,0,0");
1254 if (kernel == (KernelInfo *) NULL)
1255 return(kernel);
1256 kernel->type = type;
anthony46a369d2010-05-19 02:41:48 +00001257 RotateKernelInfo(kernel, args->rho);
anthonyc1061722010-05-14 06:23:49 +00001258 break;
1259 }
1260 case PrewittKernel:
1261 {
1262 kernel=ParseKernelArray("3: -1,1,1 0,0,0 -1,1,1");
1263 if (kernel == (KernelInfo *) NULL)
1264 return(kernel);
1265 kernel->type = type;
anthony46a369d2010-05-19 02:41:48 +00001266 RotateKernelInfo(kernel, args->rho);
anthonyc1061722010-05-14 06:23:49 +00001267 break;
1268 }
1269 case CompassKernel:
1270 {
1271 kernel=ParseKernelArray("3: -1,1,1 -1,-2,1 -1,1,1");
1272 if (kernel == (KernelInfo *) NULL)
1273 return(kernel);
1274 kernel->type = type;
anthony46a369d2010-05-19 02:41:48 +00001275 RotateKernelInfo(kernel, args->rho);
anthonyc1061722010-05-14 06:23:49 +00001276 break;
1277 }
anthony9eb4f742010-05-18 02:45:54 +00001278 case KirschKernel:
1279 {
1280 kernel=ParseKernelArray("3: -3,-3,5 -3,0,5 -3,-3,5");
1281 if (kernel == (KernelInfo *) NULL)
1282 return(kernel);
1283 kernel->type = type;
anthony46a369d2010-05-19 02:41:48 +00001284 RotateKernelInfo(kernel, args->rho);
anthony9eb4f742010-05-18 02:45:54 +00001285 break;
1286 }
anthonye2a60ce2010-05-19 12:30:40 +00001287 case FreiChenKernel:
anthony6915d062010-05-19 12:45:51 +00001288 /* http://www.math.tau.ac.il/~turkel/notes/edge_detectors.pdf */
1289 /* http://ltswww.epfl.ch/~courstiv/exos_labos/sol3.pdf */
anthonye2a60ce2010-05-19 12:30:40 +00001290 { switch ( (int) args->rho ) {
1291 default:
1292 case 1:
1293 kernel=ParseKernelArray("3: 1,2,1 0,0,0 -1,2,-1");
1294 if (kernel == (KernelInfo *) NULL)
1295 return(kernel);
1296 kernel->values[1] = +MagickSQ2;
1297 kernel->values[7] = -MagickSQ2;
1298 CalcKernelMetaData(kernel); /* recalculate meta-data */
1299 ScaleKernelInfo(kernel, 1.0/2.0*MagickSQ2, NoValue);
1300 break;
1301 case 2:
1302 kernel=ParseKernelArray("3: 1,0,1 2,0,2 1,0,1");
1303 if (kernel == (KernelInfo *) NULL)
1304 return(kernel);
1305 kernel->values[3] = +MagickSQ2;
1306 kernel->values[5] = +MagickSQ2;
1307 CalcKernelMetaData(kernel);
1308 ScaleKernelInfo(kernel, 1.0/2.0*MagickSQ2, NoValue);
1309 break;
1310 case 3:
1311 kernel=ParseKernelArray("3: 0,-1,2 1,0,-1 -2,1,0");
1312 if (kernel == (KernelInfo *) NULL)
1313 return(kernel);
1314 kernel->values[2] = +MagickSQ2;
1315 kernel->values[6] = -MagickSQ2;
1316 CalcKernelMetaData(kernel);
1317 ScaleKernelInfo(kernel, 1.0/2.0*MagickSQ2, NoValue);
1318 break;
1319 case 4:
anthony6915d062010-05-19 12:45:51 +00001320 kernel=ParseKernelArray("3: 2,-1,0 -1,0,1 0,1,-2");
anthonye2a60ce2010-05-19 12:30:40 +00001321 if (kernel == (KernelInfo *) NULL)
1322 return(kernel);
1323 kernel->values[0] = +MagickSQ2;
1324 kernel->values[8] = -MagickSQ2;
1325 CalcKernelMetaData(kernel);
1326 ScaleKernelInfo(kernel, 1.0/2.0*MagickSQ2, NoValue);
1327 break;
1328 case 5:
1329 kernel=ParseKernelArray("3: 0,1,0 -1,0,-1 0,1,0");
1330 if (kernel == (KernelInfo *) NULL)
1331 return(kernel);
1332 ScaleKernelInfo(kernel, 1.0/2.0, NoValue);
1333 break;
1334 case 6:
1335 kernel=ParseKernelArray("3: -1,0,1 0,0,0 1,0,-1");
1336 if (kernel == (KernelInfo *) NULL)
1337 return(kernel);
1338 ScaleKernelInfo(kernel, 1.0/2.0, NoValue);
1339 break;
1340 case 7:
anthonyf4e00312010-05-20 12:06:35 +00001341 kernel=ParseKernelArray("3: 1,-2,1 -2,4,-2 1,-2,1");
anthonye2a60ce2010-05-19 12:30:40 +00001342 if (kernel == (KernelInfo *) NULL)
1343 return(kernel);
1344 ScaleKernelInfo(kernel, 1.0/6.0, NoValue);
1345 break;
1346 case 8:
1347 kernel=ParseKernelArray("3: -2,1,-2 1,4,1 -2,1,-2");
1348 if (kernel == (KernelInfo *) NULL)
1349 return(kernel);
1350 ScaleKernelInfo(kernel, 1.0/6.0, NoValue);
1351 break;
1352 case 9:
anthony6915d062010-05-19 12:45:51 +00001353 kernel=ParseKernelName("3: 1,1,1 1,1,1 1,1,1");
anthonye2a60ce2010-05-19 12:30:40 +00001354 if (kernel == (KernelInfo *) NULL)
1355 return(kernel);
1356 ScaleKernelInfo(kernel, 1.0/3.0, NoValue);
1357 break;
1358 }
1359 RotateKernelInfo(kernel, args->sigma); /* Rotate by angle */
1360 break;
1361 }
1362
anthonyc1061722010-05-14 06:23:49 +00001363 /* Boolean Kernels */
1364 case DiamondKernel:
1365 {
1366 if (args->rho < 1.0)
1367 kernel->width = kernel->height = 3; /* default radius = 1 */
1368 else
1369 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
1370 kernel->x = kernel->y = (long) (kernel->width-1)/2;
1371
1372 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1373 kernel->height*sizeof(double));
1374 if (kernel->values == (double *) NULL)
1375 return(DestroyKernelInfo(kernel));
1376
1377 /* set all kernel values within diamond area to scale given */
1378 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1379 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1380 if ((labs(u)+labs(v)) <= (long)kernel->x)
1381 kernel->positive_range += kernel->values[i] = args->sigma;
1382 else
1383 kernel->values[i] = nan;
1384 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1385 break;
1386 }
1387 case SquareKernel:
1388 case RectangleKernel:
1389 { double
1390 scale;
anthony602ab9b2010-01-05 08:06:50 +00001391 if ( type == SquareKernel )
1392 {
1393 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001394 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001395 else
cristy150989e2010-02-01 14:59:39 +00001396 kernel->width = kernel->height = (unsigned long) (2*args->rho+1);
cristyc99304f2010-02-01 15:26:27 +00001397 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony4fd27e22010-02-07 08:17:18 +00001398 scale = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +00001399 }
1400 else {
cristy2be15382010-01-21 02:38:03 +00001401 /* NOTE: user defaults set in "AcquireKernelInfo()" */
anthony602ab9b2010-01-05 08:06:50 +00001402 if ( args->rho < 1.0 || args->sigma < 1.0 )
anthony83ba99b2010-01-24 08:48:15 +00001403 return(DestroyKernelInfo(kernel)); /* invalid args given */
anthony602ab9b2010-01-05 08:06:50 +00001404 kernel->width = (unsigned long)args->rho;
1405 kernel->height = (unsigned long)args->sigma;
1406 if ( args->xi < 0.0 || args->xi > (double)kernel->width ||
1407 args->psi < 0.0 || args->psi > (double)kernel->height )
anthony83ba99b2010-01-24 08:48:15 +00001408 return(DestroyKernelInfo(kernel)); /* invalid args given */
cristyc99304f2010-02-01 15:26:27 +00001409 kernel->x = (long) args->xi;
1410 kernel->y = (long) args->psi;
anthony4fd27e22010-02-07 08:17:18 +00001411 scale = 1.0;
anthony602ab9b2010-01-05 08:06:50 +00001412 }
1413 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1414 kernel->height*sizeof(double));
1415 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001416 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001417
anthony3dd0f622010-05-13 12:57:32 +00001418 /* set all kernel values to scale given */
cristy150989e2010-02-01 14:59:39 +00001419 u=(long) kernel->width*kernel->height;
1420 for ( i=0; i < u; i++)
anthony4fd27e22010-02-07 08:17:18 +00001421 kernel->values[i] = scale;
1422 kernel->minimum = kernel->maximum = scale; /* a flat shape */
1423 kernel->positive_range = scale*u;
anthonycc6c8362010-01-25 04:14:01 +00001424 break;
anthony602ab9b2010-01-05 08:06:50 +00001425 }
anthony602ab9b2010-01-05 08:06:50 +00001426 case DiskKernel:
1427 {
anthonyc1061722010-05-14 06:23:49 +00001428 long limit = (long)(args->rho*args->rho);
anthony83ba99b2010-01-24 08:48:15 +00001429 if (args->rho < 0.1) /* default radius approx 3.5 */
1430 kernel->width = kernel->height = 7L, limit = 10L;
anthony602ab9b2010-01-05 08:06:50 +00001431 else
1432 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001433 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001434
1435 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1436 kernel->height*sizeof(double));
1437 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001438 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001439
anthony3dd0f622010-05-13 12:57:32 +00001440 /* set all kernel values within disk area to scale given */
1441 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
cristyc99304f2010-02-01 15:26:27 +00001442 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony602ab9b2010-01-05 08:06:50 +00001443 if ((u*u+v*v) <= limit)
anthony4fd27e22010-02-07 08:17:18 +00001444 kernel->positive_range += kernel->values[i] = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +00001445 else
1446 kernel->values[i] = nan;
anthony4fd27e22010-02-07 08:17:18 +00001447 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
anthony602ab9b2010-01-05 08:06:50 +00001448 break;
1449 }
1450 case PlusKernel:
1451 {
1452 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001453 kernel->width = kernel->height = 5; /* default radius 2 */
anthony602ab9b2010-01-05 08:06:50 +00001454 else
1455 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001456 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001457
1458 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1459 kernel->height*sizeof(double));
1460 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001461 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001462
anthony3dd0f622010-05-13 12:57:32 +00001463 /* set all kernel values along axises to given scale */
cristyc99304f2010-02-01 15:26:27 +00001464 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1465 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony4fd27e22010-02-07 08:17:18 +00001466 kernel->values[i] = (u == 0 || v == 0) ? args->sigma : nan;
1467 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1468 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
anthony602ab9b2010-01-05 08:06:50 +00001469 break;
1470 }
anthony3dd0f622010-05-13 12:57:32 +00001471 case CrossKernel:
1472 {
1473 if (args->rho < 1.0)
1474 kernel->width = kernel->height = 5; /* default radius 2 */
1475 else
1476 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
1477 kernel->x = kernel->y = (long) (kernel->width-1)/2;
1478
1479 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1480 kernel->height*sizeof(double));
1481 if (kernel->values == (double *) NULL)
1482 return(DestroyKernelInfo(kernel));
1483
1484 /* set all kernel values along axises to given scale */
1485 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1486 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1487 kernel->values[i] = (u == v || u == -v) ? args->sigma : nan;
1488 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1489 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
1490 break;
1491 }
1492 /* HitAndMiss Kernels */
anthonyc1061722010-05-14 06:23:49 +00001493 case RingKernel:
anthony3dd0f622010-05-13 12:57:32 +00001494 case PeaksKernel:
1495 {
1496 long
1497 limit1,
anthonyc1061722010-05-14 06:23:49 +00001498 limit2,
1499 scale;
anthony3dd0f622010-05-13 12:57:32 +00001500
1501 if (args->rho < args->sigma)
1502 {
1503 kernel->width = ((unsigned long)args->sigma)*2+1;
1504 limit1 = (long)args->rho*args->rho;
1505 limit2 = (long)args->sigma*args->sigma;
1506 }
1507 else
1508 {
1509 kernel->width = ((unsigned long)args->rho)*2+1;
1510 limit1 = (long)args->sigma*args->sigma;
1511 limit2 = (long)args->rho*args->rho;
1512 }
anthonyc1061722010-05-14 06:23:49 +00001513 if ( limit2 <= 0 )
1514 kernel->width = 7L, limit1 = 7L, limit2 = 11L;
1515
anthony3dd0f622010-05-13 12:57:32 +00001516 kernel->height = kernel->width;
1517 kernel->x = kernel->y = (long) (kernel->width-1)/2;
1518 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1519 kernel->height*sizeof(double));
1520 if (kernel->values == (double *) NULL)
1521 return(DestroyKernelInfo(kernel));
1522
anthonyc1061722010-05-14 06:23:49 +00001523 /* set a ring of points of 'scale' ( 0.0 for PeaksKernel ) */
cristye96405a2010-05-19 02:24:31 +00001524 scale = (long) (( type == PeaksKernel) ? 0.0 : args->xi);
anthony3dd0f622010-05-13 12:57:32 +00001525 for ( i=0, v= -kernel->y; v <= (long)kernel->y; v++)
1526 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1527 { long radius=u*u+v*v;
anthonyc1061722010-05-14 06:23:49 +00001528 if (limit1 < radius && radius <= limit2)
cristye96405a2010-05-19 02:24:31 +00001529 kernel->positive_range += kernel->values[i] = (double) scale;
anthony3dd0f622010-05-13 12:57:32 +00001530 else
1531 kernel->values[i] = nan;
1532 }
cristye96405a2010-05-19 02:24:31 +00001533 kernel->minimum = kernel->minimum = (double) scale;
anthonyc1061722010-05-14 06:23:49 +00001534 if ( type == PeaksKernel ) {
1535 /* set the central point in the middle */
1536 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1537 kernel->positive_range = 1.0;
1538 kernel->maximum = 1.0;
1539 }
anthony3dd0f622010-05-13 12:57:32 +00001540 break;
1541 }
anthony43c49252010-05-18 10:59:50 +00001542 case EdgesKernel:
1543 {
1544 kernel=ParseKernelArray("3: 0,0,0 -,1,- 1,1,1");
1545 if (kernel == (KernelInfo *) NULL)
1546 return(kernel);
1547 kernel->type = type;
1548 ExpandKernelInfo(kernel, 90.0); /* Create a list of 4 rotated kernels */
1549 break;
1550 }
anthony3dd0f622010-05-13 12:57:32 +00001551 case CornersKernel:
1552 {
anthony4f1dcb72010-05-14 08:43:10 +00001553 kernel=ParseKernelArray("3: 0,0,- 0,1,1 -,1,-");
anthony3dd0f622010-05-13 12:57:32 +00001554 if (kernel == (KernelInfo *) NULL)
1555 return(kernel);
1556 kernel->type = type;
1557 ExpandKernelInfo(kernel, 90.0); /* Create a list of 4 rotated kernels */
1558 break;
1559 }
anthony47f5d062010-05-23 07:47:50 +00001560 case RidgesKernel:
1561 {
anthonya648a302010-05-27 02:14:36 +00001562 kernel=ParseKernelArray("3: 0,1,0 ");
anthony47f5d062010-05-23 07:47:50 +00001563 if (kernel == (KernelInfo *) NULL)
1564 return(kernel);
1565 kernel->type = type;
1566 ExpandKernelInfo(kernel, 45.0); /* 4 rotated kernels (symmetrical) */
1567 break;
1568 }
anthony1d45eb92010-05-25 11:13:23 +00001569 case Ridges2Kernel:
1570 {
1571 KernelInfo
1572 *new_kernel;
1573 kernel=ParseKernelArray("4x1^:0,1,1,0");
1574 if (kernel == (KernelInfo *) NULL)
1575 return(kernel);
1576 kernel->type = type;
1577 ExpandKernelInfo(kernel, 90.0); /* 4 rotated kernels */
anthonya648a302010-05-27 02:14:36 +00001578#if 0
1579 /* 2 pixel diagonaly thick - 4 rotates - not needed? */
anthony1d45eb92010-05-25 11:13:23 +00001580 new_kernel=ParseKernelArray("4x4^:0,-,-,- -,1,-,- -,-,1,- -,-,-,0'");
1581 if (new_kernel == (KernelInfo *) NULL)
1582 return(DestroyKernelInfo(kernel));
1583 new_kernel->type = type;
1584 ExpandKernelInfo(new_kernel, 90.0); /* 4 rotated kernels */
1585 LastKernelInfo(kernel)->next = new_kernel;
anthonya648a302010-05-27 02:14:36 +00001586#endif
1587 /* kernels to find a stepped 'thick' line - 4 rotates * mirror */
1588 /* Unfortunatally we can not yet rotate a non-square kernel */
1589 /* But then we can't flip a non-symetrical kernel either */
1590 new_kernel=ParseKernelArray("4x3+1+1:0,1,1,- -,1,1,- -,1,1,0");
1591 if (new_kernel == (KernelInfo *) NULL)
1592 return(DestroyKernelInfo(kernel));
1593 new_kernel->type = type;
1594 LastKernelInfo(kernel)->next = new_kernel;
1595 new_kernel=ParseKernelArray("4x3+2+1^:0,1,1,- -,1,1,- -,1,1,0");
1596 if (new_kernel == (KernelInfo *) NULL)
1597 return(DestroyKernelInfo(kernel));
1598 new_kernel->type = type;
1599 LastKernelInfo(kernel)->next = new_kernel;
1600 new_kernel=ParseKernelArray("4x3+1+1^:-,1,1,0 -,1,1,- 0,1,1,-");
1601 if (new_kernel == (KernelInfo *) NULL)
1602 return(DestroyKernelInfo(kernel));
1603 new_kernel->type = type;
1604 LastKernelInfo(kernel)->next = new_kernel;
1605 new_kernel=ParseKernelArray("4x3+2+1^:-,1,1,0 -,1,1,- 0,1,1,-");
1606 if (new_kernel == (KernelInfo *) NULL)
1607 return(DestroyKernelInfo(kernel));
1608 new_kernel->type = type;
1609 LastKernelInfo(kernel)->next = new_kernel;
1610 new_kernel=ParseKernelArray("3x4+1+1^:0,-,- 1,1,1 1,1,1 -,-,0");
1611 if (new_kernel == (KernelInfo *) NULL)
1612 return(DestroyKernelInfo(kernel));
1613 new_kernel->type = type;
1614 LastKernelInfo(kernel)->next = new_kernel;
1615 new_kernel=ParseKernelArray("3x4+1+2^:0,-,- 1,1,1 1,1,1 -,-,0");
1616 if (new_kernel == (KernelInfo *) NULL)
1617 return(DestroyKernelInfo(kernel));
1618 new_kernel->type = type;
1619 LastKernelInfo(kernel)->next = new_kernel;
1620 new_kernel=ParseKernelArray("3x4+1+1^:-,-,0 1,1,1 1,1,1 0,-,-");
1621 if (new_kernel == (KernelInfo *) NULL)
1622 return(DestroyKernelInfo(kernel));
1623 new_kernel->type = type;
1624 LastKernelInfo(kernel)->next = new_kernel;
1625 new_kernel=ParseKernelArray("3x4+1+2^:-,-,0 1,1,1 1,1,1 0,-,-");
1626 if (new_kernel == (KernelInfo *) NULL)
1627 return(DestroyKernelInfo(kernel));
1628 new_kernel->type = type;
1629 LastKernelInfo(kernel)->next = new_kernel;
anthony1d45eb92010-05-25 11:13:23 +00001630 break;
1631 }
anthony3dd0f622010-05-13 12:57:32 +00001632 case LineEndsKernel:
1633 {
anthony43c49252010-05-18 10:59:50 +00001634 KernelInfo
1635 *new_kernel;
1636 kernel=ParseKernelArray("3: 0,0,0 0,1,0 -,1,-");
anthony3dd0f622010-05-13 12:57:32 +00001637 if (kernel == (KernelInfo *) NULL)
1638 return(kernel);
1639 kernel->type = type;
anthony43c49252010-05-18 10:59:50 +00001640 ExpandKernelInfo(kernel, 90.0);
1641 /* append second set of 4 kernels */
1642 new_kernel=ParseKernelArray("3: 0,0,0 0,1,0 0,0,1");
1643 if (new_kernel == (KernelInfo *) NULL)
1644 return(DestroyKernelInfo(kernel));
1645 new_kernel->type = type;
1646 ExpandKernelInfo(new_kernel, 90.0);
1647 LastKernelInfo(kernel)->next = new_kernel;
anthony3dd0f622010-05-13 12:57:32 +00001648 break;
1649 }
1650 case LineJunctionsKernel:
1651 {
1652 KernelInfo
1653 *new_kernel;
anthony3dd0f622010-05-13 12:57:32 +00001654 /* first set of 4 kernels */
anthony4f1dcb72010-05-14 08:43:10 +00001655 kernel=ParseKernelArray("3: -,1,- -,1,- 1,-,1");
anthony3dd0f622010-05-13 12:57:32 +00001656 if (kernel == (KernelInfo *) NULL)
1657 return(kernel);
1658 kernel->type = type;
anthony43c49252010-05-18 10:59:50 +00001659 ExpandKernelInfo(kernel, 45.0);
anthony3dd0f622010-05-13 12:57:32 +00001660 /* append second set of 4 kernels */
anthony4f1dcb72010-05-14 08:43:10 +00001661 new_kernel=ParseKernelArray("3: 1,-,- -,1,- 1,-,1");
anthony3dd0f622010-05-13 12:57:32 +00001662 if (new_kernel == (KernelInfo *) NULL)
1663 return(DestroyKernelInfo(kernel));
anthony43c49252010-05-18 10:59:50 +00001664 new_kernel->type = type;
anthony3dd0f622010-05-13 12:57:32 +00001665 ExpandKernelInfo(new_kernel, 90.0);
1666 LastKernelInfo(kernel)->next = new_kernel;
anthony4f1dcb72010-05-14 08:43:10 +00001667 break;
1668 }
anthony3dd0f622010-05-13 12:57:32 +00001669 case ConvexHullKernel:
1670 {
1671 KernelInfo
1672 *new_kernel;
anthony3dd0f622010-05-13 12:57:32 +00001673 /* first set of 4 kernels */
anthony4f1dcb72010-05-14 08:43:10 +00001674 kernel=ParseKernelArray("3: 1,1,- 1,0,- 1,-,0");
anthony3dd0f622010-05-13 12:57:32 +00001675 if (kernel == (KernelInfo *) NULL)
1676 return(kernel);
1677 kernel->type = type;
1678 ExpandKernelInfo(kernel, 90.0);
1679 /* append second set of 4 kernels */
anthony43c49252010-05-18 10:59:50 +00001680 new_kernel=ParseKernelArray("3: 1,1,1 1,0,0 -,-,0");
anthony3dd0f622010-05-13 12:57:32 +00001681 if (new_kernel == (KernelInfo *) NULL)
1682 return(DestroyKernelInfo(kernel));
anthony43c49252010-05-18 10:59:50 +00001683 new_kernel->type = type;
anthony3dd0f622010-05-13 12:57:32 +00001684 ExpandKernelInfo(new_kernel, 90.0);
1685 LastKernelInfo(kernel)->next = new_kernel;
1686 break;
1687 }
anthony47f5d062010-05-23 07:47:50 +00001688 case SkeletonKernel:
anthonya648a302010-05-27 02:14:36 +00001689 { /* what is the best form for skeletonization by thinning? */
anthony47f5d062010-05-23 07:47:50 +00001690#if 0
1691# if 0
1692 kernel=AcquireKernelInfo("Corners;Edges");
1693# else
1694 kernel=AcquireKernelInfo("Edges;Corners");
1695# endif
1696#else
anthony43c49252010-05-18 10:59:50 +00001697 kernel=ParseKernelArray("3: 0,0,- 0,1,1 -,1,1");
anthony3dd0f622010-05-13 12:57:32 +00001698 if (kernel == (KernelInfo *) NULL)
1699 return(kernel);
1700 kernel->type = type;
anthony43c49252010-05-18 10:59:50 +00001701 ExpandKernelInfo(kernel, 45);
1702 break;
anthony47f5d062010-05-23 07:47:50 +00001703#endif
anthony3dd0f622010-05-13 12:57:32 +00001704 break;
1705 }
anthonya648a302010-05-27 02:14:36 +00001706 case MatKernel: /* experimental - MAT from a Distance Gradient */
1707 {
1708 KernelInfo
1709 *new_kernel;
1710 /* Ridge Kernel but without the diagonal */
1711 kernel=ParseKernelArray("3x1: 0,1,0");
1712 if (kernel == (KernelInfo *) NULL)
1713 return(kernel);
1714 kernel->type = RidgesKernel;
1715 ExpandKernelInfo(kernel, 90.0); /* 2 rotated kernels (symmetrical) */
1716 /* Plus the 2 pixel ridges kernel - no diagonal */
1717 new_kernel=AcquireKernelBuiltIn(Ridges2Kernel,args);
1718 if (new_kernel == (KernelInfo *) NULL)
1719 return(kernel);
1720 LastKernelInfo(kernel)->next = new_kernel;
1721 break;
1722 }
anthony602ab9b2010-01-05 08:06:50 +00001723 /* Distance Measuring Kernels */
1724 case ChebyshevKernel:
1725 {
anthony602ab9b2010-01-05 08:06:50 +00001726 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001727 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001728 else
1729 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001730 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001731
1732 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1733 kernel->height*sizeof(double));
1734 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001735 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001736
cristyc99304f2010-02-01 15:26:27 +00001737 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1738 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1739 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001740 args->sigma*((labs(u)>labs(v)) ? labs(u) : labs(v)) );
cristyc99304f2010-02-01 15:26:27 +00001741 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001742 break;
1743 }
1744 case ManhattenKernel:
1745 {
anthony602ab9b2010-01-05 08:06:50 +00001746 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001747 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001748 else
1749 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001750 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001751
1752 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1753 kernel->height*sizeof(double));
1754 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001755 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001756
cristyc99304f2010-02-01 15:26:27 +00001757 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1758 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1759 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001760 args->sigma*(labs(u)+labs(v)) );
cristyc99304f2010-02-01 15:26:27 +00001761 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001762 break;
1763 }
1764 case EuclideanKernel:
1765 {
anthony602ab9b2010-01-05 08:06:50 +00001766 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001767 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001768 else
1769 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001770 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001771
1772 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1773 kernel->height*sizeof(double));
1774 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001775 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001776
cristyc99304f2010-02-01 15:26:27 +00001777 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1778 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1779 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001780 args->sigma*sqrt((double)(u*u+v*v)) );
cristyc99304f2010-02-01 15:26:27 +00001781 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001782 break;
1783 }
anthony46a369d2010-05-19 02:41:48 +00001784 case UnityKernel:
anthony602ab9b2010-01-05 08:06:50 +00001785 default:
anthonyc1061722010-05-14 06:23:49 +00001786 {
anthony46a369d2010-05-19 02:41:48 +00001787 /* Unity or No-Op Kernel - 3x3 with 1 in center */
1788 kernel=ParseKernelArray("3:0,0,0,0,1,0,0,0,0");
anthonyc1061722010-05-14 06:23:49 +00001789 if (kernel == (KernelInfo *) NULL)
1790 return(kernel);
anthony46a369d2010-05-19 02:41:48 +00001791 kernel->type = ( type == UnityKernel ) ? UnityKernel : UndefinedKernel;
anthonyc1061722010-05-14 06:23:49 +00001792 break;
1793 }
anthony602ab9b2010-01-05 08:06:50 +00001794 break;
1795 }
1796
1797 return(kernel);
1798}
anthonyc94cdb02010-01-06 08:15:29 +00001799
anthony602ab9b2010-01-05 08:06:50 +00001800/*
1801%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1802% %
1803% %
1804% %
cristy6771f1e2010-03-05 19:43:39 +00001805% C l o n e K e r n e l I n f o %
anthony4fd27e22010-02-07 08:17:18 +00001806% %
1807% %
1808% %
1809%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1810%
anthony1b2bc0a2010-05-12 05:25:22 +00001811% CloneKernelInfo() creates a new clone of the given Kernel List so that its
1812% can be modified without effecting the original. The cloned kernel should
1813% be destroyed using DestoryKernelInfo() when no longer needed.
anthony7a01dcf2010-05-11 12:25:52 +00001814%
cristye6365592010-04-02 17:31:23 +00001815% The format of the CloneKernelInfo method is:
anthony4fd27e22010-02-07 08:17:18 +00001816%
anthony930be612010-02-08 04:26:15 +00001817% KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +00001818%
1819% A description of each parameter follows:
1820%
1821% o kernel: the Morphology/Convolution kernel to be cloned
1822%
1823*/
cristyef656912010-03-05 19:54:59 +00001824MagickExport KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +00001825{
1826 register long
1827 i;
1828
cristy19eb6412010-04-23 14:42:29 +00001829 KernelInfo
anthony7a01dcf2010-05-11 12:25:52 +00001830 *new_kernel;
anthony4fd27e22010-02-07 08:17:18 +00001831
1832 assert(kernel != (KernelInfo *) NULL);
anthony7a01dcf2010-05-11 12:25:52 +00001833 new_kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
1834 if (new_kernel == (KernelInfo *) NULL)
1835 return(new_kernel);
1836 *new_kernel=(*kernel); /* copy values in structure */
anthony7a01dcf2010-05-11 12:25:52 +00001837
1838 /* replace the values with a copy of the values */
1839 new_kernel->values=(double *) AcquireQuantumMemory(kernel->width,
cristy19eb6412010-04-23 14:42:29 +00001840 kernel->height*sizeof(double));
anthony7a01dcf2010-05-11 12:25:52 +00001841 if (new_kernel->values == (double *) NULL)
1842 return(DestroyKernelInfo(new_kernel));
anthony4fd27e22010-02-07 08:17:18 +00001843 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthony7a01dcf2010-05-11 12:25:52 +00001844 new_kernel->values[i]=kernel->values[i];
anthony1b2bc0a2010-05-12 05:25:22 +00001845
1846 /* Also clone the next kernel in the kernel list */
1847 if ( kernel->next != (KernelInfo *) NULL ) {
1848 new_kernel->next = CloneKernelInfo(kernel->next);
1849 if ( new_kernel->next == (KernelInfo *) NULL )
1850 return(DestroyKernelInfo(new_kernel));
1851 }
1852
anthony7a01dcf2010-05-11 12:25:52 +00001853 return(new_kernel);
anthony4fd27e22010-02-07 08:17:18 +00001854}
1855
1856/*
1857%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1858% %
1859% %
1860% %
anthony83ba99b2010-01-24 08:48:15 +00001861% D e s t r o y K e r n e l I n f o %
anthony602ab9b2010-01-05 08:06:50 +00001862% %
1863% %
1864% %
1865%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1866%
anthony83ba99b2010-01-24 08:48:15 +00001867% DestroyKernelInfo() frees the memory used by a Convolution/Morphology
1868% kernel.
anthony602ab9b2010-01-05 08:06:50 +00001869%
anthony83ba99b2010-01-24 08:48:15 +00001870% The format of the DestroyKernelInfo method is:
anthony602ab9b2010-01-05 08:06:50 +00001871%
anthony83ba99b2010-01-24 08:48:15 +00001872% KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +00001873%
1874% A description of each parameter follows:
1875%
1876% o kernel: the Morphology/Convolution kernel to be destroyed
1877%
1878*/
1879
anthony83ba99b2010-01-24 08:48:15 +00001880MagickExport KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +00001881{
cristy2be15382010-01-21 02:38:03 +00001882 assert(kernel != (KernelInfo *) NULL);
anthony4fd27e22010-02-07 08:17:18 +00001883
anthony7a01dcf2010-05-11 12:25:52 +00001884 if ( kernel->next != (KernelInfo *) NULL )
1885 kernel->next = DestroyKernelInfo(kernel->next);
1886
1887 kernel->values = (double *)RelinquishMagickMemory(kernel->values);
1888 kernel = (KernelInfo *) RelinquishMagickMemory(kernel);
anthony602ab9b2010-01-05 08:06:50 +00001889 return(kernel);
1890}
anthonyc94cdb02010-01-06 08:15:29 +00001891
1892/*
1893%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1894% %
1895% %
1896% %
anthony3c10fc82010-05-13 02:40:51 +00001897% E x p a n d K e r n e l I n f o %
1898% %
1899% %
1900% %
1901%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1902%
1903% ExpandKernelInfo() takes a single kernel, and expands it into a list
1904% of kernels each incrementally rotated the angle given.
1905%
1906% WARNING: 45 degree rotations only works for 3x3 kernels.
1907% While 90 degree roatations only works for linear and square kernels
1908%
1909% The format of the RotateKernelInfo method is:
1910%
1911% void ExpandKernelInfo(KernelInfo *kernel, double angle)
1912%
1913% A description of each parameter follows:
1914%
1915% o kernel: the Morphology/Convolution kernel
1916%
1917% o angle: angle to rotate in degrees
1918%
1919% This function is only internel to this module, as it is not finalized,
1920% especially with regard to non-orthogonal angles, and rotation of larger
1921% 2D kernels.
1922*/
anthony47f5d062010-05-23 07:47:50 +00001923
1924/* Internal Routine - Return true if two kernels are the same */
1925static MagickBooleanType SameKernelInfo(const KernelInfo *kernel1,
1926 const KernelInfo *kernel2)
1927{
1928 register unsigned long
1929 i;
anthony1d45eb92010-05-25 11:13:23 +00001930
1931 /* check size and origin location */
1932 if ( kernel1->width != kernel2->width
1933 || kernel1->height != kernel2->height
1934 || kernel1->x != kernel2->x
1935 || kernel1->y != kernel2->y )
anthony47f5d062010-05-23 07:47:50 +00001936 return MagickFalse;
anthony1d45eb92010-05-25 11:13:23 +00001937
1938 /* check actual kernel values */
anthony47f5d062010-05-23 07:47:50 +00001939 for (i=0; i < (kernel1->width*kernel1->height); i++) {
anthony1d45eb92010-05-25 11:13:23 +00001940 /* Test for Nan equivelence */
anthony47f5d062010-05-23 07:47:50 +00001941 if ( IsNan(kernel1->values[i]) && !IsNan(kernel2->values[i]) )
1942 return MagickFalse;
1943 if ( IsNan(kernel2->values[i]) && !IsNan(kernel1->values[i]) )
1944 return MagickFalse;
anthony1d45eb92010-05-25 11:13:23 +00001945 /* Test actual values are equivelent */
anthony47f5d062010-05-23 07:47:50 +00001946 if ( fabs(kernel1->values[i] - kernel2->values[i]) > MagickEpsilon )
1947 return MagickFalse;
1948 }
anthony1d45eb92010-05-25 11:13:23 +00001949
anthony47f5d062010-05-23 07:47:50 +00001950 return MagickTrue;
1951}
1952
1953static void ExpandKernelInfo(KernelInfo *kernel, const double angle)
anthony3c10fc82010-05-13 02:40:51 +00001954{
1955 KernelInfo
cristy84d9b552010-05-24 18:23:54 +00001956 *clone,
anthony3c10fc82010-05-13 02:40:51 +00001957 *last;
cristya9a61ad2010-05-13 12:47:41 +00001958
anthony3c10fc82010-05-13 02:40:51 +00001959 last = kernel;
anthony47f5d062010-05-23 07:47:50 +00001960 while(1) {
cristy84d9b552010-05-24 18:23:54 +00001961 clone = CloneKernelInfo(last);
1962 RotateKernelInfo(clone, angle);
1963 if ( SameKernelInfo(kernel, clone) == MagickTrue )
anthony47f5d062010-05-23 07:47:50 +00001964 break;
cristy84d9b552010-05-24 18:23:54 +00001965 last->next = clone;
1966 last = clone;
anthony3c10fc82010-05-13 02:40:51 +00001967 }
cristy84d9b552010-05-24 18:23:54 +00001968 clone = DestroyKernelInfo(clone); /* This was the same as the first - junk */
anthony47f5d062010-05-23 07:47:50 +00001969 return;
anthony3c10fc82010-05-13 02:40:51 +00001970}
1971
1972/*
1973%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1974% %
1975% %
1976% %
anthony46a369d2010-05-19 02:41:48 +00001977+ C a l c M e t a K e r n a l I n f o %
1978% %
1979% %
1980% %
1981%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1982%
1983% CalcKernelMetaData() recalculate the KernelInfo meta-data of this kernel only,
1984% using the kernel values. This should only ne used if it is not posible to
1985% calculate that meta-data in some easier way.
1986%
1987% It is important that the meta-data is correct before ScaleKernelInfo() is
1988% used to perform kernel normalization.
1989%
1990% The format of the CalcKernelMetaData method is:
1991%
1992% void CalcKernelMetaData(KernelInfo *kernel, const double scale )
1993%
1994% A description of each parameter follows:
1995%
1996% o kernel: the Morphology/Convolution kernel to modify
1997%
1998% WARNING: Minimum and Maximum values are assumed to include zero, even if
1999% zero is not part of the kernel (as in Gaussian Derived kernels). This
2000% however is not true for flat-shaped morphological kernels.
2001%
2002% WARNING: Only the specific kernel pointed to is modified, not a list of
2003% multiple kernels.
2004%
2005% This is an internal function and not expected to be useful outside this
2006% module. This could change however.
2007*/
2008static void CalcKernelMetaData(KernelInfo *kernel)
2009{
2010 register unsigned long
2011 i;
2012
2013 kernel->minimum = kernel->maximum = 0.0;
2014 kernel->negative_range = kernel->positive_range = 0.0;
2015 for (i=0; i < (kernel->width*kernel->height); i++)
2016 {
2017 if ( fabs(kernel->values[i]) < MagickEpsilon )
2018 kernel->values[i] = 0.0;
2019 ( kernel->values[i] < 0)
2020 ? ( kernel->negative_range += kernel->values[i] )
2021 : ( kernel->positive_range += kernel->values[i] );
2022 Minimize(kernel->minimum, kernel->values[i]);
2023 Maximize(kernel->maximum, kernel->values[i]);
2024 }
2025
2026 return;
2027}
2028
2029/*
2030%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2031% %
2032% %
2033% %
anthony9eb4f742010-05-18 02:45:54 +00002034% M o r p h o l o g y A p p l y %
anthony602ab9b2010-01-05 08:06:50 +00002035% %
2036% %
2037% %
2038%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2039%
anthony9eb4f742010-05-18 02:45:54 +00002040% MorphologyApply() applies a morphological method, multiple times using
2041% a list of multiple kernels.
anthony602ab9b2010-01-05 08:06:50 +00002042%
anthony9eb4f742010-05-18 02:45:54 +00002043% It is basically equivelent to as MorphologyImageChannel() (see below) but
2044% without user controls, that that function extracts and applies to kernels
2045% and morphology methods.
2046%
2047% More specifically kernels are not normalized/scaled/blended by the
2048% 'convolve:scale' Image Artifact (-set setting), and the convolve bias
2049% (-bias setting or image->bias) is passed directly to this function,
2050% and not extracted from an image.
anthony602ab9b2010-01-05 08:06:50 +00002051%
anthony47f5d062010-05-23 07:47:50 +00002052% The format of the MorphologyApply method is:
anthony602ab9b2010-01-05 08:06:50 +00002053%
anthony9eb4f742010-05-18 02:45:54 +00002054% Image *MorphologyApply(const Image *image,MorphologyMethod method,
anthony47f5d062010-05-23 07:47:50 +00002055% const long iterations,const KernelInfo *kernel,
2056% const CompositeMethod compose, const double bias,
anthony9eb4f742010-05-18 02:45:54 +00002057% ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00002058%
2059% A description of each parameter follows:
2060%
2061% o image: the image.
2062%
2063% o method: the morphology method to be applied.
2064%
2065% o iterations: apply the operation this many times (or no change).
2066% A value of -1 means loop until no change found.
2067% How this is applied may depend on the morphology method.
2068% Typically this is a value of 1.
2069%
2070% o channel: the channel type.
2071%
2072% o kernel: An array of double representing the morphology kernel.
anthony29188a82010-01-22 10:12:34 +00002073% Warning: kernel may be normalized for the Convolve method.
anthony602ab9b2010-01-05 08:06:50 +00002074%
anthony47f5d062010-05-23 07:47:50 +00002075% o compose: How to handle or merge multi-kernel results.
2076% If 'Undefined' use default of the Morphology method.
2077% If 'No' force image to be re-iterated by each kernel.
2078% Otherwise merge the results using the mathematical compose
2079% method given.
2080%
2081% o bias: Convolution Output Bias.
anthony9eb4f742010-05-18 02:45:54 +00002082%
anthony602ab9b2010-01-05 08:06:50 +00002083% o exception: return any errors or warnings in this structure.
2084%
anthony602ab9b2010-01-05 08:06:50 +00002085*/
2086
anthony930be612010-02-08 04:26:15 +00002087
anthony9eb4f742010-05-18 02:45:54 +00002088/* Apply a Morphology Primative to an image using the given kernel.
2089** Two pre-created images must be provided, no image is created.
2090** Returning the number of pixels that changed.
2091*/
anthony46a369d2010-05-19 02:41:48 +00002092static unsigned long MorphologyPrimitive(const Image *image, Image
anthony602ab9b2010-01-05 08:06:50 +00002093 *result_image, const MorphologyMethod method, const ChannelType channel,
anthony9eb4f742010-05-18 02:45:54 +00002094 const KernelInfo *kernel,const double bias,ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00002095{
cristy2be15382010-01-21 02:38:03 +00002096#define MorphologyTag "Morphology/Image"
anthony602ab9b2010-01-05 08:06:50 +00002097
2098 long
cristy150989e2010-02-01 14:59:39 +00002099 progress,
anthony29188a82010-01-22 10:12:34 +00002100 y, offx, offy,
anthony602ab9b2010-01-05 08:06:50 +00002101 changed;
2102
2103 MagickBooleanType
2104 status;
2105
anthony602ab9b2010-01-05 08:06:50 +00002106 CacheView
2107 *p_view,
2108 *q_view;
2109
anthony602ab9b2010-01-05 08:06:50 +00002110 status=MagickTrue;
2111 changed=0;
2112 progress=0;
2113
anthony602ab9b2010-01-05 08:06:50 +00002114 p_view=AcquireCacheView(image);
2115 q_view=AcquireCacheView(result_image);
anthony29188a82010-01-22 10:12:34 +00002116
anthonycc6c8362010-01-25 04:14:01 +00002117 /* Some methods (including convolve) needs use a reflected kernel.
anthony9eb4f742010-05-18 02:45:54 +00002118 * Adjust 'origin' offsets to loop though kernel as a reflection.
anthony29188a82010-01-22 10:12:34 +00002119 */
cristyc99304f2010-02-01 15:26:27 +00002120 offx = kernel->x;
2121 offy = kernel->y;
anthony29188a82010-01-22 10:12:34 +00002122 switch(method) {
anthony930be612010-02-08 04:26:15 +00002123 case ConvolveMorphology:
2124 case DilateMorphology:
2125 case DilateIntensityMorphology:
2126 case DistanceMorphology:
anthony5ef8e942010-05-11 06:51:12 +00002127 /* kernel needs to used with reflection about origin */
cristy150989e2010-02-01 14:59:39 +00002128 offx = (long) kernel->width-offx-1;
2129 offy = (long) kernel->height-offy-1;
anthony29188a82010-01-22 10:12:34 +00002130 break;
anthony5ef8e942010-05-11 06:51:12 +00002131 case ErodeMorphology:
2132 case ErodeIntensityMorphology:
2133 case HitAndMissMorphology:
2134 case ThinningMorphology:
2135 case ThickenMorphology:
2136 /* kernel is user as is, without reflection */
2137 break;
anthony930be612010-02-08 04:26:15 +00002138 default:
anthony9eb4f742010-05-18 02:45:54 +00002139 assert("Not a Primitive Morphology Method" != (char *) NULL);
anthony930be612010-02-08 04:26:15 +00002140 break;
anthony29188a82010-01-22 10:12:34 +00002141 }
2142
anthony602ab9b2010-01-05 08:06:50 +00002143#if defined(MAGICKCORE_OPENMP_SUPPORT)
2144 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2145#endif
cristy150989e2010-02-01 14:59:39 +00002146 for (y=0; y < (long) image->rows; y++)
anthony602ab9b2010-01-05 08:06:50 +00002147 {
2148 MagickBooleanType
2149 sync;
2150
2151 register const PixelPacket
2152 *restrict p;
2153
2154 register const IndexPacket
2155 *restrict p_indexes;
2156
2157 register PixelPacket
2158 *restrict q;
2159
2160 register IndexPacket
2161 *restrict q_indexes;
2162
cristy150989e2010-02-01 14:59:39 +00002163 register long
anthony602ab9b2010-01-05 08:06:50 +00002164 x;
2165
anthony29188a82010-01-22 10:12:34 +00002166 unsigned long
anthony602ab9b2010-01-05 08:06:50 +00002167 r;
2168
2169 if (status == MagickFalse)
2170 continue;
anthony29188a82010-01-22 10:12:34 +00002171 p=GetCacheViewVirtualPixels(p_view, -offx, y-offy,
2172 image->columns+kernel->width, kernel->height, exception);
anthony602ab9b2010-01-05 08:06:50 +00002173 q=GetCacheViewAuthenticPixels(q_view,0,y,result_image->columns,1,
2174 exception);
2175 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2176 {
2177 status=MagickFalse;
2178 continue;
2179 }
2180 p_indexes=GetCacheViewVirtualIndexQueue(p_view);
2181 q_indexes=GetCacheViewAuthenticIndexQueue(q_view);
anthony29188a82010-01-22 10:12:34 +00002182 r = (image->columns+kernel->width)*offy+offx; /* constant */
2183
cristy150989e2010-02-01 14:59:39 +00002184 for (x=0; x < (long) image->columns; x++)
anthony602ab9b2010-01-05 08:06:50 +00002185 {
cristy150989e2010-02-01 14:59:39 +00002186 long
anthony602ab9b2010-01-05 08:06:50 +00002187 v;
2188
cristy150989e2010-02-01 14:59:39 +00002189 register long
anthony602ab9b2010-01-05 08:06:50 +00002190 u;
2191
2192 register const double
2193 *restrict k;
2194
2195 register const PixelPacket
2196 *restrict k_pixels;
2197
2198 register const IndexPacket
2199 *restrict k_indexes;
2200
2201 MagickPixelPacket
anthony5ef8e942010-05-11 06:51:12 +00002202 result,
2203 min,
2204 max;
anthony602ab9b2010-01-05 08:06:50 +00002205
anthony29188a82010-01-22 10:12:34 +00002206 /* Copy input to ouput image for unused channels
anthony83ba99b2010-01-24 08:48:15 +00002207 * This removes need for 'cloning' a new image every iteration
anthony29188a82010-01-22 10:12:34 +00002208 */
anthony602ab9b2010-01-05 08:06:50 +00002209 *q = p[r];
2210 if (image->colorspace == CMYKColorspace)
2211 q_indexes[x] = p_indexes[r];
2212
anthony5ef8e942010-05-11 06:51:12 +00002213 /* Defaults */
2214 min.red =
2215 min.green =
2216 min.blue =
2217 min.opacity =
2218 min.index = (MagickRealType) QuantumRange;
2219 max.red =
2220 max.green =
2221 max.blue =
2222 max.opacity =
2223 max.index = (MagickRealType) 0;
anthony9eb4f742010-05-18 02:45:54 +00002224 /* default result is the original pixel value */
anthony5ef8e942010-05-11 06:51:12 +00002225 result.red = (MagickRealType) p[r].red;
2226 result.green = (MagickRealType) p[r].green;
2227 result.blue = (MagickRealType) p[r].blue;
2228 result.opacity = QuantumRange - (MagickRealType) p[r].opacity;
cristye96405a2010-05-19 02:24:31 +00002229 result.index = 0.0;
anthony5ef8e942010-05-11 06:51:12 +00002230 if ( image->colorspace == CMYKColorspace)
2231 result.index = (MagickRealType) p_indexes[r];
2232
anthony602ab9b2010-01-05 08:06:50 +00002233 switch (method) {
2234 case ConvolveMorphology:
anthony9eb4f742010-05-18 02:45:54 +00002235 /* Set the user defined bias of the weighted average output */
2236 result.red =
2237 result.green =
2238 result.blue =
2239 result.opacity =
2240 result.index = bias;
anthony930be612010-02-08 04:26:15 +00002241 break;
anthony4fd27e22010-02-07 08:17:18 +00002242 case DilateIntensityMorphology:
2243 case ErodeIntensityMorphology:
anthony9eb4f742010-05-18 02:45:54 +00002244 /* use a boolean flag indicating when first match found */
2245 result.red = 0.0; /* result is not used otherwise */
anthony4fd27e22010-02-07 08:17:18 +00002246 break;
anthony602ab9b2010-01-05 08:06:50 +00002247 default:
anthony602ab9b2010-01-05 08:06:50 +00002248 break;
2249 }
2250
2251 switch ( method ) {
2252 case ConvolveMorphology:
anthony930be612010-02-08 04:26:15 +00002253 /* Weighted Average of pixels using reflected kernel
2254 **
2255 ** NOTE for correct working of this operation for asymetrical
2256 ** kernels, the kernel needs to be applied in its reflected form.
2257 ** That is its values needs to be reversed.
2258 **
2259 ** Correlation is actually the same as this but without reflecting
2260 ** the kernel, and thus 'lower-level' that Convolution. However
2261 ** as Convolution is the more common method used, and it does not
2262 ** really cost us much in terms of processing to use a reflected
anthony5ef8e942010-05-11 06:51:12 +00002263 ** kernel, so it is Convolution that is implemented.
anthony930be612010-02-08 04:26:15 +00002264 **
2265 ** Correlation will have its kernel reflected before calling
2266 ** this function to do a Convolve.
2267 **
2268 ** For more details of Correlation vs Convolution see
2269 ** http://www.cs.umd.edu/~djacobs/CMSC426/Convolution.pdf
2270 */
anthony5ef8e942010-05-11 06:51:12 +00002271 if (((channel & SyncChannels) != 0 ) &&
2272 (image->matte == MagickTrue))
2273 { /* Channel has a 'Sync' Flag, and Alpha Channel enabled.
2274 ** Weight the color channels with Alpha Channel so that
2275 ** transparent pixels are not part of the results.
2276 */
anthony602ab9b2010-01-05 08:06:50 +00002277 MagickRealType
anthony5ef8e942010-05-11 06:51:12 +00002278 alpha, /* color channel weighting : kernel*alpha */
2279 gamma; /* divisor, sum of weighting values */
anthony602ab9b2010-01-05 08:06:50 +00002280
2281 gamma=0.0;
anthony29188a82010-01-22 10:12:34 +00002282 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00002283 k_pixels = p;
2284 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002285 for (v=0; v < (long) kernel->height; v++) {
2286 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00002287 if ( IsNan(*k) ) continue;
2288 alpha=(*k)*(QuantumScale*(QuantumRange-
2289 k_pixels[u].opacity));
2290 gamma += alpha;
2291 result.red += alpha*k_pixels[u].red;
2292 result.green += alpha*k_pixels[u].green;
2293 result.blue += alpha*k_pixels[u].blue;
anthony83ba99b2010-01-24 08:48:15 +00002294 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00002295 if ( image->colorspace == CMYKColorspace)
2296 result.index += alpha*k_indexes[u];
2297 }
2298 k_pixels += image->columns+kernel->width;
2299 k_indexes += image->columns+kernel->width;
2300 }
2301 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
anthony83ba99b2010-01-24 08:48:15 +00002302 result.red *= gamma;
2303 result.green *= gamma;
2304 result.blue *= gamma;
2305 result.opacity *= gamma;
2306 result.index *= gamma;
anthony602ab9b2010-01-05 08:06:50 +00002307 }
anthony5ef8e942010-05-11 06:51:12 +00002308 else
2309 {
2310 /* No 'Sync' flag, or no Alpha involved.
2311 ** Convolution is simple individual channel weigthed sum.
2312 */
2313 k = &kernel->values[ kernel->width*kernel->height-1 ];
2314 k_pixels = p;
2315 k_indexes = p_indexes;
2316 for (v=0; v < (long) kernel->height; v++) {
2317 for (u=0; u < (long) kernel->width; u++, k--) {
2318 if ( IsNan(*k) ) continue;
2319 result.red += (*k)*k_pixels[u].red;
2320 result.green += (*k)*k_pixels[u].green;
2321 result.blue += (*k)*k_pixels[u].blue;
2322 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
2323 if ( image->colorspace == CMYKColorspace)
2324 result.index += (*k)*k_indexes[u];
2325 }
2326 k_pixels += image->columns+kernel->width;
2327 k_indexes += image->columns+kernel->width;
2328 }
2329 }
anthony602ab9b2010-01-05 08:06:50 +00002330 break;
2331
anthony4fd27e22010-02-07 08:17:18 +00002332 case ErodeMorphology:
anthony5ef8e942010-05-11 06:51:12 +00002333 /* Minimum Value within kernel neighbourhood
anthony930be612010-02-08 04:26:15 +00002334 **
2335 ** NOTE that the kernel is not reflected for this operation!
2336 **
2337 ** NOTE: in normal Greyscale Morphology, the kernel value should
2338 ** be added to the real value, this is currently not done, due to
2339 ** the nature of the boolean kernels being used.
2340 */
anthony4fd27e22010-02-07 08:17:18 +00002341 k = kernel->values;
2342 k_pixels = p;
2343 k_indexes = p_indexes;
2344 for (v=0; v < (long) kernel->height; v++) {
2345 for (u=0; u < (long) kernel->width; u++, k++) {
2346 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony5ef8e942010-05-11 06:51:12 +00002347 Minimize(min.red, (double) k_pixels[u].red);
2348 Minimize(min.green, (double) k_pixels[u].green);
2349 Minimize(min.blue, (double) k_pixels[u].blue);
2350 Minimize(min.opacity,
anthonyd37a5cb2010-05-07 06:37:03 +00002351 QuantumRange-(double) k_pixels[u].opacity);
anthony4fd27e22010-02-07 08:17:18 +00002352 if ( image->colorspace == CMYKColorspace)
anthony5ef8e942010-05-11 06:51:12 +00002353 Minimize(min.index, (double) k_indexes[u]);
anthony4fd27e22010-02-07 08:17:18 +00002354 }
2355 k_pixels += image->columns+kernel->width;
2356 k_indexes += image->columns+kernel->width;
2357 }
2358 break;
2359
anthony5ef8e942010-05-11 06:51:12 +00002360
anthony83ba99b2010-01-24 08:48:15 +00002361 case DilateMorphology:
anthony5ef8e942010-05-11 06:51:12 +00002362 /* Maximum Value within kernel neighbourhood
anthony930be612010-02-08 04:26:15 +00002363 **
2364 ** NOTE for correct working of this operation for asymetrical
2365 ** kernels, the kernel needs to be applied in its reflected form.
2366 ** That is its values needs to be reversed.
2367 **
2368 ** NOTE: in normal Greyscale Morphology, the kernel value should
2369 ** be added to the real value, this is currently not done, due to
2370 ** the nature of the boolean kernels being used.
2371 **
2372 */
anthony29188a82010-01-22 10:12:34 +00002373 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00002374 k_pixels = p;
2375 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002376 for (v=0; v < (long) kernel->height; v++) {
2377 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00002378 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony5ef8e942010-05-11 06:51:12 +00002379 Maximize(max.red, (double) k_pixels[u].red);
2380 Maximize(max.green, (double) k_pixels[u].green);
2381 Maximize(max.blue, (double) k_pixels[u].blue);
2382 Maximize(max.opacity,
anthonyd37a5cb2010-05-07 06:37:03 +00002383 QuantumRange-(double) k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00002384 if ( image->colorspace == CMYKColorspace)
anthony5ef8e942010-05-11 06:51:12 +00002385 Maximize(max.index, (double) k_indexes[u]);
anthony602ab9b2010-01-05 08:06:50 +00002386 }
2387 k_pixels += image->columns+kernel->width;
2388 k_indexes += image->columns+kernel->width;
2389 }
anthony602ab9b2010-01-05 08:06:50 +00002390 break;
2391
anthony5ef8e942010-05-11 06:51:12 +00002392 case HitAndMissMorphology:
2393 case ThinningMorphology:
2394 case ThickenMorphology:
2395 /* Minimum of Foreground Pixel minus Maxumum of Background Pixels
2396 **
2397 ** NOTE that the kernel is not reflected for this operation,
2398 ** and consists of both foreground and background pixel
2399 ** neighbourhoods, 0.0 for background, and 1.0 for foreground
2400 ** with either Nan or 0.5 values for don't care.
2401 **
2402 ** Note that this can produce negative results, though really
2403 ** only a positive match has any real value.
2404 */
2405 k = kernel->values;
2406 k_pixels = p;
2407 k_indexes = p_indexes;
2408 for (v=0; v < (long) kernel->height; v++) {
2409 for (u=0; u < (long) kernel->width; u++, k++) {
2410 if ( IsNan(*k) ) continue;
2411 if ( (*k) > 0.7 )
2412 { /* minimim of foreground pixels */
2413 Minimize(min.red, (double) k_pixels[u].red);
2414 Minimize(min.green, (double) k_pixels[u].green);
2415 Minimize(min.blue, (double) k_pixels[u].blue);
2416 Minimize(min.opacity,
2417 QuantumRange-(double) k_pixels[u].opacity);
2418 if ( image->colorspace == CMYKColorspace)
2419 Minimize(min.index, (double) k_indexes[u]);
2420 }
2421 else if ( (*k) < 0.3 )
2422 { /* maximum of background pixels */
2423 Maximize(max.red, (double) k_pixels[u].red);
2424 Maximize(max.green, (double) k_pixels[u].green);
2425 Maximize(max.blue, (double) k_pixels[u].blue);
2426 Maximize(max.opacity,
2427 QuantumRange-(double) k_pixels[u].opacity);
2428 if ( image->colorspace == CMYKColorspace)
2429 Maximize(max.index, (double) k_indexes[u]);
2430 }
2431 }
2432 k_pixels += image->columns+kernel->width;
2433 k_indexes += image->columns+kernel->width;
2434 }
2435 /* Pattern Match only if min fg larger than min bg pixels */
2436 min.red -= max.red; Maximize( min.red, 0.0 );
2437 min.green -= max.green; Maximize( min.green, 0.0 );
2438 min.blue -= max.blue; Maximize( min.blue, 0.0 );
2439 min.opacity -= max.opacity; Maximize( min.opacity, 0.0 );
2440 min.index -= max.index; Maximize( min.index, 0.0 );
2441 break;
2442
anthony4fd27e22010-02-07 08:17:18 +00002443 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00002444 /* Select Pixel with Minimum Intensity within kernel neighbourhood
2445 **
2446 ** WARNING: the intensity test fails for CMYK and does not
2447 ** take into account the moderating effect of teh alpha channel
2448 ** on the intensity.
2449 **
2450 ** NOTE that the kernel is not reflected for this operation!
2451 */
anthony602ab9b2010-01-05 08:06:50 +00002452 k = kernel->values;
2453 k_pixels = p;
2454 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002455 for (v=0; v < (long) kernel->height; v++) {
2456 for (u=0; u < (long) kernel->width; u++, k++) {
anthony602ab9b2010-01-05 08:06:50 +00002457 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony4fd27e22010-02-07 08:17:18 +00002458 if ( result.red == 0.0 ||
2459 PixelIntensity(&(k_pixels[u])) < PixelIntensity(q) ) {
2460 /* copy the whole pixel - no channel selection */
2461 *q = k_pixels[u];
2462 if ( result.red > 0.0 ) changed++;
2463 result.red = 1.0;
2464 }
anthony602ab9b2010-01-05 08:06:50 +00002465 }
2466 k_pixels += image->columns+kernel->width;
2467 k_indexes += image->columns+kernel->width;
2468 }
anthony602ab9b2010-01-05 08:06:50 +00002469 break;
2470
anthony83ba99b2010-01-24 08:48:15 +00002471 case DilateIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00002472 /* Select Pixel with Maximum Intensity within kernel neighbourhood
2473 **
2474 ** WARNING: the intensity test fails for CMYK and does not
anthony9eb4f742010-05-18 02:45:54 +00002475 ** take into account the moderating effect of the alpha channel
2476 ** on the intensity (yet).
anthony930be612010-02-08 04:26:15 +00002477 **
2478 ** NOTE for correct working of this operation for asymetrical
2479 ** kernels, the kernel needs to be applied in its reflected form.
2480 ** That is its values needs to be reversed.
2481 */
anthony29188a82010-01-22 10:12:34 +00002482 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00002483 k_pixels = p;
2484 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002485 for (v=0; v < (long) kernel->height; v++) {
2486 for (u=0; u < (long) kernel->width; u++, k--) {
anthony29188a82010-01-22 10:12:34 +00002487 if ( IsNan(*k) || (*k) < 0.5 ) continue; /* boolean kernel */
2488 if ( result.red == 0.0 ||
2489 PixelIntensity(&(k_pixels[u])) > PixelIntensity(q) ) {
2490 /* copy the whole pixel - no channel selection */
2491 *q = k_pixels[u];
2492 if ( result.red > 0.0 ) changed++;
2493 result.red = 1.0;
2494 }
anthony602ab9b2010-01-05 08:06:50 +00002495 }
2496 k_pixels += image->columns+kernel->width;
2497 k_indexes += image->columns+kernel->width;
2498 }
anthony602ab9b2010-01-05 08:06:50 +00002499 break;
2500
anthony5ef8e942010-05-11 06:51:12 +00002501
anthony602ab9b2010-01-05 08:06:50 +00002502 case DistanceMorphology:
anthony930be612010-02-08 04:26:15 +00002503 /* Add kernel Value and select the minimum value found.
2504 ** The result is a iterative distance from edge of image shape.
2505 **
2506 ** All Distance Kernels are symetrical, but that may not always
2507 ** be the case. For example how about a distance from left edges?
2508 ** To work correctly with asymetrical kernels the reflected kernel
2509 ** needs to be applied.
anthony5ef8e942010-05-11 06:51:12 +00002510 **
2511 ** Actually this is really a GreyErode with a negative kernel!
2512 **
anthony930be612010-02-08 04:26:15 +00002513 */
anthony29188a82010-01-22 10:12:34 +00002514 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00002515 k_pixels = p;
2516 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002517 for (v=0; v < (long) kernel->height; v++) {
2518 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00002519 if ( IsNan(*k) ) continue;
2520 Minimize(result.red, (*k)+k_pixels[u].red);
2521 Minimize(result.green, (*k)+k_pixels[u].green);
2522 Minimize(result.blue, (*k)+k_pixels[u].blue);
2523 Minimize(result.opacity, (*k)+QuantumRange-k_pixels[u].opacity);
2524 if ( image->colorspace == CMYKColorspace)
2525 Minimize(result.index, (*k)+k_indexes[u]);
2526 }
2527 k_pixels += image->columns+kernel->width;
2528 k_indexes += image->columns+kernel->width;
2529 }
anthony602ab9b2010-01-05 08:06:50 +00002530 break;
2531
2532 case UndefinedMorphology:
2533 default:
2534 break; /* Do nothing */
anthony83ba99b2010-01-24 08:48:15 +00002535 }
anthony5ef8e942010-05-11 06:51:12 +00002536 /* Final mathematics of results (combine with original image?)
2537 **
2538 ** NOTE: Difference Morphology operators Edge* and *Hat could also
2539 ** be done here but works better with iteration as a image difference
2540 ** in the controling function (below). Thicken and Thinning however
2541 ** should be done here so thay can be iterated correctly.
2542 */
2543 switch ( method ) {
2544 case HitAndMissMorphology:
2545 case ErodeMorphology:
2546 result = min; /* minimum of neighbourhood */
2547 break;
2548 case DilateMorphology:
2549 result = max; /* maximum of neighbourhood */
2550 break;
2551 case ThinningMorphology:
2552 /* subtract pattern match from original */
2553 result.red -= min.red;
2554 result.green -= min.green;
2555 result.blue -= min.blue;
2556 result.opacity -= min.opacity;
2557 result.index -= min.index;
2558 break;
2559 case ThickenMorphology:
2560 /* Union with original image (maximize) - or should this be + */
2561 Maximize( result.red, min.red );
2562 Maximize( result.green, min.green );
2563 Maximize( result.blue, min.blue );
2564 Maximize( result.opacity, min.opacity );
2565 Maximize( result.index, min.index );
2566 break;
2567 default:
2568 /* result directly calculated or assigned */
2569 break;
2570 }
2571 /* Assign the resulting pixel values - Clamping Result */
anthony83ba99b2010-01-24 08:48:15 +00002572 switch ( method ) {
2573 case UndefinedMorphology:
2574 case DilateIntensityMorphology:
2575 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00002576 break; /* full pixel was directly assigned - not a channel method */
anthony83ba99b2010-01-24 08:48:15 +00002577 default:
anthony83ba99b2010-01-24 08:48:15 +00002578 if ((channel & RedChannel) != 0)
2579 q->red = ClampToQuantum(result.red);
2580 if ((channel & GreenChannel) != 0)
2581 q->green = ClampToQuantum(result.green);
2582 if ((channel & BlueChannel) != 0)
2583 q->blue = ClampToQuantum(result.blue);
2584 if ((channel & OpacityChannel) != 0
2585 && image->matte == MagickTrue )
2586 q->opacity = ClampToQuantum(QuantumRange-result.opacity);
2587 if ((channel & IndexChannel) != 0
2588 && image->colorspace == CMYKColorspace)
2589 q_indexes[x] = ClampToQuantum(result.index);
2590 break;
2591 }
anthony5ef8e942010-05-11 06:51:12 +00002592 /* Count up changed pixels */
anthony83ba99b2010-01-24 08:48:15 +00002593 if ( ( p[r].red != q->red )
2594 || ( p[r].green != q->green )
2595 || ( p[r].blue != q->blue )
2596 || ( p[r].opacity != q->opacity )
2597 || ( image->colorspace == CMYKColorspace &&
2598 p_indexes[r] != q_indexes[x] ) )
2599 changed++; /* The pixel had some value changed! */
anthony602ab9b2010-01-05 08:06:50 +00002600 p++;
2601 q++;
anthony83ba99b2010-01-24 08:48:15 +00002602 } /* x */
anthony602ab9b2010-01-05 08:06:50 +00002603 sync=SyncCacheViewAuthenticPixels(q_view,exception);
2604 if (sync == MagickFalse)
2605 status=MagickFalse;
2606 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2607 {
2608 MagickBooleanType
2609 proceed;
2610
2611#if defined(MAGICKCORE_OPENMP_SUPPORT)
2612 #pragma omp critical (MagickCore_MorphologyImage)
2613#endif
2614 proceed=SetImageProgress(image,MorphologyTag,progress++,image->rows);
2615 if (proceed == MagickFalse)
2616 status=MagickFalse;
2617 }
anthony83ba99b2010-01-24 08:48:15 +00002618 } /* y */
anthony602ab9b2010-01-05 08:06:50 +00002619 result_image->type=image->type;
2620 q_view=DestroyCacheView(q_view);
2621 p_view=DestroyCacheView(p_view);
cristy150989e2010-02-01 14:59:39 +00002622 return(status ? (unsigned long) changed : 0);
anthony602ab9b2010-01-05 08:06:50 +00002623}
2624
anthony4fd27e22010-02-07 08:17:18 +00002625
anthony9eb4f742010-05-18 02:45:54 +00002626MagickExport Image *MorphologyApply(const Image *image, const ChannelType
2627 channel,const MorphologyMethod method, const long iterations,
anthony47f5d062010-05-23 07:47:50 +00002628 const KernelInfo *kernel, const CompositeOperator compose,
2629 const double bias, ExceptionInfo *exception)
cristy2be15382010-01-21 02:38:03 +00002630{
2631 Image
anthony47f5d062010-05-23 07:47:50 +00002632 *curr_image, /* Image we are working with or iterating */
2633 *work_image, /* secondary image for primative iteration */
2634 *save_image, /* saved image - for 'edge' method only */
2635 *rslt_image; /* resultant image - after multi-kernel handling */
anthony602ab9b2010-01-05 08:06:50 +00002636
anthony4fd27e22010-02-07 08:17:18 +00002637 KernelInfo
anthony47f5d062010-05-23 07:47:50 +00002638 *reflected_kernel, /* A reflected copy of the kernel (if needed) */
2639 *norm_kernel, /* the current normal un-reflected kernel */
2640 *rflt_kernel, /* the current reflected kernel (if needed) */
2641 *this_kernel; /* the kernel being applied */
anthony4fd27e22010-02-07 08:17:18 +00002642
2643 MorphologyMethod
anthony47f5d062010-05-23 07:47:50 +00002644 primative; /* the current morphology primative being applied */
anthony9eb4f742010-05-18 02:45:54 +00002645
2646 CompositeOperator
anthony47f5d062010-05-23 07:47:50 +00002647 rslt_compose; /* multi-kernel compose method for results to use */
2648
2649 MagickBooleanType
2650 verbose; /* verbose output of results */
anthony4fd27e22010-02-07 08:17:18 +00002651
anthony1b2bc0a2010-05-12 05:25:22 +00002652 unsigned long
anthony47f5d062010-05-23 07:47:50 +00002653 method_loop, /* Loop 1: number of compound method iterations */
2654 method_limit, /* maximum number of compound method iterations */
2655 kernel_number, /* Loop 2: the kernel number being applied */
2656 stage_loop, /* Loop 3: primative loop for compound morphology */
2657 stage_limit, /* how many primatives in this compound */
2658 kernel_loop, /* Loop 4: iterate the kernel (basic morphology) */
2659 kernel_limit, /* number of times to iterate kernel */
2660 count, /* total count of primative steps applied */
2661 changed, /* number pixels changed by last primative operation */
2662 kernel_changed, /* total count of changed using iterated kernel */
2663 method_changed; /* total count of changed over method iteration */
2664
2665 char
2666 v_info[80];
anthony1b2bc0a2010-05-12 05:25:22 +00002667
anthony602ab9b2010-01-05 08:06:50 +00002668 assert(image != (Image *) NULL);
2669 assert(image->signature == MagickSignature);
anthony4fd27e22010-02-07 08:17:18 +00002670 assert(kernel != (KernelInfo *) NULL);
2671 assert(kernel->signature == MagickSignature);
anthony602ab9b2010-01-05 08:06:50 +00002672 assert(exception != (ExceptionInfo *) NULL);
2673 assert(exception->signature == MagickSignature);
2674
anthonyc3e48252010-05-24 12:43:11 +00002675 count = 0; /* number of low-level morphology primatives performed */
anthony602ab9b2010-01-05 08:06:50 +00002676 if ( iterations == 0 )
anthony47f5d062010-05-23 07:47:50 +00002677 return((Image *)NULL); /* null operation - nothing to do! */
anthony602ab9b2010-01-05 08:06:50 +00002678
anthony47f5d062010-05-23 07:47:50 +00002679 kernel_limit = (unsigned long) iterations;
2680 if ( iterations < 0 ) /* negative interations = infinite (well alomst) */
2681 kernel_limit = image->columns > image->rows ? image->columns : image->rows;
anthony602ab9b2010-01-05 08:06:50 +00002682
cristye96405a2010-05-19 02:24:31 +00002683 verbose = ( GetImageArtifact(image,"verbose") != (const char *) NULL ) ?
2684 MagickTrue : MagickFalse;
anthony4f1dcb72010-05-14 08:43:10 +00002685
anthony9eb4f742010-05-18 02:45:54 +00002686 /* initialise for cleanup */
anthony47f5d062010-05-23 07:47:50 +00002687 curr_image = (Image *) image;
2688 work_image = save_image = rslt_image = (Image *) NULL;
2689 reflected_kernel = (KernelInfo *) NULL;
anthony4fd27e22010-02-07 08:17:18 +00002690
anthony47f5d062010-05-23 07:47:50 +00002691 /* Initialize specific methods
2692 * + which loop should use the given iteratations
2693 * + how many primatives make up the compound morphology
2694 * + multi-kernel compose method to use (by default)
2695 */
2696 method_limit = 1; /* just do method once, unless otherwise set */
2697 stage_limit = 1; /* assume method is not a compount */
2698 rslt_compose = compose; /* and we are composing multi-kernels as given */
anthony9eb4f742010-05-18 02:45:54 +00002699 switch( method ) {
anthony47f5d062010-05-23 07:47:50 +00002700 case SmoothMorphology: /* 4 primative compound morphology */
2701 stage_limit = 4;
anthony9eb4f742010-05-18 02:45:54 +00002702 break;
anthony47f5d062010-05-23 07:47:50 +00002703 case OpenMorphology: /* 2 primative compound morphology */
anthony9eb4f742010-05-18 02:45:54 +00002704 case OpenIntensityMorphology:
anthony47f5d062010-05-23 07:47:50 +00002705 case TopHatMorphology:
2706 case CloseMorphology:
anthony9eb4f742010-05-18 02:45:54 +00002707 case CloseIntensityMorphology:
anthony47f5d062010-05-23 07:47:50 +00002708 case BottomHatMorphology:
2709 case EdgeMorphology:
2710 stage_limit = 2;
anthony9eb4f742010-05-18 02:45:54 +00002711 break;
2712 case HitAndMissMorphology:
anthonyc3e48252010-05-24 12:43:11 +00002713 kernel_limit = 1; /* no method or kernel iteration */
anthony47f5d062010-05-23 07:47:50 +00002714 rslt_compose = LightenCompositeOp; /* Union of multi-kernel results */
anthony9eb4f742010-05-18 02:45:54 +00002715 break;
anthonyc3e48252010-05-24 12:43:11 +00002716 case ThinningMorphology:
anthony9eb4f742010-05-18 02:45:54 +00002717 case ThickenMorphology:
anthonyc3e48252010-05-24 12:43:11 +00002718 case DistanceMorphology:
2719 method_limit = kernel_limit; /* iterate method with each kernel */
2720 kernel_limit = 1; /* do not do kernel iteration */
2721 rslt_compose = NoCompositeOp; /* Re-iterate with multiple kernels */
anthony47f5d062010-05-23 07:47:50 +00002722 break;
2723 default:
anthony930be612010-02-08 04:26:15 +00002724 break;
anthony602ab9b2010-01-05 08:06:50 +00002725 }
2726
anthonyc3e48252010-05-24 12:43:11 +00002727 /* Handle user (caller) specified multi-kernel composition method */
anthony47f5d062010-05-23 07:47:50 +00002728 if ( compose != UndefinedCompositeOp )
2729 rslt_compose = compose; /* override default composition for method */
2730 if ( rslt_compose == UndefinedCompositeOp )
2731 rslt_compose = NoCompositeOp; /* still not defined! Then re-iterate */
2732
anthonyc3e48252010-05-24 12:43:11 +00002733 /* Some methods require a reflected kernel to use with primatives.
2734 * Create the reflected kernel for those methods. */
anthony47f5d062010-05-23 07:47:50 +00002735 switch ( method ) {
2736 case CorrelateMorphology:
2737 case CloseMorphology:
2738 case CloseIntensityMorphology:
2739 case BottomHatMorphology:
2740 case SmoothMorphology:
2741 reflected_kernel = CloneKernelInfo(kernel);
2742 if (reflected_kernel == (KernelInfo *) NULL)
2743 goto error_cleanup;
2744 RotateKernelInfo(reflected_kernel,180);
2745 break;
2746 default:
2747 break;
anthony9eb4f742010-05-18 02:45:54 +00002748 }
anthony7a01dcf2010-05-11 12:25:52 +00002749
anthony47f5d062010-05-23 07:47:50 +00002750 /* Loop 1: iterate the compound method */
2751 method_loop = 0;
2752 method_changed = 1;
2753 while ( method_loop < method_limit && method_changed > 0 ) {
2754 method_loop++;
2755 method_changed = 0;
anthony9eb4f742010-05-18 02:45:54 +00002756
anthony47f5d062010-05-23 07:47:50 +00002757 /* Loop 2: iterate over each kernel in a multi-kernel list */
2758 norm_kernel = (KernelInfo *) kernel;
2759 rflt_kernel = reflected_kernel;
2760 kernel_number = 0;
2761 while ( norm_kernel != NULL ) {
anthony9eb4f742010-05-18 02:45:54 +00002762
anthony47f5d062010-05-23 07:47:50 +00002763 /* Loop 3: Compound Morphology Staging - Select Primative to apply */
2764 stage_loop = 0; /* the compound morphology stage number */
2765 while ( stage_loop < stage_limit ) {
2766 stage_loop++; /* The stage of the compound morphology */
anthony9eb4f742010-05-18 02:45:54 +00002767
anthony47f5d062010-05-23 07:47:50 +00002768 /* Select primative morphology for this stage of compound method */
2769 this_kernel = norm_kernel; /* default use unreflected kernel */
anthonybd0f5562010-05-24 13:05:02 +00002770 primative = method; /* Assume method is a primative */
anthony47f5d062010-05-23 07:47:50 +00002771 switch( method ) {
2772 case ErodeMorphology: /* just erode */
2773 case EdgeInMorphology: /* erode and image difference */
2774 primative = ErodeMorphology;
2775 break;
2776 case DilateMorphology: /* just dilate */
2777 case EdgeOutMorphology: /* dilate and image difference */
2778 primative = DilateMorphology;
2779 break;
2780 case OpenMorphology: /* erode then dialate */
2781 case TopHatMorphology: /* open and image difference */
2782 primative = ErodeMorphology;
2783 if ( stage_loop == 2 )
2784 primative = DilateMorphology;
2785 break;
2786 case OpenIntensityMorphology:
2787 primative = ErodeIntensityMorphology;
2788 if ( stage_loop == 2 )
2789 primative = DilateIntensityMorphology;
2790 case CloseMorphology: /* dilate, then erode */
2791 case BottomHatMorphology: /* close and image difference */
2792 this_kernel = rflt_kernel; /* use the reflected kernel */
2793 primative = DilateMorphology;
2794 if ( stage_loop == 2 )
2795 primative = ErodeMorphology;
2796 break;
2797 case CloseIntensityMorphology:
2798 this_kernel = rflt_kernel; /* use the reflected kernel */
2799 primative = DilateIntensityMorphology;
2800 if ( stage_loop == 2 )
2801 primative = ErodeIntensityMorphology;
2802 break;
2803 case SmoothMorphology: /* open, close */
2804 switch ( stage_loop ) {
2805 case 1: /* start an open method, which starts with Erode */
2806 primative = ErodeMorphology;
2807 break;
2808 case 2: /* now Dilate the Erode */
2809 primative = DilateMorphology;
2810 break;
2811 case 3: /* Reflect kernel a close */
2812 this_kernel = rflt_kernel; /* use the reflected kernel */
2813 primative = DilateMorphology;
2814 break;
2815 case 4: /* Finish the Close */
2816 this_kernel = rflt_kernel; /* use the reflected kernel */
2817 primative = ErodeMorphology;
2818 break;
2819 }
2820 break;
2821 case EdgeMorphology: /* dilate and erode difference */
2822 primative = DilateMorphology;
2823 if ( stage_loop == 2 ) {
2824 save_image = curr_image; /* save the image difference */
2825 curr_image = (Image *) image;
2826 primative = ErodeMorphology;
2827 }
2828 break;
2829 case CorrelateMorphology:
2830 /* A Correlation is a Convolution with a reflected kernel.
2831 ** However a Convolution is a weighted sum using a reflected
2832 ** kernel. It may seem stange to convert a Correlation into a
2833 ** Convolution as the Correlation is the simplier method, but
2834 ** Convolution is much more commonly used, and it makes sense to
2835 ** implement it directly so as to avoid the need to duplicate the
2836 ** kernel when it is not required (which is typically the
2837 ** default).
2838 */
2839 this_kernel = rflt_kernel; /* use the reflected kernel */
2840 primative = ConvolveMorphology;
2841 break;
2842 default:
anthony47f5d062010-05-23 07:47:50 +00002843 break;
2844 }
anthony9eb4f742010-05-18 02:45:54 +00002845
anthony47f5d062010-05-23 07:47:50 +00002846 /* Extra information for debugging compound operations */
2847 if ( verbose == MagickTrue ) {
2848 if ( stage_limit > 1 )
cristydc1c30b2010-05-23 14:23:12 +00002849 (void) FormatMagickString(v_info, MaxTextExtent, "%s:%lu.%lu -> ",
anthony47f5d062010-05-23 07:47:50 +00002850 MagickOptionToMnemonic(MagickMorphologyOptions, method),
2851 method_loop, stage_loop );
2852 else if ( primative != method )
cristydc1c30b2010-05-23 14:23:12 +00002853 (void) FormatMagickString(v_info, MaxTextExtent, "%s:%lu -> ",
anthony47f5d062010-05-23 07:47:50 +00002854 MagickOptionToMnemonic(MagickMorphologyOptions, method),
2855 method_loop );
2856 else
2857 v_info[0] = '\0';
2858 }
2859
2860 /* Loop 4: Iterate the kernel with primative */
2861 kernel_loop = 0;
2862 kernel_changed = 0;
2863 changed = 1;
2864 while ( kernel_loop < kernel_limit && changed > 0 ) {
2865 kernel_loop++; /* the iteration of this kernel */
anthony9eb4f742010-05-18 02:45:54 +00002866
2867 /* Create a destination image, if not yet defined */
2868 if ( work_image == (Image *) NULL )
2869 {
2870 work_image=CloneImage(image,0,0,MagickTrue,exception);
2871 if (work_image == (Image *) NULL)
2872 goto error_cleanup;
2873 if (SetImageStorageClass(work_image,DirectClass) == MagickFalse)
2874 {
2875 InheritException(exception,&work_image->exception);
2876 goto error_cleanup;
2877 }
2878 }
2879
anthony47f5d062010-05-23 07:47:50 +00002880 /* APPLY THE MORPHOLOGICAL PRIMITIVE (curr -> work) */
anthony9eb4f742010-05-18 02:45:54 +00002881 count++;
anthony47f5d062010-05-23 07:47:50 +00002882 changed = MorphologyPrimitive(curr_image, work_image, primative,
anthony9eb4f742010-05-18 02:45:54 +00002883 channel, this_kernel, bias, exception);
anthony47f5d062010-05-23 07:47:50 +00002884 kernel_changed += changed;
2885 method_changed += changed;
anthony9eb4f742010-05-18 02:45:54 +00002886
anthony47f5d062010-05-23 07:47:50 +00002887 if ( verbose == MagickTrue ) {
2888 if ( kernel_loop > 1 )
2889 fprintf(stderr, "\n"); /* add end-of-line from previous */
2890 fprintf(stderr, "%s%s%s:%lu.%lu #%lu => Changed %lu", v_info,
2891 MagickOptionToMnemonic(MagickMorphologyOptions, primative),
2892 ( this_kernel == rflt_kernel ) ? "*" : "",
2893 method_loop+kernel_loop-1, kernel_number, count, changed);
2894 }
anthony9eb4f742010-05-18 02:45:54 +00002895 /* prepare next loop */
2896 { Image *tmp = work_image; /* swap images for iteration */
2897 work_image = curr_image;
2898 curr_image = tmp;
2899 }
2900 if ( work_image == image )
anthony47f5d062010-05-23 07:47:50 +00002901 work_image = (Image *) NULL; /* replace input 'image' */
anthony7a01dcf2010-05-11 12:25:52 +00002902
anthony47f5d062010-05-23 07:47:50 +00002903 } /* End Loop 4: Iterate the kernel with primative */
anthony1b2bc0a2010-05-12 05:25:22 +00002904
anthony47f5d062010-05-23 07:47:50 +00002905 if ( verbose == MagickTrue && kernel_changed != changed )
2906 fprintf(stderr, " Total %lu", kernel_changed);
2907 if ( verbose == MagickTrue && stage_loop < stage_limit )
2908 fprintf(stderr, "\n"); /* add end-of-line before looping */
anthony9eb4f742010-05-18 02:45:54 +00002909
2910#if 0
anthony47f5d062010-05-23 07:47:50 +00002911 fprintf(stderr, "--E-- image=0x%lx\n", (unsigned long)image);
2912 fprintf(stderr, " curr =0x%lx\n", (unsigned long)curr_image);
2913 fprintf(stderr, " work =0x%lx\n", (unsigned long)work_image);
2914 fprintf(stderr, " save =0x%lx\n", (unsigned long)save_image);
2915 fprintf(stderr, " union=0x%lx\n", (unsigned long)rslt_image);
anthony9eb4f742010-05-18 02:45:54 +00002916#endif
2917
anthony47f5d062010-05-23 07:47:50 +00002918 } /* End Loop 3: Primative (staging) Loop for Coumpound Methods */
anthony9eb4f742010-05-18 02:45:54 +00002919
anthony47f5d062010-05-23 07:47:50 +00002920 /* Final Post-processing for some Compound Methods
2921 **
2922 ** The removal of any 'Sync' channel flag in the Image Compositon
2923 ** below ensures the methematical compose method is applied in a
2924 ** purely mathematical way, and only to the selected channels.
2925 ** Turn off SVG composition 'alpha blending'.
2926 */
2927 switch( method ) {
2928 case EdgeOutMorphology:
2929 case EdgeInMorphology:
2930 case TopHatMorphology:
2931 case BottomHatMorphology:
2932 if ( verbose == MagickTrue )
2933 fprintf(stderr, "\n%s: Difference with original image",
2934 MagickOptionToMnemonic(MagickMorphologyOptions, method) );
2935 (void) CompositeImageChannel(curr_image,
2936 (ChannelType) (channel & ~SyncChannels),
2937 DifferenceCompositeOp, image, 0, 0);
2938 break;
2939 case EdgeMorphology:
2940 if ( verbose == MagickTrue )
2941 fprintf(stderr, "\n%s: Difference of Dilate and Erode",
2942 MagickOptionToMnemonic(MagickMorphologyOptions, method) );
2943 (void) CompositeImageChannel(curr_image,
2944 (ChannelType) (channel & ~SyncChannels),
2945 DifferenceCompositeOp, save_image, 0, 0);
2946 save_image = DestroyImage(save_image); /* finished with save image */
2947 break;
2948 default:
2949 break;
2950 }
2951
2952 /* multi-kernel handling: re-iterate, or compose results */
2953 if ( kernel->next == (KernelInfo *) NULL )
anthonyc3e48252010-05-24 12:43:11 +00002954 rslt_image = curr_image; /* just return the resulting image */
anthony47f5d062010-05-23 07:47:50 +00002955 else if ( rslt_compose == NoCompositeOp )
anthonyc3e48252010-05-24 12:43:11 +00002956 { if ( verbose == MagickTrue ) {
2957 if ( this_kernel->next != (KernelInfo *) NULL )
2958 fprintf(stderr, " (re-iterate)");
2959 else
2960 fprintf(stderr, " (done)");
2961 }
2962 rslt_image = curr_image; /* return result, and re-iterate */
anthony9eb4f742010-05-18 02:45:54 +00002963 }
anthony47f5d062010-05-23 07:47:50 +00002964 else if ( rslt_image == (Image *) NULL)
2965 { if ( verbose == MagickTrue )
2966 fprintf(stderr, " (save for compose)");
2967 rslt_image = curr_image;
2968 curr_image = (Image *) image; /* continue with original image */
anthony9eb4f742010-05-18 02:45:54 +00002969 }
anthony47f5d062010-05-23 07:47:50 +00002970 else
2971 { /* add the new 'current' result to the composition
2972 **
2973 ** The removal of any 'Sync' channel flag in the Image Compositon
2974 ** below ensures the methematical compose method is applied in a
2975 ** purely mathematical way, and only to the selected channels.
2976 ** Turn off SVG composition 'alpha blending'.
2977 */
2978 if ( verbose == MagickTrue )
2979 fprintf(stderr, " (compose \"%s\")",
2980 MagickOptionToMnemonic(MagickComposeOptions, rslt_compose) );
2981 (void) CompositeImageChannel(rslt_image,
2982 (ChannelType) (channel & ~SyncChannels), rslt_compose,
2983 curr_image, 0, 0);
2984 curr_image = (Image *) image; /* continue with original image */
2985 }
2986 if ( verbose == MagickTrue )
2987 fprintf(stderr, "\n");
anthony9eb4f742010-05-18 02:45:54 +00002988
anthony47f5d062010-05-23 07:47:50 +00002989 /* loop to the next kernel in a multi-kernel list */
2990 norm_kernel = norm_kernel->next;
2991 if ( rflt_kernel != (KernelInfo *) NULL )
2992 rflt_kernel = rflt_kernel->next;
2993 kernel_number++;
2994 } /* End Loop 2: Loop over each kernel */
anthony9eb4f742010-05-18 02:45:54 +00002995
anthony47f5d062010-05-23 07:47:50 +00002996 } /* End Loop 1: compound method interation */
anthony602ab9b2010-01-05 08:06:50 +00002997
anthony9eb4f742010-05-18 02:45:54 +00002998 goto exit_cleanup;
anthony1b2bc0a2010-05-12 05:25:22 +00002999
anthony47f5d062010-05-23 07:47:50 +00003000 /* Yes goto's are bad, but it makes cleanup lot more efficient */
anthony1b2bc0a2010-05-12 05:25:22 +00003001error_cleanup:
anthony47f5d062010-05-23 07:47:50 +00003002 if ( curr_image != (Image *) NULL &&
3003 curr_image != rslt_image &&
3004 curr_image != image )
3005 curr_image = DestroyImage(curr_image);
3006 if ( rslt_image != (Image *) NULL )
3007 rslt_image = DestroyImage(rslt_image);
anthony1b2bc0a2010-05-12 05:25:22 +00003008exit_cleanup:
anthony47f5d062010-05-23 07:47:50 +00003009 if ( curr_image != (Image *) NULL &&
3010 curr_image != rslt_image &&
3011 curr_image != image )
3012 curr_image = DestroyImage(curr_image);
anthony9eb4f742010-05-18 02:45:54 +00003013 if ( work_image != (Image *) NULL )
anthony47f5d062010-05-23 07:47:50 +00003014 work_image = DestroyImage(work_image);
anthony9eb4f742010-05-18 02:45:54 +00003015 if ( save_image != (Image *) NULL )
anthony47f5d062010-05-23 07:47:50 +00003016 save_image = DestroyImage(save_image);
3017 if ( reflected_kernel != (KernelInfo *) NULL )
3018 reflected_kernel = DestroyKernelInfo(reflected_kernel);
3019 return(rslt_image);
anthony9eb4f742010-05-18 02:45:54 +00003020}
3021
3022/*
3023%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3024% %
3025% %
3026% %
3027% M o r p h o l o g y I m a g e C h a n n e l %
3028% %
3029% %
3030% %
3031%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3032%
3033% MorphologyImageChannel() applies a user supplied kernel to the image
3034% according to the given mophology method.
3035%
3036% This function applies any and all user defined settings before calling
3037% the above internal function MorphologyApply().
3038%
3039% User defined settings include...
anthony46a369d2010-05-19 02:41:48 +00003040% * Output Bias for Convolution and correlation ("-bias")
3041% * Kernel Scale/normalize settings ("-set 'option:convolve:scale'")
3042% This can also includes the addition of a scaled unity kernel.
3043% * Show Kernel being applied ("-set option:showkernel 1")
anthony9eb4f742010-05-18 02:45:54 +00003044%
3045% The format of the MorphologyImage method is:
3046%
3047% Image *MorphologyImage(const Image *image,MorphologyMethod method,
3048% const long iterations,KernelInfo *kernel,ExceptionInfo *exception)
3049%
3050% Image *MorphologyImageChannel(const Image *image, const ChannelType
3051% channel,MorphologyMethod method,const long iterations,
3052% KernelInfo *kernel,ExceptionInfo *exception)
3053%
3054% A description of each parameter follows:
3055%
3056% o image: the image.
3057%
3058% o method: the morphology method to be applied.
3059%
3060% o iterations: apply the operation this many times (or no change).
3061% A value of -1 means loop until no change found.
3062% How this is applied may depend on the morphology method.
3063% Typically this is a value of 1.
3064%
3065% o channel: the channel type.
3066%
3067% o kernel: An array of double representing the morphology kernel.
3068% Warning: kernel may be normalized for the Convolve method.
3069%
3070% o exception: return any errors or warnings in this structure.
3071%
3072*/
3073
3074MagickExport Image *MorphologyImageChannel(const Image *image,
3075 const ChannelType channel,const MorphologyMethod method,
3076 const long iterations,const KernelInfo *kernel,ExceptionInfo *exception)
3077{
3078 const char
3079 *artifact;
3080
3081 KernelInfo
3082 *curr_kernel;
3083
anthony47f5d062010-05-23 07:47:50 +00003084 CompositeOperator
3085 compose;
3086
anthony9eb4f742010-05-18 02:45:54 +00003087 Image
3088 *morphology_image;
3089
3090
anthony46a369d2010-05-19 02:41:48 +00003091 /* Apply Convolve/Correlate Normalization and Scaling Factors.
3092 * This is done BEFORE the ShowKernelInfo() function is called so that
3093 * users can see the results of the 'option:convolve:scale' option.
anthony9eb4f742010-05-18 02:45:54 +00003094 */
3095 curr_kernel = (KernelInfo *) kernel;
anthonyf71ca292010-05-19 04:08:43 +00003096 if ( method == ConvolveMorphology || method == CorrelateMorphology )
anthony9eb4f742010-05-18 02:45:54 +00003097 {
3098 artifact = GetImageArtifact(image,"convolve:scale");
3099 if ( artifact != (char *)NULL ) {
anthony9eb4f742010-05-18 02:45:54 +00003100 if ( curr_kernel == kernel )
3101 curr_kernel = CloneKernelInfo(kernel);
3102 if (curr_kernel == (KernelInfo *) NULL) {
3103 curr_kernel=DestroyKernelInfo(curr_kernel);
3104 return((Image *) NULL);
3105 }
anthony46a369d2010-05-19 02:41:48 +00003106 ScaleGeometryKernelInfo(curr_kernel, artifact);
anthony9eb4f742010-05-18 02:45:54 +00003107 }
3108 }
3109
3110 /* display the (normalized) kernel via stderr */
3111 artifact = GetImageArtifact(image,"showkernel");
anthony47f5d062010-05-23 07:47:50 +00003112 if ( artifact == (const char *) NULL)
3113 artifact = GetImageArtifact(image,"convolve:showkernel");
3114 if ( artifact == (const char *) NULL)
3115 artifact = GetImageArtifact(image,"morphology:showkernel");
anthony9eb4f742010-05-18 02:45:54 +00003116 if ( artifact != (const char *) NULL)
3117 ShowKernelInfo(curr_kernel);
3118
anthony47f5d062010-05-23 07:47:50 +00003119 /* override the default handling of multi-kernel morphology results
3120 * if 'Undefined' use the default method
3121 * if 'None' (default for 'Convolve') re-iterate previous result
3122 * otherwise merge resulting images using compose method given
3123 */
3124 compose = UndefinedCompositeOp; /* use default for method */
3125 artifact = GetImageArtifact(image,"morphology:compose");
3126 if ( artifact != (const char *) NULL)
3127 compose = (CompositeOperator) ParseMagickOption(
3128 MagickComposeOptions,MagickFalse,artifact);
3129
anthony9eb4f742010-05-18 02:45:54 +00003130 /* Apply the Morphology */
3131 morphology_image = MorphologyApply(image, channel, method, iterations,
anthony47f5d062010-05-23 07:47:50 +00003132 curr_kernel, compose, image->bias, exception);
anthony9eb4f742010-05-18 02:45:54 +00003133
3134 /* Cleanup and Exit */
3135 if ( curr_kernel != kernel )
anthony1b2bc0a2010-05-12 05:25:22 +00003136 curr_kernel=DestroyKernelInfo(curr_kernel);
anthony9eb4f742010-05-18 02:45:54 +00003137 return(morphology_image);
3138}
3139
3140MagickExport Image *MorphologyImage(const Image *image, const MorphologyMethod
3141 method, const long iterations,const KernelInfo *kernel, ExceptionInfo
3142 *exception)
3143{
3144 Image
3145 *morphology_image;
3146
3147 morphology_image=MorphologyImageChannel(image,DefaultChannels,method,
3148 iterations,kernel,exception);
3149 return(morphology_image);
anthony602ab9b2010-01-05 08:06:50 +00003150}
anthony83ba99b2010-01-24 08:48:15 +00003151
3152/*
3153%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3154% %
3155% %
3156% %
anthony4fd27e22010-02-07 08:17:18 +00003157+ R o t a t e K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00003158% %
3159% %
3160% %
3161%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3162%
anthony46a369d2010-05-19 02:41:48 +00003163% RotateKernelInfo() rotates the kernel by the angle given.
3164%
3165% Currently it is restricted to 90 degree angles, of either 1D kernels
3166% or square kernels. And 'circular' rotations of 45 degrees for 3x3 kernels.
3167% It will ignore usless rotations for specific 'named' built-in kernels.
anthony83ba99b2010-01-24 08:48:15 +00003168%
anthony4fd27e22010-02-07 08:17:18 +00003169% The format of the RotateKernelInfo method is:
anthony83ba99b2010-01-24 08:48:15 +00003170%
anthony4fd27e22010-02-07 08:17:18 +00003171% void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00003172%
3173% A description of each parameter follows:
3174%
3175% o kernel: the Morphology/Convolution kernel
3176%
3177% o angle: angle to rotate in degrees
3178%
anthony46a369d2010-05-19 02:41:48 +00003179% This function is currently internal to this module only, but can be exported
3180% to other modules if needed.
anthony83ba99b2010-01-24 08:48:15 +00003181*/
anthony4fd27e22010-02-07 08:17:18 +00003182static void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00003183{
anthony1b2bc0a2010-05-12 05:25:22 +00003184 /* angle the lower kernels first */
3185 if ( kernel->next != (KernelInfo *) NULL)
3186 RotateKernelInfo(kernel->next, angle);
3187
anthony83ba99b2010-01-24 08:48:15 +00003188 /* WARNING: Currently assumes the kernel (rightly) is horizontally symetrical
3189 **
3190 ** TODO: expand beyond simple 90 degree rotates, flips and flops
3191 */
3192
3193 /* Modulus the angle */
3194 angle = fmod(angle, 360.0);
3195 if ( angle < 0 )
3196 angle += 360.0;
3197
anthony3c10fc82010-05-13 02:40:51 +00003198 if ( 337.5 < angle || angle <= 22.5 )
anthony43c49252010-05-18 10:59:50 +00003199 return; /* Near zero angle - no change! - At least not at this time */
anthony83ba99b2010-01-24 08:48:15 +00003200
anthony3dd0f622010-05-13 12:57:32 +00003201 /* Handle special cases */
anthony83ba99b2010-01-24 08:48:15 +00003202 switch (kernel->type) {
3203 /* These built-in kernels are cylindrical kernels, rotating is useless */
3204 case GaussianKernel:
anthony83ba99b2010-01-24 08:48:15 +00003205 case DOGKernel:
3206 case DiskKernel:
anthony3dd0f622010-05-13 12:57:32 +00003207 case PeaksKernel:
3208 case LaplacianKernel:
anthony83ba99b2010-01-24 08:48:15 +00003209 case ChebyshevKernel:
3210 case ManhattenKernel:
3211 case EuclideanKernel:
3212 return;
3213
3214 /* These may be rotatable at non-90 angles in the future */
3215 /* but simply rotating them in multiples of 90 degrees is useless */
3216 case SquareKernel:
3217 case DiamondKernel:
3218 case PlusKernel:
anthony3dd0f622010-05-13 12:57:32 +00003219 case CrossKernel:
anthony83ba99b2010-01-24 08:48:15 +00003220 return;
3221
3222 /* These only allows a +/-90 degree rotation (by transpose) */
3223 /* A 180 degree rotation is useless */
3224 case BlurKernel:
3225 case RectangleKernel:
3226 if ( 135.0 < angle && angle <= 225.0 )
3227 return;
3228 if ( 225.0 < angle && angle <= 315.0 )
3229 angle -= 180;
3230 break;
3231
anthony3dd0f622010-05-13 12:57:32 +00003232 default:
anthony83ba99b2010-01-24 08:48:15 +00003233 break;
3234 }
anthony3c10fc82010-05-13 02:40:51 +00003235 /* Attempt rotations by 45 degrees */
3236 if ( 22.5 < fmod(angle,90.0) && fmod(angle,90.0) <= 67.5 )
3237 {
3238 if ( kernel->width == 3 && kernel->height == 3 )
3239 { /* Rotate a 3x3 square by 45 degree angle */
3240 MagickRealType t = kernel->values[0];
anthony43c49252010-05-18 10:59:50 +00003241 kernel->values[0] = kernel->values[3];
3242 kernel->values[3] = kernel->values[6];
3243 kernel->values[6] = kernel->values[7];
3244 kernel->values[7] = kernel->values[8];
3245 kernel->values[8] = kernel->values[5];
3246 kernel->values[5] = kernel->values[2];
3247 kernel->values[2] = kernel->values[1];
3248 kernel->values[1] = t;
anthony1d45eb92010-05-25 11:13:23 +00003249 /* rotate non-centered origin */
3250 if ( kernel->x != 1 || kernel->y != 1 ) {
3251 long x,y;
3252 x = (long) kernel->x-1;
3253 y = (long) kernel->y-1;
3254 if ( x == y ) x = 0;
3255 else if ( x == 0 ) x = -y;
3256 else if ( x == -y ) y = 0;
3257 else if ( y == 0 ) y = x;
3258 kernel->x = (unsigned long) x+1;
3259 kernel->y = (unsigned long) y+1;
3260 }
anthony43c49252010-05-18 10:59:50 +00003261 angle = fmod(angle+315.0, 360.0); /* angle reduced 45 degrees */
3262 kernel->angle = fmod(kernel->angle+45.0, 360.0);
anthony3c10fc82010-05-13 02:40:51 +00003263 }
3264 else
3265 perror("Unable to rotate non-3x3 kernel by 45 degrees");
3266 }
3267 if ( 45.0 < fmod(angle, 180.0) && fmod(angle,180.0) <= 135.0 )
3268 {
3269 if ( kernel->width == 1 || kernel->height == 1 )
3270 { /* Do a transpose of the image, which results in a 90
3271 ** degree rotation of a 1 dimentional kernel
3272 */
3273 long
3274 t;
3275 t = (long) kernel->width;
3276 kernel->width = kernel->height;
3277 kernel->height = (unsigned long) t;
3278 t = kernel->x;
3279 kernel->x = kernel->y;
3280 kernel->y = t;
anthony43c49252010-05-18 10:59:50 +00003281 if ( kernel->width == 1 ) {
3282 angle = fmod(angle+270.0, 360.0); /* angle reduced 90 degrees */
3283 kernel->angle = fmod(kernel->angle+90.0, 360.0);
3284 } else {
3285 angle = fmod(angle+90.0, 360.0); /* angle increased 90 degrees */
3286 kernel->angle = fmod(kernel->angle+270.0, 360.0);
3287 }
anthony3c10fc82010-05-13 02:40:51 +00003288 }
3289 else if ( kernel->width == kernel->height )
3290 { /* Rotate a square array of values by 90 degrees */
anthony1d45eb92010-05-25 11:13:23 +00003291 { register unsigned long
3292 i,j,x,y;
3293 register MagickRealType
3294 *k,t;
3295 k=kernel->values;
3296 for( i=0, x=kernel->width-1; i<=x; i++, x--)
3297 for( j=0, y=kernel->height-1; j<y; j++, y--)
3298 { t = k[i+j*kernel->width];
3299 k[i+j*kernel->width] = k[j+x*kernel->width];
3300 k[j+x*kernel->width] = k[x+y*kernel->width];
3301 k[x+y*kernel->width] = k[y+i*kernel->width];
3302 k[y+i*kernel->width] = t;
3303 }
3304 }
3305 /* rotate the origin - relative to center of array */
3306 { register long x,y;
3307 x = (long) kernel->x*2-kernel->width+1;
3308 y = (long) kernel->y*2-kernel->height+1;
3309 kernel->x = (unsigned long) ( -y +kernel->width-1)/2;
3310 kernel->y = (unsigned long) ( +x +kernel->height-1)/2;
3311 }
anthony43c49252010-05-18 10:59:50 +00003312 angle = fmod(angle+270.0, 360.0); /* angle reduced 90 degrees */
3313 kernel->angle = fmod(kernel->angle+90.0, 360.0);
anthony3c10fc82010-05-13 02:40:51 +00003314 }
3315 else
3316 perror("Unable to rotate a non-square, non-linear kernel 90 degrees");
3317 }
anthony83ba99b2010-01-24 08:48:15 +00003318 if ( 135.0 < angle && angle <= 225.0 )
3319 {
anthony43c49252010-05-18 10:59:50 +00003320 /* For a 180 degree rotation - also know as a reflection
3321 * This is actually a very very common operation!
3322 * Basically all that is needed is a reversal of the kernel data!
3323 * And a reflection of the origon
3324 */
anthony83ba99b2010-01-24 08:48:15 +00003325 unsigned long
3326 i,j;
3327 register double
3328 *k,t;
3329
3330 k=kernel->values;
3331 for ( i=0, j=kernel->width*kernel->height-1; i<j; i++, j--)
3332 t=k[i], k[i]=k[j], k[j]=t;
3333
anthony930be612010-02-08 04:26:15 +00003334 kernel->x = (long) kernel->width - kernel->x - 1;
3335 kernel->y = (long) kernel->height - kernel->y - 1;
anthony43c49252010-05-18 10:59:50 +00003336 angle = fmod(angle-180.0, 360.0); /* angle+180 degrees */
3337 kernel->angle = fmod(kernel->angle+180.0, 360.0);
anthony83ba99b2010-01-24 08:48:15 +00003338 }
anthony3c10fc82010-05-13 02:40:51 +00003339 /* At this point angle should at least between -45 (315) and +45 degrees
anthony83ba99b2010-01-24 08:48:15 +00003340 * In the future some form of non-orthogonal angled rotates could be
3341 * performed here, posibily with a linear kernel restriction.
3342 */
3343
3344#if 0
anthony3c10fc82010-05-13 02:40:51 +00003345 { /* Do a Flop by reversing each row.
anthony83ba99b2010-01-24 08:48:15 +00003346 */
3347 unsigned long
3348 y;
cristy150989e2010-02-01 14:59:39 +00003349 register long
anthony83ba99b2010-01-24 08:48:15 +00003350 x,r;
3351 register double
3352 *k,t;
3353
3354 for ( y=0, k=kernel->values; y < kernel->height; y++, k+=kernel->width)
3355 for ( x=0, r=kernel->width-1; x<kernel->width/2; x++, r--)
3356 t=k[x], k[x]=k[r], k[r]=t;
3357
cristyc99304f2010-02-01 15:26:27 +00003358 kernel->x = kernel->width - kernel->x - 1;
anthony83ba99b2010-01-24 08:48:15 +00003359 angle = fmod(angle+180.0, 360.0);
3360 }
3361#endif
3362 return;
3363}
3364
3365/*
3366%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3367% %
3368% %
3369% %
anthony46a369d2010-05-19 02:41:48 +00003370% S c a l e G e o m e t r y K e r n e l I n f o %
3371% %
3372% %
3373% %
3374%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3375%
3376% ScaleGeometryKernelInfo() takes a geometry argument string, typically
3377% provided as a "-set option:convolve:scale {geometry}" user setting,
3378% and modifies the kernel according to the parsed arguments of that setting.
3379%
3380% The first argument (and any normalization flags) are passed to
3381% ScaleKernelInfo() to scale/normalize the kernel. The second argument
3382% is then passed to UnityAddKernelInfo() to add a scled unity kernel
3383% into the scaled/normalized kernel.
3384%
3385% The format of the ScaleKernelInfo method is:
3386%
3387% void ScaleKernelInfo(KernelInfo *kernel, const double scaling_factor,
3388% const MagickStatusType normalize_flags )
3389%
3390% A description of each parameter follows:
3391%
3392% o kernel: the Morphology/Convolution kernel to modify
3393%
3394% o geometry:
3395% The geometry string to parse, typically from the user provided
3396% "-set option:convolve:scale {geometry}" setting.
3397%
3398*/
3399MagickExport void ScaleGeometryKernelInfo (KernelInfo *kernel,
3400 const char *geometry)
3401{
3402 GeometryFlags
3403 flags;
3404 GeometryInfo
3405 args;
3406
3407 SetGeometryInfo(&args);
3408 flags = (GeometryFlags) ParseGeometry(geometry, &args);
3409
3410#if 0
3411 /* For Debugging Geometry Input */
3412 fprintf(stderr, "Geometry = 0x%04X : %lg x %lg %+lg %+lg\n",
3413 flags, args.rho, args.sigma, args.xi, args.psi );
3414#endif
3415
3416 if ( (flags & PercentValue) != 0 ) /* Handle Percentage flag*/
3417 args.rho *= 0.01, args.sigma *= 0.01;
3418
3419 if ( (flags & RhoValue) == 0 ) /* Set Defaults for missing args */
3420 args.rho = 1.0;
3421 if ( (flags & SigmaValue) == 0 )
3422 args.sigma = 0.0;
3423
3424 /* Scale/Normalize the input kernel */
3425 ScaleKernelInfo(kernel, args.rho, flags);
3426
3427 /* Add Unity Kernel, for blending with original */
3428 if ( (flags & SigmaValue) != 0 )
3429 UnityAddKernelInfo(kernel, args.sigma);
3430
3431 return;
3432}
3433/*
3434%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3435% %
3436% %
3437% %
cristy6771f1e2010-03-05 19:43:39 +00003438% S c a l e K e r n e l I n f o %
anthonycc6c8362010-01-25 04:14:01 +00003439% %
3440% %
3441% %
3442%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3443%
anthony1b2bc0a2010-05-12 05:25:22 +00003444% ScaleKernelInfo() scales the given kernel list by the given amount, with or
3445% without normalization of the sum of the kernel values (as per given flags).
anthonycc6c8362010-01-25 04:14:01 +00003446%
anthony999bb2c2010-02-18 12:38:01 +00003447% By default (no flags given) the values within the kernel is scaled
anthony1b2bc0a2010-05-12 05:25:22 +00003448% directly using given scaling factor without change.
anthonycc6c8362010-01-25 04:14:01 +00003449%
anthony46a369d2010-05-19 02:41:48 +00003450% If either of the two 'normalize_flags' are given the kernel will first be
3451% normalized and then further scaled by the scaling factor value given.
anthony999bb2c2010-02-18 12:38:01 +00003452%
3453% Kernel normalization ('normalize_flags' given) is designed to ensure that
3454% any use of the kernel scaling factor with 'Convolve' or 'Correlate'
anthony1b2bc0a2010-05-12 05:25:22 +00003455% morphology methods will fall into -1.0 to +1.0 range. Note that for
3456% non-HDRI versions of IM this may cause images to have any negative results
3457% clipped, unless some 'bias' is used.
anthony999bb2c2010-02-18 12:38:01 +00003458%
3459% More specifically. Kernels which only contain positive values (such as a
3460% 'Gaussian' kernel) will be scaled so that those values sum to +1.0,
anthony1b2bc0a2010-05-12 05:25:22 +00003461% ensuring a 0.0 to +1.0 output range for non-HDRI images.
anthony999bb2c2010-02-18 12:38:01 +00003462%
3463% For Kernels that contain some negative values, (such as 'Sharpen' kernels)
3464% the kernel will be scaled by the absolute of the sum of kernel values, so
3465% that it will generally fall within the +/- 1.0 range.
3466%
3467% For kernels whose values sum to zero, (such as 'Laplician' kernels) kernel
3468% will be scaled by just the sum of the postive values, so that its output
3469% range will again fall into the +/- 1.0 range.
3470%
3471% For special kernels designed for locating shapes using 'Correlate', (often
3472% only containing +1 and -1 values, representing foreground/brackground
3473% matching) a special normalization method is provided to scale the positive
3474% values seperatally to those of the negative values, so the kernel will be
3475% forced to become a zero-sum kernel better suited to such searches.
3476%
anthony1b2bc0a2010-05-12 05:25:22 +00003477% WARNING: Correct normalization of the kernel assumes that the '*_range'
anthony999bb2c2010-02-18 12:38:01 +00003478% attributes within the kernel structure have been correctly set during the
3479% kernels creation.
3480%
3481% NOTE: The values used for 'normalize_flags' have been selected specifically
anthony46a369d2010-05-19 02:41:48 +00003482% to match the use of geometry options, so that '!' means NormalizeValue, '^'
3483% means CorrelateNormalizeValue. All other GeometryFlags values are ignored.
anthonycc6c8362010-01-25 04:14:01 +00003484%
anthony4fd27e22010-02-07 08:17:18 +00003485% The format of the ScaleKernelInfo method is:
anthonycc6c8362010-01-25 04:14:01 +00003486%
anthony999bb2c2010-02-18 12:38:01 +00003487% void ScaleKernelInfo(KernelInfo *kernel, const double scaling_factor,
3488% const MagickStatusType normalize_flags )
anthonycc6c8362010-01-25 04:14:01 +00003489%
3490% A description of each parameter follows:
3491%
3492% o kernel: the Morphology/Convolution kernel
3493%
anthony999bb2c2010-02-18 12:38:01 +00003494% o scaling_factor:
3495% multiply all values (after normalization) by this factor if not
3496% zero. If the kernel is normalized regardless of any flags.
3497%
3498% o normalize_flags:
3499% GeometryFlags defining normalization method to use.
3500% specifically: NormalizeValue, CorrelateNormalizeValue,
3501% and/or PercentValue
anthonycc6c8362010-01-25 04:14:01 +00003502%
3503*/
cristy6771f1e2010-03-05 19:43:39 +00003504MagickExport void ScaleKernelInfo(KernelInfo *kernel,
3505 const double scaling_factor,const GeometryFlags normalize_flags)
anthonycc6c8362010-01-25 04:14:01 +00003506{
cristy150989e2010-02-01 14:59:39 +00003507 register long
anthonycc6c8362010-01-25 04:14:01 +00003508 i;
3509
anthony999bb2c2010-02-18 12:38:01 +00003510 register double
3511 pos_scale,
3512 neg_scale;
3513
anthony46a369d2010-05-19 02:41:48 +00003514 /* do the other kernels in a multi-kernel list first */
anthony1b2bc0a2010-05-12 05:25:22 +00003515 if ( kernel->next != (KernelInfo *) NULL)
3516 ScaleKernelInfo(kernel->next, scaling_factor, normalize_flags);
3517
anthony46a369d2010-05-19 02:41:48 +00003518 /* Normalization of Kernel */
anthony999bb2c2010-02-18 12:38:01 +00003519 pos_scale = 1.0;
3520 if ( (normalize_flags&NormalizeValue) != 0 ) {
anthony999bb2c2010-02-18 12:38:01 +00003521 if ( fabs(kernel->positive_range + kernel->negative_range) > MagickEpsilon )
anthonyf4e00312010-05-20 12:06:35 +00003522 /* non-zero-summing kernel (generally positive) */
anthony999bb2c2010-02-18 12:38:01 +00003523 pos_scale = fabs(kernel->positive_range + kernel->negative_range);
anthonycc6c8362010-01-25 04:14:01 +00003524 else
anthonyf4e00312010-05-20 12:06:35 +00003525 /* zero-summing kernel */
3526 pos_scale = kernel->positive_range;
anthony999bb2c2010-02-18 12:38:01 +00003527 }
anthony46a369d2010-05-19 02:41:48 +00003528 /* Force kernel into a normalized zero-summing kernel */
anthony999bb2c2010-02-18 12:38:01 +00003529 if ( (normalize_flags&CorrelateNormalizeValue) != 0 ) {
3530 pos_scale = ( fabs(kernel->positive_range) > MagickEpsilon )
3531 ? kernel->positive_range : 1.0;
3532 neg_scale = ( fabs(kernel->negative_range) > MagickEpsilon )
3533 ? -kernel->negative_range : 1.0;
3534 }
3535 else
3536 neg_scale = pos_scale;
3537
3538 /* finialize scaling_factor for positive and negative components */
3539 pos_scale = scaling_factor/pos_scale;
3540 neg_scale = scaling_factor/neg_scale;
anthonycc6c8362010-01-25 04:14:01 +00003541
cristy150989e2010-02-01 14:59:39 +00003542 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00003543 if ( ! IsNan(kernel->values[i]) )
anthony999bb2c2010-02-18 12:38:01 +00003544 kernel->values[i] *= (kernel->values[i] >= 0) ? pos_scale : neg_scale;
anthonycc6c8362010-01-25 04:14:01 +00003545
anthony999bb2c2010-02-18 12:38:01 +00003546 /* convolution output range */
3547 kernel->positive_range *= pos_scale;
3548 kernel->negative_range *= neg_scale;
3549 /* maximum and minimum values in kernel */
3550 kernel->maximum *= (kernel->maximum >= 0.0) ? pos_scale : neg_scale;
3551 kernel->minimum *= (kernel->minimum >= 0.0) ? pos_scale : neg_scale;
3552
anthony46a369d2010-05-19 02:41:48 +00003553 /* swap kernel settings if user's scaling factor is negative */
anthony999bb2c2010-02-18 12:38:01 +00003554 if ( scaling_factor < MagickEpsilon ) {
3555 double t;
3556 t = kernel->positive_range;
3557 kernel->positive_range = kernel->negative_range;
3558 kernel->negative_range = t;
3559 t = kernel->maximum;
3560 kernel->maximum = kernel->minimum;
3561 kernel->minimum = 1;
3562 }
anthonycc6c8362010-01-25 04:14:01 +00003563
3564 return;
3565}
3566
3567/*
3568%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3569% %
3570% %
3571% %
anthony46a369d2010-05-19 02:41:48 +00003572% S h o w K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00003573% %
3574% %
3575% %
3576%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3577%
anthony4fd27e22010-02-07 08:17:18 +00003578% ShowKernelInfo() outputs the details of the given kernel defination to
3579% standard error, generally due to a users 'showkernel' option request.
anthony83ba99b2010-01-24 08:48:15 +00003580%
3581% The format of the ShowKernel method is:
3582%
anthony4fd27e22010-02-07 08:17:18 +00003583% void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00003584%
3585% A description of each parameter follows:
3586%
3587% o kernel: the Morphology/Convolution kernel
3588%
anthony83ba99b2010-01-24 08:48:15 +00003589*/
anthony4fd27e22010-02-07 08:17:18 +00003590MagickExport void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00003591{
anthony7a01dcf2010-05-11 12:25:52 +00003592 KernelInfo
3593 *k;
anthony83ba99b2010-01-24 08:48:15 +00003594
anthony43c49252010-05-18 10:59:50 +00003595 unsigned long
anthony7a01dcf2010-05-11 12:25:52 +00003596 c, i, u, v;
3597
3598 for (c=0, k=kernel; k != (KernelInfo *) NULL; c++, k=k->next ) {
3599
anthony46a369d2010-05-19 02:41:48 +00003600 fprintf(stderr, "Kernel");
anthony7a01dcf2010-05-11 12:25:52 +00003601 if ( kernel->next != (KernelInfo *) NULL )
cristye96405a2010-05-19 02:24:31 +00003602 fprintf(stderr, " #%lu", c );
anthony43c49252010-05-18 10:59:50 +00003603 fprintf(stderr, " \"%s",
3604 MagickOptionToMnemonic(MagickKernelOptions, k->type) );
3605 if ( fabs(k->angle) > MagickEpsilon )
3606 fprintf(stderr, "@%lg", k->angle);
anthonya648a302010-05-27 02:14:36 +00003607 fprintf(stderr, "\" of size %lux%lu%+ld%+ld",
anthony43c49252010-05-18 10:59:50 +00003608 k->width, k->height,
3609 k->x, k->y );
anthony7a01dcf2010-05-11 12:25:52 +00003610 fprintf(stderr,
3611 " with values from %.*lg to %.*lg\n",
3612 GetMagickPrecision(), k->minimum,
3613 GetMagickPrecision(), k->maximum);
anthony46a369d2010-05-19 02:41:48 +00003614 fprintf(stderr, "Forming a output range from %.*lg to %.*lg",
anthony7a01dcf2010-05-11 12:25:52 +00003615 GetMagickPrecision(), k->negative_range,
anthony46a369d2010-05-19 02:41:48 +00003616 GetMagickPrecision(), k->positive_range);
3617 if ( fabs(k->positive_range+k->negative_range) < MagickEpsilon )
3618 fprintf(stderr, " (Zero-Summing)\n");
3619 else if ( fabs(k->positive_range+k->negative_range-1.0) < MagickEpsilon )
3620 fprintf(stderr, " (Normalized)\n");
3621 else
3622 fprintf(stderr, " (Sum %.*lg)\n",
3623 GetMagickPrecision(), k->positive_range+k->negative_range);
anthony43c49252010-05-18 10:59:50 +00003624 for (i=v=0; v < k->height; v++) {
anthony46a369d2010-05-19 02:41:48 +00003625 fprintf(stderr, "%2lu:", v );
anthony43c49252010-05-18 10:59:50 +00003626 for (u=0; u < k->width; u++, i++)
anthony7a01dcf2010-05-11 12:25:52 +00003627 if ( IsNan(k->values[i]) )
anthonyf4e00312010-05-20 12:06:35 +00003628 fprintf(stderr," %*s", GetMagickPrecision()+3, "nan");
anthony7a01dcf2010-05-11 12:25:52 +00003629 else
anthonyf4e00312010-05-20 12:06:35 +00003630 fprintf(stderr," %*.*lg", GetMagickPrecision()+3,
anthony7a01dcf2010-05-11 12:25:52 +00003631 GetMagickPrecision(), k->values[i]);
3632 fprintf(stderr,"\n");
3633 }
anthony83ba99b2010-01-24 08:48:15 +00003634 }
3635}
anthonycc6c8362010-01-25 04:14:01 +00003636
3637/*
3638%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3639% %
3640% %
3641% %
anthony43c49252010-05-18 10:59:50 +00003642% U n i t y A d d K e r n a l I n f o %
3643% %
3644% %
3645% %
3646%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3647%
3648% UnityAddKernelInfo() Adds a given amount of the 'Unity' Convolution Kernel
3649% to the given pre-scaled and normalized Kernel. This in effect adds that
3650% amount of the original image into the resulting convolution kernel. This
3651% value is usually provided by the user as a percentage value in the
3652% 'convolve:scale' setting.
3653%
3654% The resulting effect is to either convert a 'zero-summing' edge detection
3655% kernel (such as a "Laplacian", "DOG" or a "LOG") into a 'sharpening'
3656% kernel.
3657%
3658% Alternativally by using a purely positive kernel, and using a negative
3659% post-normalizing scaling factor, you can convert a 'blurring' kernel (such
3660% as a "Gaussian") into a 'unsharp' kernel.
3661%
anthony46a369d2010-05-19 02:41:48 +00003662% The format of the UnityAdditionKernelInfo method is:
anthony43c49252010-05-18 10:59:50 +00003663%
3664% void UnityAdditionKernelInfo(KernelInfo *kernel, const double scale )
3665%
3666% A description of each parameter follows:
3667%
3668% o kernel: the Morphology/Convolution kernel
3669%
3670% o scale:
3671% scaling factor for the unity kernel to be added to
3672% the given kernel.
3673%
anthony43c49252010-05-18 10:59:50 +00003674*/
3675MagickExport void UnityAddKernelInfo(KernelInfo *kernel,
3676 const double scale)
3677{
anthony46a369d2010-05-19 02:41:48 +00003678 /* do the other kernels in a multi-kernel list first */
3679 if ( kernel->next != (KernelInfo *) NULL)
3680 UnityAddKernelInfo(kernel->next, scale);
anthony43c49252010-05-18 10:59:50 +00003681
anthony46a369d2010-05-19 02:41:48 +00003682 /* Add the scaled unity kernel to the existing kernel */
anthony43c49252010-05-18 10:59:50 +00003683 kernel->values[kernel->x+kernel->y*kernel->width] += scale;
anthony46a369d2010-05-19 02:41:48 +00003684 CalcKernelMetaData(kernel); /* recalculate the meta-data */
anthony43c49252010-05-18 10:59:50 +00003685
3686 return;
3687}
3688
3689/*
3690%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3691% %
3692% %
3693% %
3694% Z e r o K e r n e l N a n s %
anthonycc6c8362010-01-25 04:14:01 +00003695% %
3696% %
3697% %
3698%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3699%
3700% ZeroKernelNans() replaces any special 'nan' value that may be present in
3701% the kernel with a zero value. This is typically done when the kernel will
3702% be used in special hardware (GPU) convolution processors, to simply
3703% matters.
3704%
3705% The format of the ZeroKernelNans method is:
3706%
anthony46a369d2010-05-19 02:41:48 +00003707% void ZeroKernelNans (KernelInfo *kernel)
anthonycc6c8362010-01-25 04:14:01 +00003708%
3709% A description of each parameter follows:
3710%
3711% o kernel: the Morphology/Convolution kernel
3712%
anthonycc6c8362010-01-25 04:14:01 +00003713*/
anthonyc4c86e02010-01-27 09:30:32 +00003714MagickExport void ZeroKernelNans(KernelInfo *kernel)
anthonycc6c8362010-01-25 04:14:01 +00003715{
anthony43c49252010-05-18 10:59:50 +00003716 register unsigned long
anthonycc6c8362010-01-25 04:14:01 +00003717 i;
3718
anthony46a369d2010-05-19 02:41:48 +00003719 /* do the other kernels in a multi-kernel list first */
anthony1b2bc0a2010-05-12 05:25:22 +00003720 if ( kernel->next != (KernelInfo *) NULL)
3721 ZeroKernelNans(kernel->next);
3722
anthony43c49252010-05-18 10:59:50 +00003723 for (i=0; i < (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00003724 if ( IsNan(kernel->values[i]) )
3725 kernel->values[i] = 0.0;
3726
3727 return;
3728}