blob: aec901cadc4d49ba65f9140c6f4bfff648eaa834 [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
anthony3dd0f622010-05-13 12:57:32 +0000795% LineEnds
796% Find end points of lines (for pruning a skeletion)
797% LineJunctions
anthony43c49252010-05-18 10:59:50 +0000798% Find three line junctions (within a skeletion)
anthony3dd0f622010-05-13 12:57:32 +0000799% ConvexHull
800% Octagonal thicken kernel, to generate convex hulls of 45 degrees
801% Skeleton
802% Thinning kernel, which leaves behind a skeletion of a shape
anthony602ab9b2010-01-05 08:06:50 +0000803%
804% Distance Measuring Kernels
805%
anthonyc1061722010-05-14 06:23:49 +0000806% Different types of distance measuring methods, which are used with the
807% a 'Distance' morphology method for generating a gradient based on
808% distance from an edge of a binary shape, though there is a technique
809% for handling a anti-aliased shape.
810%
811% See the 'Distance' Morphological Method, for information of how it is
812% applied.
813%
anthony3dd0f622010-05-13 12:57:32 +0000814% Chebyshev:[{radius}][x{scale}[%!]]
anthonyc94cdb02010-01-06 08:15:29 +0000815% Chebyshev Distance (also known as Tchebychev Distance) is a value of
816% one to any neighbour, orthogonal or diagonal. One why of thinking of
817% it is the number of squares a 'King' or 'Queen' in chess needs to
818% traverse reach any other position on a chess board. It results in a
819% 'square' like distance function, but one where diagonals are closer
820% than expected.
anthony602ab9b2010-01-05 08:06:50 +0000821%
anthonyc1061722010-05-14 06:23:49 +0000822% Manhatten:[{radius}][x{scale}[%!]]
anthonyc94cdb02010-01-06 08:15:29 +0000823% Manhatten Distance (also known as Rectilinear Distance, or the Taxi
824% Cab metric), is the distance needed when you can only travel in
825% orthogonal (horizontal or vertical) only. It is the distance a 'Rook'
826% in chess would travel. It results in a diamond like distances, where
827% diagonals are further than expected.
anthony602ab9b2010-01-05 08:06:50 +0000828%
anthonyc1061722010-05-14 06:23:49 +0000829% Euclidean:[{radius}][x{scale}[%!]]
anthonyc94cdb02010-01-06 08:15:29 +0000830% Euclidean Distance is the 'direct' or 'as the crow flys distance.
831% However by default the kernel size only has a radius of 1, which
832% limits the distance to 'Knight' like moves, with only orthogonal and
833% diagonal measurements being correct. As such for the default kernel
834% you will get octagonal like distance function, which is reasonally
835% accurate.
836%
837% However if you use a larger radius such as "Euclidean:4" you will
838% get a much smoother distance gradient from the edge of the shape.
839% Of course a larger kernel is slower to use, and generally not needed.
840%
841% To allow the use of fractional distances that you get with diagonals
842% the actual distance is scaled by a fixed value which the user can
843% provide. This is not actually nessary for either ""Chebyshev" or
844% "Manhatten" distance kernels, but is done for all three distance
845% kernels. If no scale is provided it is set to a value of 100,
846% allowing for a maximum distance measurement of 655 pixels using a Q16
847% version of IM, from any edge. However for small images this can
848% result in quite a dark gradient.
849%
anthony602ab9b2010-01-05 08:06:50 +0000850*/
851
cristy2be15382010-01-21 02:38:03 +0000852MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000853 const GeometryInfo *args)
854{
cristy2be15382010-01-21 02:38:03 +0000855 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000856 *kernel;
857
cristy150989e2010-02-01 14:59:39 +0000858 register long
anthony602ab9b2010-01-05 08:06:50 +0000859 i;
860
861 register long
862 u,
863 v;
864
865 double
866 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
867
anthonyc1061722010-05-14 06:23:49 +0000868 /* Generate a new empty kernel if needed */
cristye96405a2010-05-19 02:24:31 +0000869 kernel=(KernelInfo *) NULL;
anthonyc1061722010-05-14 06:23:49 +0000870 switch(type) {
anthony9eb4f742010-05-18 02:45:54 +0000871 case UndefinedKernel: /* These should not be used here */
872 case UserDefinedKernel:
873 break;
874 case LaplacianKernel: /* Named Descrete Convolution Kernels */
875 case SobelKernel:
876 case RobertsKernel:
877 case PrewittKernel:
878 case CompassKernel:
879 case KirschKernel:
880 case CornersKernel: /* Hit and Miss kernels */
881 case LineEndsKernel:
882 case LineJunctionsKernel:
anthony9eb4f742010-05-18 02:45:54 +0000883 case ConvexHullKernel:
884 case SkeletonKernel:
885 /* A pre-generated kernel is not needed */
886 break;
887#if 0
anthonyc1061722010-05-14 06:23:49 +0000888 case GaussianKernel:
889 case DOGKernel:
890 case BlurKernel:
891 case DOBKernel:
892 case CometKernel:
893 case DiamondKernel:
894 case SquareKernel:
895 case RectangleKernel:
896 case DiskKernel:
897 case PlusKernel:
898 case CrossKernel:
899 case RingKernel:
900 case PeaksKernel:
901 case ChebyshevKernel:
902 case ManhattenKernel:
903 case EuclideanKernel:
anthony9eb4f742010-05-18 02:45:54 +0000904#endif
905 default:
906 /* Generate the base Kernel Structure */
anthonyc1061722010-05-14 06:23:49 +0000907 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
908 if (kernel == (KernelInfo *) NULL)
909 return(kernel);
910 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
anthony43c49252010-05-18 10:59:50 +0000911 kernel->minimum = kernel->maximum = kernel->angle = 0.0;
anthonyc1061722010-05-14 06:23:49 +0000912 kernel->negative_range = kernel->positive_range = 0.0;
913 kernel->type = type;
914 kernel->next = (KernelInfo *) NULL;
915 kernel->signature = MagickSignature;
anthonyc1061722010-05-14 06:23:49 +0000916 break;
917 }
anthony602ab9b2010-01-05 08:06:50 +0000918
919 switch(type) {
920 /* Convolution Kernels */
921 case GaussianKernel:
anthonyc1061722010-05-14 06:23:49 +0000922 case DOGKernel:
anthony9eb4f742010-05-18 02:45:54 +0000923 case LOGKernel:
anthony602ab9b2010-01-05 08:06:50 +0000924 { double
anthonyc1061722010-05-14 06:23:49 +0000925 sigma = fabs(args->sigma),
926 sigma2 = fabs(args->xi),
anthony9eb4f742010-05-18 02:45:54 +0000927 A, B, R;
anthony602ab9b2010-01-05 08:06:50 +0000928
anthonyc1061722010-05-14 06:23:49 +0000929 if ( args->rho >= 1.0 )
930 kernel->width = (unsigned long)args->rho*2+1;
anthony9eb4f742010-05-18 02:45:54 +0000931 else if ( (type != DOGKernel) || (sigma >= sigma2) )
anthonyc1061722010-05-14 06:23:49 +0000932 kernel->width = GetOptimalKernelWidth2D(args->rho,sigma);
933 else
934 kernel->width = GetOptimalKernelWidth2D(args->rho,sigma2);
935 kernel->height = kernel->width;
cristyc99304f2010-02-01 15:26:27 +0000936 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000937 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
938 kernel->height*sizeof(double));
939 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000940 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000941
anthony46a369d2010-05-19 02:41:48 +0000942 /* WARNING: The following generates a 'sampled gaussian' kernel.
anthony9eb4f742010-05-18 02:45:54 +0000943 * What we really want is a 'discrete gaussian' kernel.
anthony46a369d2010-05-19 02:41:48 +0000944 *
945 * How to do this is currently not known, but appears to be
946 * basied on the Error Function 'erf()' (intergral of a gaussian)
anthony9eb4f742010-05-18 02:45:54 +0000947 */
948
949 if ( type == GaussianKernel || type == DOGKernel )
950 { /* Calculate a Gaussian, OR positive half of a DOG */
951 if ( sigma > MagickEpsilon )
952 { A = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
953 B = 1.0/(Magick2PI*sigma*sigma);
954 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
955 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
956 kernel->values[i] = exp(-((double)(u*u+v*v))*A)*B;
957 }
958 else /* limiting case - a unity (normalized Dirac) kernel */
959 { (void) ResetMagickMemory(kernel->values,0, (size_t)
960 kernel->width*kernel->height*sizeof(double));
961 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
962 }
anthonyc1061722010-05-14 06:23:49 +0000963 }
anthony9eb4f742010-05-18 02:45:54 +0000964
anthonyc1061722010-05-14 06:23:49 +0000965 if ( type == DOGKernel )
966 { /* Subtract a Negative Gaussian for "Difference of Gaussian" */
967 if ( sigma2 > MagickEpsilon )
968 { sigma = sigma2; /* simplify loop expressions */
anthony9eb4f742010-05-18 02:45:54 +0000969 A = 1.0/(2.0*sigma*sigma);
970 B = 1.0/(Magick2PI*sigma*sigma);
anthonyc1061722010-05-14 06:23:49 +0000971 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
972 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony9eb4f742010-05-18 02:45:54 +0000973 kernel->values[i] -= exp(-((double)(u*u+v*v))*A)*B;
anthonyc1061722010-05-14 06:23:49 +0000974 }
anthony9eb4f742010-05-18 02:45:54 +0000975 else /* limiting case - a unity (normalized Dirac) kernel */
anthonyc1061722010-05-14 06:23:49 +0000976 kernel->values[kernel->x+kernel->y*kernel->width] -= 1.0;
977 }
anthony9eb4f742010-05-18 02:45:54 +0000978
979 if ( type == LOGKernel )
980 { /* Calculate a Laplacian of a Gaussian - Or Mexician Hat */
981 if ( sigma > MagickEpsilon )
982 { A = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
983 B = 1.0/(MagickPI*sigma*sigma*sigma*sigma);
984 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
985 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
986 { R = ((double)(u*u+v*v))*A;
987 kernel->values[i] = (1-R)*exp(-R)*B;
988 }
989 }
990 else /* special case - generate a unity kernel */
991 { (void) ResetMagickMemory(kernel->values,0, (size_t)
992 kernel->width*kernel->height*sizeof(double));
993 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
994 }
995 }
996
997 /* Note the above kernels may have been 'clipped' by a user defined
anthonyc1061722010-05-14 06:23:49 +0000998 ** radius, producing a smaller (darker) kernel. Also for very small
999 ** sigma's (> 0.1) the central value becomes larger than one, and thus
1000 ** producing a very bright kernel.
1001 **
1002 ** Normalization will still be needed.
1003 */
anthony602ab9b2010-01-05 08:06:50 +00001004
anthony3dd0f622010-05-13 12:57:32 +00001005 /* Normalize the 2D Gaussian Kernel
1006 **
anthonyc1061722010-05-14 06:23:49 +00001007 ** NB: a CorrelateNormalize performs a normal Normalize if
1008 ** there are no negative values.
anthony3dd0f622010-05-13 12:57:32 +00001009 */
anthony46a369d2010-05-19 02:41:48 +00001010 CalcKernelMetaData(kernel); /* the other kernel meta-data */
anthonyc1061722010-05-14 06:23:49 +00001011 ScaleKernelInfo(kernel, 1.0, CorrelateNormalizeValue);
anthony602ab9b2010-01-05 08:06:50 +00001012
1013 break;
1014 }
1015 case BlurKernel:
anthonyc1061722010-05-14 06:23:49 +00001016 case DOBKernel:
anthony602ab9b2010-01-05 08:06:50 +00001017 { double
anthonyc1061722010-05-14 06:23:49 +00001018 sigma = fabs(args->sigma),
1019 sigma2 = fabs(args->xi),
anthony9eb4f742010-05-18 02:45:54 +00001020 A, B;
anthony602ab9b2010-01-05 08:06:50 +00001021
anthonyc1061722010-05-14 06:23:49 +00001022 if ( args->rho >= 1.0 )
1023 kernel->width = (unsigned long)args->rho*2+1;
1024 else if ( (type == BlurKernel) || (sigma >= sigma2) )
1025 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
1026 else
1027 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma2);
anthony602ab9b2010-01-05 08:06:50 +00001028 kernel->height = 1;
anthonyc1061722010-05-14 06:23:49 +00001029 kernel->x = (long) (kernel->width-1)/2;
cristyc99304f2010-02-01 15:26:27 +00001030 kernel->y = 0;
1031 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +00001032 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1033 kernel->height*sizeof(double));
1034 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001035 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001036
1037#if 1
1038#define KernelRank 3
1039 /* Formula derived from GetBlurKernel() in "effect.c" (plus bug fix).
1040 ** It generates a gaussian 3 times the width, and compresses it into
1041 ** the expected range. This produces a closer normalization of the
1042 ** resulting kernel, especially for very low sigma values.
1043 ** As such while wierd it is prefered.
1044 **
1045 ** I am told this method originally came from Photoshop.
anthony9eb4f742010-05-18 02:45:54 +00001046 **
1047 ** A properly normalized curve is generated (apart from edge clipping)
1048 ** even though we later normalize the result (for edge clipping)
1049 ** to allow the correct generation of a "Difference of Blurs".
anthony602ab9b2010-01-05 08:06:50 +00001050 */
anthonyc1061722010-05-14 06:23:49 +00001051
1052 /* initialize */
cristy150989e2010-02-01 14:59:39 +00001053 v = (long) (kernel->width*KernelRank-1)/2; /* start/end points to fit range */
anthony9eb4f742010-05-18 02:45:54 +00001054 (void) ResetMagickMemory(kernel->values,0, (size_t)
1055 kernel->width*kernel->height*sizeof(double));
anthonyc1061722010-05-14 06:23:49 +00001056 /* Calculate a Positive 1D Gaussian */
1057 if ( sigma > MagickEpsilon )
1058 { sigma *= KernelRank; /* simplify loop expressions */
anthony9eb4f742010-05-18 02:45:54 +00001059 A = 1.0/(2.0*sigma*sigma);
1060 B = 1.0/(MagickSQ2PI*sigma );
anthonyc1061722010-05-14 06:23:49 +00001061 for ( u=-v; u <= v; u++) {
anthony9eb4f742010-05-18 02:45:54 +00001062 kernel->values[(u+v)/KernelRank] += exp(-((double)(u*u))*A)*B;
anthonyc1061722010-05-14 06:23:49 +00001063 }
1064 }
1065 else /* special case - generate a unity kernel */
1066 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
anthony9eb4f742010-05-18 02:45:54 +00001067
1068 /* Subtract a Second 1D Gaussian for "Difference of Blur" */
anthonyc1061722010-05-14 06:23:49 +00001069 if ( type == DOBKernel )
anthony9eb4f742010-05-18 02:45:54 +00001070 {
anthonyc1061722010-05-14 06:23:49 +00001071 if ( sigma2 > MagickEpsilon )
1072 { sigma = sigma2*KernelRank; /* simplify loop expressions */
anthony9eb4f742010-05-18 02:45:54 +00001073 A = 1.0/(2.0*sigma*sigma);
1074 B = 1.0/(MagickSQ2PI*sigma);
anthonyc1061722010-05-14 06:23:49 +00001075 for ( u=-v; u <= v; u++)
anthony9eb4f742010-05-18 02:45:54 +00001076 kernel->values[(u+v)/KernelRank] -= exp(-((double)(u*u))*A)*B;
anthonyc1061722010-05-14 06:23:49 +00001077 }
anthony9eb4f742010-05-18 02:45:54 +00001078 else /* limiting case - a unity (normalized Dirac) kernel */
anthonyc1061722010-05-14 06:23:49 +00001079 kernel->values[kernel->x+kernel->y*kernel->width] -= 1.0;
1080 }
anthony602ab9b2010-01-05 08:06:50 +00001081#else
anthonyc1061722010-05-14 06:23:49 +00001082 /* Direct calculation without curve averaging */
1083
1084 /* Calculate a Positive Gaussian */
1085 if ( sigma > MagickEpsilon )
anthony9eb4f742010-05-18 02:45:54 +00001086 { A = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
1087 B = 1.0/(MagickSQ2PI*sigma);
anthonyc1061722010-05-14 06:23:49 +00001088 for ( i=0, u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony9eb4f742010-05-18 02:45:54 +00001089 kernel->values[i] = exp(-((double)(u*u))*A)*B;
anthonyc1061722010-05-14 06:23:49 +00001090 }
1091 else /* special case - generate a unity kernel */
1092 { (void) ResetMagickMemory(kernel->values,0, (size_t)
1093 kernel->width*kernel->height*sizeof(double));
1094 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1095 }
anthony9eb4f742010-05-18 02:45:54 +00001096
1097 /* Subtract a Second 1D Gaussian for "Difference of Blur" */
anthonyc1061722010-05-14 06:23:49 +00001098 if ( type == DOBKernel )
anthony9eb4f742010-05-18 02:45:54 +00001099 {
anthonyc1061722010-05-14 06:23:49 +00001100 if ( sigma2 > MagickEpsilon )
1101 { sigma = sigma2; /* simplify loop expressions */
anthony9eb4f742010-05-18 02:45:54 +00001102 A = 1.0/(2.0*sigma*sigma);
1103 B = 1.0/(MagickSQ2PI*sigma);
anthonyc1061722010-05-14 06:23:49 +00001104 for ( i=0, u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony9eb4f742010-05-18 02:45:54 +00001105 kernel->values[i] -= exp(-((double)(u*u))*A)*B;
anthonyc1061722010-05-14 06:23:49 +00001106 }
anthony9eb4f742010-05-18 02:45:54 +00001107 else /* limiting case - a unity (normalized Dirac) kernel */
anthonyc1061722010-05-14 06:23:49 +00001108 kernel->values[kernel->x+kernel->y*kernel->width] -= 1.0;
1109 }
anthony602ab9b2010-01-05 08:06:50 +00001110#endif
anthonyc1061722010-05-14 06:23:49 +00001111 /* Note the above kernel may have been 'clipped' by a user defined
anthonycc6c8362010-01-25 04:14:01 +00001112 ** radius, producing a smaller (darker) kernel. Also for very small
1113 ** sigma's (> 0.1) the central value becomes larger than one, and thus
1114 ** producing a very bright kernel.
anthonyc1061722010-05-14 06:23:49 +00001115 **
1116 ** Normalization will still be needed.
anthony602ab9b2010-01-05 08:06:50 +00001117 */
anthonycc6c8362010-01-25 04:14:01 +00001118
anthony602ab9b2010-01-05 08:06:50 +00001119 /* Normalize the 1D Gaussian Kernel
1120 **
anthonyc1061722010-05-14 06:23:49 +00001121 ** NB: a CorrelateNormalize performs a normal Normalize if
1122 ** there are no negative values.
anthony602ab9b2010-01-05 08:06:50 +00001123 */
anthony46a369d2010-05-19 02:41:48 +00001124 CalcKernelMetaData(kernel); /* the other kernel meta-data */
1125 ScaleKernelInfo(kernel, 1.0, CorrelateNormalizeValue);
anthonycc6c8362010-01-25 04:14:01 +00001126
anthonyc1061722010-05-14 06:23:49 +00001127 /* rotate the 1D kernel by given angle */
1128 RotateKernelInfo(kernel, (type == BlurKernel) ? args->xi : args->psi );
anthony602ab9b2010-01-05 08:06:50 +00001129 break;
1130 }
1131 case CometKernel:
1132 { double
anthony9eb4f742010-05-18 02:45:54 +00001133 sigma = fabs(args->sigma),
1134 A;
anthony602ab9b2010-01-05 08:06:50 +00001135
anthony602ab9b2010-01-05 08:06:50 +00001136 if ( args->rho < 1.0 )
anthonye1cf9462010-05-19 03:50:26 +00001137 kernel->width = (GetOptimalKernelWidth1D(args->rho,sigma)-1)/2+1;
anthony602ab9b2010-01-05 08:06:50 +00001138 else
1139 kernel->width = (unsigned long)args->rho;
cristyc99304f2010-02-01 15:26:27 +00001140 kernel->x = kernel->y = 0;
anthony602ab9b2010-01-05 08:06:50 +00001141 kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +00001142 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +00001143 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1144 kernel->height*sizeof(double));
1145 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001146 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001147
anthonyc1061722010-05-14 06:23:49 +00001148 /* A comet blur is half a 1D gaussian curve, so that the object is
anthony602ab9b2010-01-05 08:06:50 +00001149 ** blurred in one direction only. This may not be quite the right
anthony3dd0f622010-05-13 12:57:32 +00001150 ** curve to use so may change in the future. The function must be
1151 ** normalised after generation, which also resolves any clipping.
anthonyc1061722010-05-14 06:23:49 +00001152 **
1153 ** As we are normalizing and not subtracting gaussians,
1154 ** there is no need for a divisor in the gaussian formula
1155 **
anthony43c49252010-05-18 10:59:50 +00001156 ** It is less comples
anthony602ab9b2010-01-05 08:06:50 +00001157 */
anthony9eb4f742010-05-18 02:45:54 +00001158 if ( sigma > MagickEpsilon )
1159 {
anthony602ab9b2010-01-05 08:06:50 +00001160#if 1
1161#define KernelRank 3
anthony9eb4f742010-05-18 02:45:54 +00001162 v = (long) kernel->width*KernelRank; /* start/end points */
1163 (void) ResetMagickMemory(kernel->values,0, (size_t)
1164 kernel->width*sizeof(double));
1165 sigma *= KernelRank; /* simplify the loop expression */
1166 A = 1.0/(2.0*sigma*sigma);
1167 /* B = 1.0/(MagickSQ2PI*sigma); */
1168 for ( u=0; u < v; u++) {
1169 kernel->values[u/KernelRank] +=
1170 exp(-((double)(u*u))*A);
1171 /* exp(-((double)(i*i))/2.0*sigma*sigma)/(MagickSQ2PI*sigma); */
1172 }
1173 for (i=0; i < (long) kernel->width; i++)
1174 kernel->positive_range += kernel->values[i];
anthony602ab9b2010-01-05 08:06:50 +00001175#else
anthony9eb4f742010-05-18 02:45:54 +00001176 A = 1.0/(2.0*sigma*sigma); /* simplify the loop expression */
1177 /* B = 1.0/(MagickSQ2PI*sigma); */
1178 for ( i=0; i < (long) kernel->width; i++)
1179 kernel->positive_range +=
1180 kernel->values[i] =
1181 exp(-((double)(i*i))*A);
1182 /* exp(-((double)(i*i))/2.0*sigma*sigma)/(MagickSQ2PI*sigma); */
anthony602ab9b2010-01-05 08:06:50 +00001183#endif
anthony9eb4f742010-05-18 02:45:54 +00001184 }
1185 else /* special case - generate a unity kernel */
1186 { (void) ResetMagickMemory(kernel->values,0, (size_t)
1187 kernel->width*kernel->height*sizeof(double));
1188 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1189 kernel->positive_range = 1.0;
1190 }
anthony46a369d2010-05-19 02:41:48 +00001191
1192 kernel->minimum = 0.0;
cristyc99304f2010-02-01 15:26:27 +00001193 kernel->maximum = kernel->values[0];
anthony46a369d2010-05-19 02:41:48 +00001194 kernel->negative_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +00001195
anthony999bb2c2010-02-18 12:38:01 +00001196 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
1197 RotateKernelInfo(kernel, args->xi); /* Rotate by angle */
anthony602ab9b2010-01-05 08:06:50 +00001198 break;
1199 }
anthonyc1061722010-05-14 06:23:49 +00001200
anthony3c10fc82010-05-13 02:40:51 +00001201 /* Convolution Kernels - Well Known Constants */
anthony3c10fc82010-05-13 02:40:51 +00001202 case LaplacianKernel:
anthonye2a60ce2010-05-19 12:30:40 +00001203 { switch ( (int) args->rho ) {
anthony3dd0f622010-05-13 12:57:32 +00001204 case 0:
anthony9eb4f742010-05-18 02:45:54 +00001205 default: /* laplacian square filter -- default */
anthonyc1061722010-05-14 06:23:49 +00001206 kernel=ParseKernelArray("3: -1,-1,-1 -1,8,-1 -1,-1,-1");
anthony3dd0f622010-05-13 12:57:32 +00001207 break;
anthony9eb4f742010-05-18 02:45:54 +00001208 case 1: /* laplacian diamond filter */
anthonyc1061722010-05-14 06:23:49 +00001209 kernel=ParseKernelArray("3: 0,-1,0 -1,4,-1 0,-1,0");
anthony3c10fc82010-05-13 02:40:51 +00001210 break;
1211 case 2:
anthony9eb4f742010-05-18 02:45:54 +00001212 kernel=ParseKernelArray("3: -2,1,-2 1,4,1 -2,1,-2");
1213 break;
1214 case 3:
anthonyc1061722010-05-14 06:23:49 +00001215 kernel=ParseKernelArray("3: 1,-2,1 -2,4,-2 1,-2,1");
anthony3c10fc82010-05-13 02:40:51 +00001216 break;
anthony9eb4f742010-05-18 02:45:54 +00001217 case 5: /* a 5x5 laplacian */
anthony3c10fc82010-05-13 02:40:51 +00001218 kernel=ParseKernelArray(
anthony9eb4f742010-05-18 02:45:54 +00001219 "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 +00001220 break;
anthony9eb4f742010-05-18 02:45:54 +00001221 case 7: /* a 7x7 laplacian */
anthony3c10fc82010-05-13 02:40:51 +00001222 kernel=ParseKernelArray(
anthonyc1061722010-05-14 06:23:49 +00001223 "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 +00001224 break;
anthony43c49252010-05-18 10:59:50 +00001225 case 15: /* a 5x5 LOG (sigma approx 1.4) */
anthony9eb4f742010-05-18 02:45:54 +00001226 kernel=ParseKernelArray(
1227 "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");
1228 break;
anthony43c49252010-05-18 10:59:50 +00001229 case 19: /* a 9x9 LOG (sigma approx 1.4) */
1230 /* http://www.cscjournals.org/csc/manuscript/Journals/IJIP/volume3/Issue1/IJIP-15.pdf */
1231 kernel=ParseKernelArray(
1232 "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");
1233 break;
anthony3c10fc82010-05-13 02:40:51 +00001234 }
1235 if (kernel == (KernelInfo *) NULL)
1236 return(kernel);
1237 kernel->type = type;
1238 break;
1239 }
anthonyc1061722010-05-14 06:23:49 +00001240 case SobelKernel:
anthony602ab9b2010-01-05 08:06:50 +00001241 {
anthonyc1061722010-05-14 06:23:49 +00001242 kernel=ParseKernelArray("3: -1,0,1 -2,0,2 -1,0,1");
1243 if (kernel == (KernelInfo *) NULL)
1244 return(kernel);
1245 kernel->type = type;
1246 RotateKernelInfo(kernel, args->rho); /* Rotate by angle */
1247 break;
1248 }
1249 case RobertsKernel:
1250 {
1251 kernel=ParseKernelArray("3: 0,0,0 -1,1,0 0,0,0");
1252 if (kernel == (KernelInfo *) NULL)
1253 return(kernel);
1254 kernel->type = type;
anthony46a369d2010-05-19 02:41:48 +00001255 RotateKernelInfo(kernel, args->rho);
anthonyc1061722010-05-14 06:23:49 +00001256 break;
1257 }
1258 case PrewittKernel:
1259 {
1260 kernel=ParseKernelArray("3: -1,1,1 0,0,0 -1,1,1");
1261 if (kernel == (KernelInfo *) NULL)
1262 return(kernel);
1263 kernel->type = type;
anthony46a369d2010-05-19 02:41:48 +00001264 RotateKernelInfo(kernel, args->rho);
anthonyc1061722010-05-14 06:23:49 +00001265 break;
1266 }
1267 case CompassKernel:
1268 {
1269 kernel=ParseKernelArray("3: -1,1,1 -1,-2,1 -1,1,1");
1270 if (kernel == (KernelInfo *) NULL)
1271 return(kernel);
1272 kernel->type = type;
anthony46a369d2010-05-19 02:41:48 +00001273 RotateKernelInfo(kernel, args->rho);
anthonyc1061722010-05-14 06:23:49 +00001274 break;
1275 }
anthony9eb4f742010-05-18 02:45:54 +00001276 case KirschKernel:
1277 {
1278 kernel=ParseKernelArray("3: -3,-3,5 -3,0,5 -3,-3,5");
1279 if (kernel == (KernelInfo *) NULL)
1280 return(kernel);
1281 kernel->type = type;
anthony46a369d2010-05-19 02:41:48 +00001282 RotateKernelInfo(kernel, args->rho);
anthony9eb4f742010-05-18 02:45:54 +00001283 break;
1284 }
anthonye2a60ce2010-05-19 12:30:40 +00001285 case FreiChenKernel:
anthony6915d062010-05-19 12:45:51 +00001286 /* http://www.math.tau.ac.il/~turkel/notes/edge_detectors.pdf */
1287 /* http://ltswww.epfl.ch/~courstiv/exos_labos/sol3.pdf */
anthonye2a60ce2010-05-19 12:30:40 +00001288 { switch ( (int) args->rho ) {
1289 default:
1290 case 1:
1291 kernel=ParseKernelArray("3: 1,2,1 0,0,0 -1,2,-1");
1292 if (kernel == (KernelInfo *) NULL)
1293 return(kernel);
1294 kernel->values[1] = +MagickSQ2;
1295 kernel->values[7] = -MagickSQ2;
1296 CalcKernelMetaData(kernel); /* recalculate meta-data */
1297 ScaleKernelInfo(kernel, 1.0/2.0*MagickSQ2, NoValue);
1298 break;
1299 case 2:
1300 kernel=ParseKernelArray("3: 1,0,1 2,0,2 1,0,1");
1301 if (kernel == (KernelInfo *) NULL)
1302 return(kernel);
1303 kernel->values[3] = +MagickSQ2;
1304 kernel->values[5] = +MagickSQ2;
1305 CalcKernelMetaData(kernel);
1306 ScaleKernelInfo(kernel, 1.0/2.0*MagickSQ2, NoValue);
1307 break;
1308 case 3:
1309 kernel=ParseKernelArray("3: 0,-1,2 1,0,-1 -2,1,0");
1310 if (kernel == (KernelInfo *) NULL)
1311 return(kernel);
1312 kernel->values[2] = +MagickSQ2;
1313 kernel->values[6] = -MagickSQ2;
1314 CalcKernelMetaData(kernel);
1315 ScaleKernelInfo(kernel, 1.0/2.0*MagickSQ2, NoValue);
1316 break;
1317 case 4:
anthony6915d062010-05-19 12:45:51 +00001318 kernel=ParseKernelArray("3: 2,-1,0 -1,0,1 0,1,-2");
anthonye2a60ce2010-05-19 12:30:40 +00001319 if (kernel == (KernelInfo *) NULL)
1320 return(kernel);
1321 kernel->values[0] = +MagickSQ2;
1322 kernel->values[8] = -MagickSQ2;
1323 CalcKernelMetaData(kernel);
1324 ScaleKernelInfo(kernel, 1.0/2.0*MagickSQ2, NoValue);
1325 break;
1326 case 5:
1327 kernel=ParseKernelArray("3: 0,1,0 -1,0,-1 0,1,0");
1328 if (kernel == (KernelInfo *) NULL)
1329 return(kernel);
1330 ScaleKernelInfo(kernel, 1.0/2.0, NoValue);
1331 break;
1332 case 6:
1333 kernel=ParseKernelArray("3: -1,0,1 0,0,0 1,0,-1");
1334 if (kernel == (KernelInfo *) NULL)
1335 return(kernel);
1336 ScaleKernelInfo(kernel, 1.0/2.0, NoValue);
1337 break;
1338 case 7:
anthonyf4e00312010-05-20 12:06:35 +00001339 kernel=ParseKernelArray("3: 1,-2,1 -2,4,-2 1,-2,1");
anthonye2a60ce2010-05-19 12:30:40 +00001340 if (kernel == (KernelInfo *) NULL)
1341 return(kernel);
1342 ScaleKernelInfo(kernel, 1.0/6.0, NoValue);
1343 break;
1344 case 8:
1345 kernel=ParseKernelArray("3: -2,1,-2 1,4,1 -2,1,-2");
1346 if (kernel == (KernelInfo *) NULL)
1347 return(kernel);
1348 ScaleKernelInfo(kernel, 1.0/6.0, NoValue);
1349 break;
1350 case 9:
anthony6915d062010-05-19 12:45:51 +00001351 kernel=ParseKernelName("3: 1,1,1 1,1,1 1,1,1");
anthonye2a60ce2010-05-19 12:30:40 +00001352 if (kernel == (KernelInfo *) NULL)
1353 return(kernel);
1354 ScaleKernelInfo(kernel, 1.0/3.0, NoValue);
1355 break;
1356 }
1357 RotateKernelInfo(kernel, args->sigma); /* Rotate by angle */
1358 break;
1359 }
1360
anthonyc1061722010-05-14 06:23:49 +00001361 /* Boolean Kernels */
1362 case DiamondKernel:
1363 {
1364 if (args->rho < 1.0)
1365 kernel->width = kernel->height = 3; /* default radius = 1 */
1366 else
1367 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
1368 kernel->x = kernel->y = (long) (kernel->width-1)/2;
1369
1370 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1371 kernel->height*sizeof(double));
1372 if (kernel->values == (double *) NULL)
1373 return(DestroyKernelInfo(kernel));
1374
1375 /* set all kernel values within diamond area to scale given */
1376 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1377 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1378 if ((labs(u)+labs(v)) <= (long)kernel->x)
1379 kernel->positive_range += kernel->values[i] = args->sigma;
1380 else
1381 kernel->values[i] = nan;
1382 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1383 break;
1384 }
1385 case SquareKernel:
1386 case RectangleKernel:
1387 { double
1388 scale;
anthony602ab9b2010-01-05 08:06:50 +00001389 if ( type == SquareKernel )
1390 {
1391 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001392 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001393 else
cristy150989e2010-02-01 14:59:39 +00001394 kernel->width = kernel->height = (unsigned long) (2*args->rho+1);
cristyc99304f2010-02-01 15:26:27 +00001395 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony4fd27e22010-02-07 08:17:18 +00001396 scale = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +00001397 }
1398 else {
cristy2be15382010-01-21 02:38:03 +00001399 /* NOTE: user defaults set in "AcquireKernelInfo()" */
anthony602ab9b2010-01-05 08:06:50 +00001400 if ( args->rho < 1.0 || args->sigma < 1.0 )
anthony83ba99b2010-01-24 08:48:15 +00001401 return(DestroyKernelInfo(kernel)); /* invalid args given */
anthony602ab9b2010-01-05 08:06:50 +00001402 kernel->width = (unsigned long)args->rho;
1403 kernel->height = (unsigned long)args->sigma;
1404 if ( args->xi < 0.0 || args->xi > (double)kernel->width ||
1405 args->psi < 0.0 || args->psi > (double)kernel->height )
anthony83ba99b2010-01-24 08:48:15 +00001406 return(DestroyKernelInfo(kernel)); /* invalid args given */
cristyc99304f2010-02-01 15:26:27 +00001407 kernel->x = (long) args->xi;
1408 kernel->y = (long) args->psi;
anthony4fd27e22010-02-07 08:17:18 +00001409 scale = 1.0;
anthony602ab9b2010-01-05 08:06:50 +00001410 }
1411 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1412 kernel->height*sizeof(double));
1413 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001414 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001415
anthony3dd0f622010-05-13 12:57:32 +00001416 /* set all kernel values to scale given */
cristy150989e2010-02-01 14:59:39 +00001417 u=(long) kernel->width*kernel->height;
1418 for ( i=0; i < u; i++)
anthony4fd27e22010-02-07 08:17:18 +00001419 kernel->values[i] = scale;
1420 kernel->minimum = kernel->maximum = scale; /* a flat shape */
1421 kernel->positive_range = scale*u;
anthonycc6c8362010-01-25 04:14:01 +00001422 break;
anthony602ab9b2010-01-05 08:06:50 +00001423 }
anthony602ab9b2010-01-05 08:06:50 +00001424 case DiskKernel:
1425 {
anthonyc1061722010-05-14 06:23:49 +00001426 long limit = (long)(args->rho*args->rho);
anthony83ba99b2010-01-24 08:48:15 +00001427 if (args->rho < 0.1) /* default radius approx 3.5 */
1428 kernel->width = kernel->height = 7L, limit = 10L;
anthony602ab9b2010-01-05 08:06:50 +00001429 else
1430 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001431 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001432
1433 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1434 kernel->height*sizeof(double));
1435 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001436 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001437
anthony3dd0f622010-05-13 12:57:32 +00001438 /* set all kernel values within disk area to scale given */
1439 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
cristyc99304f2010-02-01 15:26:27 +00001440 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony602ab9b2010-01-05 08:06:50 +00001441 if ((u*u+v*v) <= limit)
anthony4fd27e22010-02-07 08:17:18 +00001442 kernel->positive_range += kernel->values[i] = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +00001443 else
1444 kernel->values[i] = nan;
anthony4fd27e22010-02-07 08:17:18 +00001445 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
anthony602ab9b2010-01-05 08:06:50 +00001446 break;
1447 }
1448 case PlusKernel:
1449 {
1450 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001451 kernel->width = kernel->height = 5; /* default radius 2 */
anthony602ab9b2010-01-05 08:06:50 +00001452 else
1453 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001454 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001455
1456 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1457 kernel->height*sizeof(double));
1458 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001459 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001460
anthony3dd0f622010-05-13 12:57:32 +00001461 /* set all kernel values along axises to given scale */
cristyc99304f2010-02-01 15:26:27 +00001462 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1463 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony4fd27e22010-02-07 08:17:18 +00001464 kernel->values[i] = (u == 0 || v == 0) ? args->sigma : nan;
1465 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1466 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
anthony602ab9b2010-01-05 08:06:50 +00001467 break;
1468 }
anthony3dd0f622010-05-13 12:57:32 +00001469 case CrossKernel:
1470 {
1471 if (args->rho < 1.0)
1472 kernel->width = kernel->height = 5; /* default radius 2 */
1473 else
1474 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
1475 kernel->x = kernel->y = (long) (kernel->width-1)/2;
1476
1477 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1478 kernel->height*sizeof(double));
1479 if (kernel->values == (double *) NULL)
1480 return(DestroyKernelInfo(kernel));
1481
1482 /* set all kernel values along axises to given scale */
1483 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1484 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1485 kernel->values[i] = (u == v || u == -v) ? args->sigma : nan;
1486 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1487 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
1488 break;
1489 }
1490 /* HitAndMiss Kernels */
anthonyc1061722010-05-14 06:23:49 +00001491 case RingKernel:
anthony3dd0f622010-05-13 12:57:32 +00001492 case PeaksKernel:
1493 {
1494 long
1495 limit1,
anthonyc1061722010-05-14 06:23:49 +00001496 limit2,
1497 scale;
anthony3dd0f622010-05-13 12:57:32 +00001498
1499 if (args->rho < args->sigma)
1500 {
1501 kernel->width = ((unsigned long)args->sigma)*2+1;
1502 limit1 = (long)args->rho*args->rho;
1503 limit2 = (long)args->sigma*args->sigma;
1504 }
1505 else
1506 {
1507 kernel->width = ((unsigned long)args->rho)*2+1;
1508 limit1 = (long)args->sigma*args->sigma;
1509 limit2 = (long)args->rho*args->rho;
1510 }
anthonyc1061722010-05-14 06:23:49 +00001511 if ( limit2 <= 0 )
1512 kernel->width = 7L, limit1 = 7L, limit2 = 11L;
1513
anthony3dd0f622010-05-13 12:57:32 +00001514 kernel->height = kernel->width;
1515 kernel->x = kernel->y = (long) (kernel->width-1)/2;
1516 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1517 kernel->height*sizeof(double));
1518 if (kernel->values == (double *) NULL)
1519 return(DestroyKernelInfo(kernel));
1520
anthonyc1061722010-05-14 06:23:49 +00001521 /* set a ring of points of 'scale' ( 0.0 for PeaksKernel ) */
cristye96405a2010-05-19 02:24:31 +00001522 scale = (long) (( type == PeaksKernel) ? 0.0 : args->xi);
anthony3dd0f622010-05-13 12:57:32 +00001523 for ( i=0, v= -kernel->y; v <= (long)kernel->y; v++)
1524 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1525 { long radius=u*u+v*v;
anthonyc1061722010-05-14 06:23:49 +00001526 if (limit1 < radius && radius <= limit2)
cristye96405a2010-05-19 02:24:31 +00001527 kernel->positive_range += kernel->values[i] = (double) scale;
anthony3dd0f622010-05-13 12:57:32 +00001528 else
1529 kernel->values[i] = nan;
1530 }
cristye96405a2010-05-19 02:24:31 +00001531 kernel->minimum = kernel->minimum = (double) scale;
anthonyc1061722010-05-14 06:23:49 +00001532 if ( type == PeaksKernel ) {
1533 /* set the central point in the middle */
1534 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1535 kernel->positive_range = 1.0;
1536 kernel->maximum = 1.0;
1537 }
anthony3dd0f622010-05-13 12:57:32 +00001538 break;
1539 }
anthony43c49252010-05-18 10:59:50 +00001540 case EdgesKernel:
1541 {
1542 kernel=ParseKernelArray("3: 0,0,0 -,1,- 1,1,1");
1543 if (kernel == (KernelInfo *) NULL)
1544 return(kernel);
1545 kernel->type = type;
1546 ExpandKernelInfo(kernel, 90.0); /* Create a list of 4 rotated kernels */
1547 break;
1548 }
anthony3dd0f622010-05-13 12:57:32 +00001549 case CornersKernel:
1550 {
anthony4f1dcb72010-05-14 08:43:10 +00001551 kernel=ParseKernelArray("3: 0,0,- 0,1,1 -,1,-");
anthony3dd0f622010-05-13 12:57:32 +00001552 if (kernel == (KernelInfo *) NULL)
1553 return(kernel);
1554 kernel->type = type;
1555 ExpandKernelInfo(kernel, 90.0); /* Create a list of 4 rotated kernels */
1556 break;
1557 }
anthony47f5d062010-05-23 07:47:50 +00001558 case RidgesKernel:
1559 {
1560 kernel=ParseKernelArray("3: -,-,- 0,1,0 -,-,-");
1561 if (kernel == (KernelInfo *) NULL)
1562 return(kernel);
1563 kernel->type = type;
1564 ExpandKernelInfo(kernel, 45.0); /* 4 rotated kernels (symmetrical) */
1565 break;
1566 }
anthony1d45eb92010-05-25 11:13:23 +00001567 case Ridges2Kernel:
1568 {
1569 KernelInfo
1570 *new_kernel;
1571 kernel=ParseKernelArray("4x1^:0,1,1,0");
1572 if (kernel == (KernelInfo *) NULL)
1573 return(kernel);
1574 kernel->type = type;
1575 ExpandKernelInfo(kernel, 90.0); /* 4 rotated kernels */
1576 /* append second set of 4 kernels */
1577 new_kernel=ParseKernelArray("4x4^:0,-,-,- -,1,-,- -,-,1,- -,-,-,0'");
1578 if (new_kernel == (KernelInfo *) NULL)
1579 return(DestroyKernelInfo(kernel));
1580 new_kernel->type = type;
1581 ExpandKernelInfo(new_kernel, 90.0); /* 4 rotated kernels */
1582 LastKernelInfo(kernel)->next = new_kernel;
1583 break;
1584 break;
1585 }
anthony3dd0f622010-05-13 12:57:32 +00001586 case LineEndsKernel:
1587 {
anthony43c49252010-05-18 10:59:50 +00001588 KernelInfo
1589 *new_kernel;
1590 kernel=ParseKernelArray("3: 0,0,0 0,1,0 -,1,-");
anthony3dd0f622010-05-13 12:57:32 +00001591 if (kernel == (KernelInfo *) NULL)
1592 return(kernel);
1593 kernel->type = type;
anthony43c49252010-05-18 10:59:50 +00001594 ExpandKernelInfo(kernel, 90.0);
1595 /* append second set of 4 kernels */
1596 new_kernel=ParseKernelArray("3: 0,0,0 0,1,0 0,0,1");
1597 if (new_kernel == (KernelInfo *) NULL)
1598 return(DestroyKernelInfo(kernel));
1599 new_kernel->type = type;
1600 ExpandKernelInfo(new_kernel, 90.0);
1601 LastKernelInfo(kernel)->next = new_kernel;
anthony3dd0f622010-05-13 12:57:32 +00001602 break;
1603 }
1604 case LineJunctionsKernel:
1605 {
1606 KernelInfo
1607 *new_kernel;
anthony3dd0f622010-05-13 12:57:32 +00001608 /* first set of 4 kernels */
anthony4f1dcb72010-05-14 08:43:10 +00001609 kernel=ParseKernelArray("3: -,1,- -,1,- 1,-,1");
anthony3dd0f622010-05-13 12:57:32 +00001610 if (kernel == (KernelInfo *) NULL)
1611 return(kernel);
1612 kernel->type = type;
anthony43c49252010-05-18 10:59:50 +00001613 ExpandKernelInfo(kernel, 45.0);
anthony3dd0f622010-05-13 12:57:32 +00001614 /* append second set of 4 kernels */
anthony4f1dcb72010-05-14 08:43:10 +00001615 new_kernel=ParseKernelArray("3: 1,-,- -,1,- 1,-,1");
anthony3dd0f622010-05-13 12:57:32 +00001616 if (new_kernel == (KernelInfo *) NULL)
1617 return(DestroyKernelInfo(kernel));
anthony43c49252010-05-18 10:59:50 +00001618 new_kernel->type = type;
anthony3dd0f622010-05-13 12:57:32 +00001619 ExpandKernelInfo(new_kernel, 90.0);
1620 LastKernelInfo(kernel)->next = new_kernel;
anthony4f1dcb72010-05-14 08:43:10 +00001621 break;
1622 }
anthony3dd0f622010-05-13 12:57:32 +00001623 case ConvexHullKernel:
1624 {
1625 KernelInfo
1626 *new_kernel;
anthony3dd0f622010-05-13 12:57:32 +00001627 /* first set of 4 kernels */
anthony4f1dcb72010-05-14 08:43:10 +00001628 kernel=ParseKernelArray("3: 1,1,- 1,0,- 1,-,0");
anthony3dd0f622010-05-13 12:57:32 +00001629 if (kernel == (KernelInfo *) NULL)
1630 return(kernel);
1631 kernel->type = type;
1632 ExpandKernelInfo(kernel, 90.0);
1633 /* append second set of 4 kernels */
anthony43c49252010-05-18 10:59:50 +00001634 new_kernel=ParseKernelArray("3: 1,1,1 1,0,0 -,-,0");
anthony3dd0f622010-05-13 12:57:32 +00001635 if (new_kernel == (KernelInfo *) NULL)
1636 return(DestroyKernelInfo(kernel));
anthony43c49252010-05-18 10:59:50 +00001637 new_kernel->type = type;
anthony3dd0f622010-05-13 12:57:32 +00001638 ExpandKernelInfo(new_kernel, 90.0);
1639 LastKernelInfo(kernel)->next = new_kernel;
1640 break;
1641 }
anthony47f5d062010-05-23 07:47:50 +00001642 case SkeletonKernel:
1643 { /* what is the best form for medial axis skeletonization? */
1644#if 0
1645# if 0
1646 kernel=AcquireKernelInfo("Corners;Edges");
1647# else
1648 kernel=AcquireKernelInfo("Edges;Corners");
1649# endif
1650#else
anthony43c49252010-05-18 10:59:50 +00001651 kernel=ParseKernelArray("3: 0,0,- 0,1,1 -,1,1");
anthony3dd0f622010-05-13 12:57:32 +00001652 if (kernel == (KernelInfo *) NULL)
1653 return(kernel);
1654 kernel->type = type;
anthony43c49252010-05-18 10:59:50 +00001655 ExpandKernelInfo(kernel, 45);
1656 break;
anthony47f5d062010-05-23 07:47:50 +00001657#endif
anthony3dd0f622010-05-13 12:57:32 +00001658 break;
1659 }
anthony602ab9b2010-01-05 08:06:50 +00001660 /* Distance Measuring Kernels */
1661 case ChebyshevKernel:
1662 {
anthony602ab9b2010-01-05 08:06:50 +00001663 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001664 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001665 else
1666 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001667 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001668
1669 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1670 kernel->height*sizeof(double));
1671 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001672 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001673
cristyc99304f2010-02-01 15:26:27 +00001674 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1675 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1676 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001677 args->sigma*((labs(u)>labs(v)) ? labs(u) : labs(v)) );
cristyc99304f2010-02-01 15:26:27 +00001678 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001679 break;
1680 }
1681 case ManhattenKernel:
1682 {
anthony602ab9b2010-01-05 08:06:50 +00001683 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001684 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001685 else
1686 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001687 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001688
1689 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1690 kernel->height*sizeof(double));
1691 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001692 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001693
cristyc99304f2010-02-01 15:26:27 +00001694 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1695 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1696 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001697 args->sigma*(labs(u)+labs(v)) );
cristyc99304f2010-02-01 15:26:27 +00001698 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001699 break;
1700 }
1701 case EuclideanKernel:
1702 {
anthony602ab9b2010-01-05 08:06:50 +00001703 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001704 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001705 else
1706 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001707 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001708
1709 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1710 kernel->height*sizeof(double));
1711 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001712 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001713
cristyc99304f2010-02-01 15:26:27 +00001714 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1715 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1716 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001717 args->sigma*sqrt((double)(u*u+v*v)) );
cristyc99304f2010-02-01 15:26:27 +00001718 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001719 break;
1720 }
anthony46a369d2010-05-19 02:41:48 +00001721 case UnityKernel:
anthony602ab9b2010-01-05 08:06:50 +00001722 default:
anthonyc1061722010-05-14 06:23:49 +00001723 {
anthony46a369d2010-05-19 02:41:48 +00001724 /* Unity or No-Op Kernel - 3x3 with 1 in center */
1725 kernel=ParseKernelArray("3:0,0,0,0,1,0,0,0,0");
anthonyc1061722010-05-14 06:23:49 +00001726 if (kernel == (KernelInfo *) NULL)
1727 return(kernel);
anthony46a369d2010-05-19 02:41:48 +00001728 kernel->type = ( type == UnityKernel ) ? UnityKernel : UndefinedKernel;
anthonyc1061722010-05-14 06:23:49 +00001729 break;
1730 }
anthony602ab9b2010-01-05 08:06:50 +00001731 break;
1732 }
1733
1734 return(kernel);
1735}
anthonyc94cdb02010-01-06 08:15:29 +00001736
anthony602ab9b2010-01-05 08:06:50 +00001737/*
1738%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1739% %
1740% %
1741% %
cristy6771f1e2010-03-05 19:43:39 +00001742% C l o n e K e r n e l I n f o %
anthony4fd27e22010-02-07 08:17:18 +00001743% %
1744% %
1745% %
1746%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1747%
anthony1b2bc0a2010-05-12 05:25:22 +00001748% CloneKernelInfo() creates a new clone of the given Kernel List so that its
1749% can be modified without effecting the original. The cloned kernel should
1750% be destroyed using DestoryKernelInfo() when no longer needed.
anthony7a01dcf2010-05-11 12:25:52 +00001751%
cristye6365592010-04-02 17:31:23 +00001752% The format of the CloneKernelInfo method is:
anthony4fd27e22010-02-07 08:17:18 +00001753%
anthony930be612010-02-08 04:26:15 +00001754% KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +00001755%
1756% A description of each parameter follows:
1757%
1758% o kernel: the Morphology/Convolution kernel to be cloned
1759%
1760*/
cristyef656912010-03-05 19:54:59 +00001761MagickExport KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +00001762{
1763 register long
1764 i;
1765
cristy19eb6412010-04-23 14:42:29 +00001766 KernelInfo
anthony7a01dcf2010-05-11 12:25:52 +00001767 *new_kernel;
anthony4fd27e22010-02-07 08:17:18 +00001768
1769 assert(kernel != (KernelInfo *) NULL);
anthony7a01dcf2010-05-11 12:25:52 +00001770 new_kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
1771 if (new_kernel == (KernelInfo *) NULL)
1772 return(new_kernel);
1773 *new_kernel=(*kernel); /* copy values in structure */
anthony7a01dcf2010-05-11 12:25:52 +00001774
1775 /* replace the values with a copy of the values */
1776 new_kernel->values=(double *) AcquireQuantumMemory(kernel->width,
cristy19eb6412010-04-23 14:42:29 +00001777 kernel->height*sizeof(double));
anthony7a01dcf2010-05-11 12:25:52 +00001778 if (new_kernel->values == (double *) NULL)
1779 return(DestroyKernelInfo(new_kernel));
anthony4fd27e22010-02-07 08:17:18 +00001780 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthony7a01dcf2010-05-11 12:25:52 +00001781 new_kernel->values[i]=kernel->values[i];
anthony1b2bc0a2010-05-12 05:25:22 +00001782
1783 /* Also clone the next kernel in the kernel list */
1784 if ( kernel->next != (KernelInfo *) NULL ) {
1785 new_kernel->next = CloneKernelInfo(kernel->next);
1786 if ( new_kernel->next == (KernelInfo *) NULL )
1787 return(DestroyKernelInfo(new_kernel));
1788 }
1789
anthony7a01dcf2010-05-11 12:25:52 +00001790 return(new_kernel);
anthony4fd27e22010-02-07 08:17:18 +00001791}
1792
1793/*
1794%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1795% %
1796% %
1797% %
anthony83ba99b2010-01-24 08:48:15 +00001798% D e s t r o y K e r n e l I n f o %
anthony602ab9b2010-01-05 08:06:50 +00001799% %
1800% %
1801% %
1802%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1803%
anthony83ba99b2010-01-24 08:48:15 +00001804% DestroyKernelInfo() frees the memory used by a Convolution/Morphology
1805% kernel.
anthony602ab9b2010-01-05 08:06:50 +00001806%
anthony83ba99b2010-01-24 08:48:15 +00001807% The format of the DestroyKernelInfo method is:
anthony602ab9b2010-01-05 08:06:50 +00001808%
anthony83ba99b2010-01-24 08:48:15 +00001809% KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +00001810%
1811% A description of each parameter follows:
1812%
1813% o kernel: the Morphology/Convolution kernel to be destroyed
1814%
1815*/
1816
anthony83ba99b2010-01-24 08:48:15 +00001817MagickExport KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +00001818{
cristy2be15382010-01-21 02:38:03 +00001819 assert(kernel != (KernelInfo *) NULL);
anthony4fd27e22010-02-07 08:17:18 +00001820
anthony7a01dcf2010-05-11 12:25:52 +00001821 if ( kernel->next != (KernelInfo *) NULL )
1822 kernel->next = DestroyKernelInfo(kernel->next);
1823
1824 kernel->values = (double *)RelinquishMagickMemory(kernel->values);
1825 kernel = (KernelInfo *) RelinquishMagickMemory(kernel);
anthony602ab9b2010-01-05 08:06:50 +00001826 return(kernel);
1827}
anthonyc94cdb02010-01-06 08:15:29 +00001828
1829/*
1830%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1831% %
1832% %
1833% %
anthony3c10fc82010-05-13 02:40:51 +00001834% E x p a n d K e r n e l I n f o %
1835% %
1836% %
1837% %
1838%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1839%
1840% ExpandKernelInfo() takes a single kernel, and expands it into a list
1841% of kernels each incrementally rotated the angle given.
1842%
1843% WARNING: 45 degree rotations only works for 3x3 kernels.
1844% While 90 degree roatations only works for linear and square kernels
1845%
1846% The format of the RotateKernelInfo method is:
1847%
1848% void ExpandKernelInfo(KernelInfo *kernel, double angle)
1849%
1850% A description of each parameter follows:
1851%
1852% o kernel: the Morphology/Convolution kernel
1853%
1854% o angle: angle to rotate in degrees
1855%
1856% This function is only internel to this module, as it is not finalized,
1857% especially with regard to non-orthogonal angles, and rotation of larger
1858% 2D kernels.
1859*/
anthony47f5d062010-05-23 07:47:50 +00001860
1861/* Internal Routine - Return true if two kernels are the same */
1862static MagickBooleanType SameKernelInfo(const KernelInfo *kernel1,
1863 const KernelInfo *kernel2)
1864{
1865 register unsigned long
1866 i;
anthony1d45eb92010-05-25 11:13:23 +00001867
1868 /* check size and origin location */
1869 if ( kernel1->width != kernel2->width
1870 || kernel1->height != kernel2->height
1871 || kernel1->x != kernel2->x
1872 || kernel1->y != kernel2->y )
anthony47f5d062010-05-23 07:47:50 +00001873 return MagickFalse;
anthony1d45eb92010-05-25 11:13:23 +00001874
1875 /* check actual kernel values */
anthony47f5d062010-05-23 07:47:50 +00001876 for (i=0; i < (kernel1->width*kernel1->height); i++) {
anthony1d45eb92010-05-25 11:13:23 +00001877 /* Test for Nan equivelence */
anthony47f5d062010-05-23 07:47:50 +00001878 if ( IsNan(kernel1->values[i]) && !IsNan(kernel2->values[i]) )
1879 return MagickFalse;
1880 if ( IsNan(kernel2->values[i]) && !IsNan(kernel1->values[i]) )
1881 return MagickFalse;
anthony1d45eb92010-05-25 11:13:23 +00001882 /* Test actual values are equivelent */
anthony47f5d062010-05-23 07:47:50 +00001883 if ( fabs(kernel1->values[i] - kernel2->values[i]) > MagickEpsilon )
1884 return MagickFalse;
1885 }
anthony1d45eb92010-05-25 11:13:23 +00001886
anthony47f5d062010-05-23 07:47:50 +00001887 return MagickTrue;
1888}
1889
1890static void ExpandKernelInfo(KernelInfo *kernel, const double angle)
anthony3c10fc82010-05-13 02:40:51 +00001891{
1892 KernelInfo
cristy84d9b552010-05-24 18:23:54 +00001893 *clone,
anthony3c10fc82010-05-13 02:40:51 +00001894 *last;
cristya9a61ad2010-05-13 12:47:41 +00001895
anthony3c10fc82010-05-13 02:40:51 +00001896 last = kernel;
anthony47f5d062010-05-23 07:47:50 +00001897 while(1) {
cristy84d9b552010-05-24 18:23:54 +00001898 clone = CloneKernelInfo(last);
1899 RotateKernelInfo(clone, angle);
1900 if ( SameKernelInfo(kernel, clone) == MagickTrue )
anthony47f5d062010-05-23 07:47:50 +00001901 break;
cristy84d9b552010-05-24 18:23:54 +00001902 last->next = clone;
1903 last = clone;
anthony3c10fc82010-05-13 02:40:51 +00001904 }
cristy84d9b552010-05-24 18:23:54 +00001905 clone = DestroyKernelInfo(clone); /* This was the same as the first - junk */
anthony47f5d062010-05-23 07:47:50 +00001906 return;
anthony3c10fc82010-05-13 02:40:51 +00001907}
1908
1909/*
1910%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1911% %
1912% %
1913% %
anthony46a369d2010-05-19 02:41:48 +00001914+ C a l c M e t a K e r n a l I n f o %
1915% %
1916% %
1917% %
1918%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1919%
1920% CalcKernelMetaData() recalculate the KernelInfo meta-data of this kernel only,
1921% using the kernel values. This should only ne used if it is not posible to
1922% calculate that meta-data in some easier way.
1923%
1924% It is important that the meta-data is correct before ScaleKernelInfo() is
1925% used to perform kernel normalization.
1926%
1927% The format of the CalcKernelMetaData method is:
1928%
1929% void CalcKernelMetaData(KernelInfo *kernel, const double scale )
1930%
1931% A description of each parameter follows:
1932%
1933% o kernel: the Morphology/Convolution kernel to modify
1934%
1935% WARNING: Minimum and Maximum values are assumed to include zero, even if
1936% zero is not part of the kernel (as in Gaussian Derived kernels). This
1937% however is not true for flat-shaped morphological kernels.
1938%
1939% WARNING: Only the specific kernel pointed to is modified, not a list of
1940% multiple kernels.
1941%
1942% This is an internal function and not expected to be useful outside this
1943% module. This could change however.
1944*/
1945static void CalcKernelMetaData(KernelInfo *kernel)
1946{
1947 register unsigned long
1948 i;
1949
1950 kernel->minimum = kernel->maximum = 0.0;
1951 kernel->negative_range = kernel->positive_range = 0.0;
1952 for (i=0; i < (kernel->width*kernel->height); i++)
1953 {
1954 if ( fabs(kernel->values[i]) < MagickEpsilon )
1955 kernel->values[i] = 0.0;
1956 ( kernel->values[i] < 0)
1957 ? ( kernel->negative_range += kernel->values[i] )
1958 : ( kernel->positive_range += kernel->values[i] );
1959 Minimize(kernel->minimum, kernel->values[i]);
1960 Maximize(kernel->maximum, kernel->values[i]);
1961 }
1962
1963 return;
1964}
1965
1966/*
1967%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1968% %
1969% %
1970% %
anthony9eb4f742010-05-18 02:45:54 +00001971% M o r p h o l o g y A p p l y %
anthony602ab9b2010-01-05 08:06:50 +00001972% %
1973% %
1974% %
1975%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1976%
anthony9eb4f742010-05-18 02:45:54 +00001977% MorphologyApply() applies a morphological method, multiple times using
1978% a list of multiple kernels.
anthony602ab9b2010-01-05 08:06:50 +00001979%
anthony9eb4f742010-05-18 02:45:54 +00001980% It is basically equivelent to as MorphologyImageChannel() (see below) but
1981% without user controls, that that function extracts and applies to kernels
1982% and morphology methods.
1983%
1984% More specifically kernels are not normalized/scaled/blended by the
1985% 'convolve:scale' Image Artifact (-set setting), and the convolve bias
1986% (-bias setting or image->bias) is passed directly to this function,
1987% and not extracted from an image.
anthony602ab9b2010-01-05 08:06:50 +00001988%
anthony47f5d062010-05-23 07:47:50 +00001989% The format of the MorphologyApply method is:
anthony602ab9b2010-01-05 08:06:50 +00001990%
anthony9eb4f742010-05-18 02:45:54 +00001991% Image *MorphologyApply(const Image *image,MorphologyMethod method,
anthony47f5d062010-05-23 07:47:50 +00001992% const long iterations,const KernelInfo *kernel,
1993% const CompositeMethod compose, const double bias,
anthony9eb4f742010-05-18 02:45:54 +00001994% ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001995%
1996% A description of each parameter follows:
1997%
1998% o image: the image.
1999%
2000% o method: the morphology method to be applied.
2001%
2002% o iterations: apply the operation this many times (or no change).
2003% A value of -1 means loop until no change found.
2004% How this is applied may depend on the morphology method.
2005% Typically this is a value of 1.
2006%
2007% o channel: the channel type.
2008%
2009% o kernel: An array of double representing the morphology kernel.
anthony29188a82010-01-22 10:12:34 +00002010% Warning: kernel may be normalized for the Convolve method.
anthony602ab9b2010-01-05 08:06:50 +00002011%
anthony47f5d062010-05-23 07:47:50 +00002012% o compose: How to handle or merge multi-kernel results.
2013% If 'Undefined' use default of the Morphology method.
2014% If 'No' force image to be re-iterated by each kernel.
2015% Otherwise merge the results using the mathematical compose
2016% method given.
2017%
2018% o bias: Convolution Output Bias.
anthony9eb4f742010-05-18 02:45:54 +00002019%
anthony602ab9b2010-01-05 08:06:50 +00002020% o exception: return any errors or warnings in this structure.
2021%
anthony602ab9b2010-01-05 08:06:50 +00002022*/
2023
anthony930be612010-02-08 04:26:15 +00002024
anthony9eb4f742010-05-18 02:45:54 +00002025/* Apply a Morphology Primative to an image using the given kernel.
2026** Two pre-created images must be provided, no image is created.
2027** Returning the number of pixels that changed.
2028*/
anthony46a369d2010-05-19 02:41:48 +00002029static unsigned long MorphologyPrimitive(const Image *image, Image
anthony602ab9b2010-01-05 08:06:50 +00002030 *result_image, const MorphologyMethod method, const ChannelType channel,
anthony9eb4f742010-05-18 02:45:54 +00002031 const KernelInfo *kernel,const double bias,ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00002032{
cristy2be15382010-01-21 02:38:03 +00002033#define MorphologyTag "Morphology/Image"
anthony602ab9b2010-01-05 08:06:50 +00002034
2035 long
cristy150989e2010-02-01 14:59:39 +00002036 progress,
anthony29188a82010-01-22 10:12:34 +00002037 y, offx, offy,
anthony602ab9b2010-01-05 08:06:50 +00002038 changed;
2039
2040 MagickBooleanType
2041 status;
2042
anthony602ab9b2010-01-05 08:06:50 +00002043 CacheView
2044 *p_view,
2045 *q_view;
2046
anthony602ab9b2010-01-05 08:06:50 +00002047 status=MagickTrue;
2048 changed=0;
2049 progress=0;
2050
anthony602ab9b2010-01-05 08:06:50 +00002051 p_view=AcquireCacheView(image);
2052 q_view=AcquireCacheView(result_image);
anthony29188a82010-01-22 10:12:34 +00002053
anthonycc6c8362010-01-25 04:14:01 +00002054 /* Some methods (including convolve) needs use a reflected kernel.
anthony9eb4f742010-05-18 02:45:54 +00002055 * Adjust 'origin' offsets to loop though kernel as a reflection.
anthony29188a82010-01-22 10:12:34 +00002056 */
cristyc99304f2010-02-01 15:26:27 +00002057 offx = kernel->x;
2058 offy = kernel->y;
anthony29188a82010-01-22 10:12:34 +00002059 switch(method) {
anthony930be612010-02-08 04:26:15 +00002060 case ConvolveMorphology:
2061 case DilateMorphology:
2062 case DilateIntensityMorphology:
2063 case DistanceMorphology:
anthony5ef8e942010-05-11 06:51:12 +00002064 /* kernel needs to used with reflection about origin */
cristy150989e2010-02-01 14:59:39 +00002065 offx = (long) kernel->width-offx-1;
2066 offy = (long) kernel->height-offy-1;
anthony29188a82010-01-22 10:12:34 +00002067 break;
anthony5ef8e942010-05-11 06:51:12 +00002068 case ErodeMorphology:
2069 case ErodeIntensityMorphology:
2070 case HitAndMissMorphology:
2071 case ThinningMorphology:
2072 case ThickenMorphology:
2073 /* kernel is user as is, without reflection */
2074 break;
anthony930be612010-02-08 04:26:15 +00002075 default:
anthony9eb4f742010-05-18 02:45:54 +00002076 assert("Not a Primitive Morphology Method" != (char *) NULL);
anthony930be612010-02-08 04:26:15 +00002077 break;
anthony29188a82010-01-22 10:12:34 +00002078 }
2079
anthony602ab9b2010-01-05 08:06:50 +00002080#if defined(MAGICKCORE_OPENMP_SUPPORT)
2081 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2082#endif
cristy150989e2010-02-01 14:59:39 +00002083 for (y=0; y < (long) image->rows; y++)
anthony602ab9b2010-01-05 08:06:50 +00002084 {
2085 MagickBooleanType
2086 sync;
2087
2088 register const PixelPacket
2089 *restrict p;
2090
2091 register const IndexPacket
2092 *restrict p_indexes;
2093
2094 register PixelPacket
2095 *restrict q;
2096
2097 register IndexPacket
2098 *restrict q_indexes;
2099
cristy150989e2010-02-01 14:59:39 +00002100 register long
anthony602ab9b2010-01-05 08:06:50 +00002101 x;
2102
anthony29188a82010-01-22 10:12:34 +00002103 unsigned long
anthony602ab9b2010-01-05 08:06:50 +00002104 r;
2105
2106 if (status == MagickFalse)
2107 continue;
anthony29188a82010-01-22 10:12:34 +00002108 p=GetCacheViewVirtualPixels(p_view, -offx, y-offy,
2109 image->columns+kernel->width, kernel->height, exception);
anthony602ab9b2010-01-05 08:06:50 +00002110 q=GetCacheViewAuthenticPixels(q_view,0,y,result_image->columns,1,
2111 exception);
2112 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2113 {
2114 status=MagickFalse;
2115 continue;
2116 }
2117 p_indexes=GetCacheViewVirtualIndexQueue(p_view);
2118 q_indexes=GetCacheViewAuthenticIndexQueue(q_view);
anthony29188a82010-01-22 10:12:34 +00002119 r = (image->columns+kernel->width)*offy+offx; /* constant */
2120
cristy150989e2010-02-01 14:59:39 +00002121 for (x=0; x < (long) image->columns; x++)
anthony602ab9b2010-01-05 08:06:50 +00002122 {
cristy150989e2010-02-01 14:59:39 +00002123 long
anthony602ab9b2010-01-05 08:06:50 +00002124 v;
2125
cristy150989e2010-02-01 14:59:39 +00002126 register long
anthony602ab9b2010-01-05 08:06:50 +00002127 u;
2128
2129 register const double
2130 *restrict k;
2131
2132 register const PixelPacket
2133 *restrict k_pixels;
2134
2135 register const IndexPacket
2136 *restrict k_indexes;
2137
2138 MagickPixelPacket
anthony5ef8e942010-05-11 06:51:12 +00002139 result,
2140 min,
2141 max;
anthony602ab9b2010-01-05 08:06:50 +00002142
anthony29188a82010-01-22 10:12:34 +00002143 /* Copy input to ouput image for unused channels
anthony83ba99b2010-01-24 08:48:15 +00002144 * This removes need for 'cloning' a new image every iteration
anthony29188a82010-01-22 10:12:34 +00002145 */
anthony602ab9b2010-01-05 08:06:50 +00002146 *q = p[r];
2147 if (image->colorspace == CMYKColorspace)
2148 q_indexes[x] = p_indexes[r];
2149
anthony5ef8e942010-05-11 06:51:12 +00002150 /* Defaults */
2151 min.red =
2152 min.green =
2153 min.blue =
2154 min.opacity =
2155 min.index = (MagickRealType) QuantumRange;
2156 max.red =
2157 max.green =
2158 max.blue =
2159 max.opacity =
2160 max.index = (MagickRealType) 0;
anthony9eb4f742010-05-18 02:45:54 +00002161 /* default result is the original pixel value */
anthony5ef8e942010-05-11 06:51:12 +00002162 result.red = (MagickRealType) p[r].red;
2163 result.green = (MagickRealType) p[r].green;
2164 result.blue = (MagickRealType) p[r].blue;
2165 result.opacity = QuantumRange - (MagickRealType) p[r].opacity;
cristye96405a2010-05-19 02:24:31 +00002166 result.index = 0.0;
anthony5ef8e942010-05-11 06:51:12 +00002167 if ( image->colorspace == CMYKColorspace)
2168 result.index = (MagickRealType) p_indexes[r];
2169
anthony602ab9b2010-01-05 08:06:50 +00002170 switch (method) {
2171 case ConvolveMorphology:
anthony9eb4f742010-05-18 02:45:54 +00002172 /* Set the user defined bias of the weighted average output */
2173 result.red =
2174 result.green =
2175 result.blue =
2176 result.opacity =
2177 result.index = bias;
anthony930be612010-02-08 04:26:15 +00002178 break;
anthony4fd27e22010-02-07 08:17:18 +00002179 case DilateIntensityMorphology:
2180 case ErodeIntensityMorphology:
anthony9eb4f742010-05-18 02:45:54 +00002181 /* use a boolean flag indicating when first match found */
2182 result.red = 0.0; /* result is not used otherwise */
anthony4fd27e22010-02-07 08:17:18 +00002183 break;
anthony602ab9b2010-01-05 08:06:50 +00002184 default:
anthony602ab9b2010-01-05 08:06:50 +00002185 break;
2186 }
2187
2188 switch ( method ) {
2189 case ConvolveMorphology:
anthony930be612010-02-08 04:26:15 +00002190 /* Weighted Average of pixels using reflected kernel
2191 **
2192 ** NOTE for correct working of this operation for asymetrical
2193 ** kernels, the kernel needs to be applied in its reflected form.
2194 ** That is its values needs to be reversed.
2195 **
2196 ** Correlation is actually the same as this but without reflecting
2197 ** the kernel, and thus 'lower-level' that Convolution. However
2198 ** as Convolution is the more common method used, and it does not
2199 ** really cost us much in terms of processing to use a reflected
anthony5ef8e942010-05-11 06:51:12 +00002200 ** kernel, so it is Convolution that is implemented.
anthony930be612010-02-08 04:26:15 +00002201 **
2202 ** Correlation will have its kernel reflected before calling
2203 ** this function to do a Convolve.
2204 **
2205 ** For more details of Correlation vs Convolution see
2206 ** http://www.cs.umd.edu/~djacobs/CMSC426/Convolution.pdf
2207 */
anthony5ef8e942010-05-11 06:51:12 +00002208 if (((channel & SyncChannels) != 0 ) &&
2209 (image->matte == MagickTrue))
2210 { /* Channel has a 'Sync' Flag, and Alpha Channel enabled.
2211 ** Weight the color channels with Alpha Channel so that
2212 ** transparent pixels are not part of the results.
2213 */
anthony602ab9b2010-01-05 08:06:50 +00002214 MagickRealType
anthony5ef8e942010-05-11 06:51:12 +00002215 alpha, /* color channel weighting : kernel*alpha */
2216 gamma; /* divisor, sum of weighting values */
anthony602ab9b2010-01-05 08:06:50 +00002217
2218 gamma=0.0;
anthony29188a82010-01-22 10:12:34 +00002219 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00002220 k_pixels = p;
2221 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002222 for (v=0; v < (long) kernel->height; v++) {
2223 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00002224 if ( IsNan(*k) ) continue;
2225 alpha=(*k)*(QuantumScale*(QuantumRange-
2226 k_pixels[u].opacity));
2227 gamma += alpha;
2228 result.red += alpha*k_pixels[u].red;
2229 result.green += alpha*k_pixels[u].green;
2230 result.blue += alpha*k_pixels[u].blue;
anthony83ba99b2010-01-24 08:48:15 +00002231 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00002232 if ( image->colorspace == CMYKColorspace)
2233 result.index += alpha*k_indexes[u];
2234 }
2235 k_pixels += image->columns+kernel->width;
2236 k_indexes += image->columns+kernel->width;
2237 }
2238 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
anthony83ba99b2010-01-24 08:48:15 +00002239 result.red *= gamma;
2240 result.green *= gamma;
2241 result.blue *= gamma;
2242 result.opacity *= gamma;
2243 result.index *= gamma;
anthony602ab9b2010-01-05 08:06:50 +00002244 }
anthony5ef8e942010-05-11 06:51:12 +00002245 else
2246 {
2247 /* No 'Sync' flag, or no Alpha involved.
2248 ** Convolution is simple individual channel weigthed sum.
2249 */
2250 k = &kernel->values[ kernel->width*kernel->height-1 ];
2251 k_pixels = p;
2252 k_indexes = p_indexes;
2253 for (v=0; v < (long) kernel->height; v++) {
2254 for (u=0; u < (long) kernel->width; u++, k--) {
2255 if ( IsNan(*k) ) continue;
2256 result.red += (*k)*k_pixels[u].red;
2257 result.green += (*k)*k_pixels[u].green;
2258 result.blue += (*k)*k_pixels[u].blue;
2259 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
2260 if ( image->colorspace == CMYKColorspace)
2261 result.index += (*k)*k_indexes[u];
2262 }
2263 k_pixels += image->columns+kernel->width;
2264 k_indexes += image->columns+kernel->width;
2265 }
2266 }
anthony602ab9b2010-01-05 08:06:50 +00002267 break;
2268
anthony4fd27e22010-02-07 08:17:18 +00002269 case ErodeMorphology:
anthony5ef8e942010-05-11 06:51:12 +00002270 /* Minimum Value within kernel neighbourhood
anthony930be612010-02-08 04:26:15 +00002271 **
2272 ** NOTE that the kernel is not reflected for this operation!
2273 **
2274 ** NOTE: in normal Greyscale Morphology, the kernel value should
2275 ** be added to the real value, this is currently not done, due to
2276 ** the nature of the boolean kernels being used.
2277 */
anthony4fd27e22010-02-07 08:17:18 +00002278 k = kernel->values;
2279 k_pixels = p;
2280 k_indexes = p_indexes;
2281 for (v=0; v < (long) kernel->height; v++) {
2282 for (u=0; u < (long) kernel->width; u++, k++) {
2283 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony5ef8e942010-05-11 06:51:12 +00002284 Minimize(min.red, (double) k_pixels[u].red);
2285 Minimize(min.green, (double) k_pixels[u].green);
2286 Minimize(min.blue, (double) k_pixels[u].blue);
2287 Minimize(min.opacity,
anthonyd37a5cb2010-05-07 06:37:03 +00002288 QuantumRange-(double) k_pixels[u].opacity);
anthony4fd27e22010-02-07 08:17:18 +00002289 if ( image->colorspace == CMYKColorspace)
anthony5ef8e942010-05-11 06:51:12 +00002290 Minimize(min.index, (double) k_indexes[u]);
anthony4fd27e22010-02-07 08:17:18 +00002291 }
2292 k_pixels += image->columns+kernel->width;
2293 k_indexes += image->columns+kernel->width;
2294 }
2295 break;
2296
anthony5ef8e942010-05-11 06:51:12 +00002297
anthony83ba99b2010-01-24 08:48:15 +00002298 case DilateMorphology:
anthony5ef8e942010-05-11 06:51:12 +00002299 /* Maximum Value within kernel neighbourhood
anthony930be612010-02-08 04:26:15 +00002300 **
2301 ** NOTE for correct working of this operation for asymetrical
2302 ** kernels, the kernel needs to be applied in its reflected form.
2303 ** That is its values needs to be reversed.
2304 **
2305 ** NOTE: in normal Greyscale Morphology, the kernel value should
2306 ** be added to the real value, this is currently not done, due to
2307 ** the nature of the boolean kernels being used.
2308 **
2309 */
anthony29188a82010-01-22 10:12:34 +00002310 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00002311 k_pixels = p;
2312 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002313 for (v=0; v < (long) kernel->height; v++) {
2314 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00002315 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony5ef8e942010-05-11 06:51:12 +00002316 Maximize(max.red, (double) k_pixels[u].red);
2317 Maximize(max.green, (double) k_pixels[u].green);
2318 Maximize(max.blue, (double) k_pixels[u].blue);
2319 Maximize(max.opacity,
anthonyd37a5cb2010-05-07 06:37:03 +00002320 QuantumRange-(double) k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00002321 if ( image->colorspace == CMYKColorspace)
anthony5ef8e942010-05-11 06:51:12 +00002322 Maximize(max.index, (double) k_indexes[u]);
anthony602ab9b2010-01-05 08:06:50 +00002323 }
2324 k_pixels += image->columns+kernel->width;
2325 k_indexes += image->columns+kernel->width;
2326 }
anthony602ab9b2010-01-05 08:06:50 +00002327 break;
2328
anthony5ef8e942010-05-11 06:51:12 +00002329 case HitAndMissMorphology:
2330 case ThinningMorphology:
2331 case ThickenMorphology:
2332 /* Minimum of Foreground Pixel minus Maxumum of Background Pixels
2333 **
2334 ** NOTE that the kernel is not reflected for this operation,
2335 ** and consists of both foreground and background pixel
2336 ** neighbourhoods, 0.0 for background, and 1.0 for foreground
2337 ** with either Nan or 0.5 values for don't care.
2338 **
2339 ** Note that this can produce negative results, though really
2340 ** only a positive match has any real value.
2341 */
2342 k = kernel->values;
2343 k_pixels = p;
2344 k_indexes = p_indexes;
2345 for (v=0; v < (long) kernel->height; v++) {
2346 for (u=0; u < (long) kernel->width; u++, k++) {
2347 if ( IsNan(*k) ) continue;
2348 if ( (*k) > 0.7 )
2349 { /* minimim of foreground pixels */
2350 Minimize(min.red, (double) k_pixels[u].red);
2351 Minimize(min.green, (double) k_pixels[u].green);
2352 Minimize(min.blue, (double) k_pixels[u].blue);
2353 Minimize(min.opacity,
2354 QuantumRange-(double) k_pixels[u].opacity);
2355 if ( image->colorspace == CMYKColorspace)
2356 Minimize(min.index, (double) k_indexes[u]);
2357 }
2358 else if ( (*k) < 0.3 )
2359 { /* maximum of background pixels */
2360 Maximize(max.red, (double) k_pixels[u].red);
2361 Maximize(max.green, (double) k_pixels[u].green);
2362 Maximize(max.blue, (double) k_pixels[u].blue);
2363 Maximize(max.opacity,
2364 QuantumRange-(double) k_pixels[u].opacity);
2365 if ( image->colorspace == CMYKColorspace)
2366 Maximize(max.index, (double) k_indexes[u]);
2367 }
2368 }
2369 k_pixels += image->columns+kernel->width;
2370 k_indexes += image->columns+kernel->width;
2371 }
2372 /* Pattern Match only if min fg larger than min bg pixels */
2373 min.red -= max.red; Maximize( min.red, 0.0 );
2374 min.green -= max.green; Maximize( min.green, 0.0 );
2375 min.blue -= max.blue; Maximize( min.blue, 0.0 );
2376 min.opacity -= max.opacity; Maximize( min.opacity, 0.0 );
2377 min.index -= max.index; Maximize( min.index, 0.0 );
2378 break;
2379
anthony4fd27e22010-02-07 08:17:18 +00002380 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00002381 /* Select Pixel with Minimum Intensity within kernel neighbourhood
2382 **
2383 ** WARNING: the intensity test fails for CMYK and does not
2384 ** take into account the moderating effect of teh alpha channel
2385 ** on the intensity.
2386 **
2387 ** NOTE that the kernel is not reflected for this operation!
2388 */
anthony602ab9b2010-01-05 08:06:50 +00002389 k = kernel->values;
2390 k_pixels = p;
2391 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002392 for (v=0; v < (long) kernel->height; v++) {
2393 for (u=0; u < (long) kernel->width; u++, k++) {
anthony602ab9b2010-01-05 08:06:50 +00002394 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony4fd27e22010-02-07 08:17:18 +00002395 if ( result.red == 0.0 ||
2396 PixelIntensity(&(k_pixels[u])) < PixelIntensity(q) ) {
2397 /* copy the whole pixel - no channel selection */
2398 *q = k_pixels[u];
2399 if ( result.red > 0.0 ) changed++;
2400 result.red = 1.0;
2401 }
anthony602ab9b2010-01-05 08:06:50 +00002402 }
2403 k_pixels += image->columns+kernel->width;
2404 k_indexes += image->columns+kernel->width;
2405 }
anthony602ab9b2010-01-05 08:06:50 +00002406 break;
2407
anthony83ba99b2010-01-24 08:48:15 +00002408 case DilateIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00002409 /* Select Pixel with Maximum Intensity within kernel neighbourhood
2410 **
2411 ** WARNING: the intensity test fails for CMYK and does not
anthony9eb4f742010-05-18 02:45:54 +00002412 ** take into account the moderating effect of the alpha channel
2413 ** on the intensity (yet).
anthony930be612010-02-08 04:26:15 +00002414 **
2415 ** NOTE for correct working of this operation for asymetrical
2416 ** kernels, the kernel needs to be applied in its reflected form.
2417 ** That is its values needs to be reversed.
2418 */
anthony29188a82010-01-22 10:12:34 +00002419 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00002420 k_pixels = p;
2421 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002422 for (v=0; v < (long) kernel->height; v++) {
2423 for (u=0; u < (long) kernel->width; u++, k--) {
anthony29188a82010-01-22 10:12:34 +00002424 if ( IsNan(*k) || (*k) < 0.5 ) continue; /* boolean kernel */
2425 if ( result.red == 0.0 ||
2426 PixelIntensity(&(k_pixels[u])) > PixelIntensity(q) ) {
2427 /* copy the whole pixel - no channel selection */
2428 *q = k_pixels[u];
2429 if ( result.red > 0.0 ) changed++;
2430 result.red = 1.0;
2431 }
anthony602ab9b2010-01-05 08:06:50 +00002432 }
2433 k_pixels += image->columns+kernel->width;
2434 k_indexes += image->columns+kernel->width;
2435 }
anthony602ab9b2010-01-05 08:06:50 +00002436 break;
2437
anthony5ef8e942010-05-11 06:51:12 +00002438
anthony602ab9b2010-01-05 08:06:50 +00002439 case DistanceMorphology:
anthony930be612010-02-08 04:26:15 +00002440 /* Add kernel Value and select the minimum value found.
2441 ** The result is a iterative distance from edge of image shape.
2442 **
2443 ** All Distance Kernels are symetrical, but that may not always
2444 ** be the case. For example how about a distance from left edges?
2445 ** To work correctly with asymetrical kernels the reflected kernel
2446 ** needs to be applied.
anthony5ef8e942010-05-11 06:51:12 +00002447 **
2448 ** Actually this is really a GreyErode with a negative kernel!
2449 **
anthony930be612010-02-08 04:26:15 +00002450 */
anthony29188a82010-01-22 10:12:34 +00002451 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00002452 k_pixels = p;
2453 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002454 for (v=0; v < (long) kernel->height; v++) {
2455 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00002456 if ( IsNan(*k) ) continue;
2457 Minimize(result.red, (*k)+k_pixels[u].red);
2458 Minimize(result.green, (*k)+k_pixels[u].green);
2459 Minimize(result.blue, (*k)+k_pixels[u].blue);
2460 Minimize(result.opacity, (*k)+QuantumRange-k_pixels[u].opacity);
2461 if ( image->colorspace == CMYKColorspace)
2462 Minimize(result.index, (*k)+k_indexes[u]);
2463 }
2464 k_pixels += image->columns+kernel->width;
2465 k_indexes += image->columns+kernel->width;
2466 }
anthony602ab9b2010-01-05 08:06:50 +00002467 break;
2468
2469 case UndefinedMorphology:
2470 default:
2471 break; /* Do nothing */
anthony83ba99b2010-01-24 08:48:15 +00002472 }
anthony5ef8e942010-05-11 06:51:12 +00002473 /* Final mathematics of results (combine with original image?)
2474 **
2475 ** NOTE: Difference Morphology operators Edge* and *Hat could also
2476 ** be done here but works better with iteration as a image difference
2477 ** in the controling function (below). Thicken and Thinning however
2478 ** should be done here so thay can be iterated correctly.
2479 */
2480 switch ( method ) {
2481 case HitAndMissMorphology:
2482 case ErodeMorphology:
2483 result = min; /* minimum of neighbourhood */
2484 break;
2485 case DilateMorphology:
2486 result = max; /* maximum of neighbourhood */
2487 break;
2488 case ThinningMorphology:
2489 /* subtract pattern match from original */
2490 result.red -= min.red;
2491 result.green -= min.green;
2492 result.blue -= min.blue;
2493 result.opacity -= min.opacity;
2494 result.index -= min.index;
2495 break;
2496 case ThickenMorphology:
2497 /* Union with original image (maximize) - or should this be + */
2498 Maximize( result.red, min.red );
2499 Maximize( result.green, min.green );
2500 Maximize( result.blue, min.blue );
2501 Maximize( result.opacity, min.opacity );
2502 Maximize( result.index, min.index );
2503 break;
2504 default:
2505 /* result directly calculated or assigned */
2506 break;
2507 }
2508 /* Assign the resulting pixel values - Clamping Result */
anthony83ba99b2010-01-24 08:48:15 +00002509 switch ( method ) {
2510 case UndefinedMorphology:
2511 case DilateIntensityMorphology:
2512 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00002513 break; /* full pixel was directly assigned - not a channel method */
anthony83ba99b2010-01-24 08:48:15 +00002514 default:
anthony83ba99b2010-01-24 08:48:15 +00002515 if ((channel & RedChannel) != 0)
2516 q->red = ClampToQuantum(result.red);
2517 if ((channel & GreenChannel) != 0)
2518 q->green = ClampToQuantum(result.green);
2519 if ((channel & BlueChannel) != 0)
2520 q->blue = ClampToQuantum(result.blue);
2521 if ((channel & OpacityChannel) != 0
2522 && image->matte == MagickTrue )
2523 q->opacity = ClampToQuantum(QuantumRange-result.opacity);
2524 if ((channel & IndexChannel) != 0
2525 && image->colorspace == CMYKColorspace)
2526 q_indexes[x] = ClampToQuantum(result.index);
2527 break;
2528 }
anthony5ef8e942010-05-11 06:51:12 +00002529 /* Count up changed pixels */
anthony83ba99b2010-01-24 08:48:15 +00002530 if ( ( p[r].red != q->red )
2531 || ( p[r].green != q->green )
2532 || ( p[r].blue != q->blue )
2533 || ( p[r].opacity != q->opacity )
2534 || ( image->colorspace == CMYKColorspace &&
2535 p_indexes[r] != q_indexes[x] ) )
2536 changed++; /* The pixel had some value changed! */
anthony602ab9b2010-01-05 08:06:50 +00002537 p++;
2538 q++;
anthony83ba99b2010-01-24 08:48:15 +00002539 } /* x */
anthony602ab9b2010-01-05 08:06:50 +00002540 sync=SyncCacheViewAuthenticPixels(q_view,exception);
2541 if (sync == MagickFalse)
2542 status=MagickFalse;
2543 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2544 {
2545 MagickBooleanType
2546 proceed;
2547
2548#if defined(MAGICKCORE_OPENMP_SUPPORT)
2549 #pragma omp critical (MagickCore_MorphologyImage)
2550#endif
2551 proceed=SetImageProgress(image,MorphologyTag,progress++,image->rows);
2552 if (proceed == MagickFalse)
2553 status=MagickFalse;
2554 }
anthony83ba99b2010-01-24 08:48:15 +00002555 } /* y */
anthony602ab9b2010-01-05 08:06:50 +00002556 result_image->type=image->type;
2557 q_view=DestroyCacheView(q_view);
2558 p_view=DestroyCacheView(p_view);
cristy150989e2010-02-01 14:59:39 +00002559 return(status ? (unsigned long) changed : 0);
anthony602ab9b2010-01-05 08:06:50 +00002560}
2561
anthony4fd27e22010-02-07 08:17:18 +00002562
anthony9eb4f742010-05-18 02:45:54 +00002563MagickExport Image *MorphologyApply(const Image *image, const ChannelType
2564 channel,const MorphologyMethod method, const long iterations,
anthony47f5d062010-05-23 07:47:50 +00002565 const KernelInfo *kernel, const CompositeOperator compose,
2566 const double bias, ExceptionInfo *exception)
cristy2be15382010-01-21 02:38:03 +00002567{
2568 Image
anthony47f5d062010-05-23 07:47:50 +00002569 *curr_image, /* Image we are working with or iterating */
2570 *work_image, /* secondary image for primative iteration */
2571 *save_image, /* saved image - for 'edge' method only */
2572 *rslt_image; /* resultant image - after multi-kernel handling */
anthony602ab9b2010-01-05 08:06:50 +00002573
anthony4fd27e22010-02-07 08:17:18 +00002574 KernelInfo
anthony47f5d062010-05-23 07:47:50 +00002575 *reflected_kernel, /* A reflected copy of the kernel (if needed) */
2576 *norm_kernel, /* the current normal un-reflected kernel */
2577 *rflt_kernel, /* the current reflected kernel (if needed) */
2578 *this_kernel; /* the kernel being applied */
anthony4fd27e22010-02-07 08:17:18 +00002579
2580 MorphologyMethod
anthony47f5d062010-05-23 07:47:50 +00002581 primative; /* the current morphology primative being applied */
anthony9eb4f742010-05-18 02:45:54 +00002582
2583 CompositeOperator
anthony47f5d062010-05-23 07:47:50 +00002584 rslt_compose; /* multi-kernel compose method for results to use */
2585
2586 MagickBooleanType
2587 verbose; /* verbose output of results */
anthony4fd27e22010-02-07 08:17:18 +00002588
anthony1b2bc0a2010-05-12 05:25:22 +00002589 unsigned long
anthony47f5d062010-05-23 07:47:50 +00002590 method_loop, /* Loop 1: number of compound method iterations */
2591 method_limit, /* maximum number of compound method iterations */
2592 kernel_number, /* Loop 2: the kernel number being applied */
2593 stage_loop, /* Loop 3: primative loop for compound morphology */
2594 stage_limit, /* how many primatives in this compound */
2595 kernel_loop, /* Loop 4: iterate the kernel (basic morphology) */
2596 kernel_limit, /* number of times to iterate kernel */
2597 count, /* total count of primative steps applied */
2598 changed, /* number pixels changed by last primative operation */
2599 kernel_changed, /* total count of changed using iterated kernel */
2600 method_changed; /* total count of changed over method iteration */
2601
2602 char
2603 v_info[80];
anthony1b2bc0a2010-05-12 05:25:22 +00002604
anthony602ab9b2010-01-05 08:06:50 +00002605 assert(image != (Image *) NULL);
2606 assert(image->signature == MagickSignature);
anthony4fd27e22010-02-07 08:17:18 +00002607 assert(kernel != (KernelInfo *) NULL);
2608 assert(kernel->signature == MagickSignature);
anthony602ab9b2010-01-05 08:06:50 +00002609 assert(exception != (ExceptionInfo *) NULL);
2610 assert(exception->signature == MagickSignature);
2611
anthonyc3e48252010-05-24 12:43:11 +00002612 count = 0; /* number of low-level morphology primatives performed */
anthony602ab9b2010-01-05 08:06:50 +00002613 if ( iterations == 0 )
anthony47f5d062010-05-23 07:47:50 +00002614 return((Image *)NULL); /* null operation - nothing to do! */
anthony602ab9b2010-01-05 08:06:50 +00002615
anthony47f5d062010-05-23 07:47:50 +00002616 kernel_limit = (unsigned long) iterations;
2617 if ( iterations < 0 ) /* negative interations = infinite (well alomst) */
2618 kernel_limit = image->columns > image->rows ? image->columns : image->rows;
anthony602ab9b2010-01-05 08:06:50 +00002619
cristye96405a2010-05-19 02:24:31 +00002620 verbose = ( GetImageArtifact(image,"verbose") != (const char *) NULL ) ?
2621 MagickTrue : MagickFalse;
anthony4f1dcb72010-05-14 08:43:10 +00002622
anthony9eb4f742010-05-18 02:45:54 +00002623 /* initialise for cleanup */
anthony47f5d062010-05-23 07:47:50 +00002624 curr_image = (Image *) image;
2625 work_image = save_image = rslt_image = (Image *) NULL;
2626 reflected_kernel = (KernelInfo *) NULL;
anthony4fd27e22010-02-07 08:17:18 +00002627
anthony47f5d062010-05-23 07:47:50 +00002628 /* Initialize specific methods
2629 * + which loop should use the given iteratations
2630 * + how many primatives make up the compound morphology
2631 * + multi-kernel compose method to use (by default)
2632 */
2633 method_limit = 1; /* just do method once, unless otherwise set */
2634 stage_limit = 1; /* assume method is not a compount */
2635 rslt_compose = compose; /* and we are composing multi-kernels as given */
anthony9eb4f742010-05-18 02:45:54 +00002636 switch( method ) {
anthony47f5d062010-05-23 07:47:50 +00002637 case SmoothMorphology: /* 4 primative compound morphology */
2638 stage_limit = 4;
anthony9eb4f742010-05-18 02:45:54 +00002639 break;
anthony47f5d062010-05-23 07:47:50 +00002640 case OpenMorphology: /* 2 primative compound morphology */
anthony9eb4f742010-05-18 02:45:54 +00002641 case OpenIntensityMorphology:
anthony47f5d062010-05-23 07:47:50 +00002642 case TopHatMorphology:
2643 case CloseMorphology:
anthony9eb4f742010-05-18 02:45:54 +00002644 case CloseIntensityMorphology:
anthony47f5d062010-05-23 07:47:50 +00002645 case BottomHatMorphology:
2646 case EdgeMorphology:
2647 stage_limit = 2;
anthony9eb4f742010-05-18 02:45:54 +00002648 break;
2649 case HitAndMissMorphology:
anthonyc3e48252010-05-24 12:43:11 +00002650 kernel_limit = 1; /* no method or kernel iteration */
anthony47f5d062010-05-23 07:47:50 +00002651 rslt_compose = LightenCompositeOp; /* Union of multi-kernel results */
anthony9eb4f742010-05-18 02:45:54 +00002652 break;
anthonyc3e48252010-05-24 12:43:11 +00002653 case ThinningMorphology:
anthony9eb4f742010-05-18 02:45:54 +00002654 case ThickenMorphology:
anthonyc3e48252010-05-24 12:43:11 +00002655 case DistanceMorphology:
2656 method_limit = kernel_limit; /* iterate method with each kernel */
2657 kernel_limit = 1; /* do not do kernel iteration */
2658 rslt_compose = NoCompositeOp; /* Re-iterate with multiple kernels */
anthony47f5d062010-05-23 07:47:50 +00002659 break;
2660 default:
anthony930be612010-02-08 04:26:15 +00002661 break;
anthony602ab9b2010-01-05 08:06:50 +00002662 }
2663
anthonyc3e48252010-05-24 12:43:11 +00002664 /* Handle user (caller) specified multi-kernel composition method */
anthony47f5d062010-05-23 07:47:50 +00002665 if ( compose != UndefinedCompositeOp )
2666 rslt_compose = compose; /* override default composition for method */
2667 if ( rslt_compose == UndefinedCompositeOp )
2668 rslt_compose = NoCompositeOp; /* still not defined! Then re-iterate */
2669
anthonyc3e48252010-05-24 12:43:11 +00002670 /* Some methods require a reflected kernel to use with primatives.
2671 * Create the reflected kernel for those methods. */
anthony47f5d062010-05-23 07:47:50 +00002672 switch ( method ) {
2673 case CorrelateMorphology:
2674 case CloseMorphology:
2675 case CloseIntensityMorphology:
2676 case BottomHatMorphology:
2677 case SmoothMorphology:
2678 reflected_kernel = CloneKernelInfo(kernel);
2679 if (reflected_kernel == (KernelInfo *) NULL)
2680 goto error_cleanup;
2681 RotateKernelInfo(reflected_kernel,180);
2682 break;
2683 default:
2684 break;
anthony9eb4f742010-05-18 02:45:54 +00002685 }
anthony7a01dcf2010-05-11 12:25:52 +00002686
anthony47f5d062010-05-23 07:47:50 +00002687 /* Loop 1: iterate the compound method */
2688 method_loop = 0;
2689 method_changed = 1;
2690 while ( method_loop < method_limit && method_changed > 0 ) {
2691 method_loop++;
2692 method_changed = 0;
anthony9eb4f742010-05-18 02:45:54 +00002693
anthony47f5d062010-05-23 07:47:50 +00002694 /* Loop 2: iterate over each kernel in a multi-kernel list */
2695 norm_kernel = (KernelInfo *) kernel;
2696 rflt_kernel = reflected_kernel;
2697 kernel_number = 0;
2698 while ( norm_kernel != NULL ) {
anthony9eb4f742010-05-18 02:45:54 +00002699
anthony47f5d062010-05-23 07:47:50 +00002700 /* Loop 3: Compound Morphology Staging - Select Primative to apply */
2701 stage_loop = 0; /* the compound morphology stage number */
2702 while ( stage_loop < stage_limit ) {
2703 stage_loop++; /* The stage of the compound morphology */
anthony9eb4f742010-05-18 02:45:54 +00002704
anthony47f5d062010-05-23 07:47:50 +00002705 /* Select primative morphology for this stage of compound method */
2706 this_kernel = norm_kernel; /* default use unreflected kernel */
anthonybd0f5562010-05-24 13:05:02 +00002707 primative = method; /* Assume method is a primative */
anthony47f5d062010-05-23 07:47:50 +00002708 switch( method ) {
2709 case ErodeMorphology: /* just erode */
2710 case EdgeInMorphology: /* erode and image difference */
2711 primative = ErodeMorphology;
2712 break;
2713 case DilateMorphology: /* just dilate */
2714 case EdgeOutMorphology: /* dilate and image difference */
2715 primative = DilateMorphology;
2716 break;
2717 case OpenMorphology: /* erode then dialate */
2718 case TopHatMorphology: /* open and image difference */
2719 primative = ErodeMorphology;
2720 if ( stage_loop == 2 )
2721 primative = DilateMorphology;
2722 break;
2723 case OpenIntensityMorphology:
2724 primative = ErodeIntensityMorphology;
2725 if ( stage_loop == 2 )
2726 primative = DilateIntensityMorphology;
2727 case CloseMorphology: /* dilate, then erode */
2728 case BottomHatMorphology: /* close and image difference */
2729 this_kernel = rflt_kernel; /* use the reflected kernel */
2730 primative = DilateMorphology;
2731 if ( stage_loop == 2 )
2732 primative = ErodeMorphology;
2733 break;
2734 case CloseIntensityMorphology:
2735 this_kernel = rflt_kernel; /* use the reflected kernel */
2736 primative = DilateIntensityMorphology;
2737 if ( stage_loop == 2 )
2738 primative = ErodeIntensityMorphology;
2739 break;
2740 case SmoothMorphology: /* open, close */
2741 switch ( stage_loop ) {
2742 case 1: /* start an open method, which starts with Erode */
2743 primative = ErodeMorphology;
2744 break;
2745 case 2: /* now Dilate the Erode */
2746 primative = DilateMorphology;
2747 break;
2748 case 3: /* Reflect kernel a close */
2749 this_kernel = rflt_kernel; /* use the reflected kernel */
2750 primative = DilateMorphology;
2751 break;
2752 case 4: /* Finish the Close */
2753 this_kernel = rflt_kernel; /* use the reflected kernel */
2754 primative = ErodeMorphology;
2755 break;
2756 }
2757 break;
2758 case EdgeMorphology: /* dilate and erode difference */
2759 primative = DilateMorphology;
2760 if ( stage_loop == 2 ) {
2761 save_image = curr_image; /* save the image difference */
2762 curr_image = (Image *) image;
2763 primative = ErodeMorphology;
2764 }
2765 break;
2766 case CorrelateMorphology:
2767 /* A Correlation is a Convolution with a reflected kernel.
2768 ** However a Convolution is a weighted sum using a reflected
2769 ** kernel. It may seem stange to convert a Correlation into a
2770 ** Convolution as the Correlation is the simplier method, but
2771 ** Convolution is much more commonly used, and it makes sense to
2772 ** implement it directly so as to avoid the need to duplicate the
2773 ** kernel when it is not required (which is typically the
2774 ** default).
2775 */
2776 this_kernel = rflt_kernel; /* use the reflected kernel */
2777 primative = ConvolveMorphology;
2778 break;
2779 default:
anthony47f5d062010-05-23 07:47:50 +00002780 break;
2781 }
anthony9eb4f742010-05-18 02:45:54 +00002782
anthony47f5d062010-05-23 07:47:50 +00002783 /* Extra information for debugging compound operations */
2784 if ( verbose == MagickTrue ) {
2785 if ( stage_limit > 1 )
cristydc1c30b2010-05-23 14:23:12 +00002786 (void) FormatMagickString(v_info, MaxTextExtent, "%s:%lu.%lu -> ",
anthony47f5d062010-05-23 07:47:50 +00002787 MagickOptionToMnemonic(MagickMorphologyOptions, method),
2788 method_loop, stage_loop );
2789 else if ( primative != method )
cristydc1c30b2010-05-23 14:23:12 +00002790 (void) FormatMagickString(v_info, MaxTextExtent, "%s:%lu -> ",
anthony47f5d062010-05-23 07:47:50 +00002791 MagickOptionToMnemonic(MagickMorphologyOptions, method),
2792 method_loop );
2793 else
2794 v_info[0] = '\0';
2795 }
2796
2797 /* Loop 4: Iterate the kernel with primative */
2798 kernel_loop = 0;
2799 kernel_changed = 0;
2800 changed = 1;
2801 while ( kernel_loop < kernel_limit && changed > 0 ) {
2802 kernel_loop++; /* the iteration of this kernel */
anthony9eb4f742010-05-18 02:45:54 +00002803
2804 /* Create a destination image, if not yet defined */
2805 if ( work_image == (Image *) NULL )
2806 {
2807 work_image=CloneImage(image,0,0,MagickTrue,exception);
2808 if (work_image == (Image *) NULL)
2809 goto error_cleanup;
2810 if (SetImageStorageClass(work_image,DirectClass) == MagickFalse)
2811 {
2812 InheritException(exception,&work_image->exception);
2813 goto error_cleanup;
2814 }
2815 }
2816
anthony47f5d062010-05-23 07:47:50 +00002817 /* APPLY THE MORPHOLOGICAL PRIMITIVE (curr -> work) */
anthony9eb4f742010-05-18 02:45:54 +00002818 count++;
anthony47f5d062010-05-23 07:47:50 +00002819 changed = MorphologyPrimitive(curr_image, work_image, primative,
anthony9eb4f742010-05-18 02:45:54 +00002820 channel, this_kernel, bias, exception);
anthony47f5d062010-05-23 07:47:50 +00002821 kernel_changed += changed;
2822 method_changed += changed;
anthony9eb4f742010-05-18 02:45:54 +00002823
anthony47f5d062010-05-23 07:47:50 +00002824 if ( verbose == MagickTrue ) {
2825 if ( kernel_loop > 1 )
2826 fprintf(stderr, "\n"); /* add end-of-line from previous */
2827 fprintf(stderr, "%s%s%s:%lu.%lu #%lu => Changed %lu", v_info,
2828 MagickOptionToMnemonic(MagickMorphologyOptions, primative),
2829 ( this_kernel == rflt_kernel ) ? "*" : "",
2830 method_loop+kernel_loop-1, kernel_number, count, changed);
2831 }
anthony9eb4f742010-05-18 02:45:54 +00002832 /* prepare next loop */
2833 { Image *tmp = work_image; /* swap images for iteration */
2834 work_image = curr_image;
2835 curr_image = tmp;
2836 }
2837 if ( work_image == image )
anthony47f5d062010-05-23 07:47:50 +00002838 work_image = (Image *) NULL; /* replace input 'image' */
anthony7a01dcf2010-05-11 12:25:52 +00002839
anthony47f5d062010-05-23 07:47:50 +00002840 } /* End Loop 4: Iterate the kernel with primative */
anthony1b2bc0a2010-05-12 05:25:22 +00002841
anthony47f5d062010-05-23 07:47:50 +00002842 if ( verbose == MagickTrue && kernel_changed != changed )
2843 fprintf(stderr, " Total %lu", kernel_changed);
2844 if ( verbose == MagickTrue && stage_loop < stage_limit )
2845 fprintf(stderr, "\n"); /* add end-of-line before looping */
anthony9eb4f742010-05-18 02:45:54 +00002846
2847#if 0
anthony47f5d062010-05-23 07:47:50 +00002848 fprintf(stderr, "--E-- image=0x%lx\n", (unsigned long)image);
2849 fprintf(stderr, " curr =0x%lx\n", (unsigned long)curr_image);
2850 fprintf(stderr, " work =0x%lx\n", (unsigned long)work_image);
2851 fprintf(stderr, " save =0x%lx\n", (unsigned long)save_image);
2852 fprintf(stderr, " union=0x%lx\n", (unsigned long)rslt_image);
anthony9eb4f742010-05-18 02:45:54 +00002853#endif
2854
anthony47f5d062010-05-23 07:47:50 +00002855 } /* End Loop 3: Primative (staging) Loop for Coumpound Methods */
anthony9eb4f742010-05-18 02:45:54 +00002856
anthony47f5d062010-05-23 07:47:50 +00002857 /* Final Post-processing for some Compound Methods
2858 **
2859 ** The removal of any 'Sync' channel flag in the Image Compositon
2860 ** below ensures the methematical compose method is applied in a
2861 ** purely mathematical way, and only to the selected channels.
2862 ** Turn off SVG composition 'alpha blending'.
2863 */
2864 switch( method ) {
2865 case EdgeOutMorphology:
2866 case EdgeInMorphology:
2867 case TopHatMorphology:
2868 case BottomHatMorphology:
2869 if ( verbose == MagickTrue )
2870 fprintf(stderr, "\n%s: Difference with original image",
2871 MagickOptionToMnemonic(MagickMorphologyOptions, method) );
2872 (void) CompositeImageChannel(curr_image,
2873 (ChannelType) (channel & ~SyncChannels),
2874 DifferenceCompositeOp, image, 0, 0);
2875 break;
2876 case EdgeMorphology:
2877 if ( verbose == MagickTrue )
2878 fprintf(stderr, "\n%s: Difference of Dilate and Erode",
2879 MagickOptionToMnemonic(MagickMorphologyOptions, method) );
2880 (void) CompositeImageChannel(curr_image,
2881 (ChannelType) (channel & ~SyncChannels),
2882 DifferenceCompositeOp, save_image, 0, 0);
2883 save_image = DestroyImage(save_image); /* finished with save image */
2884 break;
2885 default:
2886 break;
2887 }
2888
2889 /* multi-kernel handling: re-iterate, or compose results */
2890 if ( kernel->next == (KernelInfo *) NULL )
anthonyc3e48252010-05-24 12:43:11 +00002891 rslt_image = curr_image; /* just return the resulting image */
anthony47f5d062010-05-23 07:47:50 +00002892 else if ( rslt_compose == NoCompositeOp )
anthonyc3e48252010-05-24 12:43:11 +00002893 { if ( verbose == MagickTrue ) {
2894 if ( this_kernel->next != (KernelInfo *) NULL )
2895 fprintf(stderr, " (re-iterate)");
2896 else
2897 fprintf(stderr, " (done)");
2898 }
2899 rslt_image = curr_image; /* return result, and re-iterate */
anthony9eb4f742010-05-18 02:45:54 +00002900 }
anthony47f5d062010-05-23 07:47:50 +00002901 else if ( rslt_image == (Image *) NULL)
2902 { if ( verbose == MagickTrue )
2903 fprintf(stderr, " (save for compose)");
2904 rslt_image = curr_image;
2905 curr_image = (Image *) image; /* continue with original image */
anthony9eb4f742010-05-18 02:45:54 +00002906 }
anthony47f5d062010-05-23 07:47:50 +00002907 else
2908 { /* add the new 'current' result to the composition
2909 **
2910 ** The removal of any 'Sync' channel flag in the Image Compositon
2911 ** below ensures the methematical compose method is applied in a
2912 ** purely mathematical way, and only to the selected channels.
2913 ** Turn off SVG composition 'alpha blending'.
2914 */
2915 if ( verbose == MagickTrue )
2916 fprintf(stderr, " (compose \"%s\")",
2917 MagickOptionToMnemonic(MagickComposeOptions, rslt_compose) );
2918 (void) CompositeImageChannel(rslt_image,
2919 (ChannelType) (channel & ~SyncChannels), rslt_compose,
2920 curr_image, 0, 0);
2921 curr_image = (Image *) image; /* continue with original image */
2922 }
2923 if ( verbose == MagickTrue )
2924 fprintf(stderr, "\n");
anthony9eb4f742010-05-18 02:45:54 +00002925
anthony47f5d062010-05-23 07:47:50 +00002926 /* loop to the next kernel in a multi-kernel list */
2927 norm_kernel = norm_kernel->next;
2928 if ( rflt_kernel != (KernelInfo *) NULL )
2929 rflt_kernel = rflt_kernel->next;
2930 kernel_number++;
2931 } /* End Loop 2: Loop over each kernel */
anthony9eb4f742010-05-18 02:45:54 +00002932
anthony47f5d062010-05-23 07:47:50 +00002933 } /* End Loop 1: compound method interation */
anthony602ab9b2010-01-05 08:06:50 +00002934
anthony9eb4f742010-05-18 02:45:54 +00002935 goto exit_cleanup;
anthony1b2bc0a2010-05-12 05:25:22 +00002936
anthony47f5d062010-05-23 07:47:50 +00002937 /* Yes goto's are bad, but it makes cleanup lot more efficient */
anthony1b2bc0a2010-05-12 05:25:22 +00002938error_cleanup:
anthony47f5d062010-05-23 07:47:50 +00002939 if ( curr_image != (Image *) NULL &&
2940 curr_image != rslt_image &&
2941 curr_image != image )
2942 curr_image = DestroyImage(curr_image);
2943 if ( rslt_image != (Image *) NULL )
2944 rslt_image = DestroyImage(rslt_image);
anthony1b2bc0a2010-05-12 05:25:22 +00002945exit_cleanup:
anthony47f5d062010-05-23 07:47:50 +00002946 if ( curr_image != (Image *) NULL &&
2947 curr_image != rslt_image &&
2948 curr_image != image )
2949 curr_image = DestroyImage(curr_image);
anthony9eb4f742010-05-18 02:45:54 +00002950 if ( work_image != (Image *) NULL )
anthony47f5d062010-05-23 07:47:50 +00002951 work_image = DestroyImage(work_image);
anthony9eb4f742010-05-18 02:45:54 +00002952 if ( save_image != (Image *) NULL )
anthony47f5d062010-05-23 07:47:50 +00002953 save_image = DestroyImage(save_image);
2954 if ( reflected_kernel != (KernelInfo *) NULL )
2955 reflected_kernel = DestroyKernelInfo(reflected_kernel);
2956 return(rslt_image);
anthony9eb4f742010-05-18 02:45:54 +00002957}
2958
2959/*
2960%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2961% %
2962% %
2963% %
2964% M o r p h o l o g y I m a g e C h a n n e l %
2965% %
2966% %
2967% %
2968%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2969%
2970% MorphologyImageChannel() applies a user supplied kernel to the image
2971% according to the given mophology method.
2972%
2973% This function applies any and all user defined settings before calling
2974% the above internal function MorphologyApply().
2975%
2976% User defined settings include...
anthony46a369d2010-05-19 02:41:48 +00002977% * Output Bias for Convolution and correlation ("-bias")
2978% * Kernel Scale/normalize settings ("-set 'option:convolve:scale'")
2979% This can also includes the addition of a scaled unity kernel.
2980% * Show Kernel being applied ("-set option:showkernel 1")
anthony9eb4f742010-05-18 02:45:54 +00002981%
2982% The format of the MorphologyImage method is:
2983%
2984% Image *MorphologyImage(const Image *image,MorphologyMethod method,
2985% const long iterations,KernelInfo *kernel,ExceptionInfo *exception)
2986%
2987% Image *MorphologyImageChannel(const Image *image, const ChannelType
2988% channel,MorphologyMethod method,const long iterations,
2989% KernelInfo *kernel,ExceptionInfo *exception)
2990%
2991% A description of each parameter follows:
2992%
2993% o image: the image.
2994%
2995% o method: the morphology method to be applied.
2996%
2997% o iterations: apply the operation this many times (or no change).
2998% A value of -1 means loop until no change found.
2999% How this is applied may depend on the morphology method.
3000% Typically this is a value of 1.
3001%
3002% o channel: the channel type.
3003%
3004% o kernel: An array of double representing the morphology kernel.
3005% Warning: kernel may be normalized for the Convolve method.
3006%
3007% o exception: return any errors or warnings in this structure.
3008%
3009*/
3010
3011MagickExport Image *MorphologyImageChannel(const Image *image,
3012 const ChannelType channel,const MorphologyMethod method,
3013 const long iterations,const KernelInfo *kernel,ExceptionInfo *exception)
3014{
3015 const char
3016 *artifact;
3017
3018 KernelInfo
3019 *curr_kernel;
3020
anthony47f5d062010-05-23 07:47:50 +00003021 CompositeOperator
3022 compose;
3023
anthony9eb4f742010-05-18 02:45:54 +00003024 Image
3025 *morphology_image;
3026
3027
anthony46a369d2010-05-19 02:41:48 +00003028 /* Apply Convolve/Correlate Normalization and Scaling Factors.
3029 * This is done BEFORE the ShowKernelInfo() function is called so that
3030 * users can see the results of the 'option:convolve:scale' option.
anthony9eb4f742010-05-18 02:45:54 +00003031 */
3032 curr_kernel = (KernelInfo *) kernel;
anthonyf71ca292010-05-19 04:08:43 +00003033 if ( method == ConvolveMorphology || method == CorrelateMorphology )
anthony9eb4f742010-05-18 02:45:54 +00003034 {
3035 artifact = GetImageArtifact(image,"convolve:scale");
3036 if ( artifact != (char *)NULL ) {
anthony9eb4f742010-05-18 02:45:54 +00003037 if ( curr_kernel == kernel )
3038 curr_kernel = CloneKernelInfo(kernel);
3039 if (curr_kernel == (KernelInfo *) NULL) {
3040 curr_kernel=DestroyKernelInfo(curr_kernel);
3041 return((Image *) NULL);
3042 }
anthony46a369d2010-05-19 02:41:48 +00003043 ScaleGeometryKernelInfo(curr_kernel, artifact);
anthony9eb4f742010-05-18 02:45:54 +00003044 }
3045 }
3046
3047 /* display the (normalized) kernel via stderr */
3048 artifact = GetImageArtifact(image,"showkernel");
anthony47f5d062010-05-23 07:47:50 +00003049 if ( artifact == (const char *) NULL)
3050 artifact = GetImageArtifact(image,"convolve:showkernel");
3051 if ( artifact == (const char *) NULL)
3052 artifact = GetImageArtifact(image,"morphology:showkernel");
anthony9eb4f742010-05-18 02:45:54 +00003053 if ( artifact != (const char *) NULL)
3054 ShowKernelInfo(curr_kernel);
3055
anthony47f5d062010-05-23 07:47:50 +00003056 /* override the default handling of multi-kernel morphology results
3057 * if 'Undefined' use the default method
3058 * if 'None' (default for 'Convolve') re-iterate previous result
3059 * otherwise merge resulting images using compose method given
3060 */
3061 compose = UndefinedCompositeOp; /* use default for method */
3062 artifact = GetImageArtifact(image,"morphology:compose");
3063 if ( artifact != (const char *) NULL)
3064 compose = (CompositeOperator) ParseMagickOption(
3065 MagickComposeOptions,MagickFalse,artifact);
3066
anthony9eb4f742010-05-18 02:45:54 +00003067 /* Apply the Morphology */
3068 morphology_image = MorphologyApply(image, channel, method, iterations,
anthony47f5d062010-05-23 07:47:50 +00003069 curr_kernel, compose, image->bias, exception);
anthony9eb4f742010-05-18 02:45:54 +00003070
3071 /* Cleanup and Exit */
3072 if ( curr_kernel != kernel )
anthony1b2bc0a2010-05-12 05:25:22 +00003073 curr_kernel=DestroyKernelInfo(curr_kernel);
anthony9eb4f742010-05-18 02:45:54 +00003074 return(morphology_image);
3075}
3076
3077MagickExport Image *MorphologyImage(const Image *image, const MorphologyMethod
3078 method, const long iterations,const KernelInfo *kernel, ExceptionInfo
3079 *exception)
3080{
3081 Image
3082 *morphology_image;
3083
3084 morphology_image=MorphologyImageChannel(image,DefaultChannels,method,
3085 iterations,kernel,exception);
3086 return(morphology_image);
anthony602ab9b2010-01-05 08:06:50 +00003087}
anthony83ba99b2010-01-24 08:48:15 +00003088
3089/*
3090%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3091% %
3092% %
3093% %
anthony4fd27e22010-02-07 08:17:18 +00003094+ R o t a t e K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00003095% %
3096% %
3097% %
3098%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3099%
anthony46a369d2010-05-19 02:41:48 +00003100% RotateKernelInfo() rotates the kernel by the angle given.
3101%
3102% Currently it is restricted to 90 degree angles, of either 1D kernels
3103% or square kernels. And 'circular' rotations of 45 degrees for 3x3 kernels.
3104% It will ignore usless rotations for specific 'named' built-in kernels.
anthony83ba99b2010-01-24 08:48:15 +00003105%
anthony4fd27e22010-02-07 08:17:18 +00003106% The format of the RotateKernelInfo method is:
anthony83ba99b2010-01-24 08:48:15 +00003107%
anthony4fd27e22010-02-07 08:17:18 +00003108% void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00003109%
3110% A description of each parameter follows:
3111%
3112% o kernel: the Morphology/Convolution kernel
3113%
3114% o angle: angle to rotate in degrees
3115%
anthony46a369d2010-05-19 02:41:48 +00003116% This function is currently internal to this module only, but can be exported
3117% to other modules if needed.
anthony83ba99b2010-01-24 08:48:15 +00003118*/
anthony4fd27e22010-02-07 08:17:18 +00003119static void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00003120{
anthony1b2bc0a2010-05-12 05:25:22 +00003121 /* angle the lower kernels first */
3122 if ( kernel->next != (KernelInfo *) NULL)
3123 RotateKernelInfo(kernel->next, angle);
3124
anthony83ba99b2010-01-24 08:48:15 +00003125 /* WARNING: Currently assumes the kernel (rightly) is horizontally symetrical
3126 **
3127 ** TODO: expand beyond simple 90 degree rotates, flips and flops
3128 */
3129
3130 /* Modulus the angle */
3131 angle = fmod(angle, 360.0);
3132 if ( angle < 0 )
3133 angle += 360.0;
3134
anthony3c10fc82010-05-13 02:40:51 +00003135 if ( 337.5 < angle || angle <= 22.5 )
anthony43c49252010-05-18 10:59:50 +00003136 return; /* Near zero angle - no change! - At least not at this time */
anthony83ba99b2010-01-24 08:48:15 +00003137
anthony3dd0f622010-05-13 12:57:32 +00003138 /* Handle special cases */
anthony83ba99b2010-01-24 08:48:15 +00003139 switch (kernel->type) {
3140 /* These built-in kernels are cylindrical kernels, rotating is useless */
3141 case GaussianKernel:
anthony83ba99b2010-01-24 08:48:15 +00003142 case DOGKernel:
3143 case DiskKernel:
anthony3dd0f622010-05-13 12:57:32 +00003144 case PeaksKernel:
3145 case LaplacianKernel:
anthony83ba99b2010-01-24 08:48:15 +00003146 case ChebyshevKernel:
3147 case ManhattenKernel:
3148 case EuclideanKernel:
3149 return;
3150
3151 /* These may be rotatable at non-90 angles in the future */
3152 /* but simply rotating them in multiples of 90 degrees is useless */
3153 case SquareKernel:
3154 case DiamondKernel:
3155 case PlusKernel:
anthony3dd0f622010-05-13 12:57:32 +00003156 case CrossKernel:
anthony83ba99b2010-01-24 08:48:15 +00003157 return;
3158
3159 /* These only allows a +/-90 degree rotation (by transpose) */
3160 /* A 180 degree rotation is useless */
3161 case BlurKernel:
3162 case RectangleKernel:
3163 if ( 135.0 < angle && angle <= 225.0 )
3164 return;
3165 if ( 225.0 < angle && angle <= 315.0 )
3166 angle -= 180;
3167 break;
3168
anthony3dd0f622010-05-13 12:57:32 +00003169 default:
anthony83ba99b2010-01-24 08:48:15 +00003170 break;
3171 }
anthony3c10fc82010-05-13 02:40:51 +00003172 /* Attempt rotations by 45 degrees */
3173 if ( 22.5 < fmod(angle,90.0) && fmod(angle,90.0) <= 67.5 )
3174 {
3175 if ( kernel->width == 3 && kernel->height == 3 )
3176 { /* Rotate a 3x3 square by 45 degree angle */
3177 MagickRealType t = kernel->values[0];
anthony43c49252010-05-18 10:59:50 +00003178 kernel->values[0] = kernel->values[3];
3179 kernel->values[3] = kernel->values[6];
3180 kernel->values[6] = kernel->values[7];
3181 kernel->values[7] = kernel->values[8];
3182 kernel->values[8] = kernel->values[5];
3183 kernel->values[5] = kernel->values[2];
3184 kernel->values[2] = kernel->values[1];
3185 kernel->values[1] = t;
anthony1d45eb92010-05-25 11:13:23 +00003186 /* rotate non-centered origin */
3187 if ( kernel->x != 1 || kernel->y != 1 ) {
3188 long x,y;
3189 x = (long) kernel->x-1;
3190 y = (long) kernel->y-1;
3191 if ( x == y ) x = 0;
3192 else if ( x == 0 ) x = -y;
3193 else if ( x == -y ) y = 0;
3194 else if ( y == 0 ) y = x;
3195 kernel->x = (unsigned long) x+1;
3196 kernel->y = (unsigned long) y+1;
3197 }
anthony43c49252010-05-18 10:59:50 +00003198 angle = fmod(angle+315.0, 360.0); /* angle reduced 45 degrees */
3199 kernel->angle = fmod(kernel->angle+45.0, 360.0);
anthony3c10fc82010-05-13 02:40:51 +00003200 }
3201 else
3202 perror("Unable to rotate non-3x3 kernel by 45 degrees");
3203 }
3204 if ( 45.0 < fmod(angle, 180.0) && fmod(angle,180.0) <= 135.0 )
3205 {
3206 if ( kernel->width == 1 || kernel->height == 1 )
3207 { /* Do a transpose of the image, which results in a 90
3208 ** degree rotation of a 1 dimentional kernel
3209 */
3210 long
3211 t;
3212 t = (long) kernel->width;
3213 kernel->width = kernel->height;
3214 kernel->height = (unsigned long) t;
3215 t = kernel->x;
3216 kernel->x = kernel->y;
3217 kernel->y = t;
anthony43c49252010-05-18 10:59:50 +00003218 if ( kernel->width == 1 ) {
3219 angle = fmod(angle+270.0, 360.0); /* angle reduced 90 degrees */
3220 kernel->angle = fmod(kernel->angle+90.0, 360.0);
3221 } else {
3222 angle = fmod(angle+90.0, 360.0); /* angle increased 90 degrees */
3223 kernel->angle = fmod(kernel->angle+270.0, 360.0);
3224 }
anthony3c10fc82010-05-13 02:40:51 +00003225 }
3226 else if ( kernel->width == kernel->height )
3227 { /* Rotate a square array of values by 90 degrees */
anthony1d45eb92010-05-25 11:13:23 +00003228 { register unsigned long
3229 i,j,x,y;
3230 register MagickRealType
3231 *k,t;
3232 k=kernel->values;
3233 for( i=0, x=kernel->width-1; i<=x; i++, x--)
3234 for( j=0, y=kernel->height-1; j<y; j++, y--)
3235 { t = k[i+j*kernel->width];
3236 k[i+j*kernel->width] = k[j+x*kernel->width];
3237 k[j+x*kernel->width] = k[x+y*kernel->width];
3238 k[x+y*kernel->width] = k[y+i*kernel->width];
3239 k[y+i*kernel->width] = t;
3240 }
3241 }
3242 /* rotate the origin - relative to center of array */
3243 { register long x,y;
3244 x = (long) kernel->x*2-kernel->width+1;
3245 y = (long) kernel->y*2-kernel->height+1;
3246 kernel->x = (unsigned long) ( -y +kernel->width-1)/2;
3247 kernel->y = (unsigned long) ( +x +kernel->height-1)/2;
3248 }
anthony43c49252010-05-18 10:59:50 +00003249 angle = fmod(angle+270.0, 360.0); /* angle reduced 90 degrees */
3250 kernel->angle = fmod(kernel->angle+90.0, 360.0);
anthony3c10fc82010-05-13 02:40:51 +00003251 }
3252 else
3253 perror("Unable to rotate a non-square, non-linear kernel 90 degrees");
3254 }
anthony83ba99b2010-01-24 08:48:15 +00003255 if ( 135.0 < angle && angle <= 225.0 )
3256 {
anthony43c49252010-05-18 10:59:50 +00003257 /* For a 180 degree rotation - also know as a reflection
3258 * This is actually a very very common operation!
3259 * Basically all that is needed is a reversal of the kernel data!
3260 * And a reflection of the origon
3261 */
anthony83ba99b2010-01-24 08:48:15 +00003262 unsigned long
3263 i,j;
3264 register double
3265 *k,t;
3266
3267 k=kernel->values;
3268 for ( i=0, j=kernel->width*kernel->height-1; i<j; i++, j--)
3269 t=k[i], k[i]=k[j], k[j]=t;
3270
anthony930be612010-02-08 04:26:15 +00003271 kernel->x = (long) kernel->width - kernel->x - 1;
3272 kernel->y = (long) kernel->height - kernel->y - 1;
anthony43c49252010-05-18 10:59:50 +00003273 angle = fmod(angle-180.0, 360.0); /* angle+180 degrees */
3274 kernel->angle = fmod(kernel->angle+180.0, 360.0);
anthony83ba99b2010-01-24 08:48:15 +00003275 }
anthony3c10fc82010-05-13 02:40:51 +00003276 /* At this point angle should at least between -45 (315) and +45 degrees
anthony83ba99b2010-01-24 08:48:15 +00003277 * In the future some form of non-orthogonal angled rotates could be
3278 * performed here, posibily with a linear kernel restriction.
3279 */
3280
3281#if 0
anthony3c10fc82010-05-13 02:40:51 +00003282 { /* Do a Flop by reversing each row.
anthony83ba99b2010-01-24 08:48:15 +00003283 */
3284 unsigned long
3285 y;
cristy150989e2010-02-01 14:59:39 +00003286 register long
anthony83ba99b2010-01-24 08:48:15 +00003287 x,r;
3288 register double
3289 *k,t;
3290
3291 for ( y=0, k=kernel->values; y < kernel->height; y++, k+=kernel->width)
3292 for ( x=0, r=kernel->width-1; x<kernel->width/2; x++, r--)
3293 t=k[x], k[x]=k[r], k[r]=t;
3294
cristyc99304f2010-02-01 15:26:27 +00003295 kernel->x = kernel->width - kernel->x - 1;
anthony83ba99b2010-01-24 08:48:15 +00003296 angle = fmod(angle+180.0, 360.0);
3297 }
3298#endif
3299 return;
3300}
3301
3302/*
3303%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3304% %
3305% %
3306% %
anthony46a369d2010-05-19 02:41:48 +00003307% S c a l e G e o m e t r y K e r n e l I n f o %
3308% %
3309% %
3310% %
3311%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3312%
3313% ScaleGeometryKernelInfo() takes a geometry argument string, typically
3314% provided as a "-set option:convolve:scale {geometry}" user setting,
3315% and modifies the kernel according to the parsed arguments of that setting.
3316%
3317% The first argument (and any normalization flags) are passed to
3318% ScaleKernelInfo() to scale/normalize the kernel. The second argument
3319% is then passed to UnityAddKernelInfo() to add a scled unity kernel
3320% into the scaled/normalized kernel.
3321%
3322% The format of the ScaleKernelInfo method is:
3323%
3324% void ScaleKernelInfo(KernelInfo *kernel, const double scaling_factor,
3325% const MagickStatusType normalize_flags )
3326%
3327% A description of each parameter follows:
3328%
3329% o kernel: the Morphology/Convolution kernel to modify
3330%
3331% o geometry:
3332% The geometry string to parse, typically from the user provided
3333% "-set option:convolve:scale {geometry}" setting.
3334%
3335*/
3336MagickExport void ScaleGeometryKernelInfo (KernelInfo *kernel,
3337 const char *geometry)
3338{
3339 GeometryFlags
3340 flags;
3341 GeometryInfo
3342 args;
3343
3344 SetGeometryInfo(&args);
3345 flags = (GeometryFlags) ParseGeometry(geometry, &args);
3346
3347#if 0
3348 /* For Debugging Geometry Input */
3349 fprintf(stderr, "Geometry = 0x%04X : %lg x %lg %+lg %+lg\n",
3350 flags, args.rho, args.sigma, args.xi, args.psi );
3351#endif
3352
3353 if ( (flags & PercentValue) != 0 ) /* Handle Percentage flag*/
3354 args.rho *= 0.01, args.sigma *= 0.01;
3355
3356 if ( (flags & RhoValue) == 0 ) /* Set Defaults for missing args */
3357 args.rho = 1.0;
3358 if ( (flags & SigmaValue) == 0 )
3359 args.sigma = 0.0;
3360
3361 /* Scale/Normalize the input kernel */
3362 ScaleKernelInfo(kernel, args.rho, flags);
3363
3364 /* Add Unity Kernel, for blending with original */
3365 if ( (flags & SigmaValue) != 0 )
3366 UnityAddKernelInfo(kernel, args.sigma);
3367
3368 return;
3369}
3370/*
3371%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3372% %
3373% %
3374% %
cristy6771f1e2010-03-05 19:43:39 +00003375% S c a l e K e r n e l I n f o %
anthonycc6c8362010-01-25 04:14:01 +00003376% %
3377% %
3378% %
3379%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3380%
anthony1b2bc0a2010-05-12 05:25:22 +00003381% ScaleKernelInfo() scales the given kernel list by the given amount, with or
3382% without normalization of the sum of the kernel values (as per given flags).
anthonycc6c8362010-01-25 04:14:01 +00003383%
anthony999bb2c2010-02-18 12:38:01 +00003384% By default (no flags given) the values within the kernel is scaled
anthony1b2bc0a2010-05-12 05:25:22 +00003385% directly using given scaling factor without change.
anthonycc6c8362010-01-25 04:14:01 +00003386%
anthony46a369d2010-05-19 02:41:48 +00003387% If either of the two 'normalize_flags' are given the kernel will first be
3388% normalized and then further scaled by the scaling factor value given.
anthony999bb2c2010-02-18 12:38:01 +00003389%
3390% Kernel normalization ('normalize_flags' given) is designed to ensure that
3391% any use of the kernel scaling factor with 'Convolve' or 'Correlate'
anthony1b2bc0a2010-05-12 05:25:22 +00003392% morphology methods will fall into -1.0 to +1.0 range. Note that for
3393% non-HDRI versions of IM this may cause images to have any negative results
3394% clipped, unless some 'bias' is used.
anthony999bb2c2010-02-18 12:38:01 +00003395%
3396% More specifically. Kernels which only contain positive values (such as a
3397% 'Gaussian' kernel) will be scaled so that those values sum to +1.0,
anthony1b2bc0a2010-05-12 05:25:22 +00003398% ensuring a 0.0 to +1.0 output range for non-HDRI images.
anthony999bb2c2010-02-18 12:38:01 +00003399%
3400% For Kernels that contain some negative values, (such as 'Sharpen' kernels)
3401% the kernel will be scaled by the absolute of the sum of kernel values, so
3402% that it will generally fall within the +/- 1.0 range.
3403%
3404% For kernels whose values sum to zero, (such as 'Laplician' kernels) kernel
3405% will be scaled by just the sum of the postive values, so that its output
3406% range will again fall into the +/- 1.0 range.
3407%
3408% For special kernels designed for locating shapes using 'Correlate', (often
3409% only containing +1 and -1 values, representing foreground/brackground
3410% matching) a special normalization method is provided to scale the positive
3411% values seperatally to those of the negative values, so the kernel will be
3412% forced to become a zero-sum kernel better suited to such searches.
3413%
anthony1b2bc0a2010-05-12 05:25:22 +00003414% WARNING: Correct normalization of the kernel assumes that the '*_range'
anthony999bb2c2010-02-18 12:38:01 +00003415% attributes within the kernel structure have been correctly set during the
3416% kernels creation.
3417%
3418% NOTE: The values used for 'normalize_flags' have been selected specifically
anthony46a369d2010-05-19 02:41:48 +00003419% to match the use of geometry options, so that '!' means NormalizeValue, '^'
3420% means CorrelateNormalizeValue. All other GeometryFlags values are ignored.
anthonycc6c8362010-01-25 04:14:01 +00003421%
anthony4fd27e22010-02-07 08:17:18 +00003422% The format of the ScaleKernelInfo method is:
anthonycc6c8362010-01-25 04:14:01 +00003423%
anthony999bb2c2010-02-18 12:38:01 +00003424% void ScaleKernelInfo(KernelInfo *kernel, const double scaling_factor,
3425% const MagickStatusType normalize_flags )
anthonycc6c8362010-01-25 04:14:01 +00003426%
3427% A description of each parameter follows:
3428%
3429% o kernel: the Morphology/Convolution kernel
3430%
anthony999bb2c2010-02-18 12:38:01 +00003431% o scaling_factor:
3432% multiply all values (after normalization) by this factor if not
3433% zero. If the kernel is normalized regardless of any flags.
3434%
3435% o normalize_flags:
3436% GeometryFlags defining normalization method to use.
3437% specifically: NormalizeValue, CorrelateNormalizeValue,
3438% and/or PercentValue
anthonycc6c8362010-01-25 04:14:01 +00003439%
3440*/
cristy6771f1e2010-03-05 19:43:39 +00003441MagickExport void ScaleKernelInfo(KernelInfo *kernel,
3442 const double scaling_factor,const GeometryFlags normalize_flags)
anthonycc6c8362010-01-25 04:14:01 +00003443{
cristy150989e2010-02-01 14:59:39 +00003444 register long
anthonycc6c8362010-01-25 04:14:01 +00003445 i;
3446
anthony999bb2c2010-02-18 12:38:01 +00003447 register double
3448 pos_scale,
3449 neg_scale;
3450
anthony46a369d2010-05-19 02:41:48 +00003451 /* do the other kernels in a multi-kernel list first */
anthony1b2bc0a2010-05-12 05:25:22 +00003452 if ( kernel->next != (KernelInfo *) NULL)
3453 ScaleKernelInfo(kernel->next, scaling_factor, normalize_flags);
3454
anthony46a369d2010-05-19 02:41:48 +00003455 /* Normalization of Kernel */
anthony999bb2c2010-02-18 12:38:01 +00003456 pos_scale = 1.0;
3457 if ( (normalize_flags&NormalizeValue) != 0 ) {
anthony999bb2c2010-02-18 12:38:01 +00003458 if ( fabs(kernel->positive_range + kernel->negative_range) > MagickEpsilon )
anthonyf4e00312010-05-20 12:06:35 +00003459 /* non-zero-summing kernel (generally positive) */
anthony999bb2c2010-02-18 12:38:01 +00003460 pos_scale = fabs(kernel->positive_range + kernel->negative_range);
anthonycc6c8362010-01-25 04:14:01 +00003461 else
anthonyf4e00312010-05-20 12:06:35 +00003462 /* zero-summing kernel */
3463 pos_scale = kernel->positive_range;
anthony999bb2c2010-02-18 12:38:01 +00003464 }
anthony46a369d2010-05-19 02:41:48 +00003465 /* Force kernel into a normalized zero-summing kernel */
anthony999bb2c2010-02-18 12:38:01 +00003466 if ( (normalize_flags&CorrelateNormalizeValue) != 0 ) {
3467 pos_scale = ( fabs(kernel->positive_range) > MagickEpsilon )
3468 ? kernel->positive_range : 1.0;
3469 neg_scale = ( fabs(kernel->negative_range) > MagickEpsilon )
3470 ? -kernel->negative_range : 1.0;
3471 }
3472 else
3473 neg_scale = pos_scale;
3474
3475 /* finialize scaling_factor for positive and negative components */
3476 pos_scale = scaling_factor/pos_scale;
3477 neg_scale = scaling_factor/neg_scale;
anthonycc6c8362010-01-25 04:14:01 +00003478
cristy150989e2010-02-01 14:59:39 +00003479 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00003480 if ( ! IsNan(kernel->values[i]) )
anthony999bb2c2010-02-18 12:38:01 +00003481 kernel->values[i] *= (kernel->values[i] >= 0) ? pos_scale : neg_scale;
anthonycc6c8362010-01-25 04:14:01 +00003482
anthony999bb2c2010-02-18 12:38:01 +00003483 /* convolution output range */
3484 kernel->positive_range *= pos_scale;
3485 kernel->negative_range *= neg_scale;
3486 /* maximum and minimum values in kernel */
3487 kernel->maximum *= (kernel->maximum >= 0.0) ? pos_scale : neg_scale;
3488 kernel->minimum *= (kernel->minimum >= 0.0) ? pos_scale : neg_scale;
3489
anthony46a369d2010-05-19 02:41:48 +00003490 /* swap kernel settings if user's scaling factor is negative */
anthony999bb2c2010-02-18 12:38:01 +00003491 if ( scaling_factor < MagickEpsilon ) {
3492 double t;
3493 t = kernel->positive_range;
3494 kernel->positive_range = kernel->negative_range;
3495 kernel->negative_range = t;
3496 t = kernel->maximum;
3497 kernel->maximum = kernel->minimum;
3498 kernel->minimum = 1;
3499 }
anthonycc6c8362010-01-25 04:14:01 +00003500
3501 return;
3502}
3503
3504/*
3505%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3506% %
3507% %
3508% %
anthony46a369d2010-05-19 02:41:48 +00003509% S h o w K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00003510% %
3511% %
3512% %
3513%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3514%
anthony4fd27e22010-02-07 08:17:18 +00003515% ShowKernelInfo() outputs the details of the given kernel defination to
3516% standard error, generally due to a users 'showkernel' option request.
anthony83ba99b2010-01-24 08:48:15 +00003517%
3518% The format of the ShowKernel method is:
3519%
anthony4fd27e22010-02-07 08:17:18 +00003520% void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00003521%
3522% A description of each parameter follows:
3523%
3524% o kernel: the Morphology/Convolution kernel
3525%
anthony83ba99b2010-01-24 08:48:15 +00003526*/
anthony4fd27e22010-02-07 08:17:18 +00003527MagickExport void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00003528{
anthony7a01dcf2010-05-11 12:25:52 +00003529 KernelInfo
3530 *k;
anthony83ba99b2010-01-24 08:48:15 +00003531
anthony43c49252010-05-18 10:59:50 +00003532 unsigned long
anthony7a01dcf2010-05-11 12:25:52 +00003533 c, i, u, v;
3534
3535 for (c=0, k=kernel; k != (KernelInfo *) NULL; c++, k=k->next ) {
3536
anthony46a369d2010-05-19 02:41:48 +00003537 fprintf(stderr, "Kernel");
anthony7a01dcf2010-05-11 12:25:52 +00003538 if ( kernel->next != (KernelInfo *) NULL )
cristye96405a2010-05-19 02:24:31 +00003539 fprintf(stderr, " #%lu", c );
anthony43c49252010-05-18 10:59:50 +00003540 fprintf(stderr, " \"%s",
3541 MagickOptionToMnemonic(MagickKernelOptions, k->type) );
3542 if ( fabs(k->angle) > MagickEpsilon )
3543 fprintf(stderr, "@%lg", k->angle);
3544 fprintf(stderr, "\" of size %lux%lu%+ld%+ld ",
3545 k->width, k->height,
3546 k->x, k->y );
anthony7a01dcf2010-05-11 12:25:52 +00003547 fprintf(stderr,
3548 " with values from %.*lg to %.*lg\n",
3549 GetMagickPrecision(), k->minimum,
3550 GetMagickPrecision(), k->maximum);
anthony46a369d2010-05-19 02:41:48 +00003551 fprintf(stderr, "Forming a output range from %.*lg to %.*lg",
anthony7a01dcf2010-05-11 12:25:52 +00003552 GetMagickPrecision(), k->negative_range,
anthony46a369d2010-05-19 02:41:48 +00003553 GetMagickPrecision(), k->positive_range);
3554 if ( fabs(k->positive_range+k->negative_range) < MagickEpsilon )
3555 fprintf(stderr, " (Zero-Summing)\n");
3556 else if ( fabs(k->positive_range+k->negative_range-1.0) < MagickEpsilon )
3557 fprintf(stderr, " (Normalized)\n");
3558 else
3559 fprintf(stderr, " (Sum %.*lg)\n",
3560 GetMagickPrecision(), k->positive_range+k->negative_range);
anthony43c49252010-05-18 10:59:50 +00003561 for (i=v=0; v < k->height; v++) {
anthony46a369d2010-05-19 02:41:48 +00003562 fprintf(stderr, "%2lu:", v );
anthony43c49252010-05-18 10:59:50 +00003563 for (u=0; u < k->width; u++, i++)
anthony7a01dcf2010-05-11 12:25:52 +00003564 if ( IsNan(k->values[i]) )
anthonyf4e00312010-05-20 12:06:35 +00003565 fprintf(stderr," %*s", GetMagickPrecision()+3, "nan");
anthony7a01dcf2010-05-11 12:25:52 +00003566 else
anthonyf4e00312010-05-20 12:06:35 +00003567 fprintf(stderr," %*.*lg", GetMagickPrecision()+3,
anthony7a01dcf2010-05-11 12:25:52 +00003568 GetMagickPrecision(), k->values[i]);
3569 fprintf(stderr,"\n");
3570 }
anthony83ba99b2010-01-24 08:48:15 +00003571 }
3572}
anthonycc6c8362010-01-25 04:14:01 +00003573
3574/*
3575%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3576% %
3577% %
3578% %
anthony43c49252010-05-18 10:59:50 +00003579% U n i t y A d d K e r n a l I n f o %
3580% %
3581% %
3582% %
3583%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3584%
3585% UnityAddKernelInfo() Adds a given amount of the 'Unity' Convolution Kernel
3586% to the given pre-scaled and normalized Kernel. This in effect adds that
3587% amount of the original image into the resulting convolution kernel. This
3588% value is usually provided by the user as a percentage value in the
3589% 'convolve:scale' setting.
3590%
3591% The resulting effect is to either convert a 'zero-summing' edge detection
3592% kernel (such as a "Laplacian", "DOG" or a "LOG") into a 'sharpening'
3593% kernel.
3594%
3595% Alternativally by using a purely positive kernel, and using a negative
3596% post-normalizing scaling factor, you can convert a 'blurring' kernel (such
3597% as a "Gaussian") into a 'unsharp' kernel.
3598%
anthony46a369d2010-05-19 02:41:48 +00003599% The format of the UnityAdditionKernelInfo method is:
anthony43c49252010-05-18 10:59:50 +00003600%
3601% void UnityAdditionKernelInfo(KernelInfo *kernel, const double scale )
3602%
3603% A description of each parameter follows:
3604%
3605% o kernel: the Morphology/Convolution kernel
3606%
3607% o scale:
3608% scaling factor for the unity kernel to be added to
3609% the given kernel.
3610%
anthony43c49252010-05-18 10:59:50 +00003611*/
3612MagickExport void UnityAddKernelInfo(KernelInfo *kernel,
3613 const double scale)
3614{
anthony46a369d2010-05-19 02:41:48 +00003615 /* do the other kernels in a multi-kernel list first */
3616 if ( kernel->next != (KernelInfo *) NULL)
3617 UnityAddKernelInfo(kernel->next, scale);
anthony43c49252010-05-18 10:59:50 +00003618
anthony46a369d2010-05-19 02:41:48 +00003619 /* Add the scaled unity kernel to the existing kernel */
anthony43c49252010-05-18 10:59:50 +00003620 kernel->values[kernel->x+kernel->y*kernel->width] += scale;
anthony46a369d2010-05-19 02:41:48 +00003621 CalcKernelMetaData(kernel); /* recalculate the meta-data */
anthony43c49252010-05-18 10:59:50 +00003622
3623 return;
3624}
3625
3626/*
3627%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3628% %
3629% %
3630% %
3631% Z e r o K e r n e l N a n s %
anthonycc6c8362010-01-25 04:14:01 +00003632% %
3633% %
3634% %
3635%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3636%
3637% ZeroKernelNans() replaces any special 'nan' value that may be present in
3638% the kernel with a zero value. This is typically done when the kernel will
3639% be used in special hardware (GPU) convolution processors, to simply
3640% matters.
3641%
3642% The format of the ZeroKernelNans method is:
3643%
anthony46a369d2010-05-19 02:41:48 +00003644% void ZeroKernelNans (KernelInfo *kernel)
anthonycc6c8362010-01-25 04:14:01 +00003645%
3646% A description of each parameter follows:
3647%
3648% o kernel: the Morphology/Convolution kernel
3649%
anthonycc6c8362010-01-25 04:14:01 +00003650*/
anthonyc4c86e02010-01-27 09:30:32 +00003651MagickExport void ZeroKernelNans(KernelInfo *kernel)
anthonycc6c8362010-01-25 04:14:01 +00003652{
anthony43c49252010-05-18 10:59:50 +00003653 register unsigned long
anthonycc6c8362010-01-25 04:14:01 +00003654 i;
3655
anthony46a369d2010-05-19 02:41:48 +00003656 /* do the other kernels in a multi-kernel list first */
anthony1b2bc0a2010-05-12 05:25:22 +00003657 if ( kernel->next != (KernelInfo *) NULL)
3658 ZeroKernelNans(kernel->next);
3659
anthony43c49252010-05-18 10:59:50 +00003660 for (i=0; i < (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00003661 if ( IsNan(kernel->values[i]) )
3662 kernel->values[i] = 0.0;
3663
3664 return;
3665}