blob: a1b7f806481271d69d78877452df02921fd83d0b [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 *),
anthony3c10fc82010-05-13 02:40:51 +0000112 ExpandKernelInfo(KernelInfo *, 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{
367 char
368 token[MaxTextExtent];
369
anthony5ef8e942010-05-11 06:51:12 +0000370 long
371 type;
372
anthonyc84dce52010-05-07 05:42:23 +0000373 const char
anthony7a01dcf2010-05-11 12:25:52 +0000374 *p,
375 *end;
anthonyc84dce52010-05-07 05:42:23 +0000376
377 MagickStatusType
378 flags;
379
380 GeometryInfo
381 args;
382
anthonyc84dce52010-05-07 05:42:23 +0000383 /* Parse special 'named' kernel */
anthony5ef8e942010-05-11 06:51:12 +0000384 GetMagickToken(kernel_string,&p,token);
anthonyc84dce52010-05-07 05:42:23 +0000385 type=ParseMagickOption(MagickKernelOptions,MagickFalse,token);
386 if ( type < 0 || type == UserDefinedKernel )
anthony5ef8e942010-05-11 06:51:12 +0000387 return((KernelInfo *)NULL); /* not a valid named kernel */
anthonyc84dce52010-05-07 05:42:23 +0000388
389 while (((isspace((int) ((unsigned char) *p)) != 0) ||
anthony5ef8e942010-05-11 06:51:12 +0000390 (*p == ',') || (*p == ':' )) && (*p != '\0') && (*p != ';'))
anthonyc84dce52010-05-07 05:42:23 +0000391 p++;
anthony7a01dcf2010-05-11 12:25:52 +0000392
393 end = strchr(p, ';'); /* end of this kernel defintion */
394 if ( end == (char *) NULL )
395 end = strchr(p, '\0');
396
397 /* ParseGeometry() needs the geometry separated! -- Arrgghh */
398 memcpy(token, p, (size_t) (end-p));
399 token[end-p] = '\0';
anthonyc84dce52010-05-07 05:42:23 +0000400 SetGeometryInfo(&args);
anthony7a01dcf2010-05-11 12:25:52 +0000401 flags = ParseGeometry(token, &args);
anthonyc84dce52010-05-07 05:42:23 +0000402
anthony3c10fc82010-05-13 02:40:51 +0000403#if 0
404 /* For Debugging Geometry Input */
anthony46a369d2010-05-19 02:41:48 +0000405 fprintf(stderr, "Geometry = 0x%04X : %lg x %lg %+lg %+lg\n",
anthony3c10fc82010-05-13 02:40:51 +0000406 flags, args.rho, args.sigma, args.xi, args.psi );
407#endif
408
anthonyc84dce52010-05-07 05:42:23 +0000409 /* special handling of missing values in input string */
410 switch( type ) {
anthony5ef8e942010-05-11 06:51:12 +0000411 case RectangleKernel:
412 if ( (flags & WidthValue) == 0 ) /* if no width then */
413 args.rho = args.sigma; /* then width = height */
414 if ( args.rho < 1.0 ) /* if width too small */
415 args.rho = 3; /* then width = 3 */
416 if ( args.sigma < 1.0 ) /* if height too small */
417 args.sigma = args.rho; /* then height = width */
418 if ( (flags & XValue) == 0 ) /* center offset if not defined */
419 args.xi = (double)(((long)args.rho-1)/2);
420 if ( (flags & YValue) == 0 )
421 args.psi = (double)(((long)args.sigma-1)/2);
422 break;
423 case SquareKernel:
424 case DiamondKernel:
425 case DiskKernel:
426 case PlusKernel:
anthony3dd0f622010-05-13 12:57:32 +0000427 case CrossKernel:
anthony5ef8e942010-05-11 06:51:12 +0000428 /* If no scale given (a 0 scale is valid! - set it to 1.0 */
429 if ( (flags & HeightValue) == 0 )
430 args.sigma = 1.0;
431 break;
anthonyc1061722010-05-14 06:23:49 +0000432 case RingKernel:
433 if ( (flags & XValue) == 0 )
434 args.xi = 1.0;
435 break;
anthony5ef8e942010-05-11 06:51:12 +0000436 case ChebyshevKernel:
437 case ManhattenKernel:
438 case EuclideanKernel:
anthony43c49252010-05-18 10:59:50 +0000439 if ( (flags & HeightValue) == 0 ) /* no distance scale */
440 args.sigma = 100.0; /* default distance scaling */
441 else if ( (flags & AspectValue ) != 0 ) /* '!' flag */
442 args.sigma = QuantumRange/(args.sigma+1); /* maximum pixel distance */
443 else if ( (flags & PercentValue ) != 0 ) /* '%' flag */
444 args.sigma *= QuantumRange/100.0; /* percentage of color range */
anthony5ef8e942010-05-11 06:51:12 +0000445 break;
446 default:
447 break;
anthonyc84dce52010-05-07 05:42:23 +0000448 }
449
450 return(AcquireKernelBuiltIn((KernelInfoType)type, &args));
451}
452
anthony5ef8e942010-05-11 06:51:12 +0000453MagickExport KernelInfo *AcquireKernelInfo(const char *kernel_string)
454{
anthony7a01dcf2010-05-11 12:25:52 +0000455
456 KernelInfo
anthonydbc89892010-05-12 07:05:27 +0000457 *kernel,
anthony43c49252010-05-18 10:59:50 +0000458 *new_kernel;
anthony7a01dcf2010-05-11 12:25:52 +0000459
anthony5ef8e942010-05-11 06:51:12 +0000460 char
461 token[MaxTextExtent];
462
anthony7a01dcf2010-05-11 12:25:52 +0000463 const char
anthonydbc89892010-05-12 07:05:27 +0000464 *p;
anthony7a01dcf2010-05-11 12:25:52 +0000465
anthonye108a3f2010-05-12 07:24:03 +0000466 unsigned long
467 kernel_number;
468
anthonydbc89892010-05-12 07:05:27 +0000469 p = kernel_string;
anthony43c49252010-05-18 10:59:50 +0000470 kernel = NULL;
anthonye108a3f2010-05-12 07:24:03 +0000471 kernel_number = 0;
anthony5ef8e942010-05-11 06:51:12 +0000472
anthonydbc89892010-05-12 07:05:27 +0000473 while ( GetMagickToken(p,NULL,token), *token != '\0' ) {
anthony7a01dcf2010-05-11 12:25:52 +0000474
anthony43c49252010-05-18 10:59:50 +0000475 /* ignore extra or multiple ';' kernel seperators */
anthonydbc89892010-05-12 07:05:27 +0000476 if ( *token != ';' ) {
anthony7a01dcf2010-05-11 12:25:52 +0000477
anthonydbc89892010-05-12 07:05:27 +0000478 /* tokens starting with alpha is a Named kernel */
anthony43c49252010-05-18 10:59:50 +0000479 if (isalpha((int) *token) != 0)
480 new_kernel = ParseKernelName(p);
anthonydbc89892010-05-12 07:05:27 +0000481 else /* otherwise a user defined kernel array */
anthony43c49252010-05-18 10:59:50 +0000482 new_kernel = ParseKernelArray(p);
anthony7a01dcf2010-05-11 12:25:52 +0000483
anthonye108a3f2010-05-12 07:24:03 +0000484 /* Error handling -- this is not proper error handling! */
485 if ( new_kernel == (KernelInfo *) NULL ) {
486 fprintf(stderr, "Failed to parse kernel number #%lu\n", kernel_number);
487 if ( kernel != (KernelInfo *) NULL )
488 kernel=DestroyKernelInfo(kernel);
489 return((KernelInfo *) NULL);
anthonydbc89892010-05-12 07:05:27 +0000490 }
anthonye108a3f2010-05-12 07:24:03 +0000491
492 /* initialise or append the kernel list */
anthony3dd0f622010-05-13 12:57:32 +0000493 if ( kernel == (KernelInfo *) NULL )
494 kernel = new_kernel;
495 else
anthony43c49252010-05-18 10:59:50 +0000496 LastKernelInfo(kernel)->next = new_kernel;
anthonydbc89892010-05-12 07:05:27 +0000497 }
498
499 /* look for the next kernel in list */
500 p = strchr(p, ';');
501 if ( p == (char *) NULL )
502 break;
503 p++;
504
505 }
anthony7a01dcf2010-05-11 12:25:52 +0000506 return(kernel);
anthony5ef8e942010-05-11 06:51:12 +0000507}
508
anthony602ab9b2010-01-05 08:06:50 +0000509
510/*
511%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
512% %
513% %
514% %
515% A c q u i r e K e r n e l B u i l t I n %
516% %
517% %
518% %
519%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
520%
521% AcquireKernelBuiltIn() returned one of the 'named' built-in types of
522% kernels used for special purposes such as gaussian blurring, skeleton
523% pruning, and edge distance determination.
524%
525% They take a KernelType, and a set of geometry style arguments, which were
526% typically decoded from a user supplied string, or from a more complex
527% Morphology Method that was requested.
528%
529% The format of the AcquireKernalBuiltIn method is:
530%
cristy2be15382010-01-21 02:38:03 +0000531% KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000532% const GeometryInfo args)
533%
534% A description of each parameter follows:
535%
536% o type: the pre-defined type of kernel wanted
537%
538% o args: arguments defining or modifying the kernel
539%
540% Convolution Kernels
541%
anthony46a369d2010-05-19 02:41:48 +0000542% Unity
543% the No-Op kernel, also requivelent to Gaussian of sigma zero.
544% Basically a 3x3 kernel of a 1 surrounded by zeros.
545%
anthony3c10fc82010-05-13 02:40:51 +0000546% Gaussian:{radius},{sigma}
547% Generate a two-dimentional gaussian kernel, as used by -gaussian.
anthonyc1061722010-05-14 06:23:49 +0000548% The sigma for the curve is required. The resulting kernel is
549% normalized,
550%
551% If 'sigma' is zero, you get a single pixel on a field of zeros.
anthony602ab9b2010-01-05 08:06:50 +0000552%
553% NOTE: that the 'radius' is optional, but if provided can limit (clip)
554% the final size of the resulting kernel to a square 2*radius+1 in size.
555% The radius should be at least 2 times that of the sigma value, or
556% sever clipping and aliasing may result. If not given or set to 0 the
557% radius will be determined so as to produce the best minimal error
558% result, which is usally much larger than is normally needed.
559%
anthonyc1061722010-05-14 06:23:49 +0000560% DOG:{radius},{sigma1},{sigma2}
561% "Difference of Gaussians" Kernel.
562% As "Gaussian" but with a gaussian produced by 'sigma2' subtracted
563% from the gaussian produced by 'sigma1'. Typically sigma2 > sigma1.
564% The result is a zero-summing kernel.
anthony602ab9b2010-01-05 08:06:50 +0000565%
anthony9eb4f742010-05-18 02:45:54 +0000566% LOG:{radius},{sigma}
567% "Laplacian of a Gaussian" or "Mexician Hat" Kernel.
568% The supposed ideal edge detection, zero-summing kernel.
569%
570% An alturnative to this kernel is to use a "DOG" with a sigma ratio of
571% approx 1.6, which can also be applied as a 2 pass "DOB" (see below).
572%
anthonyc1061722010-05-14 06:23:49 +0000573% Blur:{radius},{sigma}[,{angle}]
574% Generates a 1 dimensional or linear gaussian blur, at the angle given
575% (current restricted to orthogonal angles). If a 'radius' is given the
576% kernel is clipped to a width of 2*radius+1. Kernel can be rotated
577% by a 90 degree angle.
578%
579% If 'sigma' is zero, you get a single pixel on a field of zeros.
580%
581% Note that two convolutions with two "Blur" kernels perpendicular to
582% each other, is equivelent to a far larger "Gaussian" kernel with the
583% same sigma value, However it is much faster to apply. This is how the
584% "-blur" operator actually works.
585%
586% DOB:{radius},{sigma1},{sigma2}[,{angle}]
587% "Difference of Blurs" Kernel.
588% As "Blur" but with the 1D gaussian produced by 'sigma2' subtracted
589% from thethe 1D gaussian produced by 'sigma1'.
590% The result is a zero-summing kernel.
591%
592% This can be used to generate a faster "DOG" convolution, in the same
593% way "Blur" can.
anthony602ab9b2010-01-05 08:06:50 +0000594%
anthony3c10fc82010-05-13 02:40:51 +0000595% Comet:{width},{sigma},{angle}
596% Blur in one direction only, much like how a bright object leaves
anthony602ab9b2010-01-05 08:06:50 +0000597% a comet like trail. The Kernel is actually half a gaussian curve,
anthony3c10fc82010-05-13 02:40:51 +0000598% Adding two such blurs in opposite directions produces a Blur Kernel.
599% Angle can be rotated in multiples of 90 degrees.
anthony602ab9b2010-01-05 08:06:50 +0000600%
anthony3c10fc82010-05-13 02:40:51 +0000601% Note that the first argument is the width of the kernel and not the
anthony602ab9b2010-01-05 08:06:50 +0000602% radius of the kernel.
603%
604% # Still to be implemented...
605% #
anthony4fd27e22010-02-07 08:17:18 +0000606% # Filter2D
607% # Filter1D
608% # Set kernel values using a resize filter, and given scale (sigma)
609% # Cylindrical or Linear. Is this posible with an image?
610% #
anthony602ab9b2010-01-05 08:06:50 +0000611%
anthony3c10fc82010-05-13 02:40:51 +0000612% Named Constant Convolution Kernels
613%
anthonyc1061722010-05-14 06:23:49 +0000614% All these are unscaled, zero-summing kernels by default. As such for
615% non-HDRI version of ImageMagick some form of normalization, user scaling,
616% and biasing the results is recommended, to prevent the resulting image
617% being 'clipped'.
618%
619% The 3x3 kernels (most of these) can be circularly rotated in multiples of
620% 45 degrees to generate the 8 angled varients of each of the kernels.
anthony3c10fc82010-05-13 02:40:51 +0000621%
622% Laplacian:{type}
anthony43c49252010-05-18 10:59:50 +0000623% Discrete Lapacian Kernels, (without normalization)
anthonyc1061722010-05-14 06:23:49 +0000624% Type 0 : 3x3 with center:8 surounded by -1 (8 neighbourhood)
625% Type 1 : 3x3 with center:4 edge:-1 corner:0 (4 neighbourhood)
anthony9eb4f742010-05-18 02:45:54 +0000626% Type 2 : 3x3 with center:4 edge:1 corner:-2
627% Type 3 : 3x3 with center:4 edge:-2 corner:1
628% Type 5 : 5x5 laplacian
629% Type 7 : 7x7 laplacian
anthony43c49252010-05-18 10:59:50 +0000630% Type 15 : 5x5 LOG (sigma approx 1.4)
631% Type 19 : 9x9 LOG (sigma approx 1.4)
anthonyc1061722010-05-14 06:23:49 +0000632%
633% Sobel:{angle}
anthony46a369d2010-05-19 02:41:48 +0000634% Sobel 'Edge' convolution kernel (3x3)
anthonyc1061722010-05-14 06:23:49 +0000635% -1, 0, 1
636% -2, 0,-2
637% -1, 0, 1
anthonye2a60ce2010-05-19 12:30:40 +0000638%
anthonyc1061722010-05-14 06:23:49 +0000639% Roberts:{angle}
anthony46a369d2010-05-19 02:41:48 +0000640% Roberts convolution kernel (3x3)
anthonyc1061722010-05-14 06:23:49 +0000641% 0, 0, 0
642% -1, 1, 0
643% 0, 0, 0
anthonyc1061722010-05-14 06:23:49 +0000644% Prewitt:{angle}
645% Prewitt Edge convolution kernel (3x3)
646% -1, 0, 1
647% -1, 0, 1
648% -1, 0, 1
anthony9eb4f742010-05-18 02:45:54 +0000649% Compass:{angle}
650% Prewitt's "Compass" convolution kernel (3x3)
651% -1, 1, 1
652% -1,-2, 1
653% -1, 1, 1
654% Kirsch:{angle}
655% Kirsch's "Compass" convolution kernel (3x3)
656% -3,-3, 5
657% -3, 0, 5
658% -3,-3, 5
anthony3c10fc82010-05-13 02:40:51 +0000659%
anthonye2a60ce2010-05-19 12:30:40 +0000660% FreiChen:{type},{angle}
661% Frei-Chen Edge Detector is a set of 9 unique convolution kernels that
anthony6915d062010-05-19 12:45:51 +0000662% are specially weighted. They should not be normalized. After applying
663% each to the original image, the results is then added together. The
664% square root of the resulting image is the cosine of the edge, and the
665% direction of the feature detection.
anthonye2a60ce2010-05-19 12:30:40 +0000666%
667% Type 1: | 1, sqrt(2), 1 |
668% | 0, 0, 0 | / 2*sqrt(2)
669% | -1, -sqrt(2), -1 |
670%
671% Type 2: | 1, 0, 1 |
672% | sqrt(2), 0, sqrt(2) | / 2*sqrt(2)
673% | 1, 0, 1 |
674%
675% Type 3: | 0, -1, sqrt(2) |
676% | 1, 0, -1 | / 2*sqrt(2)
677% | -sqrt(2), 1, 0 |
678%
anthony6915d062010-05-19 12:45:51 +0000679% Type 4: | sqrt(2), -1, 0 |
anthonye2a60ce2010-05-19 12:30:40 +0000680% | -1, 0, 1 | / 2*sqrt(2)
681% | 0, 1, -sqrt(2) |
682%
683% Type 5: | 0, 1, 0 |
684% | -1, 0, -1 | / 2
685% | 0, 1, 0 |
686%
687% Type 6: | -1, 0, 1 |
688% | 0, 0, 0 | / 2
689% | 1, 0, -1 |
690%
anthonyf4e00312010-05-20 12:06:35 +0000691% Type 7: | 1, -2, 1 |
anthonye2a60ce2010-05-19 12:30:40 +0000692% | -2, 4, -2 | / 6
693% | 1, -2, 1 |
694%
anthonyf4e00312010-05-20 12:06:35 +0000695% Type 8: | -2, 1, -2 |
696% | 1, 4, 1 | / 6
697% | -2, 1, -2 |
anthonye2a60ce2010-05-19 12:30:40 +0000698%
anthonyf4e00312010-05-20 12:06:35 +0000699% Type 9: | 1, 1, 1 |
700% | 1, 1, 1 | / 3
701% | 1, 1, 1 |
anthonye2a60ce2010-05-19 12:30:40 +0000702%
703% The first 4 are for edge detection, the next 4 are for line detection
704% and the last is to add a average component to the results.
705%
anthonye2a60ce2010-05-19 12:30:40 +0000706%
anthony602ab9b2010-01-05 08:06:50 +0000707% Boolean Kernels
708%
anthony3c10fc82010-05-13 02:40:51 +0000709% Diamond:[{radius}[,{scale}]]
anthony1b2bc0a2010-05-12 05:25:22 +0000710% Generate a diamond shaped kernel with given radius to the points.
anthony602ab9b2010-01-05 08:06:50 +0000711% Kernel size will again be radius*2+1 square and defaults to radius 1,
712% generating a 3x3 kernel that is slightly larger than a square.
713%
anthony3c10fc82010-05-13 02:40:51 +0000714% Square:[{radius}[,{scale}]]
anthony602ab9b2010-01-05 08:06:50 +0000715% Generate a square shaped kernel of size radius*2+1, and defaulting
716% to a 3x3 (radius 1).
717%
anthonyc1061722010-05-14 06:23:49 +0000718% Note that using a larger radius for the "Square" or the "Diamond" is
719% also equivelent to iterating the basic morphological method that many
720% times. However iterating with the smaller radius is actually faster
721% than using a larger kernel radius.
722%
723% Rectangle:{geometry}
724% Simply generate a rectangle of 1's with the size given. You can also
725% specify the location of the 'control point', otherwise the closest
726% pixel to the center of the rectangle is selected.
727%
728% Properly centered and odd sized rectangles work the best.
anthony602ab9b2010-01-05 08:06:50 +0000729%
anthony3c10fc82010-05-13 02:40:51 +0000730% Disk:[{radius}[,{scale}]]
anthony602ab9b2010-01-05 08:06:50 +0000731% Generate a binary disk of the radius given, radius may be a float.
732% Kernel size will be ceil(radius)*2+1 square.
733% NOTE: Here are some disk shapes of specific interest
anthonyc1061722010-05-14 06:23:49 +0000734% "Disk:1" => "diamond" or "cross:1"
735% "Disk:1.5" => "square"
736% "Disk:2" => "diamond:2"
737% "Disk:2.5" => a general disk shape of radius 2
738% "Disk:2.9" => "square:2"
739% "Disk:3.5" => default - octagonal/disk shape of radius 3
740% "Disk:4.2" => roughly octagonal shape of radius 4
741% "Disk:4.3" => a general disk shape of radius 4
anthony602ab9b2010-01-05 08:06:50 +0000742% After this all the kernel shape becomes more and more circular.
743%
744% Because a "disk" is more circular when using a larger radius, using a
745% larger radius is preferred over iterating the morphological operation.
746%
anthonyc1061722010-05-14 06:23:49 +0000747% Symbol Dilation Kernels
748%
749% These kernel is not a good general morphological kernel, but is used
750% more for highlighting and marking any single pixels in an image using,
751% a "Dilate" method as appropriate.
752%
753% For the same reasons iterating these kernels does not produce the
754% same result as using a larger radius for the symbol.
755%
anthony3c10fc82010-05-13 02:40:51 +0000756% Plus:[{radius}[,{scale}]]
anthony3dd0f622010-05-13 12:57:32 +0000757% Cross:[{radius}[,{scale}]]
anthonyc1061722010-05-14 06:23:49 +0000758% Generate a kernel in the shape of a 'plus' or a 'cross' with
759% a each arm the length of the given radius (default 2).
anthony3dd0f622010-05-13 12:57:32 +0000760%
761% NOTE: "plus:1" is equivelent to a "Diamond" kernel.
anthony602ab9b2010-01-05 08:06:50 +0000762%
anthonyc1061722010-05-14 06:23:49 +0000763% Ring:{radius1},{radius2}[,{scale}]
764% A ring of the values given that falls between the two radii.
765% Defaults to a ring of approximataly 3 radius in a 7x7 kernel.
766% This is the 'edge' pixels of the default "Disk" kernel,
767% More specifically, "Ring" -> "Ring:2.5,3.5,1.0"
anthony602ab9b2010-01-05 08:06:50 +0000768%
anthony3dd0f622010-05-13 12:57:32 +0000769% Hit and Miss Kernels
770%
771% Peak:radius1,radius2
anthonyc1061722010-05-14 06:23:49 +0000772% Find any peak larger than the pixels the fall between the two radii.
773% The default ring of pixels is as per "Ring".
anthony43c49252010-05-18 10:59:50 +0000774% Edges
775% Find Edges of a binary shape
anthony3dd0f622010-05-13 12:57:32 +0000776% Corners
777% Find corners of a binary shape
anthony47f5d062010-05-23 07:47:50 +0000778% Ridges
779% Find Ridges or Thin lines
anthony3dd0f622010-05-13 12:57:32 +0000780% LineEnds
781% Find end points of lines (for pruning a skeletion)
782% LineJunctions
anthony43c49252010-05-18 10:59:50 +0000783% Find three line junctions (within a skeletion)
anthony3dd0f622010-05-13 12:57:32 +0000784% ConvexHull
785% Octagonal thicken kernel, to generate convex hulls of 45 degrees
786% Skeleton
787% Thinning kernel, which leaves behind a skeletion of a shape
anthony602ab9b2010-01-05 08:06:50 +0000788%
789% Distance Measuring Kernels
790%
anthonyc1061722010-05-14 06:23:49 +0000791% Different types of distance measuring methods, which are used with the
792% a 'Distance' morphology method for generating a gradient based on
793% distance from an edge of a binary shape, though there is a technique
794% for handling a anti-aliased shape.
795%
796% See the 'Distance' Morphological Method, for information of how it is
797% applied.
798%
anthony3dd0f622010-05-13 12:57:32 +0000799% Chebyshev:[{radius}][x{scale}[%!]]
anthonyc94cdb02010-01-06 08:15:29 +0000800% Chebyshev Distance (also known as Tchebychev Distance) is a value of
801% one to any neighbour, orthogonal or diagonal. One why of thinking of
802% it is the number of squares a 'King' or 'Queen' in chess needs to
803% traverse reach any other position on a chess board. It results in a
804% 'square' like distance function, but one where diagonals are closer
805% than expected.
anthony602ab9b2010-01-05 08:06:50 +0000806%
anthonyc1061722010-05-14 06:23:49 +0000807% Manhatten:[{radius}][x{scale}[%!]]
anthonyc94cdb02010-01-06 08:15:29 +0000808% Manhatten Distance (also known as Rectilinear Distance, or the Taxi
809% Cab metric), is the distance needed when you can only travel in
810% orthogonal (horizontal or vertical) only. It is the distance a 'Rook'
811% in chess would travel. It results in a diamond like distances, where
812% diagonals are further than expected.
anthony602ab9b2010-01-05 08:06:50 +0000813%
anthonyc1061722010-05-14 06:23:49 +0000814% Euclidean:[{radius}][x{scale}[%!]]
anthonyc94cdb02010-01-06 08:15:29 +0000815% Euclidean Distance is the 'direct' or 'as the crow flys distance.
816% However by default the kernel size only has a radius of 1, which
817% limits the distance to 'Knight' like moves, with only orthogonal and
818% diagonal measurements being correct. As such for the default kernel
819% you will get octagonal like distance function, which is reasonally
820% accurate.
821%
822% However if you use a larger radius such as "Euclidean:4" you will
823% get a much smoother distance gradient from the edge of the shape.
824% Of course a larger kernel is slower to use, and generally not needed.
825%
826% To allow the use of fractional distances that you get with diagonals
827% the actual distance is scaled by a fixed value which the user can
828% provide. This is not actually nessary for either ""Chebyshev" or
829% "Manhatten" distance kernels, but is done for all three distance
830% kernels. If no scale is provided it is set to a value of 100,
831% allowing for a maximum distance measurement of 655 pixels using a Q16
832% version of IM, from any edge. However for small images this can
833% result in quite a dark gradient.
834%
anthony602ab9b2010-01-05 08:06:50 +0000835*/
836
cristy2be15382010-01-21 02:38:03 +0000837MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000838 const GeometryInfo *args)
839{
cristy2be15382010-01-21 02:38:03 +0000840 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000841 *kernel;
842
cristy150989e2010-02-01 14:59:39 +0000843 register long
anthony602ab9b2010-01-05 08:06:50 +0000844 i;
845
846 register long
847 u,
848 v;
849
850 double
851 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
852
anthonyc1061722010-05-14 06:23:49 +0000853 /* Generate a new empty kernel if needed */
cristye96405a2010-05-19 02:24:31 +0000854 kernel=(KernelInfo *) NULL;
anthonyc1061722010-05-14 06:23:49 +0000855 switch(type) {
anthony9eb4f742010-05-18 02:45:54 +0000856 case UndefinedKernel: /* These should not be used here */
857 case UserDefinedKernel:
858 break;
859 case LaplacianKernel: /* Named Descrete Convolution Kernels */
860 case SobelKernel:
861 case RobertsKernel:
862 case PrewittKernel:
863 case CompassKernel:
864 case KirschKernel:
865 case CornersKernel: /* Hit and Miss kernels */
866 case LineEndsKernel:
867 case LineJunctionsKernel:
anthony9eb4f742010-05-18 02:45:54 +0000868 case ConvexHullKernel:
869 case SkeletonKernel:
870 /* A pre-generated kernel is not needed */
871 break;
872#if 0
anthonyc1061722010-05-14 06:23:49 +0000873 case GaussianKernel:
874 case DOGKernel:
875 case BlurKernel:
876 case DOBKernel:
877 case CometKernel:
878 case DiamondKernel:
879 case SquareKernel:
880 case RectangleKernel:
881 case DiskKernel:
882 case PlusKernel:
883 case CrossKernel:
884 case RingKernel:
885 case PeaksKernel:
886 case ChebyshevKernel:
887 case ManhattenKernel:
888 case EuclideanKernel:
anthony9eb4f742010-05-18 02:45:54 +0000889#endif
890 default:
891 /* Generate the base Kernel Structure */
anthonyc1061722010-05-14 06:23:49 +0000892 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
893 if (kernel == (KernelInfo *) NULL)
894 return(kernel);
895 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
anthony43c49252010-05-18 10:59:50 +0000896 kernel->minimum = kernel->maximum = kernel->angle = 0.0;
anthonyc1061722010-05-14 06:23:49 +0000897 kernel->negative_range = kernel->positive_range = 0.0;
898 kernel->type = type;
899 kernel->next = (KernelInfo *) NULL;
900 kernel->signature = MagickSignature;
anthonyc1061722010-05-14 06:23:49 +0000901 break;
902 }
anthony602ab9b2010-01-05 08:06:50 +0000903
904 switch(type) {
905 /* Convolution Kernels */
906 case GaussianKernel:
anthonyc1061722010-05-14 06:23:49 +0000907 case DOGKernel:
anthony9eb4f742010-05-18 02:45:54 +0000908 case LOGKernel:
anthony602ab9b2010-01-05 08:06:50 +0000909 { double
anthonyc1061722010-05-14 06:23:49 +0000910 sigma = fabs(args->sigma),
911 sigma2 = fabs(args->xi),
anthony9eb4f742010-05-18 02:45:54 +0000912 A, B, R;
anthony602ab9b2010-01-05 08:06:50 +0000913
anthonyc1061722010-05-14 06:23:49 +0000914 if ( args->rho >= 1.0 )
915 kernel->width = (unsigned long)args->rho*2+1;
anthony9eb4f742010-05-18 02:45:54 +0000916 else if ( (type != DOGKernel) || (sigma >= sigma2) )
anthonyc1061722010-05-14 06:23:49 +0000917 kernel->width = GetOptimalKernelWidth2D(args->rho,sigma);
918 else
919 kernel->width = GetOptimalKernelWidth2D(args->rho,sigma2);
920 kernel->height = kernel->width;
cristyc99304f2010-02-01 15:26:27 +0000921 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000922 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
923 kernel->height*sizeof(double));
924 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000925 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000926
anthony46a369d2010-05-19 02:41:48 +0000927 /* WARNING: The following generates a 'sampled gaussian' kernel.
anthony9eb4f742010-05-18 02:45:54 +0000928 * What we really want is a 'discrete gaussian' kernel.
anthony46a369d2010-05-19 02:41:48 +0000929 *
930 * How to do this is currently not known, but appears to be
931 * basied on the Error Function 'erf()' (intergral of a gaussian)
anthony9eb4f742010-05-18 02:45:54 +0000932 */
933
934 if ( type == GaussianKernel || type == DOGKernel )
935 { /* Calculate a Gaussian, OR positive half of a DOG */
936 if ( sigma > MagickEpsilon )
937 { A = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
938 B = 1.0/(Magick2PI*sigma*sigma);
939 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
940 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
941 kernel->values[i] = exp(-((double)(u*u+v*v))*A)*B;
942 }
943 else /* limiting case - a unity (normalized Dirac) kernel */
944 { (void) ResetMagickMemory(kernel->values,0, (size_t)
945 kernel->width*kernel->height*sizeof(double));
946 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
947 }
anthonyc1061722010-05-14 06:23:49 +0000948 }
anthony9eb4f742010-05-18 02:45:54 +0000949
anthonyc1061722010-05-14 06:23:49 +0000950 if ( type == DOGKernel )
951 { /* Subtract a Negative Gaussian for "Difference of Gaussian" */
952 if ( sigma2 > MagickEpsilon )
953 { sigma = sigma2; /* simplify loop expressions */
anthony9eb4f742010-05-18 02:45:54 +0000954 A = 1.0/(2.0*sigma*sigma);
955 B = 1.0/(Magick2PI*sigma*sigma);
anthonyc1061722010-05-14 06:23:49 +0000956 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
957 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony9eb4f742010-05-18 02:45:54 +0000958 kernel->values[i] -= exp(-((double)(u*u+v*v))*A)*B;
anthonyc1061722010-05-14 06:23:49 +0000959 }
anthony9eb4f742010-05-18 02:45:54 +0000960 else /* limiting case - a unity (normalized Dirac) kernel */
anthonyc1061722010-05-14 06:23:49 +0000961 kernel->values[kernel->x+kernel->y*kernel->width] -= 1.0;
962 }
anthony9eb4f742010-05-18 02:45:54 +0000963
964 if ( type == LOGKernel )
965 { /* Calculate a Laplacian of a Gaussian - Or Mexician Hat */
966 if ( sigma > MagickEpsilon )
967 { A = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
968 B = 1.0/(MagickPI*sigma*sigma*sigma*sigma);
969 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
970 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
971 { R = ((double)(u*u+v*v))*A;
972 kernel->values[i] = (1-R)*exp(-R)*B;
973 }
974 }
975 else /* special case - generate a unity kernel */
976 { (void) ResetMagickMemory(kernel->values,0, (size_t)
977 kernel->width*kernel->height*sizeof(double));
978 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
979 }
980 }
981
982 /* Note the above kernels may have been 'clipped' by a user defined
anthonyc1061722010-05-14 06:23:49 +0000983 ** radius, producing a smaller (darker) kernel. Also for very small
984 ** sigma's (> 0.1) the central value becomes larger than one, and thus
985 ** producing a very bright kernel.
986 **
987 ** Normalization will still be needed.
988 */
anthony602ab9b2010-01-05 08:06:50 +0000989
anthony3dd0f622010-05-13 12:57:32 +0000990 /* Normalize the 2D Gaussian Kernel
991 **
anthonyc1061722010-05-14 06:23:49 +0000992 ** NB: a CorrelateNormalize performs a normal Normalize if
993 ** there are no negative values.
anthony3dd0f622010-05-13 12:57:32 +0000994 */
anthony46a369d2010-05-19 02:41:48 +0000995 CalcKernelMetaData(kernel); /* the other kernel meta-data */
anthonyc1061722010-05-14 06:23:49 +0000996 ScaleKernelInfo(kernel, 1.0, CorrelateNormalizeValue);
anthony602ab9b2010-01-05 08:06:50 +0000997
998 break;
999 }
1000 case BlurKernel:
anthonyc1061722010-05-14 06:23:49 +00001001 case DOBKernel:
anthony602ab9b2010-01-05 08:06:50 +00001002 { double
anthonyc1061722010-05-14 06:23:49 +00001003 sigma = fabs(args->sigma),
1004 sigma2 = fabs(args->xi),
anthony9eb4f742010-05-18 02:45:54 +00001005 A, B;
anthony602ab9b2010-01-05 08:06:50 +00001006
anthonyc1061722010-05-14 06:23:49 +00001007 if ( args->rho >= 1.0 )
1008 kernel->width = (unsigned long)args->rho*2+1;
1009 else if ( (type == BlurKernel) || (sigma >= sigma2) )
1010 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
1011 else
1012 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma2);
anthony602ab9b2010-01-05 08:06:50 +00001013 kernel->height = 1;
anthonyc1061722010-05-14 06:23:49 +00001014 kernel->x = (long) (kernel->width-1)/2;
cristyc99304f2010-02-01 15:26:27 +00001015 kernel->y = 0;
1016 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +00001017 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1018 kernel->height*sizeof(double));
1019 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001020 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001021
1022#if 1
1023#define KernelRank 3
1024 /* Formula derived from GetBlurKernel() in "effect.c" (plus bug fix).
1025 ** It generates a gaussian 3 times the width, and compresses it into
1026 ** the expected range. This produces a closer normalization of the
1027 ** resulting kernel, especially for very low sigma values.
1028 ** As such while wierd it is prefered.
1029 **
1030 ** I am told this method originally came from Photoshop.
anthony9eb4f742010-05-18 02:45:54 +00001031 **
1032 ** A properly normalized curve is generated (apart from edge clipping)
1033 ** even though we later normalize the result (for edge clipping)
1034 ** to allow the correct generation of a "Difference of Blurs".
anthony602ab9b2010-01-05 08:06:50 +00001035 */
anthonyc1061722010-05-14 06:23:49 +00001036
1037 /* initialize */
cristy150989e2010-02-01 14:59:39 +00001038 v = (long) (kernel->width*KernelRank-1)/2; /* start/end points to fit range */
anthony9eb4f742010-05-18 02:45:54 +00001039 (void) ResetMagickMemory(kernel->values,0, (size_t)
1040 kernel->width*kernel->height*sizeof(double));
anthonyc1061722010-05-14 06:23:49 +00001041 /* Calculate a Positive 1D Gaussian */
1042 if ( sigma > MagickEpsilon )
1043 { sigma *= KernelRank; /* simplify loop expressions */
anthony9eb4f742010-05-18 02:45:54 +00001044 A = 1.0/(2.0*sigma*sigma);
1045 B = 1.0/(MagickSQ2PI*sigma );
anthonyc1061722010-05-14 06:23:49 +00001046 for ( u=-v; u <= v; u++) {
anthony9eb4f742010-05-18 02:45:54 +00001047 kernel->values[(u+v)/KernelRank] += exp(-((double)(u*u))*A)*B;
anthonyc1061722010-05-14 06:23:49 +00001048 }
1049 }
1050 else /* special case - generate a unity kernel */
1051 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
anthony9eb4f742010-05-18 02:45:54 +00001052
1053 /* Subtract a Second 1D Gaussian for "Difference of Blur" */
anthonyc1061722010-05-14 06:23:49 +00001054 if ( type == DOBKernel )
anthony9eb4f742010-05-18 02:45:54 +00001055 {
anthonyc1061722010-05-14 06:23:49 +00001056 if ( sigma2 > MagickEpsilon )
1057 { sigma = sigma2*KernelRank; /* simplify loop expressions */
anthony9eb4f742010-05-18 02:45:54 +00001058 A = 1.0/(2.0*sigma*sigma);
1059 B = 1.0/(MagickSQ2PI*sigma);
anthonyc1061722010-05-14 06:23:49 +00001060 for ( u=-v; u <= v; u++)
anthony9eb4f742010-05-18 02:45:54 +00001061 kernel->values[(u+v)/KernelRank] -= exp(-((double)(u*u))*A)*B;
anthonyc1061722010-05-14 06:23:49 +00001062 }
anthony9eb4f742010-05-18 02:45:54 +00001063 else /* limiting case - a unity (normalized Dirac) kernel */
anthonyc1061722010-05-14 06:23:49 +00001064 kernel->values[kernel->x+kernel->y*kernel->width] -= 1.0;
1065 }
anthony602ab9b2010-01-05 08:06:50 +00001066#else
anthonyc1061722010-05-14 06:23:49 +00001067 /* Direct calculation without curve averaging */
1068
1069 /* Calculate a Positive Gaussian */
1070 if ( sigma > MagickEpsilon )
anthony9eb4f742010-05-18 02:45:54 +00001071 { A = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
1072 B = 1.0/(MagickSQ2PI*sigma);
anthonyc1061722010-05-14 06:23:49 +00001073 for ( i=0, u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony9eb4f742010-05-18 02:45:54 +00001074 kernel->values[i] = exp(-((double)(u*u))*A)*B;
anthonyc1061722010-05-14 06:23:49 +00001075 }
1076 else /* special case - generate a unity kernel */
1077 { (void) ResetMagickMemory(kernel->values,0, (size_t)
1078 kernel->width*kernel->height*sizeof(double));
1079 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1080 }
anthony9eb4f742010-05-18 02:45:54 +00001081
1082 /* Subtract a Second 1D Gaussian for "Difference of Blur" */
anthonyc1061722010-05-14 06:23:49 +00001083 if ( type == DOBKernel )
anthony9eb4f742010-05-18 02:45:54 +00001084 {
anthonyc1061722010-05-14 06:23:49 +00001085 if ( sigma2 > MagickEpsilon )
1086 { sigma = sigma2; /* simplify loop expressions */
anthony9eb4f742010-05-18 02:45:54 +00001087 A = 1.0/(2.0*sigma*sigma);
1088 B = 1.0/(MagickSQ2PI*sigma);
anthonyc1061722010-05-14 06:23:49 +00001089 for ( i=0, u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony9eb4f742010-05-18 02:45:54 +00001090 kernel->values[i] -= exp(-((double)(u*u))*A)*B;
anthonyc1061722010-05-14 06:23:49 +00001091 }
anthony9eb4f742010-05-18 02:45:54 +00001092 else /* limiting case - a unity (normalized Dirac) kernel */
anthonyc1061722010-05-14 06:23:49 +00001093 kernel->values[kernel->x+kernel->y*kernel->width] -= 1.0;
1094 }
anthony602ab9b2010-01-05 08:06:50 +00001095#endif
anthonyc1061722010-05-14 06:23:49 +00001096 /* Note the above kernel may have been 'clipped' by a user defined
anthonycc6c8362010-01-25 04:14:01 +00001097 ** radius, producing a smaller (darker) kernel. Also for very small
1098 ** sigma's (> 0.1) the central value becomes larger than one, and thus
1099 ** producing a very bright kernel.
anthonyc1061722010-05-14 06:23:49 +00001100 **
1101 ** Normalization will still be needed.
anthony602ab9b2010-01-05 08:06:50 +00001102 */
anthonycc6c8362010-01-25 04:14:01 +00001103
anthony602ab9b2010-01-05 08:06:50 +00001104 /* Normalize the 1D Gaussian Kernel
1105 **
anthonyc1061722010-05-14 06:23:49 +00001106 ** NB: a CorrelateNormalize performs a normal Normalize if
1107 ** there are no negative values.
anthony602ab9b2010-01-05 08:06:50 +00001108 */
anthony46a369d2010-05-19 02:41:48 +00001109 CalcKernelMetaData(kernel); /* the other kernel meta-data */
1110 ScaleKernelInfo(kernel, 1.0, CorrelateNormalizeValue);
anthonycc6c8362010-01-25 04:14:01 +00001111
anthonyc1061722010-05-14 06:23:49 +00001112 /* rotate the 1D kernel by given angle */
1113 RotateKernelInfo(kernel, (type == BlurKernel) ? args->xi : args->psi );
anthony602ab9b2010-01-05 08:06:50 +00001114 break;
1115 }
1116 case CometKernel:
1117 { double
anthony9eb4f742010-05-18 02:45:54 +00001118 sigma = fabs(args->sigma),
1119 A;
anthony602ab9b2010-01-05 08:06:50 +00001120
anthony602ab9b2010-01-05 08:06:50 +00001121 if ( args->rho < 1.0 )
anthonye1cf9462010-05-19 03:50:26 +00001122 kernel->width = (GetOptimalKernelWidth1D(args->rho,sigma)-1)/2+1;
anthony602ab9b2010-01-05 08:06:50 +00001123 else
1124 kernel->width = (unsigned long)args->rho;
cristyc99304f2010-02-01 15:26:27 +00001125 kernel->x = kernel->y = 0;
anthony602ab9b2010-01-05 08:06:50 +00001126 kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +00001127 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +00001128 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1129 kernel->height*sizeof(double));
1130 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001131 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001132
anthonyc1061722010-05-14 06:23:49 +00001133 /* A comet blur is half a 1D gaussian curve, so that the object is
anthony602ab9b2010-01-05 08:06:50 +00001134 ** blurred in one direction only. This may not be quite the right
anthony3dd0f622010-05-13 12:57:32 +00001135 ** curve to use so may change in the future. The function must be
1136 ** normalised after generation, which also resolves any clipping.
anthonyc1061722010-05-14 06:23:49 +00001137 **
1138 ** As we are normalizing and not subtracting gaussians,
1139 ** there is no need for a divisor in the gaussian formula
1140 **
anthony43c49252010-05-18 10:59:50 +00001141 ** It is less comples
anthony602ab9b2010-01-05 08:06:50 +00001142 */
anthony9eb4f742010-05-18 02:45:54 +00001143 if ( sigma > MagickEpsilon )
1144 {
anthony602ab9b2010-01-05 08:06:50 +00001145#if 1
1146#define KernelRank 3
anthony9eb4f742010-05-18 02:45:54 +00001147 v = (long) kernel->width*KernelRank; /* start/end points */
1148 (void) ResetMagickMemory(kernel->values,0, (size_t)
1149 kernel->width*sizeof(double));
1150 sigma *= KernelRank; /* simplify the loop expression */
1151 A = 1.0/(2.0*sigma*sigma);
1152 /* B = 1.0/(MagickSQ2PI*sigma); */
1153 for ( u=0; u < v; u++) {
1154 kernel->values[u/KernelRank] +=
1155 exp(-((double)(u*u))*A);
1156 /* exp(-((double)(i*i))/2.0*sigma*sigma)/(MagickSQ2PI*sigma); */
1157 }
1158 for (i=0; i < (long) kernel->width; i++)
1159 kernel->positive_range += kernel->values[i];
anthony602ab9b2010-01-05 08:06:50 +00001160#else
anthony9eb4f742010-05-18 02:45:54 +00001161 A = 1.0/(2.0*sigma*sigma); /* simplify the loop expression */
1162 /* B = 1.0/(MagickSQ2PI*sigma); */
1163 for ( i=0; i < (long) kernel->width; i++)
1164 kernel->positive_range +=
1165 kernel->values[i] =
1166 exp(-((double)(i*i))*A);
1167 /* exp(-((double)(i*i))/2.0*sigma*sigma)/(MagickSQ2PI*sigma); */
anthony602ab9b2010-01-05 08:06:50 +00001168#endif
anthony9eb4f742010-05-18 02:45:54 +00001169 }
1170 else /* special case - generate a unity kernel */
1171 { (void) ResetMagickMemory(kernel->values,0, (size_t)
1172 kernel->width*kernel->height*sizeof(double));
1173 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1174 kernel->positive_range = 1.0;
1175 }
anthony46a369d2010-05-19 02:41:48 +00001176
1177 kernel->minimum = 0.0;
cristyc99304f2010-02-01 15:26:27 +00001178 kernel->maximum = kernel->values[0];
anthony46a369d2010-05-19 02:41:48 +00001179 kernel->negative_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +00001180
anthony999bb2c2010-02-18 12:38:01 +00001181 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
1182 RotateKernelInfo(kernel, args->xi); /* Rotate by angle */
anthony602ab9b2010-01-05 08:06:50 +00001183 break;
1184 }
anthonyc1061722010-05-14 06:23:49 +00001185
anthony3c10fc82010-05-13 02:40:51 +00001186 /* Convolution Kernels - Well Known Constants */
anthony3c10fc82010-05-13 02:40:51 +00001187 case LaplacianKernel:
anthonye2a60ce2010-05-19 12:30:40 +00001188 { switch ( (int) args->rho ) {
anthony3dd0f622010-05-13 12:57:32 +00001189 case 0:
anthony9eb4f742010-05-18 02:45:54 +00001190 default: /* laplacian square filter -- default */
anthonyc1061722010-05-14 06:23:49 +00001191 kernel=ParseKernelArray("3: -1,-1,-1 -1,8,-1 -1,-1,-1");
anthony3dd0f622010-05-13 12:57:32 +00001192 break;
anthony9eb4f742010-05-18 02:45:54 +00001193 case 1: /* laplacian diamond filter */
anthonyc1061722010-05-14 06:23:49 +00001194 kernel=ParseKernelArray("3: 0,-1,0 -1,4,-1 0,-1,0");
anthony3c10fc82010-05-13 02:40:51 +00001195 break;
1196 case 2:
anthony9eb4f742010-05-18 02:45:54 +00001197 kernel=ParseKernelArray("3: -2,1,-2 1,4,1 -2,1,-2");
1198 break;
1199 case 3:
anthonyc1061722010-05-14 06:23:49 +00001200 kernel=ParseKernelArray("3: 1,-2,1 -2,4,-2 1,-2,1");
anthony3c10fc82010-05-13 02:40:51 +00001201 break;
anthony9eb4f742010-05-18 02:45:54 +00001202 case 5: /* a 5x5 laplacian */
anthony3c10fc82010-05-13 02:40:51 +00001203 kernel=ParseKernelArray(
anthony9eb4f742010-05-18 02:45:54 +00001204 "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 +00001205 break;
anthony9eb4f742010-05-18 02:45:54 +00001206 case 7: /* a 7x7 laplacian */
anthony3c10fc82010-05-13 02:40:51 +00001207 kernel=ParseKernelArray(
anthonyc1061722010-05-14 06:23:49 +00001208 "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 +00001209 break;
anthony43c49252010-05-18 10:59:50 +00001210 case 15: /* a 5x5 LOG (sigma approx 1.4) */
anthony9eb4f742010-05-18 02:45:54 +00001211 kernel=ParseKernelArray(
1212 "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");
1213 break;
anthony43c49252010-05-18 10:59:50 +00001214 case 19: /* a 9x9 LOG (sigma approx 1.4) */
1215 /* http://www.cscjournals.org/csc/manuscript/Journals/IJIP/volume3/Issue1/IJIP-15.pdf */
1216 kernel=ParseKernelArray(
1217 "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");
1218 break;
anthony3c10fc82010-05-13 02:40:51 +00001219 }
1220 if (kernel == (KernelInfo *) NULL)
1221 return(kernel);
1222 kernel->type = type;
1223 break;
1224 }
anthonyc1061722010-05-14 06:23:49 +00001225 case SobelKernel:
anthony602ab9b2010-01-05 08:06:50 +00001226 {
anthonyc1061722010-05-14 06:23:49 +00001227 kernel=ParseKernelArray("3: -1,0,1 -2,0,2 -1,0,1");
1228 if (kernel == (KernelInfo *) NULL)
1229 return(kernel);
1230 kernel->type = type;
1231 RotateKernelInfo(kernel, args->rho); /* Rotate by angle */
1232 break;
1233 }
1234 case RobertsKernel:
1235 {
1236 kernel=ParseKernelArray("3: 0,0,0 -1,1,0 0,0,0");
1237 if (kernel == (KernelInfo *) NULL)
1238 return(kernel);
1239 kernel->type = type;
anthony46a369d2010-05-19 02:41:48 +00001240 RotateKernelInfo(kernel, args->rho);
anthonyc1061722010-05-14 06:23:49 +00001241 break;
1242 }
1243 case PrewittKernel:
1244 {
1245 kernel=ParseKernelArray("3: -1,1,1 0,0,0 -1,1,1");
1246 if (kernel == (KernelInfo *) NULL)
1247 return(kernel);
1248 kernel->type = type;
anthony46a369d2010-05-19 02:41:48 +00001249 RotateKernelInfo(kernel, args->rho);
anthonyc1061722010-05-14 06:23:49 +00001250 break;
1251 }
1252 case CompassKernel:
1253 {
1254 kernel=ParseKernelArray("3: -1,1,1 -1,-2,1 -1,1,1");
1255 if (kernel == (KernelInfo *) NULL)
1256 return(kernel);
1257 kernel->type = type;
anthony46a369d2010-05-19 02:41:48 +00001258 RotateKernelInfo(kernel, args->rho);
anthonyc1061722010-05-14 06:23:49 +00001259 break;
1260 }
anthony9eb4f742010-05-18 02:45:54 +00001261 case KirschKernel:
1262 {
1263 kernel=ParseKernelArray("3: -3,-3,5 -3,0,5 -3,-3,5");
1264 if (kernel == (KernelInfo *) NULL)
1265 return(kernel);
1266 kernel->type = type;
anthony46a369d2010-05-19 02:41:48 +00001267 RotateKernelInfo(kernel, args->rho);
anthony9eb4f742010-05-18 02:45:54 +00001268 break;
1269 }
anthonye2a60ce2010-05-19 12:30:40 +00001270 case FreiChenKernel:
anthony6915d062010-05-19 12:45:51 +00001271 /* http://www.math.tau.ac.il/~turkel/notes/edge_detectors.pdf */
1272 /* http://ltswww.epfl.ch/~courstiv/exos_labos/sol3.pdf */
anthonye2a60ce2010-05-19 12:30:40 +00001273 { switch ( (int) args->rho ) {
1274 default:
1275 case 1:
1276 kernel=ParseKernelArray("3: 1,2,1 0,0,0 -1,2,-1");
1277 if (kernel == (KernelInfo *) NULL)
1278 return(kernel);
1279 kernel->values[1] = +MagickSQ2;
1280 kernel->values[7] = -MagickSQ2;
1281 CalcKernelMetaData(kernel); /* recalculate meta-data */
1282 ScaleKernelInfo(kernel, 1.0/2.0*MagickSQ2, NoValue);
1283 break;
1284 case 2:
1285 kernel=ParseKernelArray("3: 1,0,1 2,0,2 1,0,1");
1286 if (kernel == (KernelInfo *) NULL)
1287 return(kernel);
1288 kernel->values[3] = +MagickSQ2;
1289 kernel->values[5] = +MagickSQ2;
1290 CalcKernelMetaData(kernel);
1291 ScaleKernelInfo(kernel, 1.0/2.0*MagickSQ2, NoValue);
1292 break;
1293 case 3:
1294 kernel=ParseKernelArray("3: 0,-1,2 1,0,-1 -2,1,0");
1295 if (kernel == (KernelInfo *) NULL)
1296 return(kernel);
1297 kernel->values[2] = +MagickSQ2;
1298 kernel->values[6] = -MagickSQ2;
1299 CalcKernelMetaData(kernel);
1300 ScaleKernelInfo(kernel, 1.0/2.0*MagickSQ2, NoValue);
1301 break;
1302 case 4:
anthony6915d062010-05-19 12:45:51 +00001303 kernel=ParseKernelArray("3: 2,-1,0 -1,0,1 0,1,-2");
anthonye2a60ce2010-05-19 12:30:40 +00001304 if (kernel == (KernelInfo *) NULL)
1305 return(kernel);
1306 kernel->values[0] = +MagickSQ2;
1307 kernel->values[8] = -MagickSQ2;
1308 CalcKernelMetaData(kernel);
1309 ScaleKernelInfo(kernel, 1.0/2.0*MagickSQ2, NoValue);
1310 break;
1311 case 5:
1312 kernel=ParseKernelArray("3: 0,1,0 -1,0,-1 0,1,0");
1313 if (kernel == (KernelInfo *) NULL)
1314 return(kernel);
1315 ScaleKernelInfo(kernel, 1.0/2.0, NoValue);
1316 break;
1317 case 6:
1318 kernel=ParseKernelArray("3: -1,0,1 0,0,0 1,0,-1");
1319 if (kernel == (KernelInfo *) NULL)
1320 return(kernel);
1321 ScaleKernelInfo(kernel, 1.0/2.0, NoValue);
1322 break;
1323 case 7:
anthonyf4e00312010-05-20 12:06:35 +00001324 kernel=ParseKernelArray("3: 1,-2,1 -2,4,-2 1,-2,1");
anthonye2a60ce2010-05-19 12:30:40 +00001325 if (kernel == (KernelInfo *) NULL)
1326 return(kernel);
1327 ScaleKernelInfo(kernel, 1.0/6.0, NoValue);
1328 break;
1329 case 8:
1330 kernel=ParseKernelArray("3: -2,1,-2 1,4,1 -2,1,-2");
1331 if (kernel == (KernelInfo *) NULL)
1332 return(kernel);
1333 ScaleKernelInfo(kernel, 1.0/6.0, NoValue);
1334 break;
1335 case 9:
anthony6915d062010-05-19 12:45:51 +00001336 kernel=ParseKernelName("3: 1,1,1 1,1,1 1,1,1");
anthonye2a60ce2010-05-19 12:30:40 +00001337 if (kernel == (KernelInfo *) NULL)
1338 return(kernel);
1339 ScaleKernelInfo(kernel, 1.0/3.0, NoValue);
1340 break;
1341 }
1342 RotateKernelInfo(kernel, args->sigma); /* Rotate by angle */
1343 break;
1344 }
1345
anthonyc1061722010-05-14 06:23:49 +00001346 /* Boolean Kernels */
1347 case DiamondKernel:
1348 {
1349 if (args->rho < 1.0)
1350 kernel->width = kernel->height = 3; /* default radius = 1 */
1351 else
1352 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
1353 kernel->x = kernel->y = (long) (kernel->width-1)/2;
1354
1355 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1356 kernel->height*sizeof(double));
1357 if (kernel->values == (double *) NULL)
1358 return(DestroyKernelInfo(kernel));
1359
1360 /* set all kernel values within diamond area to scale given */
1361 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1362 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1363 if ((labs(u)+labs(v)) <= (long)kernel->x)
1364 kernel->positive_range += kernel->values[i] = args->sigma;
1365 else
1366 kernel->values[i] = nan;
1367 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1368 break;
1369 }
1370 case SquareKernel:
1371 case RectangleKernel:
1372 { double
1373 scale;
anthony602ab9b2010-01-05 08:06:50 +00001374 if ( type == SquareKernel )
1375 {
1376 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001377 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001378 else
cristy150989e2010-02-01 14:59:39 +00001379 kernel->width = kernel->height = (unsigned long) (2*args->rho+1);
cristyc99304f2010-02-01 15:26:27 +00001380 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony4fd27e22010-02-07 08:17:18 +00001381 scale = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +00001382 }
1383 else {
cristy2be15382010-01-21 02:38:03 +00001384 /* NOTE: user defaults set in "AcquireKernelInfo()" */
anthony602ab9b2010-01-05 08:06:50 +00001385 if ( args->rho < 1.0 || args->sigma < 1.0 )
anthony83ba99b2010-01-24 08:48:15 +00001386 return(DestroyKernelInfo(kernel)); /* invalid args given */
anthony602ab9b2010-01-05 08:06:50 +00001387 kernel->width = (unsigned long)args->rho;
1388 kernel->height = (unsigned long)args->sigma;
1389 if ( args->xi < 0.0 || args->xi > (double)kernel->width ||
1390 args->psi < 0.0 || args->psi > (double)kernel->height )
anthony83ba99b2010-01-24 08:48:15 +00001391 return(DestroyKernelInfo(kernel)); /* invalid args given */
cristyc99304f2010-02-01 15:26:27 +00001392 kernel->x = (long) args->xi;
1393 kernel->y = (long) args->psi;
anthony4fd27e22010-02-07 08:17:18 +00001394 scale = 1.0;
anthony602ab9b2010-01-05 08:06:50 +00001395 }
1396 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1397 kernel->height*sizeof(double));
1398 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001399 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001400
anthony3dd0f622010-05-13 12:57:32 +00001401 /* set all kernel values to scale given */
cristy150989e2010-02-01 14:59:39 +00001402 u=(long) kernel->width*kernel->height;
1403 for ( i=0; i < u; i++)
anthony4fd27e22010-02-07 08:17:18 +00001404 kernel->values[i] = scale;
1405 kernel->minimum = kernel->maximum = scale; /* a flat shape */
1406 kernel->positive_range = scale*u;
anthonycc6c8362010-01-25 04:14:01 +00001407 break;
anthony602ab9b2010-01-05 08:06:50 +00001408 }
anthony602ab9b2010-01-05 08:06:50 +00001409 case DiskKernel:
1410 {
anthonyc1061722010-05-14 06:23:49 +00001411 long limit = (long)(args->rho*args->rho);
anthony83ba99b2010-01-24 08:48:15 +00001412 if (args->rho < 0.1) /* default radius approx 3.5 */
1413 kernel->width = kernel->height = 7L, limit = 10L;
anthony602ab9b2010-01-05 08:06:50 +00001414 else
1415 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001416 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001417
1418 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1419 kernel->height*sizeof(double));
1420 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001421 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001422
anthony3dd0f622010-05-13 12:57:32 +00001423 /* set all kernel values within disk area to scale given */
1424 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
cristyc99304f2010-02-01 15:26:27 +00001425 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony602ab9b2010-01-05 08:06:50 +00001426 if ((u*u+v*v) <= limit)
anthony4fd27e22010-02-07 08:17:18 +00001427 kernel->positive_range += kernel->values[i] = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +00001428 else
1429 kernel->values[i] = nan;
anthony4fd27e22010-02-07 08:17:18 +00001430 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
anthony602ab9b2010-01-05 08:06:50 +00001431 break;
1432 }
1433 case PlusKernel:
1434 {
1435 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001436 kernel->width = kernel->height = 5; /* default radius 2 */
anthony602ab9b2010-01-05 08:06:50 +00001437 else
1438 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001439 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001440
1441 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1442 kernel->height*sizeof(double));
1443 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001444 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001445
anthony3dd0f622010-05-13 12:57:32 +00001446 /* set all kernel values along axises to given scale */
cristyc99304f2010-02-01 15:26:27 +00001447 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1448 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony4fd27e22010-02-07 08:17:18 +00001449 kernel->values[i] = (u == 0 || v == 0) ? args->sigma : nan;
1450 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1451 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
anthony602ab9b2010-01-05 08:06:50 +00001452 break;
1453 }
anthony3dd0f622010-05-13 12:57:32 +00001454 case CrossKernel:
1455 {
1456 if (args->rho < 1.0)
1457 kernel->width = kernel->height = 5; /* default radius 2 */
1458 else
1459 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
1460 kernel->x = kernel->y = (long) (kernel->width-1)/2;
1461
1462 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1463 kernel->height*sizeof(double));
1464 if (kernel->values == (double *) NULL)
1465 return(DestroyKernelInfo(kernel));
1466
1467 /* set all kernel values along axises to given scale */
1468 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1469 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1470 kernel->values[i] = (u == v || u == -v) ? args->sigma : nan;
1471 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1472 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
1473 break;
1474 }
1475 /* HitAndMiss Kernels */
anthonyc1061722010-05-14 06:23:49 +00001476 case RingKernel:
anthony3dd0f622010-05-13 12:57:32 +00001477 case PeaksKernel:
1478 {
1479 long
1480 limit1,
anthonyc1061722010-05-14 06:23:49 +00001481 limit2,
1482 scale;
anthony3dd0f622010-05-13 12:57:32 +00001483
1484 if (args->rho < args->sigma)
1485 {
1486 kernel->width = ((unsigned long)args->sigma)*2+1;
1487 limit1 = (long)args->rho*args->rho;
1488 limit2 = (long)args->sigma*args->sigma;
1489 }
1490 else
1491 {
1492 kernel->width = ((unsigned long)args->rho)*2+1;
1493 limit1 = (long)args->sigma*args->sigma;
1494 limit2 = (long)args->rho*args->rho;
1495 }
anthonyc1061722010-05-14 06:23:49 +00001496 if ( limit2 <= 0 )
1497 kernel->width = 7L, limit1 = 7L, limit2 = 11L;
1498
anthony3dd0f622010-05-13 12:57:32 +00001499 kernel->height = kernel->width;
1500 kernel->x = kernel->y = (long) (kernel->width-1)/2;
1501 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1502 kernel->height*sizeof(double));
1503 if (kernel->values == (double *) NULL)
1504 return(DestroyKernelInfo(kernel));
1505
anthonyc1061722010-05-14 06:23:49 +00001506 /* set a ring of points of 'scale' ( 0.0 for PeaksKernel ) */
cristye96405a2010-05-19 02:24:31 +00001507 scale = (long) (( type == PeaksKernel) ? 0.0 : args->xi);
anthony3dd0f622010-05-13 12:57:32 +00001508 for ( i=0, v= -kernel->y; v <= (long)kernel->y; v++)
1509 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1510 { long radius=u*u+v*v;
anthonyc1061722010-05-14 06:23:49 +00001511 if (limit1 < radius && radius <= limit2)
cristye96405a2010-05-19 02:24:31 +00001512 kernel->positive_range += kernel->values[i] = (double) scale;
anthony3dd0f622010-05-13 12:57:32 +00001513 else
1514 kernel->values[i] = nan;
1515 }
cristye96405a2010-05-19 02:24:31 +00001516 kernel->minimum = kernel->minimum = (double) scale;
anthonyc1061722010-05-14 06:23:49 +00001517 if ( type == PeaksKernel ) {
1518 /* set the central point in the middle */
1519 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1520 kernel->positive_range = 1.0;
1521 kernel->maximum = 1.0;
1522 }
anthony3dd0f622010-05-13 12:57:32 +00001523 break;
1524 }
anthony43c49252010-05-18 10:59:50 +00001525 case EdgesKernel:
1526 {
1527 kernel=ParseKernelArray("3: 0,0,0 -,1,- 1,1,1");
1528 if (kernel == (KernelInfo *) NULL)
1529 return(kernel);
1530 kernel->type = type;
1531 ExpandKernelInfo(kernel, 90.0); /* Create a list of 4 rotated kernels */
1532 break;
1533 }
anthony3dd0f622010-05-13 12:57:32 +00001534 case CornersKernel:
1535 {
anthony4f1dcb72010-05-14 08:43:10 +00001536 kernel=ParseKernelArray("3: 0,0,- 0,1,1 -,1,-");
anthony3dd0f622010-05-13 12:57:32 +00001537 if (kernel == (KernelInfo *) NULL)
1538 return(kernel);
1539 kernel->type = type;
1540 ExpandKernelInfo(kernel, 90.0); /* Create a list of 4 rotated kernels */
1541 break;
1542 }
anthony47f5d062010-05-23 07:47:50 +00001543 case RidgesKernel:
1544 {
1545 kernel=ParseKernelArray("3: -,-,- 0,1,0 -,-,-");
1546 if (kernel == (KernelInfo *) NULL)
1547 return(kernel);
1548 kernel->type = type;
1549 ExpandKernelInfo(kernel, 45.0); /* 4 rotated kernels (symmetrical) */
1550 break;
1551 }
anthony3dd0f622010-05-13 12:57:32 +00001552 case LineEndsKernel:
1553 {
anthony43c49252010-05-18 10:59:50 +00001554 KernelInfo
1555 *new_kernel;
1556 kernel=ParseKernelArray("3: 0,0,0 0,1,0 -,1,-");
anthony3dd0f622010-05-13 12:57:32 +00001557 if (kernel == (KernelInfo *) NULL)
1558 return(kernel);
1559 kernel->type = type;
anthony43c49252010-05-18 10:59:50 +00001560 ExpandKernelInfo(kernel, 90.0);
1561 /* append second set of 4 kernels */
1562 new_kernel=ParseKernelArray("3: 0,0,0 0,1,0 0,0,1");
1563 if (new_kernel == (KernelInfo *) NULL)
1564 return(DestroyKernelInfo(kernel));
1565 new_kernel->type = type;
1566 ExpandKernelInfo(new_kernel, 90.0);
1567 LastKernelInfo(kernel)->next = new_kernel;
anthony3dd0f622010-05-13 12:57:32 +00001568 break;
1569 }
1570 case LineJunctionsKernel:
1571 {
1572 KernelInfo
1573 *new_kernel;
anthony3dd0f622010-05-13 12:57:32 +00001574 /* first set of 4 kernels */
anthony4f1dcb72010-05-14 08:43:10 +00001575 kernel=ParseKernelArray("3: -,1,- -,1,- 1,-,1");
anthony3dd0f622010-05-13 12:57:32 +00001576 if (kernel == (KernelInfo *) NULL)
1577 return(kernel);
1578 kernel->type = type;
anthony43c49252010-05-18 10:59:50 +00001579 ExpandKernelInfo(kernel, 45.0);
anthony3dd0f622010-05-13 12:57:32 +00001580 /* append second set of 4 kernels */
anthony4f1dcb72010-05-14 08:43:10 +00001581 new_kernel=ParseKernelArray("3: 1,-,- -,1,- 1,-,1");
anthony3dd0f622010-05-13 12:57:32 +00001582 if (new_kernel == (KernelInfo *) NULL)
1583 return(DestroyKernelInfo(kernel));
anthony43c49252010-05-18 10:59:50 +00001584 new_kernel->type = type;
anthony3dd0f622010-05-13 12:57:32 +00001585 ExpandKernelInfo(new_kernel, 90.0);
1586 LastKernelInfo(kernel)->next = new_kernel;
anthony4f1dcb72010-05-14 08:43:10 +00001587 break;
1588 }
anthony3dd0f622010-05-13 12:57:32 +00001589 case ConvexHullKernel:
1590 {
1591 KernelInfo
1592 *new_kernel;
anthony3dd0f622010-05-13 12:57:32 +00001593 /* first set of 4 kernels */
anthony4f1dcb72010-05-14 08:43:10 +00001594 kernel=ParseKernelArray("3: 1,1,- 1,0,- 1,-,0");
anthony3dd0f622010-05-13 12:57:32 +00001595 if (kernel == (KernelInfo *) NULL)
1596 return(kernel);
1597 kernel->type = type;
1598 ExpandKernelInfo(kernel, 90.0);
1599 /* append second set of 4 kernels */
anthony43c49252010-05-18 10:59:50 +00001600 new_kernel=ParseKernelArray("3: 1,1,1 1,0,0 -,-,0");
anthony3dd0f622010-05-13 12:57:32 +00001601 if (new_kernel == (KernelInfo *) NULL)
1602 return(DestroyKernelInfo(kernel));
anthony43c49252010-05-18 10:59:50 +00001603 new_kernel->type = type;
anthony3dd0f622010-05-13 12:57:32 +00001604 ExpandKernelInfo(new_kernel, 90.0);
1605 LastKernelInfo(kernel)->next = new_kernel;
1606 break;
1607 }
anthony47f5d062010-05-23 07:47:50 +00001608 case SkeletonKernel:
1609 { /* what is the best form for medial axis skeletonization? */
1610#if 0
1611# if 0
1612 kernel=AcquireKernelInfo("Corners;Edges");
1613# else
1614 kernel=AcquireKernelInfo("Edges;Corners");
1615# endif
1616#else
anthony43c49252010-05-18 10:59:50 +00001617 kernel=ParseKernelArray("3: 0,0,- 0,1,1 -,1,1");
anthony3dd0f622010-05-13 12:57:32 +00001618 if (kernel == (KernelInfo *) NULL)
1619 return(kernel);
1620 kernel->type = type;
anthony43c49252010-05-18 10:59:50 +00001621 ExpandKernelInfo(kernel, 45);
1622 break;
anthony47f5d062010-05-23 07:47:50 +00001623#endif
anthony3dd0f622010-05-13 12:57:32 +00001624 break;
1625 }
anthony602ab9b2010-01-05 08:06:50 +00001626 /* Distance Measuring Kernels */
1627 case ChebyshevKernel:
1628 {
anthony602ab9b2010-01-05 08:06:50 +00001629 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001630 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001631 else
1632 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001633 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001634
1635 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1636 kernel->height*sizeof(double));
1637 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001638 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001639
cristyc99304f2010-02-01 15:26:27 +00001640 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1641 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1642 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001643 args->sigma*((labs(u)>labs(v)) ? labs(u) : labs(v)) );
cristyc99304f2010-02-01 15:26:27 +00001644 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001645 break;
1646 }
1647 case ManhattenKernel:
1648 {
anthony602ab9b2010-01-05 08:06:50 +00001649 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001650 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001651 else
1652 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001653 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001654
1655 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1656 kernel->height*sizeof(double));
1657 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001658 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001659
cristyc99304f2010-02-01 15:26:27 +00001660 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1661 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1662 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001663 args->sigma*(labs(u)+labs(v)) );
cristyc99304f2010-02-01 15:26:27 +00001664 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001665 break;
1666 }
1667 case EuclideanKernel:
1668 {
anthony602ab9b2010-01-05 08:06:50 +00001669 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001670 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001671 else
1672 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001673 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001674
1675 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1676 kernel->height*sizeof(double));
1677 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001678 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001679
cristyc99304f2010-02-01 15:26:27 +00001680 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1681 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1682 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001683 args->sigma*sqrt((double)(u*u+v*v)) );
cristyc99304f2010-02-01 15:26:27 +00001684 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001685 break;
1686 }
anthony46a369d2010-05-19 02:41:48 +00001687 case UnityKernel:
anthony602ab9b2010-01-05 08:06:50 +00001688 default:
anthonyc1061722010-05-14 06:23:49 +00001689 {
anthony46a369d2010-05-19 02:41:48 +00001690 /* Unity or No-Op Kernel - 3x3 with 1 in center */
1691 kernel=ParseKernelArray("3:0,0,0,0,1,0,0,0,0");
anthonyc1061722010-05-14 06:23:49 +00001692 if (kernel == (KernelInfo *) NULL)
1693 return(kernel);
anthony46a369d2010-05-19 02:41:48 +00001694 kernel->type = ( type == UnityKernel ) ? UnityKernel : UndefinedKernel;
anthonyc1061722010-05-14 06:23:49 +00001695 break;
1696 }
anthony602ab9b2010-01-05 08:06:50 +00001697 break;
1698 }
1699
1700 return(kernel);
1701}
anthonyc94cdb02010-01-06 08:15:29 +00001702
anthony602ab9b2010-01-05 08:06:50 +00001703/*
1704%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1705% %
1706% %
1707% %
cristy6771f1e2010-03-05 19:43:39 +00001708% C l o n e K e r n e l I n f o %
anthony4fd27e22010-02-07 08:17:18 +00001709% %
1710% %
1711% %
1712%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1713%
anthony1b2bc0a2010-05-12 05:25:22 +00001714% CloneKernelInfo() creates a new clone of the given Kernel List so that its
1715% can be modified without effecting the original. The cloned kernel should
1716% be destroyed using DestoryKernelInfo() when no longer needed.
anthony7a01dcf2010-05-11 12:25:52 +00001717%
cristye6365592010-04-02 17:31:23 +00001718% The format of the CloneKernelInfo method is:
anthony4fd27e22010-02-07 08:17:18 +00001719%
anthony930be612010-02-08 04:26:15 +00001720% KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +00001721%
1722% A description of each parameter follows:
1723%
1724% o kernel: the Morphology/Convolution kernel to be cloned
1725%
1726*/
cristyef656912010-03-05 19:54:59 +00001727MagickExport KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +00001728{
1729 register long
1730 i;
1731
cristy19eb6412010-04-23 14:42:29 +00001732 KernelInfo
anthony7a01dcf2010-05-11 12:25:52 +00001733 *new_kernel;
anthony4fd27e22010-02-07 08:17:18 +00001734
1735 assert(kernel != (KernelInfo *) NULL);
anthony7a01dcf2010-05-11 12:25:52 +00001736 new_kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
1737 if (new_kernel == (KernelInfo *) NULL)
1738 return(new_kernel);
1739 *new_kernel=(*kernel); /* copy values in structure */
anthony7a01dcf2010-05-11 12:25:52 +00001740
1741 /* replace the values with a copy of the values */
1742 new_kernel->values=(double *) AcquireQuantumMemory(kernel->width,
cristy19eb6412010-04-23 14:42:29 +00001743 kernel->height*sizeof(double));
anthony7a01dcf2010-05-11 12:25:52 +00001744 if (new_kernel->values == (double *) NULL)
1745 return(DestroyKernelInfo(new_kernel));
anthony4fd27e22010-02-07 08:17:18 +00001746 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthony7a01dcf2010-05-11 12:25:52 +00001747 new_kernel->values[i]=kernel->values[i];
anthony1b2bc0a2010-05-12 05:25:22 +00001748
1749 /* Also clone the next kernel in the kernel list */
1750 if ( kernel->next != (KernelInfo *) NULL ) {
1751 new_kernel->next = CloneKernelInfo(kernel->next);
1752 if ( new_kernel->next == (KernelInfo *) NULL )
1753 return(DestroyKernelInfo(new_kernel));
1754 }
1755
anthony7a01dcf2010-05-11 12:25:52 +00001756 return(new_kernel);
anthony4fd27e22010-02-07 08:17:18 +00001757}
1758
1759/*
1760%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1761% %
1762% %
1763% %
anthony83ba99b2010-01-24 08:48:15 +00001764% D e s t r o y K e r n e l I n f o %
anthony602ab9b2010-01-05 08:06:50 +00001765% %
1766% %
1767% %
1768%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1769%
anthony83ba99b2010-01-24 08:48:15 +00001770% DestroyKernelInfo() frees the memory used by a Convolution/Morphology
1771% kernel.
anthony602ab9b2010-01-05 08:06:50 +00001772%
anthony83ba99b2010-01-24 08:48:15 +00001773% The format of the DestroyKernelInfo method is:
anthony602ab9b2010-01-05 08:06:50 +00001774%
anthony83ba99b2010-01-24 08:48:15 +00001775% KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +00001776%
1777% A description of each parameter follows:
1778%
1779% o kernel: the Morphology/Convolution kernel to be destroyed
1780%
1781*/
1782
anthony83ba99b2010-01-24 08:48:15 +00001783MagickExport KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +00001784{
cristy2be15382010-01-21 02:38:03 +00001785 assert(kernel != (KernelInfo *) NULL);
anthony4fd27e22010-02-07 08:17:18 +00001786
anthony7a01dcf2010-05-11 12:25:52 +00001787 if ( kernel->next != (KernelInfo *) NULL )
1788 kernel->next = DestroyKernelInfo(kernel->next);
1789
1790 kernel->values = (double *)RelinquishMagickMemory(kernel->values);
1791 kernel = (KernelInfo *) RelinquishMagickMemory(kernel);
anthony602ab9b2010-01-05 08:06:50 +00001792 return(kernel);
1793}
anthonyc94cdb02010-01-06 08:15:29 +00001794
1795/*
1796%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1797% %
1798% %
1799% %
anthony3c10fc82010-05-13 02:40:51 +00001800% E x p a n d K e r n e l I n f o %
1801% %
1802% %
1803% %
1804%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1805%
1806% ExpandKernelInfo() takes a single kernel, and expands it into a list
1807% of kernels each incrementally rotated the angle given.
1808%
1809% WARNING: 45 degree rotations only works for 3x3 kernels.
1810% While 90 degree roatations only works for linear and square kernels
1811%
1812% The format of the RotateKernelInfo method is:
1813%
1814% void ExpandKernelInfo(KernelInfo *kernel, double angle)
1815%
1816% A description of each parameter follows:
1817%
1818% o kernel: the Morphology/Convolution kernel
1819%
1820% o angle: angle to rotate in degrees
1821%
1822% This function is only internel to this module, as it is not finalized,
1823% especially with regard to non-orthogonal angles, and rotation of larger
1824% 2D kernels.
1825*/
anthony47f5d062010-05-23 07:47:50 +00001826
1827/* Internal Routine - Return true if two kernels are the same */
1828static MagickBooleanType SameKernelInfo(const KernelInfo *kernel1,
1829 const KernelInfo *kernel2)
1830{
1831 register unsigned long
1832 i;
1833 if ( kernel1->width != kernel2->width )
1834 return MagickFalse;
1835 if ( kernel1->height != kernel2->height )
1836 return MagickFalse;
1837 for (i=0; i < (kernel1->width*kernel1->height); i++) {
1838 /* Test Nan */
1839 if ( IsNan(kernel1->values[i]) && !IsNan(kernel2->values[i]) )
1840 return MagickFalse;
1841 if ( IsNan(kernel2->values[i]) && !IsNan(kernel1->values[i]) )
1842 return MagickFalse;
1843 /* Test actual value */
1844 if ( fabs(kernel1->values[i] - kernel2->values[i]) > MagickEpsilon )
1845 return MagickFalse;
1846 }
1847 return MagickTrue;
1848}
1849
1850static void ExpandKernelInfo(KernelInfo *kernel, const double angle)
anthony3c10fc82010-05-13 02:40:51 +00001851{
1852 KernelInfo
anthony47f5d062010-05-23 07:47:50 +00001853 *new,
anthony3c10fc82010-05-13 02:40:51 +00001854 *last;
cristya9a61ad2010-05-13 12:47:41 +00001855
anthony3c10fc82010-05-13 02:40:51 +00001856 last = kernel;
anthony47f5d062010-05-23 07:47:50 +00001857 while(1) {
1858 new = CloneKernelInfo(last);
1859 RotateKernelInfo(new, angle);
1860 if ( SameKernelInfo(kernel, new) == MagickTrue )
1861 break;
1862 last->next = new;
1863 last = new;
anthony3c10fc82010-05-13 02:40:51 +00001864 }
anthony47f5d062010-05-23 07:47:50 +00001865 new = DestroyKernelInfo(new); /* This was the same as the first - junk */
1866 return;
anthony3c10fc82010-05-13 02:40:51 +00001867}
1868
1869/*
1870%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1871% %
1872% %
1873% %
anthony46a369d2010-05-19 02:41:48 +00001874+ C a l c M e t a K e r n a l I n f o %
1875% %
1876% %
1877% %
1878%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1879%
1880% CalcKernelMetaData() recalculate the KernelInfo meta-data of this kernel only,
1881% using the kernel values. This should only ne used if it is not posible to
1882% calculate that meta-data in some easier way.
1883%
1884% It is important that the meta-data is correct before ScaleKernelInfo() is
1885% used to perform kernel normalization.
1886%
1887% The format of the CalcKernelMetaData method is:
1888%
1889% void CalcKernelMetaData(KernelInfo *kernel, const double scale )
1890%
1891% A description of each parameter follows:
1892%
1893% o kernel: the Morphology/Convolution kernel to modify
1894%
1895% WARNING: Minimum and Maximum values are assumed to include zero, even if
1896% zero is not part of the kernel (as in Gaussian Derived kernels). This
1897% however is not true for flat-shaped morphological kernels.
1898%
1899% WARNING: Only the specific kernel pointed to is modified, not a list of
1900% multiple kernels.
1901%
1902% This is an internal function and not expected to be useful outside this
1903% module. This could change however.
1904*/
1905static void CalcKernelMetaData(KernelInfo *kernel)
1906{
1907 register unsigned long
1908 i;
1909
1910 kernel->minimum = kernel->maximum = 0.0;
1911 kernel->negative_range = kernel->positive_range = 0.0;
1912 for (i=0; i < (kernel->width*kernel->height); i++)
1913 {
1914 if ( fabs(kernel->values[i]) < MagickEpsilon )
1915 kernel->values[i] = 0.0;
1916 ( kernel->values[i] < 0)
1917 ? ( kernel->negative_range += kernel->values[i] )
1918 : ( kernel->positive_range += kernel->values[i] );
1919 Minimize(kernel->minimum, kernel->values[i]);
1920 Maximize(kernel->maximum, kernel->values[i]);
1921 }
1922
1923 return;
1924}
1925
1926/*
1927%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1928% %
1929% %
1930% %
anthony9eb4f742010-05-18 02:45:54 +00001931% M o r p h o l o g y A p p l y %
anthony602ab9b2010-01-05 08:06:50 +00001932% %
1933% %
1934% %
1935%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1936%
anthony9eb4f742010-05-18 02:45:54 +00001937% MorphologyApply() applies a morphological method, multiple times using
1938% a list of multiple kernels.
anthony602ab9b2010-01-05 08:06:50 +00001939%
anthony9eb4f742010-05-18 02:45:54 +00001940% It is basically equivelent to as MorphologyImageChannel() (see below) but
1941% without user controls, that that function extracts and applies to kernels
1942% and morphology methods.
1943%
1944% More specifically kernels are not normalized/scaled/blended by the
1945% 'convolve:scale' Image Artifact (-set setting), and the convolve bias
1946% (-bias setting or image->bias) is passed directly to this function,
1947% and not extracted from an image.
anthony602ab9b2010-01-05 08:06:50 +00001948%
anthony47f5d062010-05-23 07:47:50 +00001949% The format of the MorphologyApply method is:
anthony602ab9b2010-01-05 08:06:50 +00001950%
anthony9eb4f742010-05-18 02:45:54 +00001951% Image *MorphologyApply(const Image *image,MorphologyMethod method,
anthony47f5d062010-05-23 07:47:50 +00001952% const long iterations,const KernelInfo *kernel,
1953% const CompositeMethod compose, const double bias,
anthony9eb4f742010-05-18 02:45:54 +00001954% ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001955%
1956% A description of each parameter follows:
1957%
1958% o image: the image.
1959%
1960% o method: the morphology method to be applied.
1961%
1962% o iterations: apply the operation this many times (or no change).
1963% A value of -1 means loop until no change found.
1964% How this is applied may depend on the morphology method.
1965% Typically this is a value of 1.
1966%
1967% o channel: the channel type.
1968%
1969% o kernel: An array of double representing the morphology kernel.
anthony29188a82010-01-22 10:12:34 +00001970% Warning: kernel may be normalized for the Convolve method.
anthony602ab9b2010-01-05 08:06:50 +00001971%
anthony47f5d062010-05-23 07:47:50 +00001972% o compose: How to handle or merge multi-kernel results.
1973% If 'Undefined' use default of the Morphology method.
1974% If 'No' force image to be re-iterated by each kernel.
1975% Otherwise merge the results using the mathematical compose
1976% method given.
1977%
1978% o bias: Convolution Output Bias.
anthony9eb4f742010-05-18 02:45:54 +00001979%
anthony602ab9b2010-01-05 08:06:50 +00001980% o exception: return any errors or warnings in this structure.
1981%
anthony602ab9b2010-01-05 08:06:50 +00001982*/
1983
anthony930be612010-02-08 04:26:15 +00001984
anthony9eb4f742010-05-18 02:45:54 +00001985/* Apply a Morphology Primative to an image using the given kernel.
1986** Two pre-created images must be provided, no image is created.
1987** Returning the number of pixels that changed.
1988*/
anthony46a369d2010-05-19 02:41:48 +00001989static unsigned long MorphologyPrimitive(const Image *image, Image
anthony602ab9b2010-01-05 08:06:50 +00001990 *result_image, const MorphologyMethod method, const ChannelType channel,
anthony9eb4f742010-05-18 02:45:54 +00001991 const KernelInfo *kernel,const double bias,ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001992{
cristy2be15382010-01-21 02:38:03 +00001993#define MorphologyTag "Morphology/Image"
anthony602ab9b2010-01-05 08:06:50 +00001994
1995 long
cristy150989e2010-02-01 14:59:39 +00001996 progress,
anthony29188a82010-01-22 10:12:34 +00001997 y, offx, offy,
anthony602ab9b2010-01-05 08:06:50 +00001998 changed;
1999
2000 MagickBooleanType
2001 status;
2002
anthony602ab9b2010-01-05 08:06:50 +00002003 CacheView
2004 *p_view,
2005 *q_view;
2006
anthony602ab9b2010-01-05 08:06:50 +00002007 status=MagickTrue;
2008 changed=0;
2009 progress=0;
2010
anthony602ab9b2010-01-05 08:06:50 +00002011 p_view=AcquireCacheView(image);
2012 q_view=AcquireCacheView(result_image);
anthony29188a82010-01-22 10:12:34 +00002013
anthonycc6c8362010-01-25 04:14:01 +00002014 /* Some methods (including convolve) needs use a reflected kernel.
anthony9eb4f742010-05-18 02:45:54 +00002015 * Adjust 'origin' offsets to loop though kernel as a reflection.
anthony29188a82010-01-22 10:12:34 +00002016 */
cristyc99304f2010-02-01 15:26:27 +00002017 offx = kernel->x;
2018 offy = kernel->y;
anthony29188a82010-01-22 10:12:34 +00002019 switch(method) {
anthony930be612010-02-08 04:26:15 +00002020 case ConvolveMorphology:
2021 case DilateMorphology:
2022 case DilateIntensityMorphology:
2023 case DistanceMorphology:
anthony5ef8e942010-05-11 06:51:12 +00002024 /* kernel needs to used with reflection about origin */
cristy150989e2010-02-01 14:59:39 +00002025 offx = (long) kernel->width-offx-1;
2026 offy = (long) kernel->height-offy-1;
anthony29188a82010-01-22 10:12:34 +00002027 break;
anthony5ef8e942010-05-11 06:51:12 +00002028 case ErodeMorphology:
2029 case ErodeIntensityMorphology:
2030 case HitAndMissMorphology:
2031 case ThinningMorphology:
2032 case ThickenMorphology:
2033 /* kernel is user as is, without reflection */
2034 break;
anthony930be612010-02-08 04:26:15 +00002035 default:
anthony9eb4f742010-05-18 02:45:54 +00002036 assert("Not a Primitive Morphology Method" != (char *) NULL);
anthony930be612010-02-08 04:26:15 +00002037 break;
anthony29188a82010-01-22 10:12:34 +00002038 }
2039
anthony602ab9b2010-01-05 08:06:50 +00002040#if defined(MAGICKCORE_OPENMP_SUPPORT)
2041 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2042#endif
cristy150989e2010-02-01 14:59:39 +00002043 for (y=0; y < (long) image->rows; y++)
anthony602ab9b2010-01-05 08:06:50 +00002044 {
2045 MagickBooleanType
2046 sync;
2047
2048 register const PixelPacket
2049 *restrict p;
2050
2051 register const IndexPacket
2052 *restrict p_indexes;
2053
2054 register PixelPacket
2055 *restrict q;
2056
2057 register IndexPacket
2058 *restrict q_indexes;
2059
cristy150989e2010-02-01 14:59:39 +00002060 register long
anthony602ab9b2010-01-05 08:06:50 +00002061 x;
2062
anthony29188a82010-01-22 10:12:34 +00002063 unsigned long
anthony602ab9b2010-01-05 08:06:50 +00002064 r;
2065
2066 if (status == MagickFalse)
2067 continue;
anthony29188a82010-01-22 10:12:34 +00002068 p=GetCacheViewVirtualPixels(p_view, -offx, y-offy,
2069 image->columns+kernel->width, kernel->height, exception);
anthony602ab9b2010-01-05 08:06:50 +00002070 q=GetCacheViewAuthenticPixels(q_view,0,y,result_image->columns,1,
2071 exception);
2072 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2073 {
2074 status=MagickFalse;
2075 continue;
2076 }
2077 p_indexes=GetCacheViewVirtualIndexQueue(p_view);
2078 q_indexes=GetCacheViewAuthenticIndexQueue(q_view);
anthony29188a82010-01-22 10:12:34 +00002079 r = (image->columns+kernel->width)*offy+offx; /* constant */
2080
cristy150989e2010-02-01 14:59:39 +00002081 for (x=0; x < (long) image->columns; x++)
anthony602ab9b2010-01-05 08:06:50 +00002082 {
cristy150989e2010-02-01 14:59:39 +00002083 long
anthony602ab9b2010-01-05 08:06:50 +00002084 v;
2085
cristy150989e2010-02-01 14:59:39 +00002086 register long
anthony602ab9b2010-01-05 08:06:50 +00002087 u;
2088
2089 register const double
2090 *restrict k;
2091
2092 register const PixelPacket
2093 *restrict k_pixels;
2094
2095 register const IndexPacket
2096 *restrict k_indexes;
2097
2098 MagickPixelPacket
anthony5ef8e942010-05-11 06:51:12 +00002099 result,
2100 min,
2101 max;
anthony602ab9b2010-01-05 08:06:50 +00002102
anthony29188a82010-01-22 10:12:34 +00002103 /* Copy input to ouput image for unused channels
anthony83ba99b2010-01-24 08:48:15 +00002104 * This removes need for 'cloning' a new image every iteration
anthony29188a82010-01-22 10:12:34 +00002105 */
anthony602ab9b2010-01-05 08:06:50 +00002106 *q = p[r];
2107 if (image->colorspace == CMYKColorspace)
2108 q_indexes[x] = p_indexes[r];
2109
anthony5ef8e942010-05-11 06:51:12 +00002110 /* Defaults */
2111 min.red =
2112 min.green =
2113 min.blue =
2114 min.opacity =
2115 min.index = (MagickRealType) QuantumRange;
2116 max.red =
2117 max.green =
2118 max.blue =
2119 max.opacity =
2120 max.index = (MagickRealType) 0;
anthony9eb4f742010-05-18 02:45:54 +00002121 /* default result is the original pixel value */
anthony5ef8e942010-05-11 06:51:12 +00002122 result.red = (MagickRealType) p[r].red;
2123 result.green = (MagickRealType) p[r].green;
2124 result.blue = (MagickRealType) p[r].blue;
2125 result.opacity = QuantumRange - (MagickRealType) p[r].opacity;
cristye96405a2010-05-19 02:24:31 +00002126 result.index = 0.0;
anthony5ef8e942010-05-11 06:51:12 +00002127 if ( image->colorspace == CMYKColorspace)
2128 result.index = (MagickRealType) p_indexes[r];
2129
anthony602ab9b2010-01-05 08:06:50 +00002130 switch (method) {
2131 case ConvolveMorphology:
anthony9eb4f742010-05-18 02:45:54 +00002132 /* Set the user defined bias of the weighted average output */
2133 result.red =
2134 result.green =
2135 result.blue =
2136 result.opacity =
2137 result.index = bias;
anthony930be612010-02-08 04:26:15 +00002138 break;
anthony4fd27e22010-02-07 08:17:18 +00002139 case DilateIntensityMorphology:
2140 case ErodeIntensityMorphology:
anthony9eb4f742010-05-18 02:45:54 +00002141 /* use a boolean flag indicating when first match found */
2142 result.red = 0.0; /* result is not used otherwise */
anthony4fd27e22010-02-07 08:17:18 +00002143 break;
anthony602ab9b2010-01-05 08:06:50 +00002144 default:
anthony602ab9b2010-01-05 08:06:50 +00002145 break;
2146 }
2147
2148 switch ( method ) {
2149 case ConvolveMorphology:
anthony930be612010-02-08 04:26:15 +00002150 /* Weighted Average of pixels using reflected kernel
2151 **
2152 ** NOTE for correct working of this operation for asymetrical
2153 ** kernels, the kernel needs to be applied in its reflected form.
2154 ** That is its values needs to be reversed.
2155 **
2156 ** Correlation is actually the same as this but without reflecting
2157 ** the kernel, and thus 'lower-level' that Convolution. However
2158 ** as Convolution is the more common method used, and it does not
2159 ** really cost us much in terms of processing to use a reflected
anthony5ef8e942010-05-11 06:51:12 +00002160 ** kernel, so it is Convolution that is implemented.
anthony930be612010-02-08 04:26:15 +00002161 **
2162 ** Correlation will have its kernel reflected before calling
2163 ** this function to do a Convolve.
2164 **
2165 ** For more details of Correlation vs Convolution see
2166 ** http://www.cs.umd.edu/~djacobs/CMSC426/Convolution.pdf
2167 */
anthony5ef8e942010-05-11 06:51:12 +00002168 if (((channel & SyncChannels) != 0 ) &&
2169 (image->matte == MagickTrue))
2170 { /* Channel has a 'Sync' Flag, and Alpha Channel enabled.
2171 ** Weight the color channels with Alpha Channel so that
2172 ** transparent pixels are not part of the results.
2173 */
anthony602ab9b2010-01-05 08:06:50 +00002174 MagickRealType
anthony5ef8e942010-05-11 06:51:12 +00002175 alpha, /* color channel weighting : kernel*alpha */
2176 gamma; /* divisor, sum of weighting values */
anthony602ab9b2010-01-05 08:06:50 +00002177
2178 gamma=0.0;
anthony29188a82010-01-22 10:12:34 +00002179 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00002180 k_pixels = p;
2181 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002182 for (v=0; v < (long) kernel->height; v++) {
2183 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00002184 if ( IsNan(*k) ) continue;
2185 alpha=(*k)*(QuantumScale*(QuantumRange-
2186 k_pixels[u].opacity));
2187 gamma += alpha;
2188 result.red += alpha*k_pixels[u].red;
2189 result.green += alpha*k_pixels[u].green;
2190 result.blue += alpha*k_pixels[u].blue;
anthony83ba99b2010-01-24 08:48:15 +00002191 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00002192 if ( image->colorspace == CMYKColorspace)
2193 result.index += alpha*k_indexes[u];
2194 }
2195 k_pixels += image->columns+kernel->width;
2196 k_indexes += image->columns+kernel->width;
2197 }
2198 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
anthony83ba99b2010-01-24 08:48:15 +00002199 result.red *= gamma;
2200 result.green *= gamma;
2201 result.blue *= gamma;
2202 result.opacity *= gamma;
2203 result.index *= gamma;
anthony602ab9b2010-01-05 08:06:50 +00002204 }
anthony5ef8e942010-05-11 06:51:12 +00002205 else
2206 {
2207 /* No 'Sync' flag, or no Alpha involved.
2208 ** Convolution is simple individual channel weigthed sum.
2209 */
2210 k = &kernel->values[ kernel->width*kernel->height-1 ];
2211 k_pixels = p;
2212 k_indexes = p_indexes;
2213 for (v=0; v < (long) kernel->height; v++) {
2214 for (u=0; u < (long) kernel->width; u++, k--) {
2215 if ( IsNan(*k) ) continue;
2216 result.red += (*k)*k_pixels[u].red;
2217 result.green += (*k)*k_pixels[u].green;
2218 result.blue += (*k)*k_pixels[u].blue;
2219 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
2220 if ( image->colorspace == CMYKColorspace)
2221 result.index += (*k)*k_indexes[u];
2222 }
2223 k_pixels += image->columns+kernel->width;
2224 k_indexes += image->columns+kernel->width;
2225 }
2226 }
anthony602ab9b2010-01-05 08:06:50 +00002227 break;
2228
anthony4fd27e22010-02-07 08:17:18 +00002229 case ErodeMorphology:
anthony5ef8e942010-05-11 06:51:12 +00002230 /* Minimum Value within kernel neighbourhood
anthony930be612010-02-08 04:26:15 +00002231 **
2232 ** NOTE that the kernel is not reflected for this operation!
2233 **
2234 ** NOTE: in normal Greyscale Morphology, the kernel value should
2235 ** be added to the real value, this is currently not done, due to
2236 ** the nature of the boolean kernels being used.
2237 */
anthony4fd27e22010-02-07 08:17:18 +00002238 k = kernel->values;
2239 k_pixels = p;
2240 k_indexes = p_indexes;
2241 for (v=0; v < (long) kernel->height; v++) {
2242 for (u=0; u < (long) kernel->width; u++, k++) {
2243 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony5ef8e942010-05-11 06:51:12 +00002244 Minimize(min.red, (double) k_pixels[u].red);
2245 Minimize(min.green, (double) k_pixels[u].green);
2246 Minimize(min.blue, (double) k_pixels[u].blue);
2247 Minimize(min.opacity,
anthonyd37a5cb2010-05-07 06:37:03 +00002248 QuantumRange-(double) k_pixels[u].opacity);
anthony4fd27e22010-02-07 08:17:18 +00002249 if ( image->colorspace == CMYKColorspace)
anthony5ef8e942010-05-11 06:51:12 +00002250 Minimize(min.index, (double) k_indexes[u]);
anthony4fd27e22010-02-07 08:17:18 +00002251 }
2252 k_pixels += image->columns+kernel->width;
2253 k_indexes += image->columns+kernel->width;
2254 }
2255 break;
2256
anthony5ef8e942010-05-11 06:51:12 +00002257
anthony83ba99b2010-01-24 08:48:15 +00002258 case DilateMorphology:
anthony5ef8e942010-05-11 06:51:12 +00002259 /* Maximum Value within kernel neighbourhood
anthony930be612010-02-08 04:26:15 +00002260 **
2261 ** NOTE for correct working of this operation for asymetrical
2262 ** kernels, the kernel needs to be applied in its reflected form.
2263 ** That is its values needs to be reversed.
2264 **
2265 ** NOTE: in normal Greyscale Morphology, the kernel value should
2266 ** be added to the real value, this is currently not done, due to
2267 ** the nature of the boolean kernels being used.
2268 **
2269 */
anthony29188a82010-01-22 10:12:34 +00002270 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00002271 k_pixels = p;
2272 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002273 for (v=0; v < (long) kernel->height; v++) {
2274 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00002275 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony5ef8e942010-05-11 06:51:12 +00002276 Maximize(max.red, (double) k_pixels[u].red);
2277 Maximize(max.green, (double) k_pixels[u].green);
2278 Maximize(max.blue, (double) k_pixels[u].blue);
2279 Maximize(max.opacity,
anthonyd37a5cb2010-05-07 06:37:03 +00002280 QuantumRange-(double) k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00002281 if ( image->colorspace == CMYKColorspace)
anthony5ef8e942010-05-11 06:51:12 +00002282 Maximize(max.index, (double) k_indexes[u]);
anthony602ab9b2010-01-05 08:06:50 +00002283 }
2284 k_pixels += image->columns+kernel->width;
2285 k_indexes += image->columns+kernel->width;
2286 }
anthony602ab9b2010-01-05 08:06:50 +00002287 break;
2288
anthony5ef8e942010-05-11 06:51:12 +00002289 case HitAndMissMorphology:
2290 case ThinningMorphology:
2291 case ThickenMorphology:
2292 /* Minimum of Foreground Pixel minus Maxumum of Background Pixels
2293 **
2294 ** NOTE that the kernel is not reflected for this operation,
2295 ** and consists of both foreground and background pixel
2296 ** neighbourhoods, 0.0 for background, and 1.0 for foreground
2297 ** with either Nan or 0.5 values for don't care.
2298 **
2299 ** Note that this can produce negative results, though really
2300 ** only a positive match has any real value.
2301 */
2302 k = kernel->values;
2303 k_pixels = p;
2304 k_indexes = p_indexes;
2305 for (v=0; v < (long) kernel->height; v++) {
2306 for (u=0; u < (long) kernel->width; u++, k++) {
2307 if ( IsNan(*k) ) continue;
2308 if ( (*k) > 0.7 )
2309 { /* minimim of foreground pixels */
2310 Minimize(min.red, (double) k_pixels[u].red);
2311 Minimize(min.green, (double) k_pixels[u].green);
2312 Minimize(min.blue, (double) k_pixels[u].blue);
2313 Minimize(min.opacity,
2314 QuantumRange-(double) k_pixels[u].opacity);
2315 if ( image->colorspace == CMYKColorspace)
2316 Minimize(min.index, (double) k_indexes[u]);
2317 }
2318 else if ( (*k) < 0.3 )
2319 { /* maximum of background pixels */
2320 Maximize(max.red, (double) k_pixels[u].red);
2321 Maximize(max.green, (double) k_pixels[u].green);
2322 Maximize(max.blue, (double) k_pixels[u].blue);
2323 Maximize(max.opacity,
2324 QuantumRange-(double) k_pixels[u].opacity);
2325 if ( image->colorspace == CMYKColorspace)
2326 Maximize(max.index, (double) k_indexes[u]);
2327 }
2328 }
2329 k_pixels += image->columns+kernel->width;
2330 k_indexes += image->columns+kernel->width;
2331 }
2332 /* Pattern Match only if min fg larger than min bg pixels */
2333 min.red -= max.red; Maximize( min.red, 0.0 );
2334 min.green -= max.green; Maximize( min.green, 0.0 );
2335 min.blue -= max.blue; Maximize( min.blue, 0.0 );
2336 min.opacity -= max.opacity; Maximize( min.opacity, 0.0 );
2337 min.index -= max.index; Maximize( min.index, 0.0 );
2338 break;
2339
anthony4fd27e22010-02-07 08:17:18 +00002340 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00002341 /* Select Pixel with Minimum Intensity within kernel neighbourhood
2342 **
2343 ** WARNING: the intensity test fails for CMYK and does not
2344 ** take into account the moderating effect of teh alpha channel
2345 ** on the intensity.
2346 **
2347 ** NOTE that the kernel is not reflected for this operation!
2348 */
anthony602ab9b2010-01-05 08:06:50 +00002349 k = kernel->values;
2350 k_pixels = p;
2351 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002352 for (v=0; v < (long) kernel->height; v++) {
2353 for (u=0; u < (long) kernel->width; u++, k++) {
anthony602ab9b2010-01-05 08:06:50 +00002354 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony4fd27e22010-02-07 08:17:18 +00002355 if ( result.red == 0.0 ||
2356 PixelIntensity(&(k_pixels[u])) < PixelIntensity(q) ) {
2357 /* copy the whole pixel - no channel selection */
2358 *q = k_pixels[u];
2359 if ( result.red > 0.0 ) changed++;
2360 result.red = 1.0;
2361 }
anthony602ab9b2010-01-05 08:06:50 +00002362 }
2363 k_pixels += image->columns+kernel->width;
2364 k_indexes += image->columns+kernel->width;
2365 }
anthony602ab9b2010-01-05 08:06:50 +00002366 break;
2367
anthony83ba99b2010-01-24 08:48:15 +00002368 case DilateIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00002369 /* Select Pixel with Maximum Intensity within kernel neighbourhood
2370 **
2371 ** WARNING: the intensity test fails for CMYK and does not
anthony9eb4f742010-05-18 02:45:54 +00002372 ** take into account the moderating effect of the alpha channel
2373 ** on the intensity (yet).
anthony930be612010-02-08 04:26:15 +00002374 **
2375 ** NOTE for correct working of this operation for asymetrical
2376 ** kernels, the kernel needs to be applied in its reflected form.
2377 ** That is its values needs to be reversed.
2378 */
anthony29188a82010-01-22 10:12:34 +00002379 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00002380 k_pixels = p;
2381 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002382 for (v=0; v < (long) kernel->height; v++) {
2383 for (u=0; u < (long) kernel->width; u++, k--) {
anthony29188a82010-01-22 10:12:34 +00002384 if ( IsNan(*k) || (*k) < 0.5 ) continue; /* boolean kernel */
2385 if ( result.red == 0.0 ||
2386 PixelIntensity(&(k_pixels[u])) > PixelIntensity(q) ) {
2387 /* copy the whole pixel - no channel selection */
2388 *q = k_pixels[u];
2389 if ( result.red > 0.0 ) changed++;
2390 result.red = 1.0;
2391 }
anthony602ab9b2010-01-05 08:06:50 +00002392 }
2393 k_pixels += image->columns+kernel->width;
2394 k_indexes += image->columns+kernel->width;
2395 }
anthony602ab9b2010-01-05 08:06:50 +00002396 break;
2397
anthony5ef8e942010-05-11 06:51:12 +00002398
anthony602ab9b2010-01-05 08:06:50 +00002399 case DistanceMorphology:
anthony930be612010-02-08 04:26:15 +00002400 /* Add kernel Value and select the minimum value found.
2401 ** The result is a iterative distance from edge of image shape.
2402 **
2403 ** All Distance Kernels are symetrical, but that may not always
2404 ** be the case. For example how about a distance from left edges?
2405 ** To work correctly with asymetrical kernels the reflected kernel
2406 ** needs to be applied.
anthony5ef8e942010-05-11 06:51:12 +00002407 **
2408 ** Actually this is really a GreyErode with a negative kernel!
2409 **
anthony930be612010-02-08 04:26:15 +00002410 */
anthony29188a82010-01-22 10:12:34 +00002411 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00002412 k_pixels = p;
2413 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002414 for (v=0; v < (long) kernel->height; v++) {
2415 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00002416 if ( IsNan(*k) ) continue;
2417 Minimize(result.red, (*k)+k_pixels[u].red);
2418 Minimize(result.green, (*k)+k_pixels[u].green);
2419 Minimize(result.blue, (*k)+k_pixels[u].blue);
2420 Minimize(result.opacity, (*k)+QuantumRange-k_pixels[u].opacity);
2421 if ( image->colorspace == CMYKColorspace)
2422 Minimize(result.index, (*k)+k_indexes[u]);
2423 }
2424 k_pixels += image->columns+kernel->width;
2425 k_indexes += image->columns+kernel->width;
2426 }
anthony602ab9b2010-01-05 08:06:50 +00002427 break;
2428
2429 case UndefinedMorphology:
2430 default:
2431 break; /* Do nothing */
anthony83ba99b2010-01-24 08:48:15 +00002432 }
anthony5ef8e942010-05-11 06:51:12 +00002433 /* Final mathematics of results (combine with original image?)
2434 **
2435 ** NOTE: Difference Morphology operators Edge* and *Hat could also
2436 ** be done here but works better with iteration as a image difference
2437 ** in the controling function (below). Thicken and Thinning however
2438 ** should be done here so thay can be iterated correctly.
2439 */
2440 switch ( method ) {
2441 case HitAndMissMorphology:
2442 case ErodeMorphology:
2443 result = min; /* minimum of neighbourhood */
2444 break;
2445 case DilateMorphology:
2446 result = max; /* maximum of neighbourhood */
2447 break;
2448 case ThinningMorphology:
2449 /* subtract pattern match from original */
2450 result.red -= min.red;
2451 result.green -= min.green;
2452 result.blue -= min.blue;
2453 result.opacity -= min.opacity;
2454 result.index -= min.index;
2455 break;
2456 case ThickenMorphology:
2457 /* Union with original image (maximize) - or should this be + */
2458 Maximize( result.red, min.red );
2459 Maximize( result.green, min.green );
2460 Maximize( result.blue, min.blue );
2461 Maximize( result.opacity, min.opacity );
2462 Maximize( result.index, min.index );
2463 break;
2464 default:
2465 /* result directly calculated or assigned */
2466 break;
2467 }
2468 /* Assign the resulting pixel values - Clamping Result */
anthony83ba99b2010-01-24 08:48:15 +00002469 switch ( method ) {
2470 case UndefinedMorphology:
2471 case DilateIntensityMorphology:
2472 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00002473 break; /* full pixel was directly assigned - not a channel method */
anthony83ba99b2010-01-24 08:48:15 +00002474 default:
anthony83ba99b2010-01-24 08:48:15 +00002475 if ((channel & RedChannel) != 0)
2476 q->red = ClampToQuantum(result.red);
2477 if ((channel & GreenChannel) != 0)
2478 q->green = ClampToQuantum(result.green);
2479 if ((channel & BlueChannel) != 0)
2480 q->blue = ClampToQuantum(result.blue);
2481 if ((channel & OpacityChannel) != 0
2482 && image->matte == MagickTrue )
2483 q->opacity = ClampToQuantum(QuantumRange-result.opacity);
2484 if ((channel & IndexChannel) != 0
2485 && image->colorspace == CMYKColorspace)
2486 q_indexes[x] = ClampToQuantum(result.index);
2487 break;
2488 }
anthony5ef8e942010-05-11 06:51:12 +00002489 /* Count up changed pixels */
anthony83ba99b2010-01-24 08:48:15 +00002490 if ( ( p[r].red != q->red )
2491 || ( p[r].green != q->green )
2492 || ( p[r].blue != q->blue )
2493 || ( p[r].opacity != q->opacity )
2494 || ( image->colorspace == CMYKColorspace &&
2495 p_indexes[r] != q_indexes[x] ) )
2496 changed++; /* The pixel had some value changed! */
anthony602ab9b2010-01-05 08:06:50 +00002497 p++;
2498 q++;
anthony83ba99b2010-01-24 08:48:15 +00002499 } /* x */
anthony602ab9b2010-01-05 08:06:50 +00002500 sync=SyncCacheViewAuthenticPixels(q_view,exception);
2501 if (sync == MagickFalse)
2502 status=MagickFalse;
2503 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2504 {
2505 MagickBooleanType
2506 proceed;
2507
2508#if defined(MAGICKCORE_OPENMP_SUPPORT)
2509 #pragma omp critical (MagickCore_MorphologyImage)
2510#endif
2511 proceed=SetImageProgress(image,MorphologyTag,progress++,image->rows);
2512 if (proceed == MagickFalse)
2513 status=MagickFalse;
2514 }
anthony83ba99b2010-01-24 08:48:15 +00002515 } /* y */
anthony602ab9b2010-01-05 08:06:50 +00002516 result_image->type=image->type;
2517 q_view=DestroyCacheView(q_view);
2518 p_view=DestroyCacheView(p_view);
cristy150989e2010-02-01 14:59:39 +00002519 return(status ? (unsigned long) changed : 0);
anthony602ab9b2010-01-05 08:06:50 +00002520}
2521
anthony4fd27e22010-02-07 08:17:18 +00002522
anthony9eb4f742010-05-18 02:45:54 +00002523MagickExport Image *MorphologyApply(const Image *image, const ChannelType
2524 channel,const MorphologyMethod method, const long iterations,
anthony47f5d062010-05-23 07:47:50 +00002525 const KernelInfo *kernel, const CompositeOperator compose,
2526 const double bias, ExceptionInfo *exception)
cristy2be15382010-01-21 02:38:03 +00002527{
2528 Image
anthony47f5d062010-05-23 07:47:50 +00002529 *curr_image, /* Image we are working with or iterating */
2530 *work_image, /* secondary image for primative iteration */
2531 *save_image, /* saved image - for 'edge' method only */
2532 *rslt_image; /* resultant image - after multi-kernel handling */
anthony602ab9b2010-01-05 08:06:50 +00002533
anthony4fd27e22010-02-07 08:17:18 +00002534 KernelInfo
anthony47f5d062010-05-23 07:47:50 +00002535 *reflected_kernel, /* A reflected copy of the kernel (if needed) */
2536 *norm_kernel, /* the current normal un-reflected kernel */
2537 *rflt_kernel, /* the current reflected kernel (if needed) */
2538 *this_kernel; /* the kernel being applied */
anthony4fd27e22010-02-07 08:17:18 +00002539
2540 MorphologyMethod
anthony47f5d062010-05-23 07:47:50 +00002541 primative; /* the current morphology primative being applied */
anthony9eb4f742010-05-18 02:45:54 +00002542
2543 CompositeOperator
anthony47f5d062010-05-23 07:47:50 +00002544 rslt_compose; /* multi-kernel compose method for results to use */
2545
2546 MagickBooleanType
2547 verbose; /* verbose output of results */
anthony4fd27e22010-02-07 08:17:18 +00002548
anthony1b2bc0a2010-05-12 05:25:22 +00002549 unsigned long
anthony47f5d062010-05-23 07:47:50 +00002550 method_loop, /* Loop 1: number of compound method iterations */
2551 method_limit, /* maximum number of compound method iterations */
2552 kernel_number, /* Loop 2: the kernel number being applied */
2553 stage_loop, /* Loop 3: primative loop for compound morphology */
2554 stage_limit, /* how many primatives in this compound */
2555 kernel_loop, /* Loop 4: iterate the kernel (basic morphology) */
2556 kernel_limit, /* number of times to iterate kernel */
2557 count, /* total count of primative steps applied */
2558 changed, /* number pixels changed by last primative operation */
2559 kernel_changed, /* total count of changed using iterated kernel */
2560 method_changed; /* total count of changed over method iteration */
2561
2562 char
2563 v_info[80];
anthony1b2bc0a2010-05-12 05:25:22 +00002564
anthony602ab9b2010-01-05 08:06:50 +00002565 assert(image != (Image *) NULL);
2566 assert(image->signature == MagickSignature);
anthony4fd27e22010-02-07 08:17:18 +00002567 assert(kernel != (KernelInfo *) NULL);
2568 assert(kernel->signature == MagickSignature);
anthony602ab9b2010-01-05 08:06:50 +00002569 assert(exception != (ExceptionInfo *) NULL);
2570 assert(exception->signature == MagickSignature);
2571
anthony602ab9b2010-01-05 08:06:50 +00002572 if ( iterations == 0 )
anthony47f5d062010-05-23 07:47:50 +00002573 return((Image *)NULL); /* null operation - nothing to do! */
anthony602ab9b2010-01-05 08:06:50 +00002574
anthony47f5d062010-05-23 07:47:50 +00002575 kernel_limit = (unsigned long) iterations;
2576 if ( iterations < 0 ) /* negative interations = infinite (well alomst) */
2577 kernel_limit = image->columns > image->rows ? image->columns : image->rows;
anthony602ab9b2010-01-05 08:06:50 +00002578
cristye96405a2010-05-19 02:24:31 +00002579 verbose = ( GetImageArtifact(image,"verbose") != (const char *) NULL ) ?
2580 MagickTrue : MagickFalse;
anthony4f1dcb72010-05-14 08:43:10 +00002581
anthony9eb4f742010-05-18 02:45:54 +00002582 /* initialise for cleanup */
anthony47f5d062010-05-23 07:47:50 +00002583 curr_image = (Image *) image;
2584 work_image = save_image = rslt_image = (Image *) NULL;
2585 reflected_kernel = (KernelInfo *) NULL;
anthony4fd27e22010-02-07 08:17:18 +00002586
anthony602ab9b2010-01-05 08:06:50 +00002587
anthony47f5d062010-05-23 07:47:50 +00002588 count = 0; /* number of low-level morphology primatives performed */
2589
2590 /* Initialize specific methods
2591 * + which loop should use the given iteratations
2592 * + how many primatives make up the compound morphology
2593 * + multi-kernel compose method to use (by default)
2594 */
2595 method_limit = 1; /* just do method once, unless otherwise set */
2596 stage_limit = 1; /* assume method is not a compount */
2597 rslt_compose = compose; /* and we are composing multi-kernels as given */
anthony9eb4f742010-05-18 02:45:54 +00002598 switch( method ) {
anthony47f5d062010-05-23 07:47:50 +00002599 case SmoothMorphology: /* 4 primative compound morphology */
2600 stage_limit = 4;
anthony9eb4f742010-05-18 02:45:54 +00002601 break;
anthony47f5d062010-05-23 07:47:50 +00002602 case OpenMorphology: /* 2 primative compound morphology */
anthony9eb4f742010-05-18 02:45:54 +00002603 case OpenIntensityMorphology:
anthony47f5d062010-05-23 07:47:50 +00002604 case TopHatMorphology:
2605 case CloseMorphology:
anthony9eb4f742010-05-18 02:45:54 +00002606 case CloseIntensityMorphology:
anthony47f5d062010-05-23 07:47:50 +00002607 case BottomHatMorphology:
2608 case EdgeMorphology:
2609 stage_limit = 2;
anthony9eb4f742010-05-18 02:45:54 +00002610 break;
2611 case HitAndMissMorphology:
anthony47f5d062010-05-23 07:47:50 +00002612 kernel_limit = 1; /* Only apply each kernel once to image */
2613 rslt_compose = LightenCompositeOp; /* Union of multi-kernel results */
anthony9eb4f742010-05-18 02:45:54 +00002614 break;
anthony47f5d062010-05-23 07:47:50 +00002615 case ThinningMorphology: /* don't interate each kernel, iterate method */
anthony9eb4f742010-05-18 02:45:54 +00002616 case ThickenMorphology:
anthony47f5d062010-05-23 07:47:50 +00002617 method_limit = kernel_limit; /* iterate method with each kernel */
2618 kernel_limit = 1; /* do not do kernel iteration */
2619 break;
2620 default:
anthony930be612010-02-08 04:26:15 +00002621 break;
anthony602ab9b2010-01-05 08:06:50 +00002622 }
2623
anthony47f5d062010-05-23 07:47:50 +00002624 if ( compose != UndefinedCompositeOp )
2625 rslt_compose = compose; /* override default composition for method */
2626 if ( rslt_compose == UndefinedCompositeOp )
2627 rslt_compose = NoCompositeOp; /* still not defined! Then re-iterate */
2628
2629 /* Some methods require a refltected kernel to use with primatives
2630 * create the reflected kernel for the methods that need it */
2631 switch ( method ) {
2632 case CorrelateMorphology:
2633 case CloseMorphology:
2634 case CloseIntensityMorphology:
2635 case BottomHatMorphology:
2636 case SmoothMorphology:
2637 reflected_kernel = CloneKernelInfo(kernel);
2638 if (reflected_kernel == (KernelInfo *) NULL)
2639 goto error_cleanup;
2640 RotateKernelInfo(reflected_kernel,180);
2641 break;
2642 default:
2643 break;
anthony9eb4f742010-05-18 02:45:54 +00002644 }
anthony7a01dcf2010-05-11 12:25:52 +00002645
anthony47f5d062010-05-23 07:47:50 +00002646 /* Loop 1: iterate the compound method */
2647 method_loop = 0;
2648 method_changed = 1;
2649 while ( method_loop < method_limit && method_changed > 0 ) {
2650 method_loop++;
2651 method_changed = 0;
anthony9eb4f742010-05-18 02:45:54 +00002652
anthony47f5d062010-05-23 07:47:50 +00002653 /* Loop 2: iterate over each kernel in a multi-kernel list */
2654 norm_kernel = (KernelInfo *) kernel;
2655 rflt_kernel = reflected_kernel;
2656 kernel_number = 0;
2657 while ( norm_kernel != NULL ) {
anthony9eb4f742010-05-18 02:45:54 +00002658
anthony47f5d062010-05-23 07:47:50 +00002659 /* Loop 3: Compound Morphology Staging - Select Primative to apply */
2660 stage_loop = 0; /* the compound morphology stage number */
2661 while ( stage_loop < stage_limit ) {
2662 stage_loop++; /* The stage of the compound morphology */
anthony9eb4f742010-05-18 02:45:54 +00002663
anthony47f5d062010-05-23 07:47:50 +00002664 /* Select primative morphology for this stage of compound method */
2665 this_kernel = norm_kernel; /* default use unreflected kernel */
2666 switch( method ) {
2667 case ErodeMorphology: /* just erode */
2668 case EdgeInMorphology: /* erode and image difference */
2669 primative = ErodeMorphology;
2670 break;
2671 case DilateMorphology: /* just dilate */
2672 case EdgeOutMorphology: /* dilate and image difference */
2673 primative = DilateMorphology;
2674 break;
2675 case OpenMorphology: /* erode then dialate */
2676 case TopHatMorphology: /* open and image difference */
2677 primative = ErodeMorphology;
2678 if ( stage_loop == 2 )
2679 primative = DilateMorphology;
2680 break;
2681 case OpenIntensityMorphology:
2682 primative = ErodeIntensityMorphology;
2683 if ( stage_loop == 2 )
2684 primative = DilateIntensityMorphology;
2685 case CloseMorphology: /* dilate, then erode */
2686 case BottomHatMorphology: /* close and image difference */
2687 this_kernel = rflt_kernel; /* use the reflected kernel */
2688 primative = DilateMorphology;
2689 if ( stage_loop == 2 )
2690 primative = ErodeMorphology;
2691 break;
2692 case CloseIntensityMorphology:
2693 this_kernel = rflt_kernel; /* use the reflected kernel */
2694 primative = DilateIntensityMorphology;
2695 if ( stage_loop == 2 )
2696 primative = ErodeIntensityMorphology;
2697 break;
2698 case SmoothMorphology: /* open, close */
2699 switch ( stage_loop ) {
2700 case 1: /* start an open method, which starts with Erode */
2701 primative = ErodeMorphology;
2702 break;
2703 case 2: /* now Dilate the Erode */
2704 primative = DilateMorphology;
2705 break;
2706 case 3: /* Reflect kernel a close */
2707 this_kernel = rflt_kernel; /* use the reflected kernel */
2708 primative = DilateMorphology;
2709 break;
2710 case 4: /* Finish the Close */
2711 this_kernel = rflt_kernel; /* use the reflected kernel */
2712 primative = ErodeMorphology;
2713 break;
2714 }
2715 break;
2716 case EdgeMorphology: /* dilate and erode difference */
2717 primative = DilateMorphology;
2718 if ( stage_loop == 2 ) {
2719 save_image = curr_image; /* save the image difference */
2720 curr_image = (Image *) image;
2721 primative = ErodeMorphology;
2722 }
2723 break;
2724 case CorrelateMorphology:
2725 /* A Correlation is a Convolution with a reflected kernel.
2726 ** However a Convolution is a weighted sum using a reflected
2727 ** kernel. It may seem stange to convert a Correlation into a
2728 ** Convolution as the Correlation is the simplier method, but
2729 ** Convolution is much more commonly used, and it makes sense to
2730 ** implement it directly so as to avoid the need to duplicate the
2731 ** kernel when it is not required (which is typically the
2732 ** default).
2733 */
2734 this_kernel = rflt_kernel; /* use the reflected kernel */
2735 primative = ConvolveMorphology;
2736 break;
2737 default:
2738 primative = method; /* method is a primative */
2739 break;
2740 }
anthony9eb4f742010-05-18 02:45:54 +00002741
anthony47f5d062010-05-23 07:47:50 +00002742 /* Extra information for debugging compound operations */
2743 if ( verbose == MagickTrue ) {
2744 if ( stage_limit > 1 )
cristydc1c30b2010-05-23 14:23:12 +00002745 (void) FormatMagickString(v_info, MaxTextExtent, "%s:%lu.%lu -> ",
anthony47f5d062010-05-23 07:47:50 +00002746 MagickOptionToMnemonic(MagickMorphologyOptions, method),
2747 method_loop, stage_loop );
2748 else if ( primative != method )
cristydc1c30b2010-05-23 14:23:12 +00002749 (void) FormatMagickString(v_info, MaxTextExtent, "%s:%lu -> ",
anthony47f5d062010-05-23 07:47:50 +00002750 MagickOptionToMnemonic(MagickMorphologyOptions, method),
2751 method_loop );
2752 else
2753 v_info[0] = '\0';
2754 }
2755
2756 /* Loop 4: Iterate the kernel with primative */
2757 kernel_loop = 0;
2758 kernel_changed = 0;
2759 changed = 1;
2760 while ( kernel_loop < kernel_limit && changed > 0 ) {
2761 kernel_loop++; /* the iteration of this kernel */
anthony9eb4f742010-05-18 02:45:54 +00002762
2763 /* Create a destination image, if not yet defined */
2764 if ( work_image == (Image *) NULL )
2765 {
2766 work_image=CloneImage(image,0,0,MagickTrue,exception);
2767 if (work_image == (Image *) NULL)
2768 goto error_cleanup;
2769 if (SetImageStorageClass(work_image,DirectClass) == MagickFalse)
2770 {
2771 InheritException(exception,&work_image->exception);
2772 goto error_cleanup;
2773 }
2774 }
2775
anthony47f5d062010-05-23 07:47:50 +00002776 /* APPLY THE MORPHOLOGICAL PRIMITIVE (curr -> work) */
anthony9eb4f742010-05-18 02:45:54 +00002777 count++;
anthony47f5d062010-05-23 07:47:50 +00002778 changed = MorphologyPrimitive(curr_image, work_image, primative,
anthony9eb4f742010-05-18 02:45:54 +00002779 channel, this_kernel, bias, exception);
anthony47f5d062010-05-23 07:47:50 +00002780 kernel_changed += changed;
2781 method_changed += changed;
anthony9eb4f742010-05-18 02:45:54 +00002782
anthony47f5d062010-05-23 07:47:50 +00002783 if ( verbose == MagickTrue ) {
2784 if ( kernel_loop > 1 )
2785 fprintf(stderr, "\n"); /* add end-of-line from previous */
2786 fprintf(stderr, "%s%s%s:%lu.%lu #%lu => Changed %lu", v_info,
2787 MagickOptionToMnemonic(MagickMorphologyOptions, primative),
2788 ( this_kernel == rflt_kernel ) ? "*" : "",
2789 method_loop+kernel_loop-1, kernel_number, count, changed);
2790 }
anthony9eb4f742010-05-18 02:45:54 +00002791 /* prepare next loop */
2792 { Image *tmp = work_image; /* swap images for iteration */
2793 work_image = curr_image;
2794 curr_image = tmp;
2795 }
2796 if ( work_image == image )
anthony47f5d062010-05-23 07:47:50 +00002797 work_image = (Image *) NULL; /* replace input 'image' */
anthony7a01dcf2010-05-11 12:25:52 +00002798
anthony47f5d062010-05-23 07:47:50 +00002799 } /* End Loop 4: Iterate the kernel with primative */
anthony1b2bc0a2010-05-12 05:25:22 +00002800
anthony47f5d062010-05-23 07:47:50 +00002801 if ( verbose == MagickTrue && kernel_changed != changed )
2802 fprintf(stderr, " Total %lu", kernel_changed);
2803 if ( verbose == MagickTrue && stage_loop < stage_limit )
2804 fprintf(stderr, "\n"); /* add end-of-line before looping */
anthony9eb4f742010-05-18 02:45:54 +00002805
2806#if 0
anthony47f5d062010-05-23 07:47:50 +00002807 fprintf(stderr, "--E-- image=0x%lx\n", (unsigned long)image);
2808 fprintf(stderr, " curr =0x%lx\n", (unsigned long)curr_image);
2809 fprintf(stderr, " work =0x%lx\n", (unsigned long)work_image);
2810 fprintf(stderr, " save =0x%lx\n", (unsigned long)save_image);
2811 fprintf(stderr, " union=0x%lx\n", (unsigned long)rslt_image);
anthony9eb4f742010-05-18 02:45:54 +00002812#endif
2813
anthony47f5d062010-05-23 07:47:50 +00002814 } /* End Loop 3: Primative (staging) Loop for Coumpound Methods */
anthony9eb4f742010-05-18 02:45:54 +00002815
anthony47f5d062010-05-23 07:47:50 +00002816 /* Final Post-processing for some Compound Methods
2817 **
2818 ** The removal of any 'Sync' channel flag in the Image Compositon
2819 ** below ensures the methematical compose method is applied in a
2820 ** purely mathematical way, and only to the selected channels.
2821 ** Turn off SVG composition 'alpha blending'.
2822 */
2823 switch( method ) {
2824 case EdgeOutMorphology:
2825 case EdgeInMorphology:
2826 case TopHatMorphology:
2827 case BottomHatMorphology:
2828 if ( verbose == MagickTrue )
2829 fprintf(stderr, "\n%s: Difference with original image",
2830 MagickOptionToMnemonic(MagickMorphologyOptions, method) );
2831 (void) CompositeImageChannel(curr_image,
2832 (ChannelType) (channel & ~SyncChannels),
2833 DifferenceCompositeOp, image, 0, 0);
2834 break;
2835 case EdgeMorphology:
2836 if ( verbose == MagickTrue )
2837 fprintf(stderr, "\n%s: Difference of Dilate and Erode",
2838 MagickOptionToMnemonic(MagickMorphologyOptions, method) );
2839 (void) CompositeImageChannel(curr_image,
2840 (ChannelType) (channel & ~SyncChannels),
2841 DifferenceCompositeOp, save_image, 0, 0);
2842 save_image = DestroyImage(save_image); /* finished with save image */
2843 break;
2844 default:
2845 break;
2846 }
2847
2848 /* multi-kernel handling: re-iterate, or compose results */
2849 if ( kernel->next == (KernelInfo *) NULL )
2850 rslt_image = curr_image; /* not multi-kernel nothing to do */
2851 else if ( rslt_compose == NoCompositeOp )
2852 { if ( verbose == MagickTrue )
2853 fprintf(stderr, " (re-iterate)");
2854 rslt_image = curr_image;
2855 /* original image is no longer actually needed! */
anthony9eb4f742010-05-18 02:45:54 +00002856 }
anthony47f5d062010-05-23 07:47:50 +00002857 else if ( rslt_image == (Image *) NULL)
2858 { if ( verbose == MagickTrue )
2859 fprintf(stderr, " (save for compose)");
2860 rslt_image = curr_image;
2861 curr_image = (Image *) image; /* continue with original image */
anthony9eb4f742010-05-18 02:45:54 +00002862 }
anthony47f5d062010-05-23 07:47:50 +00002863 else
2864 { /* add the new 'current' result to the composition
2865 **
2866 ** The removal of any 'Sync' channel flag in the Image Compositon
2867 ** below ensures the methematical compose method is applied in a
2868 ** purely mathematical way, and only to the selected channels.
2869 ** Turn off SVG composition 'alpha blending'.
2870 */
2871 if ( verbose == MagickTrue )
2872 fprintf(stderr, " (compose \"%s\")",
2873 MagickOptionToMnemonic(MagickComposeOptions, rslt_compose) );
2874 (void) CompositeImageChannel(rslt_image,
2875 (ChannelType) (channel & ~SyncChannels), rslt_compose,
2876 curr_image, 0, 0);
2877 curr_image = (Image *) image; /* continue with original image */
2878 }
2879 if ( verbose == MagickTrue )
2880 fprintf(stderr, "\n");
anthony9eb4f742010-05-18 02:45:54 +00002881
anthony47f5d062010-05-23 07:47:50 +00002882 /* loop to the next kernel in a multi-kernel list */
2883 norm_kernel = norm_kernel->next;
2884 if ( rflt_kernel != (KernelInfo *) NULL )
2885 rflt_kernel = rflt_kernel->next;
2886 kernel_number++;
2887 } /* End Loop 2: Loop over each kernel */
anthony9eb4f742010-05-18 02:45:54 +00002888
anthony47f5d062010-05-23 07:47:50 +00002889 } /* End Loop 1: compound method interation */
anthony602ab9b2010-01-05 08:06:50 +00002890
anthony9eb4f742010-05-18 02:45:54 +00002891 goto exit_cleanup;
anthony1b2bc0a2010-05-12 05:25:22 +00002892
anthony47f5d062010-05-23 07:47:50 +00002893 /* Yes goto's are bad, but it makes cleanup lot more efficient */
anthony1b2bc0a2010-05-12 05:25:22 +00002894error_cleanup:
anthony47f5d062010-05-23 07:47:50 +00002895 if ( curr_image != (Image *) NULL &&
2896 curr_image != rslt_image &&
2897 curr_image != image )
2898 curr_image = DestroyImage(curr_image);
2899 if ( rslt_image != (Image *) NULL )
2900 rslt_image = DestroyImage(rslt_image);
anthony1b2bc0a2010-05-12 05:25:22 +00002901exit_cleanup:
anthony47f5d062010-05-23 07:47:50 +00002902 if ( curr_image != (Image *) NULL &&
2903 curr_image != rslt_image &&
2904 curr_image != image )
2905 curr_image = DestroyImage(curr_image);
anthony9eb4f742010-05-18 02:45:54 +00002906 if ( work_image != (Image *) NULL )
anthony47f5d062010-05-23 07:47:50 +00002907 work_image = DestroyImage(work_image);
anthony9eb4f742010-05-18 02:45:54 +00002908 if ( save_image != (Image *) NULL )
anthony47f5d062010-05-23 07:47:50 +00002909 save_image = DestroyImage(save_image);
2910 if ( reflected_kernel != (KernelInfo *) NULL )
2911 reflected_kernel = DestroyKernelInfo(reflected_kernel);
2912 return(rslt_image);
anthony9eb4f742010-05-18 02:45:54 +00002913}
2914
2915/*
2916%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2917% %
2918% %
2919% %
2920% M o r p h o l o g y I m a g e C h a n n e l %
2921% %
2922% %
2923% %
2924%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2925%
2926% MorphologyImageChannel() applies a user supplied kernel to the image
2927% according to the given mophology method.
2928%
2929% This function applies any and all user defined settings before calling
2930% the above internal function MorphologyApply().
2931%
2932% User defined settings include...
anthony46a369d2010-05-19 02:41:48 +00002933% * Output Bias for Convolution and correlation ("-bias")
2934% * Kernel Scale/normalize settings ("-set 'option:convolve:scale'")
2935% This can also includes the addition of a scaled unity kernel.
2936% * Show Kernel being applied ("-set option:showkernel 1")
anthony9eb4f742010-05-18 02:45:54 +00002937%
2938% The format of the MorphologyImage method is:
2939%
2940% Image *MorphologyImage(const Image *image,MorphologyMethod method,
2941% const long iterations,KernelInfo *kernel,ExceptionInfo *exception)
2942%
2943% Image *MorphologyImageChannel(const Image *image, const ChannelType
2944% channel,MorphologyMethod method,const long iterations,
2945% KernelInfo *kernel,ExceptionInfo *exception)
2946%
2947% A description of each parameter follows:
2948%
2949% o image: the image.
2950%
2951% o method: the morphology method to be applied.
2952%
2953% o iterations: apply the operation this many times (or no change).
2954% A value of -1 means loop until no change found.
2955% How this is applied may depend on the morphology method.
2956% Typically this is a value of 1.
2957%
2958% o channel: the channel type.
2959%
2960% o kernel: An array of double representing the morphology kernel.
2961% Warning: kernel may be normalized for the Convolve method.
2962%
2963% o exception: return any errors or warnings in this structure.
2964%
2965*/
2966
2967MagickExport Image *MorphologyImageChannel(const Image *image,
2968 const ChannelType channel,const MorphologyMethod method,
2969 const long iterations,const KernelInfo *kernel,ExceptionInfo *exception)
2970{
2971 const char
2972 *artifact;
2973
2974 KernelInfo
2975 *curr_kernel;
2976
anthony47f5d062010-05-23 07:47:50 +00002977 CompositeOperator
2978 compose;
2979
anthony9eb4f742010-05-18 02:45:54 +00002980 Image
2981 *morphology_image;
2982
2983
anthony46a369d2010-05-19 02:41:48 +00002984 /* Apply Convolve/Correlate Normalization and Scaling Factors.
2985 * This is done BEFORE the ShowKernelInfo() function is called so that
2986 * users can see the results of the 'option:convolve:scale' option.
anthony9eb4f742010-05-18 02:45:54 +00002987 */
2988 curr_kernel = (KernelInfo *) kernel;
anthonyf71ca292010-05-19 04:08:43 +00002989 if ( method == ConvolveMorphology || method == CorrelateMorphology )
anthony9eb4f742010-05-18 02:45:54 +00002990 {
2991 artifact = GetImageArtifact(image,"convolve:scale");
2992 if ( artifact != (char *)NULL ) {
anthony9eb4f742010-05-18 02:45:54 +00002993 if ( curr_kernel == kernel )
2994 curr_kernel = CloneKernelInfo(kernel);
2995 if (curr_kernel == (KernelInfo *) NULL) {
2996 curr_kernel=DestroyKernelInfo(curr_kernel);
2997 return((Image *) NULL);
2998 }
anthony46a369d2010-05-19 02:41:48 +00002999 ScaleGeometryKernelInfo(curr_kernel, artifact);
anthony9eb4f742010-05-18 02:45:54 +00003000 }
3001 }
3002
3003 /* display the (normalized) kernel via stderr */
3004 artifact = GetImageArtifact(image,"showkernel");
anthony47f5d062010-05-23 07:47:50 +00003005 if ( artifact == (const char *) NULL)
3006 artifact = GetImageArtifact(image,"convolve:showkernel");
3007 if ( artifact == (const char *) NULL)
3008 artifact = GetImageArtifact(image,"morphology:showkernel");
anthony9eb4f742010-05-18 02:45:54 +00003009 if ( artifact != (const char *) NULL)
3010 ShowKernelInfo(curr_kernel);
3011
anthony47f5d062010-05-23 07:47:50 +00003012 /* override the default handling of multi-kernel morphology results
3013 * if 'Undefined' use the default method
3014 * if 'None' (default for 'Convolve') re-iterate previous result
3015 * otherwise merge resulting images using compose method given
3016 */
3017 compose = UndefinedCompositeOp; /* use default for method */
3018 artifact = GetImageArtifact(image,"morphology:compose");
3019 if ( artifact != (const char *) NULL)
3020 compose = (CompositeOperator) ParseMagickOption(
3021 MagickComposeOptions,MagickFalse,artifact);
3022
anthony9eb4f742010-05-18 02:45:54 +00003023 /* Apply the Morphology */
3024 morphology_image = MorphologyApply(image, channel, method, iterations,
anthony47f5d062010-05-23 07:47:50 +00003025 curr_kernel, compose, image->bias, exception);
anthony9eb4f742010-05-18 02:45:54 +00003026
3027 /* Cleanup and Exit */
3028 if ( curr_kernel != kernel )
anthony1b2bc0a2010-05-12 05:25:22 +00003029 curr_kernel=DestroyKernelInfo(curr_kernel);
anthony9eb4f742010-05-18 02:45:54 +00003030 return(morphology_image);
3031}
3032
3033MagickExport Image *MorphologyImage(const Image *image, const MorphologyMethod
3034 method, const long iterations,const KernelInfo *kernel, ExceptionInfo
3035 *exception)
3036{
3037 Image
3038 *morphology_image;
3039
3040 morphology_image=MorphologyImageChannel(image,DefaultChannels,method,
3041 iterations,kernel,exception);
3042 return(morphology_image);
anthony602ab9b2010-01-05 08:06:50 +00003043}
anthony83ba99b2010-01-24 08:48:15 +00003044
3045/*
3046%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3047% %
3048% %
3049% %
anthony4fd27e22010-02-07 08:17:18 +00003050+ R o t a t e K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00003051% %
3052% %
3053% %
3054%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3055%
anthony46a369d2010-05-19 02:41:48 +00003056% RotateKernelInfo() rotates the kernel by the angle given.
3057%
3058% Currently it is restricted to 90 degree angles, of either 1D kernels
3059% or square kernels. And 'circular' rotations of 45 degrees for 3x3 kernels.
3060% It will ignore usless rotations for specific 'named' built-in kernels.
anthony83ba99b2010-01-24 08:48:15 +00003061%
anthony4fd27e22010-02-07 08:17:18 +00003062% The format of the RotateKernelInfo method is:
anthony83ba99b2010-01-24 08:48:15 +00003063%
anthony4fd27e22010-02-07 08:17:18 +00003064% void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00003065%
3066% A description of each parameter follows:
3067%
3068% o kernel: the Morphology/Convolution kernel
3069%
3070% o angle: angle to rotate in degrees
3071%
anthony46a369d2010-05-19 02:41:48 +00003072% This function is currently internal to this module only, but can be exported
3073% to other modules if needed.
anthony83ba99b2010-01-24 08:48:15 +00003074*/
anthony4fd27e22010-02-07 08:17:18 +00003075static void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00003076{
anthony1b2bc0a2010-05-12 05:25:22 +00003077 /* angle the lower kernels first */
3078 if ( kernel->next != (KernelInfo *) NULL)
3079 RotateKernelInfo(kernel->next, angle);
3080
anthony83ba99b2010-01-24 08:48:15 +00003081 /* WARNING: Currently assumes the kernel (rightly) is horizontally symetrical
3082 **
3083 ** TODO: expand beyond simple 90 degree rotates, flips and flops
3084 */
3085
3086 /* Modulus the angle */
3087 angle = fmod(angle, 360.0);
3088 if ( angle < 0 )
3089 angle += 360.0;
3090
anthony3c10fc82010-05-13 02:40:51 +00003091 if ( 337.5 < angle || angle <= 22.5 )
anthony43c49252010-05-18 10:59:50 +00003092 return; /* Near zero angle - no change! - At least not at this time */
anthony83ba99b2010-01-24 08:48:15 +00003093
anthony3dd0f622010-05-13 12:57:32 +00003094 /* Handle special cases */
anthony83ba99b2010-01-24 08:48:15 +00003095 switch (kernel->type) {
3096 /* These built-in kernels are cylindrical kernels, rotating is useless */
3097 case GaussianKernel:
anthony83ba99b2010-01-24 08:48:15 +00003098 case DOGKernel:
3099 case DiskKernel:
anthony3dd0f622010-05-13 12:57:32 +00003100 case PeaksKernel:
3101 case LaplacianKernel:
anthony83ba99b2010-01-24 08:48:15 +00003102 case ChebyshevKernel:
3103 case ManhattenKernel:
3104 case EuclideanKernel:
3105 return;
3106
3107 /* These may be rotatable at non-90 angles in the future */
3108 /* but simply rotating them in multiples of 90 degrees is useless */
3109 case SquareKernel:
3110 case DiamondKernel:
3111 case PlusKernel:
anthony3dd0f622010-05-13 12:57:32 +00003112 case CrossKernel:
anthony83ba99b2010-01-24 08:48:15 +00003113 return;
3114
3115 /* These only allows a +/-90 degree rotation (by transpose) */
3116 /* A 180 degree rotation is useless */
3117 case BlurKernel:
3118 case RectangleKernel:
3119 if ( 135.0 < angle && angle <= 225.0 )
3120 return;
3121 if ( 225.0 < angle && angle <= 315.0 )
3122 angle -= 180;
3123 break;
3124
anthony3dd0f622010-05-13 12:57:32 +00003125 default:
anthony83ba99b2010-01-24 08:48:15 +00003126 break;
3127 }
anthony3c10fc82010-05-13 02:40:51 +00003128 /* Attempt rotations by 45 degrees */
3129 if ( 22.5 < fmod(angle,90.0) && fmod(angle,90.0) <= 67.5 )
3130 {
3131 if ( kernel->width == 3 && kernel->height == 3 )
3132 { /* Rotate a 3x3 square by 45 degree angle */
3133 MagickRealType t = kernel->values[0];
anthony43c49252010-05-18 10:59:50 +00003134 kernel->values[0] = kernel->values[3];
3135 kernel->values[3] = kernel->values[6];
3136 kernel->values[6] = kernel->values[7];
3137 kernel->values[7] = kernel->values[8];
3138 kernel->values[8] = kernel->values[5];
3139 kernel->values[5] = kernel->values[2];
3140 kernel->values[2] = kernel->values[1];
3141 kernel->values[1] = t;
3142 /* NOT DONE - rotate a off-centered origin as well! */
3143 angle = fmod(angle+315.0, 360.0); /* angle reduced 45 degrees */
3144 kernel->angle = fmod(kernel->angle+45.0, 360.0);
anthony3c10fc82010-05-13 02:40:51 +00003145 }
3146 else
3147 perror("Unable to rotate non-3x3 kernel by 45 degrees");
3148 }
3149 if ( 45.0 < fmod(angle, 180.0) && fmod(angle,180.0) <= 135.0 )
3150 {
3151 if ( kernel->width == 1 || kernel->height == 1 )
3152 { /* Do a transpose of the image, which results in a 90
3153 ** degree rotation of a 1 dimentional kernel
3154 */
3155 long
3156 t;
3157 t = (long) kernel->width;
3158 kernel->width = kernel->height;
3159 kernel->height = (unsigned long) t;
3160 t = kernel->x;
3161 kernel->x = kernel->y;
3162 kernel->y = t;
anthony43c49252010-05-18 10:59:50 +00003163 if ( kernel->width == 1 ) {
3164 angle = fmod(angle+270.0, 360.0); /* angle reduced 90 degrees */
3165 kernel->angle = fmod(kernel->angle+90.0, 360.0);
3166 } else {
3167 angle = fmod(angle+90.0, 360.0); /* angle increased 90 degrees */
3168 kernel->angle = fmod(kernel->angle+270.0, 360.0);
3169 }
anthony3c10fc82010-05-13 02:40:51 +00003170 }
3171 else if ( kernel->width == kernel->height )
3172 { /* Rotate a square array of values by 90 degrees */
3173 register unsigned long
3174 i,j,x,y;
3175 register MagickRealType
3176 *k,t;
3177 k=kernel->values;
3178 for( i=0, x=kernel->width-1; i<=x; i++, x--)
3179 for( j=0, y=kernel->height-1; j<y; j++, y--)
3180 { t = k[i+j*kernel->width];
anthony43c49252010-05-18 10:59:50 +00003181 k[i+j*kernel->width] = k[j+x*kernel->width];
3182 k[j+x*kernel->width] = k[x+y*kernel->width];
3183 k[x+y*kernel->width] = k[y+i*kernel->width];
3184 k[y+i*kernel->width] = t;
anthony3c10fc82010-05-13 02:40:51 +00003185 }
anthony43c49252010-05-18 10:59:50 +00003186 /* NOT DONE - rotate a off-centered origin as well! */
3187 angle = fmod(angle+270.0, 360.0); /* angle reduced 90 degrees */
3188 kernel->angle = fmod(kernel->angle+90.0, 360.0);
anthony3c10fc82010-05-13 02:40:51 +00003189 }
3190 else
3191 perror("Unable to rotate a non-square, non-linear kernel 90 degrees");
3192 }
anthony83ba99b2010-01-24 08:48:15 +00003193 if ( 135.0 < angle && angle <= 225.0 )
3194 {
anthony43c49252010-05-18 10:59:50 +00003195 /* For a 180 degree rotation - also know as a reflection
3196 * This is actually a very very common operation!
3197 * Basically all that is needed is a reversal of the kernel data!
3198 * And a reflection of the origon
3199 */
anthony83ba99b2010-01-24 08:48:15 +00003200 unsigned long
3201 i,j;
3202 register double
3203 *k,t;
3204
3205 k=kernel->values;
3206 for ( i=0, j=kernel->width*kernel->height-1; i<j; i++, j--)
3207 t=k[i], k[i]=k[j], k[j]=t;
3208
anthony930be612010-02-08 04:26:15 +00003209 kernel->x = (long) kernel->width - kernel->x - 1;
3210 kernel->y = (long) kernel->height - kernel->y - 1;
anthony43c49252010-05-18 10:59:50 +00003211 angle = fmod(angle-180.0, 360.0); /* angle+180 degrees */
3212 kernel->angle = fmod(kernel->angle+180.0, 360.0);
anthony83ba99b2010-01-24 08:48:15 +00003213 }
anthony3c10fc82010-05-13 02:40:51 +00003214 /* At this point angle should at least between -45 (315) and +45 degrees
anthony83ba99b2010-01-24 08:48:15 +00003215 * In the future some form of non-orthogonal angled rotates could be
3216 * performed here, posibily with a linear kernel restriction.
3217 */
3218
3219#if 0
anthony3c10fc82010-05-13 02:40:51 +00003220 { /* Do a Flop by reversing each row.
anthony83ba99b2010-01-24 08:48:15 +00003221 */
3222 unsigned long
3223 y;
cristy150989e2010-02-01 14:59:39 +00003224 register long
anthony83ba99b2010-01-24 08:48:15 +00003225 x,r;
3226 register double
3227 *k,t;
3228
3229 for ( y=0, k=kernel->values; y < kernel->height; y++, k+=kernel->width)
3230 for ( x=0, r=kernel->width-1; x<kernel->width/2; x++, r--)
3231 t=k[x], k[x]=k[r], k[r]=t;
3232
cristyc99304f2010-02-01 15:26:27 +00003233 kernel->x = kernel->width - kernel->x - 1;
anthony83ba99b2010-01-24 08:48:15 +00003234 angle = fmod(angle+180.0, 360.0);
3235 }
3236#endif
3237 return;
3238}
3239
3240/*
3241%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3242% %
3243% %
3244% %
anthony46a369d2010-05-19 02:41:48 +00003245% S c a l e G e o m e t r y K e r n e l I n f o %
3246% %
3247% %
3248% %
3249%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3250%
3251% ScaleGeometryKernelInfo() takes a geometry argument string, typically
3252% provided as a "-set option:convolve:scale {geometry}" user setting,
3253% and modifies the kernel according to the parsed arguments of that setting.
3254%
3255% The first argument (and any normalization flags) are passed to
3256% ScaleKernelInfo() to scale/normalize the kernel. The second argument
3257% is then passed to UnityAddKernelInfo() to add a scled unity kernel
3258% into the scaled/normalized kernel.
3259%
3260% The format of the ScaleKernelInfo method is:
3261%
3262% void ScaleKernelInfo(KernelInfo *kernel, const double scaling_factor,
3263% const MagickStatusType normalize_flags )
3264%
3265% A description of each parameter follows:
3266%
3267% o kernel: the Morphology/Convolution kernel to modify
3268%
3269% o geometry:
3270% The geometry string to parse, typically from the user provided
3271% "-set option:convolve:scale {geometry}" setting.
3272%
3273*/
3274MagickExport void ScaleGeometryKernelInfo (KernelInfo *kernel,
3275 const char *geometry)
3276{
3277 GeometryFlags
3278 flags;
3279 GeometryInfo
3280 args;
3281
3282 SetGeometryInfo(&args);
3283 flags = (GeometryFlags) ParseGeometry(geometry, &args);
3284
3285#if 0
3286 /* For Debugging Geometry Input */
3287 fprintf(stderr, "Geometry = 0x%04X : %lg x %lg %+lg %+lg\n",
3288 flags, args.rho, args.sigma, args.xi, args.psi );
3289#endif
3290
3291 if ( (flags & PercentValue) != 0 ) /* Handle Percentage flag*/
3292 args.rho *= 0.01, args.sigma *= 0.01;
3293
3294 if ( (flags & RhoValue) == 0 ) /* Set Defaults for missing args */
3295 args.rho = 1.0;
3296 if ( (flags & SigmaValue) == 0 )
3297 args.sigma = 0.0;
3298
3299 /* Scale/Normalize the input kernel */
3300 ScaleKernelInfo(kernel, args.rho, flags);
3301
3302 /* Add Unity Kernel, for blending with original */
3303 if ( (flags & SigmaValue) != 0 )
3304 UnityAddKernelInfo(kernel, args.sigma);
3305
3306 return;
3307}
3308/*
3309%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3310% %
3311% %
3312% %
cristy6771f1e2010-03-05 19:43:39 +00003313% S c a l e K e r n e l I n f o %
anthonycc6c8362010-01-25 04:14:01 +00003314% %
3315% %
3316% %
3317%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3318%
anthony1b2bc0a2010-05-12 05:25:22 +00003319% ScaleKernelInfo() scales the given kernel list by the given amount, with or
3320% without normalization of the sum of the kernel values (as per given flags).
anthonycc6c8362010-01-25 04:14:01 +00003321%
anthony999bb2c2010-02-18 12:38:01 +00003322% By default (no flags given) the values within the kernel is scaled
anthony1b2bc0a2010-05-12 05:25:22 +00003323% directly using given scaling factor without change.
anthonycc6c8362010-01-25 04:14:01 +00003324%
anthony46a369d2010-05-19 02:41:48 +00003325% If either of the two 'normalize_flags' are given the kernel will first be
3326% normalized and then further scaled by the scaling factor value given.
anthony999bb2c2010-02-18 12:38:01 +00003327%
3328% Kernel normalization ('normalize_flags' given) is designed to ensure that
3329% any use of the kernel scaling factor with 'Convolve' or 'Correlate'
anthony1b2bc0a2010-05-12 05:25:22 +00003330% morphology methods will fall into -1.0 to +1.0 range. Note that for
3331% non-HDRI versions of IM this may cause images to have any negative results
3332% clipped, unless some 'bias' is used.
anthony999bb2c2010-02-18 12:38:01 +00003333%
3334% More specifically. Kernels which only contain positive values (such as a
3335% 'Gaussian' kernel) will be scaled so that those values sum to +1.0,
anthony1b2bc0a2010-05-12 05:25:22 +00003336% ensuring a 0.0 to +1.0 output range for non-HDRI images.
anthony999bb2c2010-02-18 12:38:01 +00003337%
3338% For Kernels that contain some negative values, (such as 'Sharpen' kernels)
3339% the kernel will be scaled by the absolute of the sum of kernel values, so
3340% that it will generally fall within the +/- 1.0 range.
3341%
3342% For kernels whose values sum to zero, (such as 'Laplician' kernels) kernel
3343% will be scaled by just the sum of the postive values, so that its output
3344% range will again fall into the +/- 1.0 range.
3345%
3346% For special kernels designed for locating shapes using 'Correlate', (often
3347% only containing +1 and -1 values, representing foreground/brackground
3348% matching) a special normalization method is provided to scale the positive
3349% values seperatally to those of the negative values, so the kernel will be
3350% forced to become a zero-sum kernel better suited to such searches.
3351%
anthony1b2bc0a2010-05-12 05:25:22 +00003352% WARNING: Correct normalization of the kernel assumes that the '*_range'
anthony999bb2c2010-02-18 12:38:01 +00003353% attributes within the kernel structure have been correctly set during the
3354% kernels creation.
3355%
3356% NOTE: The values used for 'normalize_flags' have been selected specifically
anthony46a369d2010-05-19 02:41:48 +00003357% to match the use of geometry options, so that '!' means NormalizeValue, '^'
3358% means CorrelateNormalizeValue. All other GeometryFlags values are ignored.
anthonycc6c8362010-01-25 04:14:01 +00003359%
anthony4fd27e22010-02-07 08:17:18 +00003360% The format of the ScaleKernelInfo method is:
anthonycc6c8362010-01-25 04:14:01 +00003361%
anthony999bb2c2010-02-18 12:38:01 +00003362% void ScaleKernelInfo(KernelInfo *kernel, const double scaling_factor,
3363% const MagickStatusType normalize_flags )
anthonycc6c8362010-01-25 04:14:01 +00003364%
3365% A description of each parameter follows:
3366%
3367% o kernel: the Morphology/Convolution kernel
3368%
anthony999bb2c2010-02-18 12:38:01 +00003369% o scaling_factor:
3370% multiply all values (after normalization) by this factor if not
3371% zero. If the kernel is normalized regardless of any flags.
3372%
3373% o normalize_flags:
3374% GeometryFlags defining normalization method to use.
3375% specifically: NormalizeValue, CorrelateNormalizeValue,
3376% and/or PercentValue
anthonycc6c8362010-01-25 04:14:01 +00003377%
3378*/
cristy6771f1e2010-03-05 19:43:39 +00003379MagickExport void ScaleKernelInfo(KernelInfo *kernel,
3380 const double scaling_factor,const GeometryFlags normalize_flags)
anthonycc6c8362010-01-25 04:14:01 +00003381{
cristy150989e2010-02-01 14:59:39 +00003382 register long
anthonycc6c8362010-01-25 04:14:01 +00003383 i;
3384
anthony999bb2c2010-02-18 12:38:01 +00003385 register double
3386 pos_scale,
3387 neg_scale;
3388
anthony46a369d2010-05-19 02:41:48 +00003389 /* do the other kernels in a multi-kernel list first */
anthony1b2bc0a2010-05-12 05:25:22 +00003390 if ( kernel->next != (KernelInfo *) NULL)
3391 ScaleKernelInfo(kernel->next, scaling_factor, normalize_flags);
3392
anthony46a369d2010-05-19 02:41:48 +00003393 /* Normalization of Kernel */
anthony999bb2c2010-02-18 12:38:01 +00003394 pos_scale = 1.0;
3395 if ( (normalize_flags&NormalizeValue) != 0 ) {
anthony999bb2c2010-02-18 12:38:01 +00003396 if ( fabs(kernel->positive_range + kernel->negative_range) > MagickEpsilon )
anthonyf4e00312010-05-20 12:06:35 +00003397 /* non-zero-summing kernel (generally positive) */
anthony999bb2c2010-02-18 12:38:01 +00003398 pos_scale = fabs(kernel->positive_range + kernel->negative_range);
anthonycc6c8362010-01-25 04:14:01 +00003399 else
anthonyf4e00312010-05-20 12:06:35 +00003400 /* zero-summing kernel */
3401 pos_scale = kernel->positive_range;
anthony999bb2c2010-02-18 12:38:01 +00003402 }
anthony46a369d2010-05-19 02:41:48 +00003403 /* Force kernel into a normalized zero-summing kernel */
anthony999bb2c2010-02-18 12:38:01 +00003404 if ( (normalize_flags&CorrelateNormalizeValue) != 0 ) {
3405 pos_scale = ( fabs(kernel->positive_range) > MagickEpsilon )
3406 ? kernel->positive_range : 1.0;
3407 neg_scale = ( fabs(kernel->negative_range) > MagickEpsilon )
3408 ? -kernel->negative_range : 1.0;
3409 }
3410 else
3411 neg_scale = pos_scale;
3412
3413 /* finialize scaling_factor for positive and negative components */
3414 pos_scale = scaling_factor/pos_scale;
3415 neg_scale = scaling_factor/neg_scale;
anthonycc6c8362010-01-25 04:14:01 +00003416
cristy150989e2010-02-01 14:59:39 +00003417 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00003418 if ( ! IsNan(kernel->values[i]) )
anthony999bb2c2010-02-18 12:38:01 +00003419 kernel->values[i] *= (kernel->values[i] >= 0) ? pos_scale : neg_scale;
anthonycc6c8362010-01-25 04:14:01 +00003420
anthony999bb2c2010-02-18 12:38:01 +00003421 /* convolution output range */
3422 kernel->positive_range *= pos_scale;
3423 kernel->negative_range *= neg_scale;
3424 /* maximum and minimum values in kernel */
3425 kernel->maximum *= (kernel->maximum >= 0.0) ? pos_scale : neg_scale;
3426 kernel->minimum *= (kernel->minimum >= 0.0) ? pos_scale : neg_scale;
3427
anthony46a369d2010-05-19 02:41:48 +00003428 /* swap kernel settings if user's scaling factor is negative */
anthony999bb2c2010-02-18 12:38:01 +00003429 if ( scaling_factor < MagickEpsilon ) {
3430 double t;
3431 t = kernel->positive_range;
3432 kernel->positive_range = kernel->negative_range;
3433 kernel->negative_range = t;
3434 t = kernel->maximum;
3435 kernel->maximum = kernel->minimum;
3436 kernel->minimum = 1;
3437 }
anthonycc6c8362010-01-25 04:14:01 +00003438
3439 return;
3440}
3441
3442/*
3443%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3444% %
3445% %
3446% %
anthony46a369d2010-05-19 02:41:48 +00003447% S h o w K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00003448% %
3449% %
3450% %
3451%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3452%
anthony4fd27e22010-02-07 08:17:18 +00003453% ShowKernelInfo() outputs the details of the given kernel defination to
3454% standard error, generally due to a users 'showkernel' option request.
anthony83ba99b2010-01-24 08:48:15 +00003455%
3456% The format of the ShowKernel method is:
3457%
anthony4fd27e22010-02-07 08:17:18 +00003458% void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00003459%
3460% A description of each parameter follows:
3461%
3462% o kernel: the Morphology/Convolution kernel
3463%
anthony83ba99b2010-01-24 08:48:15 +00003464*/
anthony4fd27e22010-02-07 08:17:18 +00003465MagickExport void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00003466{
anthony7a01dcf2010-05-11 12:25:52 +00003467 KernelInfo
3468 *k;
anthony83ba99b2010-01-24 08:48:15 +00003469
anthony43c49252010-05-18 10:59:50 +00003470 unsigned long
anthony7a01dcf2010-05-11 12:25:52 +00003471 c, i, u, v;
3472
3473 for (c=0, k=kernel; k != (KernelInfo *) NULL; c++, k=k->next ) {
3474
anthony46a369d2010-05-19 02:41:48 +00003475 fprintf(stderr, "Kernel");
anthony7a01dcf2010-05-11 12:25:52 +00003476 if ( kernel->next != (KernelInfo *) NULL )
cristye96405a2010-05-19 02:24:31 +00003477 fprintf(stderr, " #%lu", c );
anthony43c49252010-05-18 10:59:50 +00003478 fprintf(stderr, " \"%s",
3479 MagickOptionToMnemonic(MagickKernelOptions, k->type) );
3480 if ( fabs(k->angle) > MagickEpsilon )
3481 fprintf(stderr, "@%lg", k->angle);
3482 fprintf(stderr, "\" of size %lux%lu%+ld%+ld ",
3483 k->width, k->height,
3484 k->x, k->y );
anthony7a01dcf2010-05-11 12:25:52 +00003485 fprintf(stderr,
3486 " with values from %.*lg to %.*lg\n",
3487 GetMagickPrecision(), k->minimum,
3488 GetMagickPrecision(), k->maximum);
anthony46a369d2010-05-19 02:41:48 +00003489 fprintf(stderr, "Forming a output range from %.*lg to %.*lg",
anthony7a01dcf2010-05-11 12:25:52 +00003490 GetMagickPrecision(), k->negative_range,
anthony46a369d2010-05-19 02:41:48 +00003491 GetMagickPrecision(), k->positive_range);
3492 if ( fabs(k->positive_range+k->negative_range) < MagickEpsilon )
3493 fprintf(stderr, " (Zero-Summing)\n");
3494 else if ( fabs(k->positive_range+k->negative_range-1.0) < MagickEpsilon )
3495 fprintf(stderr, " (Normalized)\n");
3496 else
3497 fprintf(stderr, " (Sum %.*lg)\n",
3498 GetMagickPrecision(), k->positive_range+k->negative_range);
anthony43c49252010-05-18 10:59:50 +00003499 for (i=v=0; v < k->height; v++) {
anthony46a369d2010-05-19 02:41:48 +00003500 fprintf(stderr, "%2lu:", v );
anthony43c49252010-05-18 10:59:50 +00003501 for (u=0; u < k->width; u++, i++)
anthony7a01dcf2010-05-11 12:25:52 +00003502 if ( IsNan(k->values[i]) )
anthonyf4e00312010-05-20 12:06:35 +00003503 fprintf(stderr," %*s", GetMagickPrecision()+3, "nan");
anthony7a01dcf2010-05-11 12:25:52 +00003504 else
anthonyf4e00312010-05-20 12:06:35 +00003505 fprintf(stderr," %*.*lg", GetMagickPrecision()+3,
anthony7a01dcf2010-05-11 12:25:52 +00003506 GetMagickPrecision(), k->values[i]);
3507 fprintf(stderr,"\n");
3508 }
anthony83ba99b2010-01-24 08:48:15 +00003509 }
3510}
anthonycc6c8362010-01-25 04:14:01 +00003511
3512/*
3513%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3514% %
3515% %
3516% %
anthony43c49252010-05-18 10:59:50 +00003517% U n i t y A d d K e r n a l I n f o %
3518% %
3519% %
3520% %
3521%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3522%
3523% UnityAddKernelInfo() Adds a given amount of the 'Unity' Convolution Kernel
3524% to the given pre-scaled and normalized Kernel. This in effect adds that
3525% amount of the original image into the resulting convolution kernel. This
3526% value is usually provided by the user as a percentage value in the
3527% 'convolve:scale' setting.
3528%
3529% The resulting effect is to either convert a 'zero-summing' edge detection
3530% kernel (such as a "Laplacian", "DOG" or a "LOG") into a 'sharpening'
3531% kernel.
3532%
3533% Alternativally by using a purely positive kernel, and using a negative
3534% post-normalizing scaling factor, you can convert a 'blurring' kernel (such
3535% as a "Gaussian") into a 'unsharp' kernel.
3536%
anthony46a369d2010-05-19 02:41:48 +00003537% The format of the UnityAdditionKernelInfo method is:
anthony43c49252010-05-18 10:59:50 +00003538%
3539% void UnityAdditionKernelInfo(KernelInfo *kernel, const double scale )
3540%
3541% A description of each parameter follows:
3542%
3543% o kernel: the Morphology/Convolution kernel
3544%
3545% o scale:
3546% scaling factor for the unity kernel to be added to
3547% the given kernel.
3548%
anthony43c49252010-05-18 10:59:50 +00003549*/
3550MagickExport void UnityAddKernelInfo(KernelInfo *kernel,
3551 const double scale)
3552{
anthony46a369d2010-05-19 02:41:48 +00003553 /* do the other kernels in a multi-kernel list first */
3554 if ( kernel->next != (KernelInfo *) NULL)
3555 UnityAddKernelInfo(kernel->next, scale);
anthony43c49252010-05-18 10:59:50 +00003556
anthony46a369d2010-05-19 02:41:48 +00003557 /* Add the scaled unity kernel to the existing kernel */
anthony43c49252010-05-18 10:59:50 +00003558 kernel->values[kernel->x+kernel->y*kernel->width] += scale;
anthony46a369d2010-05-19 02:41:48 +00003559 CalcKernelMetaData(kernel); /* recalculate the meta-data */
anthony43c49252010-05-18 10:59:50 +00003560
3561 return;
3562}
3563
3564/*
3565%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3566% %
3567% %
3568% %
3569% Z e r o K e r n e l N a n s %
anthonycc6c8362010-01-25 04:14:01 +00003570% %
3571% %
3572% %
3573%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3574%
3575% ZeroKernelNans() replaces any special 'nan' value that may be present in
3576% the kernel with a zero value. This is typically done when the kernel will
3577% be used in special hardware (GPU) convolution processors, to simply
3578% matters.
3579%
3580% The format of the ZeroKernelNans method is:
3581%
anthony46a369d2010-05-19 02:41:48 +00003582% void ZeroKernelNans (KernelInfo *kernel)
anthonycc6c8362010-01-25 04:14:01 +00003583%
3584% A description of each parameter follows:
3585%
3586% o kernel: the Morphology/Convolution kernel
3587%
anthonycc6c8362010-01-25 04:14:01 +00003588*/
anthonyc4c86e02010-01-27 09:30:32 +00003589MagickExport void ZeroKernelNans(KernelInfo *kernel)
anthonycc6c8362010-01-25 04:14:01 +00003590{
anthony43c49252010-05-18 10:59:50 +00003591 register unsigned long
anthonycc6c8362010-01-25 04:14:01 +00003592 i;
3593
anthony46a369d2010-05-19 02:41:48 +00003594 /* do the other kernels in a multi-kernel list first */
anthony1b2bc0a2010-05-12 05:25:22 +00003595 if ( kernel->next != (KernelInfo *) NULL)
3596 ZeroKernelNans(kernel->next);
3597
anthony43c49252010-05-18 10:59:50 +00003598 for (i=0; i < (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00003599 if ( IsNan(kernel->values[i]) )
3600 kernel->values[i] = 0.0;
3601
3602 return;
3603}