blob: 5c6492242927a84ffc8040f3578b47e36fd47677 [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
662% are specially weighted. After applying each to the original image, the
663% results is then added together.
664%
665% Type 1: | 1, sqrt(2), 1 |
666% | 0, 0, 0 | / 2*sqrt(2)
667% | -1, -sqrt(2), -1 |
668%
669% Type 2: | 1, 0, 1 |
670% | sqrt(2), 0, sqrt(2) | / 2*sqrt(2)
671% | 1, 0, 1 |
672%
673% Type 3: | 0, -1, sqrt(2) |
674% | 1, 0, -1 | / 2*sqrt(2)
675% | -sqrt(2), 1, 0 |
676%
677% Type 4: | sqrt(2), -1, 1 |
678% | -1, 0, 1 | / 2*sqrt(2)
679% | 0, 1, -sqrt(2) |
680%
681% Type 5: | 0, 1, 0 |
682% | -1, 0, -1 | / 2
683% | 0, 1, 0 |
684%
685% Type 6: | -1, 0, 1 |
686% | 0, 0, 0 | / 2
687% | 1, 0, -1 |
688%
689% Type 7: | -1, -2, 1 |
690% | -2, 4, -2 | / 6
691% | 1, -2, 1 |
692%
693% Type 8: | -2, 1, -2 |
694% | 1, 4, 1 | / 6
695% | -2, 1, -2 |
696%
697% Type 9: | 1, 1, 1 |
698% | 1, 1, 1 | / 3
699% | 1, 1, 1 |
700%
701% The first 4 are for edge detection, the next 4 are for line detection
702% and the last is to add a average component to the results.
703%
704% The '{angle}' argument is not normally used but provided for
705% convenience of generating other kernels from these kernels.
706%
707%
anthony602ab9b2010-01-05 08:06:50 +0000708% Boolean Kernels
709%
anthony3c10fc82010-05-13 02:40:51 +0000710% Diamond:[{radius}[,{scale}]]
anthony1b2bc0a2010-05-12 05:25:22 +0000711% Generate a diamond shaped kernel with given radius to the points.
anthony602ab9b2010-01-05 08:06:50 +0000712% Kernel size will again be radius*2+1 square and defaults to radius 1,
713% generating a 3x3 kernel that is slightly larger than a square.
714%
anthony3c10fc82010-05-13 02:40:51 +0000715% Square:[{radius}[,{scale}]]
anthony602ab9b2010-01-05 08:06:50 +0000716% Generate a square shaped kernel of size radius*2+1, and defaulting
717% to a 3x3 (radius 1).
718%
anthonyc1061722010-05-14 06:23:49 +0000719% Note that using a larger radius for the "Square" or the "Diamond" is
720% also equivelent to iterating the basic morphological method that many
721% times. However iterating with the smaller radius is actually faster
722% than using a larger kernel radius.
723%
724% Rectangle:{geometry}
725% Simply generate a rectangle of 1's with the size given. You can also
726% specify the location of the 'control point', otherwise the closest
727% pixel to the center of the rectangle is selected.
728%
729% Properly centered and odd sized rectangles work the best.
anthony602ab9b2010-01-05 08:06:50 +0000730%
anthony3c10fc82010-05-13 02:40:51 +0000731% Disk:[{radius}[,{scale}]]
anthony602ab9b2010-01-05 08:06:50 +0000732% Generate a binary disk of the radius given, radius may be a float.
733% Kernel size will be ceil(radius)*2+1 square.
734% NOTE: Here are some disk shapes of specific interest
anthonyc1061722010-05-14 06:23:49 +0000735% "Disk:1" => "diamond" or "cross:1"
736% "Disk:1.5" => "square"
737% "Disk:2" => "diamond:2"
738% "Disk:2.5" => a general disk shape of radius 2
739% "Disk:2.9" => "square:2"
740% "Disk:3.5" => default - octagonal/disk shape of radius 3
741% "Disk:4.2" => roughly octagonal shape of radius 4
742% "Disk:4.3" => a general disk shape of radius 4
anthony602ab9b2010-01-05 08:06:50 +0000743% After this all the kernel shape becomes more and more circular.
744%
745% Because a "disk" is more circular when using a larger radius, using a
746% larger radius is preferred over iterating the morphological operation.
747%
anthonyc1061722010-05-14 06:23:49 +0000748% Symbol Dilation Kernels
749%
750% These kernel is not a good general morphological kernel, but is used
751% more for highlighting and marking any single pixels in an image using,
752% a "Dilate" method as appropriate.
753%
754% For the same reasons iterating these kernels does not produce the
755% same result as using a larger radius for the symbol.
756%
anthony3c10fc82010-05-13 02:40:51 +0000757% Plus:[{radius}[,{scale}]]
anthony3dd0f622010-05-13 12:57:32 +0000758% Cross:[{radius}[,{scale}]]
anthonyc1061722010-05-14 06:23:49 +0000759% Generate a kernel in the shape of a 'plus' or a 'cross' with
760% a each arm the length of the given radius (default 2).
anthony3dd0f622010-05-13 12:57:32 +0000761%
762% NOTE: "plus:1" is equivelent to a "Diamond" kernel.
anthony602ab9b2010-01-05 08:06:50 +0000763%
anthonyc1061722010-05-14 06:23:49 +0000764% Ring:{radius1},{radius2}[,{scale}]
765% A ring of the values given that falls between the two radii.
766% Defaults to a ring of approximataly 3 radius in a 7x7 kernel.
767% This is the 'edge' pixels of the default "Disk" kernel,
768% More specifically, "Ring" -> "Ring:2.5,3.5,1.0"
anthony602ab9b2010-01-05 08:06:50 +0000769%
anthony3dd0f622010-05-13 12:57:32 +0000770% Hit and Miss Kernels
771%
772% Peak:radius1,radius2
anthonyc1061722010-05-14 06:23:49 +0000773% Find any peak larger than the pixels the fall between the two radii.
774% The default ring of pixels is as per "Ring".
anthony43c49252010-05-18 10:59:50 +0000775% Edges
776% Find Edges of a binary shape
anthony3dd0f622010-05-13 12:57:32 +0000777% Corners
778% Find corners of a binary shape
779% LineEnds
780% Find end points of lines (for pruning a skeletion)
781% LineJunctions
anthony43c49252010-05-18 10:59:50 +0000782% Find three line junctions (within a skeletion)
anthony3dd0f622010-05-13 12:57:32 +0000783% ConvexHull
784% Octagonal thicken kernel, to generate convex hulls of 45 degrees
785% Skeleton
786% Thinning kernel, which leaves behind a skeletion of a shape
anthony602ab9b2010-01-05 08:06:50 +0000787%
788% Distance Measuring Kernels
789%
anthonyc1061722010-05-14 06:23:49 +0000790% Different types of distance measuring methods, which are used with the
791% a 'Distance' morphology method for generating a gradient based on
792% distance from an edge of a binary shape, though there is a technique
793% for handling a anti-aliased shape.
794%
795% See the 'Distance' Morphological Method, for information of how it is
796% applied.
797%
anthony3dd0f622010-05-13 12:57:32 +0000798% Chebyshev:[{radius}][x{scale}[%!]]
anthonyc94cdb02010-01-06 08:15:29 +0000799% Chebyshev Distance (also known as Tchebychev Distance) is a value of
800% one to any neighbour, orthogonal or diagonal. One why of thinking of
801% it is the number of squares a 'King' or 'Queen' in chess needs to
802% traverse reach any other position on a chess board. It results in a
803% 'square' like distance function, but one where diagonals are closer
804% than expected.
anthony602ab9b2010-01-05 08:06:50 +0000805%
anthonyc1061722010-05-14 06:23:49 +0000806% Manhatten:[{radius}][x{scale}[%!]]
anthonyc94cdb02010-01-06 08:15:29 +0000807% Manhatten Distance (also known as Rectilinear Distance, or the Taxi
808% Cab metric), is the distance needed when you can only travel in
809% orthogonal (horizontal or vertical) only. It is the distance a 'Rook'
810% in chess would travel. It results in a diamond like distances, where
811% diagonals are further than expected.
anthony602ab9b2010-01-05 08:06:50 +0000812%
anthonyc1061722010-05-14 06:23:49 +0000813% Euclidean:[{radius}][x{scale}[%!]]
anthonyc94cdb02010-01-06 08:15:29 +0000814% Euclidean Distance is the 'direct' or 'as the crow flys distance.
815% However by default the kernel size only has a radius of 1, which
816% limits the distance to 'Knight' like moves, with only orthogonal and
817% diagonal measurements being correct. As such for the default kernel
818% you will get octagonal like distance function, which is reasonally
819% accurate.
820%
821% However if you use a larger radius such as "Euclidean:4" you will
822% get a much smoother distance gradient from the edge of the shape.
823% Of course a larger kernel is slower to use, and generally not needed.
824%
825% To allow the use of fractional distances that you get with diagonals
826% the actual distance is scaled by a fixed value which the user can
827% provide. This is not actually nessary for either ""Chebyshev" or
828% "Manhatten" distance kernels, but is done for all three distance
829% kernels. If no scale is provided it is set to a value of 100,
830% allowing for a maximum distance measurement of 655 pixels using a Q16
831% version of IM, from any edge. However for small images this can
832% result in quite a dark gradient.
833%
anthony602ab9b2010-01-05 08:06:50 +0000834*/
835
cristy2be15382010-01-21 02:38:03 +0000836MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000837 const GeometryInfo *args)
838{
cristy2be15382010-01-21 02:38:03 +0000839 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000840 *kernel;
841
cristy150989e2010-02-01 14:59:39 +0000842 register long
anthony602ab9b2010-01-05 08:06:50 +0000843 i;
844
845 register long
846 u,
847 v;
848
849 double
850 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
851
anthonyc1061722010-05-14 06:23:49 +0000852 /* Generate a new empty kernel if needed */
cristye96405a2010-05-19 02:24:31 +0000853 kernel=(KernelInfo *) NULL;
anthonyc1061722010-05-14 06:23:49 +0000854 switch(type) {
anthony9eb4f742010-05-18 02:45:54 +0000855 case UndefinedKernel: /* These should not be used here */
856 case UserDefinedKernel:
857 break;
858 case LaplacianKernel: /* Named Descrete Convolution Kernels */
859 case SobelKernel:
860 case RobertsKernel:
861 case PrewittKernel:
862 case CompassKernel:
863 case KirschKernel:
864 case CornersKernel: /* Hit and Miss kernels */
865 case LineEndsKernel:
866 case LineJunctionsKernel:
anthony9eb4f742010-05-18 02:45:54 +0000867 case ThinningKernel:
868 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:
1271 { switch ( (int) args->rho ) {
1272 default:
1273 case 1:
1274 kernel=ParseKernelArray("3: 1,2,1 0,0,0 -1,2,-1");
1275 if (kernel == (KernelInfo *) NULL)
1276 return(kernel);
1277 kernel->values[1] = +MagickSQ2;
1278 kernel->values[7] = -MagickSQ2;
1279 CalcKernelMetaData(kernel); /* recalculate meta-data */
1280 ScaleKernelInfo(kernel, 1.0/2.0*MagickSQ2, NoValue);
1281 break;
1282 case 2:
1283 kernel=ParseKernelArray("3: 1,0,1 2,0,2 1,0,1");
1284 if (kernel == (KernelInfo *) NULL)
1285 return(kernel);
1286 kernel->values[3] = +MagickSQ2;
1287 kernel->values[5] = +MagickSQ2;
1288 CalcKernelMetaData(kernel);
1289 ScaleKernelInfo(kernel, 1.0/2.0*MagickSQ2, NoValue);
1290 break;
1291 case 3:
1292 kernel=ParseKernelArray("3: 0,-1,2 1,0,-1 -2,1,0");
1293 if (kernel == (KernelInfo *) NULL)
1294 return(kernel);
1295 kernel->values[2] = +MagickSQ2;
1296 kernel->values[6] = -MagickSQ2;
1297 CalcKernelMetaData(kernel);
1298 ScaleKernelInfo(kernel, 1.0/2.0*MagickSQ2, NoValue);
1299 break;
1300 case 4:
1301 kernel=ParseKernelArray("3: 2,-1,1 -1,0,1 0,1,-2");
1302 if (kernel == (KernelInfo *) NULL)
1303 return(kernel);
1304 kernel->values[0] = +MagickSQ2;
1305 kernel->values[8] = -MagickSQ2;
1306 CalcKernelMetaData(kernel);
1307 ScaleKernelInfo(kernel, 1.0/2.0*MagickSQ2, NoValue);
1308 break;
1309 case 5:
1310 kernel=ParseKernelArray("3: 0,1,0 -1,0,-1 0,1,0");
1311 if (kernel == (KernelInfo *) NULL)
1312 return(kernel);
1313 ScaleKernelInfo(kernel, 1.0/2.0, NoValue);
1314 break;
1315 case 6:
1316 kernel=ParseKernelArray("3: -1,0,1 0,0,0 1,0,-1");
1317 if (kernel == (KernelInfo *) NULL)
1318 return(kernel);
1319 ScaleKernelInfo(kernel, 1.0/2.0, NoValue);
1320 break;
1321 case 7:
1322 kernel=ParseKernelArray("3: -1,-2,1 -2,4,-2 1,-2,1");
1323 if (kernel == (KernelInfo *) NULL)
1324 return(kernel);
1325 ScaleKernelInfo(kernel, 1.0/6.0, NoValue);
1326 break;
1327 case 8:
1328 kernel=ParseKernelArray("3: -2,1,-2 1,4,1 -2,1,-2");
1329 if (kernel == (KernelInfo *) NULL)
1330 return(kernel);
1331 ScaleKernelInfo(kernel, 1.0/6.0, NoValue);
1332 break;
1333 case 9:
1334 kernel=ParseKernelArray("3: 1,1,1 1,1,1 1,1,1");
1335 if (kernel == (KernelInfo *) NULL)
1336 return(kernel);
1337 ScaleKernelInfo(kernel, 1.0/3.0, NoValue);
1338 break;
1339 }
1340 RotateKernelInfo(kernel, args->sigma); /* Rotate by angle */
1341 break;
1342 }
1343
anthonyc1061722010-05-14 06:23:49 +00001344 /* Boolean Kernels */
1345 case DiamondKernel:
1346 {
1347 if (args->rho < 1.0)
1348 kernel->width = kernel->height = 3; /* default radius = 1 */
1349 else
1350 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
1351 kernel->x = kernel->y = (long) (kernel->width-1)/2;
1352
1353 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1354 kernel->height*sizeof(double));
1355 if (kernel->values == (double *) NULL)
1356 return(DestroyKernelInfo(kernel));
1357
1358 /* set all kernel values within diamond area to scale given */
1359 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1360 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1361 if ((labs(u)+labs(v)) <= (long)kernel->x)
1362 kernel->positive_range += kernel->values[i] = args->sigma;
1363 else
1364 kernel->values[i] = nan;
1365 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1366 break;
1367 }
1368 case SquareKernel:
1369 case RectangleKernel:
1370 { double
1371 scale;
anthony602ab9b2010-01-05 08:06:50 +00001372 if ( type == SquareKernel )
1373 {
1374 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001375 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001376 else
cristy150989e2010-02-01 14:59:39 +00001377 kernel->width = kernel->height = (unsigned long) (2*args->rho+1);
cristyc99304f2010-02-01 15:26:27 +00001378 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony4fd27e22010-02-07 08:17:18 +00001379 scale = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +00001380 }
1381 else {
cristy2be15382010-01-21 02:38:03 +00001382 /* NOTE: user defaults set in "AcquireKernelInfo()" */
anthony602ab9b2010-01-05 08:06:50 +00001383 if ( args->rho < 1.0 || args->sigma < 1.0 )
anthony83ba99b2010-01-24 08:48:15 +00001384 return(DestroyKernelInfo(kernel)); /* invalid args given */
anthony602ab9b2010-01-05 08:06:50 +00001385 kernel->width = (unsigned long)args->rho;
1386 kernel->height = (unsigned long)args->sigma;
1387 if ( args->xi < 0.0 || args->xi > (double)kernel->width ||
1388 args->psi < 0.0 || args->psi > (double)kernel->height )
anthony83ba99b2010-01-24 08:48:15 +00001389 return(DestroyKernelInfo(kernel)); /* invalid args given */
cristyc99304f2010-02-01 15:26:27 +00001390 kernel->x = (long) args->xi;
1391 kernel->y = (long) args->psi;
anthony4fd27e22010-02-07 08:17:18 +00001392 scale = 1.0;
anthony602ab9b2010-01-05 08:06:50 +00001393 }
1394 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1395 kernel->height*sizeof(double));
1396 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001397 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001398
anthony3dd0f622010-05-13 12:57:32 +00001399 /* set all kernel values to scale given */
cristy150989e2010-02-01 14:59:39 +00001400 u=(long) kernel->width*kernel->height;
1401 for ( i=0; i < u; i++)
anthony4fd27e22010-02-07 08:17:18 +00001402 kernel->values[i] = scale;
1403 kernel->minimum = kernel->maximum = scale; /* a flat shape */
1404 kernel->positive_range = scale*u;
anthonycc6c8362010-01-25 04:14:01 +00001405 break;
anthony602ab9b2010-01-05 08:06:50 +00001406 }
anthony602ab9b2010-01-05 08:06:50 +00001407 case DiskKernel:
1408 {
anthonyc1061722010-05-14 06:23:49 +00001409 long limit = (long)(args->rho*args->rho);
anthony83ba99b2010-01-24 08:48:15 +00001410 if (args->rho < 0.1) /* default radius approx 3.5 */
1411 kernel->width = kernel->height = 7L, limit = 10L;
anthony602ab9b2010-01-05 08:06:50 +00001412 else
1413 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001414 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001415
1416 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1417 kernel->height*sizeof(double));
1418 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001419 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001420
anthony3dd0f622010-05-13 12:57:32 +00001421 /* set all kernel values within disk area to scale given */
1422 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
cristyc99304f2010-02-01 15:26:27 +00001423 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony602ab9b2010-01-05 08:06:50 +00001424 if ((u*u+v*v) <= limit)
anthony4fd27e22010-02-07 08:17:18 +00001425 kernel->positive_range += kernel->values[i] = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +00001426 else
1427 kernel->values[i] = nan;
anthony4fd27e22010-02-07 08:17:18 +00001428 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
anthony602ab9b2010-01-05 08:06:50 +00001429 break;
1430 }
1431 case PlusKernel:
1432 {
1433 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001434 kernel->width = kernel->height = 5; /* default radius 2 */
anthony602ab9b2010-01-05 08:06:50 +00001435 else
1436 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001437 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001438
1439 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1440 kernel->height*sizeof(double));
1441 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001442 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001443
anthony3dd0f622010-05-13 12:57:32 +00001444 /* set all kernel values along axises to given scale */
cristyc99304f2010-02-01 15:26:27 +00001445 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1446 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony4fd27e22010-02-07 08:17:18 +00001447 kernel->values[i] = (u == 0 || v == 0) ? args->sigma : nan;
1448 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1449 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
anthony602ab9b2010-01-05 08:06:50 +00001450 break;
1451 }
anthony3dd0f622010-05-13 12:57:32 +00001452 case CrossKernel:
1453 {
1454 if (args->rho < 1.0)
1455 kernel->width = kernel->height = 5; /* default radius 2 */
1456 else
1457 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
1458 kernel->x = kernel->y = (long) (kernel->width-1)/2;
1459
1460 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1461 kernel->height*sizeof(double));
1462 if (kernel->values == (double *) NULL)
1463 return(DestroyKernelInfo(kernel));
1464
1465 /* set all kernel values along axises to given scale */
1466 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1467 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1468 kernel->values[i] = (u == v || u == -v) ? args->sigma : nan;
1469 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1470 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
1471 break;
1472 }
1473 /* HitAndMiss Kernels */
anthonyc1061722010-05-14 06:23:49 +00001474 case RingKernel:
anthony3dd0f622010-05-13 12:57:32 +00001475 case PeaksKernel:
1476 {
1477 long
1478 limit1,
anthonyc1061722010-05-14 06:23:49 +00001479 limit2,
1480 scale;
anthony3dd0f622010-05-13 12:57:32 +00001481
1482 if (args->rho < args->sigma)
1483 {
1484 kernel->width = ((unsigned long)args->sigma)*2+1;
1485 limit1 = (long)args->rho*args->rho;
1486 limit2 = (long)args->sigma*args->sigma;
1487 }
1488 else
1489 {
1490 kernel->width = ((unsigned long)args->rho)*2+1;
1491 limit1 = (long)args->sigma*args->sigma;
1492 limit2 = (long)args->rho*args->rho;
1493 }
anthonyc1061722010-05-14 06:23:49 +00001494 if ( limit2 <= 0 )
1495 kernel->width = 7L, limit1 = 7L, limit2 = 11L;
1496
anthony3dd0f622010-05-13 12:57:32 +00001497 kernel->height = kernel->width;
1498 kernel->x = kernel->y = (long) (kernel->width-1)/2;
1499 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1500 kernel->height*sizeof(double));
1501 if (kernel->values == (double *) NULL)
1502 return(DestroyKernelInfo(kernel));
1503
anthonyc1061722010-05-14 06:23:49 +00001504 /* set a ring of points of 'scale' ( 0.0 for PeaksKernel ) */
cristye96405a2010-05-19 02:24:31 +00001505 scale = (long) (( type == PeaksKernel) ? 0.0 : args->xi);
anthony3dd0f622010-05-13 12:57:32 +00001506 for ( i=0, v= -kernel->y; v <= (long)kernel->y; v++)
1507 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1508 { long radius=u*u+v*v;
anthonyc1061722010-05-14 06:23:49 +00001509 if (limit1 < radius && radius <= limit2)
cristye96405a2010-05-19 02:24:31 +00001510 kernel->positive_range += kernel->values[i] = (double) scale;
anthony3dd0f622010-05-13 12:57:32 +00001511 else
1512 kernel->values[i] = nan;
1513 }
cristye96405a2010-05-19 02:24:31 +00001514 kernel->minimum = kernel->minimum = (double) scale;
anthonyc1061722010-05-14 06:23:49 +00001515 if ( type == PeaksKernel ) {
1516 /* set the central point in the middle */
1517 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1518 kernel->positive_range = 1.0;
1519 kernel->maximum = 1.0;
1520 }
anthony3dd0f622010-05-13 12:57:32 +00001521 break;
1522 }
anthony43c49252010-05-18 10:59:50 +00001523 case EdgesKernel:
1524 {
1525 kernel=ParseKernelArray("3: 0,0,0 -,1,- 1,1,1");
1526 if (kernel == (KernelInfo *) NULL)
1527 return(kernel);
1528 kernel->type = type;
1529 ExpandKernelInfo(kernel, 90.0); /* Create a list of 4 rotated kernels */
1530 break;
1531 }
anthony3dd0f622010-05-13 12:57:32 +00001532 case CornersKernel:
1533 {
anthony4f1dcb72010-05-14 08:43:10 +00001534 kernel=ParseKernelArray("3: 0,0,- 0,1,1 -,1,-");
anthony3dd0f622010-05-13 12:57:32 +00001535 if (kernel == (KernelInfo *) NULL)
1536 return(kernel);
1537 kernel->type = type;
1538 ExpandKernelInfo(kernel, 90.0); /* Create a list of 4 rotated kernels */
1539 break;
1540 }
1541 case LineEndsKernel:
1542 {
anthony43c49252010-05-18 10:59:50 +00001543 KernelInfo
1544 *new_kernel;
1545 kernel=ParseKernelArray("3: 0,0,0 0,1,0 -,1,-");
anthony3dd0f622010-05-13 12:57:32 +00001546 if (kernel == (KernelInfo *) NULL)
1547 return(kernel);
1548 kernel->type = type;
anthony43c49252010-05-18 10:59:50 +00001549 ExpandKernelInfo(kernel, 90.0);
1550 /* append second set of 4 kernels */
1551 new_kernel=ParseKernelArray("3: 0,0,0 0,1,0 0,0,1");
1552 if (new_kernel == (KernelInfo *) NULL)
1553 return(DestroyKernelInfo(kernel));
1554 new_kernel->type = type;
1555 ExpandKernelInfo(new_kernel, 90.0);
1556 LastKernelInfo(kernel)->next = new_kernel;
anthony3dd0f622010-05-13 12:57:32 +00001557 break;
1558 }
1559 case LineJunctionsKernel:
1560 {
1561 KernelInfo
1562 *new_kernel;
anthony3dd0f622010-05-13 12:57:32 +00001563 /* first set of 4 kernels */
anthony4f1dcb72010-05-14 08:43:10 +00001564 kernel=ParseKernelArray("3: -,1,- -,1,- 1,-,1");
anthony3dd0f622010-05-13 12:57:32 +00001565 if (kernel == (KernelInfo *) NULL)
1566 return(kernel);
1567 kernel->type = type;
anthony43c49252010-05-18 10:59:50 +00001568 ExpandKernelInfo(kernel, 45.0);
anthony3dd0f622010-05-13 12:57:32 +00001569 /* append second set of 4 kernels */
anthony4f1dcb72010-05-14 08:43:10 +00001570 new_kernel=ParseKernelArray("3: 1,-,- -,1,- 1,-,1");
anthony3dd0f622010-05-13 12:57:32 +00001571 if (new_kernel == (KernelInfo *) NULL)
1572 return(DestroyKernelInfo(kernel));
anthony43c49252010-05-18 10:59:50 +00001573 new_kernel->type = type;
anthony3dd0f622010-05-13 12:57:32 +00001574 ExpandKernelInfo(new_kernel, 90.0);
1575 LastKernelInfo(kernel)->next = new_kernel;
anthony4f1dcb72010-05-14 08:43:10 +00001576 break;
1577 }
anthony3dd0f622010-05-13 12:57:32 +00001578 case ConvexHullKernel:
1579 {
1580 KernelInfo
1581 *new_kernel;
anthony3dd0f622010-05-13 12:57:32 +00001582 /* first set of 4 kernels */
anthony4f1dcb72010-05-14 08:43:10 +00001583 kernel=ParseKernelArray("3: 1,1,- 1,0,- 1,-,0");
anthony3dd0f622010-05-13 12:57:32 +00001584 if (kernel == (KernelInfo *) NULL)
1585 return(kernel);
1586 kernel->type = type;
1587 ExpandKernelInfo(kernel, 90.0);
1588 /* append second set of 4 kernels */
anthony43c49252010-05-18 10:59:50 +00001589 new_kernel=ParseKernelArray("3: 1,1,1 1,0,0 -,-,0");
anthony3dd0f622010-05-13 12:57:32 +00001590 if (new_kernel == (KernelInfo *) NULL)
1591 return(DestroyKernelInfo(kernel));
anthony43c49252010-05-18 10:59:50 +00001592 new_kernel->type = type;
anthony3dd0f622010-05-13 12:57:32 +00001593 ExpandKernelInfo(new_kernel, 90.0);
1594 LastKernelInfo(kernel)->next = new_kernel;
1595 break;
1596 }
anthony43c49252010-05-18 10:59:50 +00001597 case ThinningKernel:
1598 { /* Thinning Kernel ?? -- filled corner and edge */
1599 kernel=ParseKernelArray("3: 0,0,- 0,1,1 -,1,1");
anthony3dd0f622010-05-13 12:57:32 +00001600 if (kernel == (KernelInfo *) NULL)
1601 return(kernel);
1602 kernel->type = type;
anthony43c49252010-05-18 10:59:50 +00001603 ExpandKernelInfo(kernel, 45);
1604 break;
1605 }
1606 case SkeletonKernel:
1607 {
1608 kernel=AcquireKernelInfo("Edges;Corners");
anthony3dd0f622010-05-13 12:57:32 +00001609 break;
1610 }
anthony602ab9b2010-01-05 08:06:50 +00001611 /* Distance Measuring Kernels */
1612 case ChebyshevKernel:
1613 {
anthony602ab9b2010-01-05 08:06:50 +00001614 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001615 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001616 else
1617 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001618 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001619
1620 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1621 kernel->height*sizeof(double));
1622 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001623 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001624
cristyc99304f2010-02-01 15:26:27 +00001625 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1626 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1627 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001628 args->sigma*((labs(u)>labs(v)) ? labs(u) : labs(v)) );
cristyc99304f2010-02-01 15:26:27 +00001629 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001630 break;
1631 }
1632 case ManhattenKernel:
1633 {
anthony602ab9b2010-01-05 08:06:50 +00001634 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001635 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001636 else
1637 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001638 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001639
1640 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1641 kernel->height*sizeof(double));
1642 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001643 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001644
cristyc99304f2010-02-01 15:26:27 +00001645 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1646 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1647 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001648 args->sigma*(labs(u)+labs(v)) );
cristyc99304f2010-02-01 15:26:27 +00001649 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001650 break;
1651 }
1652 case EuclideanKernel:
1653 {
anthony602ab9b2010-01-05 08:06:50 +00001654 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001655 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001656 else
1657 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001658 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001659
1660 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1661 kernel->height*sizeof(double));
1662 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001663 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001664
cristyc99304f2010-02-01 15:26:27 +00001665 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1666 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1667 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001668 args->sigma*sqrt((double)(u*u+v*v)) );
cristyc99304f2010-02-01 15:26:27 +00001669 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001670 break;
1671 }
anthony46a369d2010-05-19 02:41:48 +00001672 case UnityKernel:
anthony602ab9b2010-01-05 08:06:50 +00001673 default:
anthonyc1061722010-05-14 06:23:49 +00001674 {
anthony46a369d2010-05-19 02:41:48 +00001675 /* Unity or No-Op Kernel - 3x3 with 1 in center */
1676 kernel=ParseKernelArray("3:0,0,0,0,1,0,0,0,0");
anthonyc1061722010-05-14 06:23:49 +00001677 if (kernel == (KernelInfo *) NULL)
1678 return(kernel);
anthony46a369d2010-05-19 02:41:48 +00001679 kernel->type = ( type == UnityKernel ) ? UnityKernel : UndefinedKernel;
anthonyc1061722010-05-14 06:23:49 +00001680 break;
1681 }
anthony602ab9b2010-01-05 08:06:50 +00001682 break;
1683 }
1684
1685 return(kernel);
1686}
anthonyc94cdb02010-01-06 08:15:29 +00001687
anthony602ab9b2010-01-05 08:06:50 +00001688/*
1689%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1690% %
1691% %
1692% %
cristy6771f1e2010-03-05 19:43:39 +00001693% C l o n e K e r n e l I n f o %
anthony4fd27e22010-02-07 08:17:18 +00001694% %
1695% %
1696% %
1697%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1698%
anthony1b2bc0a2010-05-12 05:25:22 +00001699% CloneKernelInfo() creates a new clone of the given Kernel List so that its
1700% can be modified without effecting the original. The cloned kernel should
1701% be destroyed using DestoryKernelInfo() when no longer needed.
anthony7a01dcf2010-05-11 12:25:52 +00001702%
cristye6365592010-04-02 17:31:23 +00001703% The format of the CloneKernelInfo method is:
anthony4fd27e22010-02-07 08:17:18 +00001704%
anthony930be612010-02-08 04:26:15 +00001705% KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +00001706%
1707% A description of each parameter follows:
1708%
1709% o kernel: the Morphology/Convolution kernel to be cloned
1710%
1711*/
cristyef656912010-03-05 19:54:59 +00001712MagickExport KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +00001713{
1714 register long
1715 i;
1716
cristy19eb6412010-04-23 14:42:29 +00001717 KernelInfo
anthony7a01dcf2010-05-11 12:25:52 +00001718 *new_kernel;
anthony4fd27e22010-02-07 08:17:18 +00001719
1720 assert(kernel != (KernelInfo *) NULL);
anthony7a01dcf2010-05-11 12:25:52 +00001721 new_kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
1722 if (new_kernel == (KernelInfo *) NULL)
1723 return(new_kernel);
1724 *new_kernel=(*kernel); /* copy values in structure */
anthony7a01dcf2010-05-11 12:25:52 +00001725
1726 /* replace the values with a copy of the values */
1727 new_kernel->values=(double *) AcquireQuantumMemory(kernel->width,
cristy19eb6412010-04-23 14:42:29 +00001728 kernel->height*sizeof(double));
anthony7a01dcf2010-05-11 12:25:52 +00001729 if (new_kernel->values == (double *) NULL)
1730 return(DestroyKernelInfo(new_kernel));
anthony4fd27e22010-02-07 08:17:18 +00001731 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthony7a01dcf2010-05-11 12:25:52 +00001732 new_kernel->values[i]=kernel->values[i];
anthony1b2bc0a2010-05-12 05:25:22 +00001733
1734 /* Also clone the next kernel in the kernel list */
1735 if ( kernel->next != (KernelInfo *) NULL ) {
1736 new_kernel->next = CloneKernelInfo(kernel->next);
1737 if ( new_kernel->next == (KernelInfo *) NULL )
1738 return(DestroyKernelInfo(new_kernel));
1739 }
1740
anthony7a01dcf2010-05-11 12:25:52 +00001741 return(new_kernel);
anthony4fd27e22010-02-07 08:17:18 +00001742}
1743
1744/*
1745%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1746% %
1747% %
1748% %
anthony83ba99b2010-01-24 08:48:15 +00001749% D e s t r o y K e r n e l I n f o %
anthony602ab9b2010-01-05 08:06:50 +00001750% %
1751% %
1752% %
1753%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1754%
anthony83ba99b2010-01-24 08:48:15 +00001755% DestroyKernelInfo() frees the memory used by a Convolution/Morphology
1756% kernel.
anthony602ab9b2010-01-05 08:06:50 +00001757%
anthony83ba99b2010-01-24 08:48:15 +00001758% The format of the DestroyKernelInfo method is:
anthony602ab9b2010-01-05 08:06:50 +00001759%
anthony83ba99b2010-01-24 08:48:15 +00001760% KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +00001761%
1762% A description of each parameter follows:
1763%
1764% o kernel: the Morphology/Convolution kernel to be destroyed
1765%
1766*/
1767
anthony83ba99b2010-01-24 08:48:15 +00001768MagickExport KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +00001769{
cristy2be15382010-01-21 02:38:03 +00001770 assert(kernel != (KernelInfo *) NULL);
anthony4fd27e22010-02-07 08:17:18 +00001771
anthony7a01dcf2010-05-11 12:25:52 +00001772 if ( kernel->next != (KernelInfo *) NULL )
1773 kernel->next = DestroyKernelInfo(kernel->next);
1774
1775 kernel->values = (double *)RelinquishMagickMemory(kernel->values);
1776 kernel = (KernelInfo *) RelinquishMagickMemory(kernel);
anthony602ab9b2010-01-05 08:06:50 +00001777 return(kernel);
1778}
anthonyc94cdb02010-01-06 08:15:29 +00001779
1780/*
1781%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1782% %
1783% %
1784% %
anthony3c10fc82010-05-13 02:40:51 +00001785% E x p a n d K e r n e l I n f o %
1786% %
1787% %
1788% %
1789%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1790%
1791% ExpandKernelInfo() takes a single kernel, and expands it into a list
1792% of kernels each incrementally rotated the angle given.
1793%
1794% WARNING: 45 degree rotations only works for 3x3 kernels.
1795% While 90 degree roatations only works for linear and square kernels
1796%
1797% The format of the RotateKernelInfo method is:
1798%
1799% void ExpandKernelInfo(KernelInfo *kernel, double angle)
1800%
1801% A description of each parameter follows:
1802%
1803% o kernel: the Morphology/Convolution kernel
1804%
1805% o angle: angle to rotate in degrees
1806%
1807% This function is only internel to this module, as it is not finalized,
1808% especially with regard to non-orthogonal angles, and rotation of larger
1809% 2D kernels.
1810*/
1811static void ExpandKernelInfo(KernelInfo *kernel, double angle)
1812{
1813 KernelInfo
cristya9a61ad2010-05-13 12:47:41 +00001814 *clone,
anthony3c10fc82010-05-13 02:40:51 +00001815 *last;
cristya9a61ad2010-05-13 12:47:41 +00001816
anthony3c10fc82010-05-13 02:40:51 +00001817 double
1818 a;
1819
1820 last = kernel;
1821 for (a=angle; a<355.0; a+=angle) {
cristya9a61ad2010-05-13 12:47:41 +00001822 clone = CloneKernelInfo(last);
1823 RotateKernelInfo(clone, angle);
1824 last->next = clone;
1825 last = clone;
anthony3c10fc82010-05-13 02:40:51 +00001826 }
1827}
1828
1829/*
1830%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1831% %
1832% %
1833% %
anthony46a369d2010-05-19 02:41:48 +00001834+ C a l c M e t a K e r n a l I n f o %
1835% %
1836% %
1837% %
1838%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1839%
1840% CalcKernelMetaData() recalculate the KernelInfo meta-data of this kernel only,
1841% using the kernel values. This should only ne used if it is not posible to
1842% calculate that meta-data in some easier way.
1843%
1844% It is important that the meta-data is correct before ScaleKernelInfo() is
1845% used to perform kernel normalization.
1846%
1847% The format of the CalcKernelMetaData method is:
1848%
1849% void CalcKernelMetaData(KernelInfo *kernel, const double scale )
1850%
1851% A description of each parameter follows:
1852%
1853% o kernel: the Morphology/Convolution kernel to modify
1854%
1855% WARNING: Minimum and Maximum values are assumed to include zero, even if
1856% zero is not part of the kernel (as in Gaussian Derived kernels). This
1857% however is not true for flat-shaped morphological kernels.
1858%
1859% WARNING: Only the specific kernel pointed to is modified, not a list of
1860% multiple kernels.
1861%
1862% This is an internal function and not expected to be useful outside this
1863% module. This could change however.
1864*/
1865static void CalcKernelMetaData(KernelInfo *kernel)
1866{
1867 register unsigned long
1868 i;
1869
1870 kernel->minimum = kernel->maximum = 0.0;
1871 kernel->negative_range = kernel->positive_range = 0.0;
1872 for (i=0; i < (kernel->width*kernel->height); i++)
1873 {
1874 if ( fabs(kernel->values[i]) < MagickEpsilon )
1875 kernel->values[i] = 0.0;
1876 ( kernel->values[i] < 0)
1877 ? ( kernel->negative_range += kernel->values[i] )
1878 : ( kernel->positive_range += kernel->values[i] );
1879 Minimize(kernel->minimum, kernel->values[i]);
1880 Maximize(kernel->maximum, kernel->values[i]);
1881 }
1882
1883 return;
1884}
1885
1886/*
1887%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1888% %
1889% %
1890% %
anthony9eb4f742010-05-18 02:45:54 +00001891% M o r p h o l o g y A p p l y %
anthony602ab9b2010-01-05 08:06:50 +00001892% %
1893% %
1894% %
1895%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1896%
anthony9eb4f742010-05-18 02:45:54 +00001897% MorphologyApply() applies a morphological method, multiple times using
1898% a list of multiple kernels.
anthony602ab9b2010-01-05 08:06:50 +00001899%
anthony9eb4f742010-05-18 02:45:54 +00001900% It is basically equivelent to as MorphologyImageChannel() (see below) but
1901% without user controls, that that function extracts and applies to kernels
1902% and morphology methods.
1903%
1904% More specifically kernels are not normalized/scaled/blended by the
1905% 'convolve:scale' Image Artifact (-set setting), and the convolve bias
1906% (-bias setting or image->bias) is passed directly to this function,
1907% and not extracted from an image.
anthony602ab9b2010-01-05 08:06:50 +00001908%
1909% The format of the MorphologyImage method is:
1910%
anthony9eb4f742010-05-18 02:45:54 +00001911% Image *MorphologyApply(const Image *image,MorphologyMethod method,
1912% const long iterations,const KernelInfo *kernel,const double bias,
1913% ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001914%
1915% A description of each parameter follows:
1916%
1917% o image: the image.
1918%
1919% o method: the morphology method to be applied.
1920%
1921% o iterations: apply the operation this many times (or no change).
1922% A value of -1 means loop until no change found.
1923% How this is applied may depend on the morphology method.
1924% Typically this is a value of 1.
1925%
1926% o channel: the channel type.
1927%
1928% o kernel: An array of double representing the morphology kernel.
anthony29188a82010-01-22 10:12:34 +00001929% Warning: kernel may be normalized for the Convolve method.
anthony602ab9b2010-01-05 08:06:50 +00001930%
anthony9eb4f742010-05-18 02:45:54 +00001931% o bias: Convolution Bias to use.
1932%
anthony602ab9b2010-01-05 08:06:50 +00001933% o exception: return any errors or warnings in this structure.
1934%
anthony602ab9b2010-01-05 08:06:50 +00001935*/
1936
anthony930be612010-02-08 04:26:15 +00001937
anthony9eb4f742010-05-18 02:45:54 +00001938/* Apply a Morphology Primative to an image using the given kernel.
1939** Two pre-created images must be provided, no image is created.
1940** Returning the number of pixels that changed.
1941*/
anthony46a369d2010-05-19 02:41:48 +00001942static unsigned long MorphologyPrimitive(const Image *image, Image
anthony602ab9b2010-01-05 08:06:50 +00001943 *result_image, const MorphologyMethod method, const ChannelType channel,
anthony9eb4f742010-05-18 02:45:54 +00001944 const KernelInfo *kernel,const double bias,ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001945{
cristy2be15382010-01-21 02:38:03 +00001946#define MorphologyTag "Morphology/Image"
anthony602ab9b2010-01-05 08:06:50 +00001947
1948 long
cristy150989e2010-02-01 14:59:39 +00001949 progress,
anthony29188a82010-01-22 10:12:34 +00001950 y, offx, offy,
anthony602ab9b2010-01-05 08:06:50 +00001951 changed;
1952
1953 MagickBooleanType
1954 status;
1955
anthony602ab9b2010-01-05 08:06:50 +00001956 CacheView
1957 *p_view,
1958 *q_view;
1959
anthony602ab9b2010-01-05 08:06:50 +00001960 status=MagickTrue;
1961 changed=0;
1962 progress=0;
1963
anthony602ab9b2010-01-05 08:06:50 +00001964 p_view=AcquireCacheView(image);
1965 q_view=AcquireCacheView(result_image);
anthony29188a82010-01-22 10:12:34 +00001966
anthonycc6c8362010-01-25 04:14:01 +00001967 /* Some methods (including convolve) needs use a reflected kernel.
anthony9eb4f742010-05-18 02:45:54 +00001968 * Adjust 'origin' offsets to loop though kernel as a reflection.
anthony29188a82010-01-22 10:12:34 +00001969 */
cristyc99304f2010-02-01 15:26:27 +00001970 offx = kernel->x;
1971 offy = kernel->y;
anthony29188a82010-01-22 10:12:34 +00001972 switch(method) {
anthony930be612010-02-08 04:26:15 +00001973 case ConvolveMorphology:
1974 case DilateMorphology:
1975 case DilateIntensityMorphology:
1976 case DistanceMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001977 /* kernel needs to used with reflection about origin */
cristy150989e2010-02-01 14:59:39 +00001978 offx = (long) kernel->width-offx-1;
1979 offy = (long) kernel->height-offy-1;
anthony29188a82010-01-22 10:12:34 +00001980 break;
anthony5ef8e942010-05-11 06:51:12 +00001981 case ErodeMorphology:
1982 case ErodeIntensityMorphology:
1983 case HitAndMissMorphology:
1984 case ThinningMorphology:
1985 case ThickenMorphology:
1986 /* kernel is user as is, without reflection */
1987 break;
anthony930be612010-02-08 04:26:15 +00001988 default:
anthony9eb4f742010-05-18 02:45:54 +00001989 assert("Not a Primitive Morphology Method" != (char *) NULL);
anthony930be612010-02-08 04:26:15 +00001990 break;
anthony29188a82010-01-22 10:12:34 +00001991 }
1992
anthony602ab9b2010-01-05 08:06:50 +00001993#if defined(MAGICKCORE_OPENMP_SUPPORT)
1994 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1995#endif
cristy150989e2010-02-01 14:59:39 +00001996 for (y=0; y < (long) image->rows; y++)
anthony602ab9b2010-01-05 08:06:50 +00001997 {
1998 MagickBooleanType
1999 sync;
2000
2001 register const PixelPacket
2002 *restrict p;
2003
2004 register const IndexPacket
2005 *restrict p_indexes;
2006
2007 register PixelPacket
2008 *restrict q;
2009
2010 register IndexPacket
2011 *restrict q_indexes;
2012
cristy150989e2010-02-01 14:59:39 +00002013 register long
anthony602ab9b2010-01-05 08:06:50 +00002014 x;
2015
anthony29188a82010-01-22 10:12:34 +00002016 unsigned long
anthony602ab9b2010-01-05 08:06:50 +00002017 r;
2018
2019 if (status == MagickFalse)
2020 continue;
anthony29188a82010-01-22 10:12:34 +00002021 p=GetCacheViewVirtualPixels(p_view, -offx, y-offy,
2022 image->columns+kernel->width, kernel->height, exception);
anthony602ab9b2010-01-05 08:06:50 +00002023 q=GetCacheViewAuthenticPixels(q_view,0,y,result_image->columns,1,
2024 exception);
2025 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2026 {
2027 status=MagickFalse;
2028 continue;
2029 }
2030 p_indexes=GetCacheViewVirtualIndexQueue(p_view);
2031 q_indexes=GetCacheViewAuthenticIndexQueue(q_view);
anthony29188a82010-01-22 10:12:34 +00002032 r = (image->columns+kernel->width)*offy+offx; /* constant */
2033
cristy150989e2010-02-01 14:59:39 +00002034 for (x=0; x < (long) image->columns; x++)
anthony602ab9b2010-01-05 08:06:50 +00002035 {
cristy150989e2010-02-01 14:59:39 +00002036 long
anthony602ab9b2010-01-05 08:06:50 +00002037 v;
2038
cristy150989e2010-02-01 14:59:39 +00002039 register long
anthony602ab9b2010-01-05 08:06:50 +00002040 u;
2041
2042 register const double
2043 *restrict k;
2044
2045 register const PixelPacket
2046 *restrict k_pixels;
2047
2048 register const IndexPacket
2049 *restrict k_indexes;
2050
2051 MagickPixelPacket
anthony5ef8e942010-05-11 06:51:12 +00002052 result,
2053 min,
2054 max;
anthony602ab9b2010-01-05 08:06:50 +00002055
anthony29188a82010-01-22 10:12:34 +00002056 /* Copy input to ouput image for unused channels
anthony83ba99b2010-01-24 08:48:15 +00002057 * This removes need for 'cloning' a new image every iteration
anthony29188a82010-01-22 10:12:34 +00002058 */
anthony602ab9b2010-01-05 08:06:50 +00002059 *q = p[r];
2060 if (image->colorspace == CMYKColorspace)
2061 q_indexes[x] = p_indexes[r];
2062
anthony5ef8e942010-05-11 06:51:12 +00002063 /* Defaults */
2064 min.red =
2065 min.green =
2066 min.blue =
2067 min.opacity =
2068 min.index = (MagickRealType) QuantumRange;
2069 max.red =
2070 max.green =
2071 max.blue =
2072 max.opacity =
2073 max.index = (MagickRealType) 0;
anthony9eb4f742010-05-18 02:45:54 +00002074 /* default result is the original pixel value */
anthony5ef8e942010-05-11 06:51:12 +00002075 result.red = (MagickRealType) p[r].red;
2076 result.green = (MagickRealType) p[r].green;
2077 result.blue = (MagickRealType) p[r].blue;
2078 result.opacity = QuantumRange - (MagickRealType) p[r].opacity;
cristye96405a2010-05-19 02:24:31 +00002079 result.index = 0.0;
anthony5ef8e942010-05-11 06:51:12 +00002080 if ( image->colorspace == CMYKColorspace)
2081 result.index = (MagickRealType) p_indexes[r];
2082
anthony602ab9b2010-01-05 08:06:50 +00002083 switch (method) {
2084 case ConvolveMorphology:
anthony9eb4f742010-05-18 02:45:54 +00002085 /* Set the user defined bias of the weighted average output */
2086 result.red =
2087 result.green =
2088 result.blue =
2089 result.opacity =
2090 result.index = bias;
anthony930be612010-02-08 04:26:15 +00002091 break;
anthony4fd27e22010-02-07 08:17:18 +00002092 case DilateIntensityMorphology:
2093 case ErodeIntensityMorphology:
anthony9eb4f742010-05-18 02:45:54 +00002094 /* use a boolean flag indicating when first match found */
2095 result.red = 0.0; /* result is not used otherwise */
anthony4fd27e22010-02-07 08:17:18 +00002096 break;
anthony602ab9b2010-01-05 08:06:50 +00002097 default:
anthony602ab9b2010-01-05 08:06:50 +00002098 break;
2099 }
2100
2101 switch ( method ) {
2102 case ConvolveMorphology:
anthony930be612010-02-08 04:26:15 +00002103 /* Weighted Average of pixels using reflected kernel
2104 **
2105 ** NOTE for correct working of this operation for asymetrical
2106 ** kernels, the kernel needs to be applied in its reflected form.
2107 ** That is its values needs to be reversed.
2108 **
2109 ** Correlation is actually the same as this but without reflecting
2110 ** the kernel, and thus 'lower-level' that Convolution. However
2111 ** as Convolution is the more common method used, and it does not
2112 ** really cost us much in terms of processing to use a reflected
anthony5ef8e942010-05-11 06:51:12 +00002113 ** kernel, so it is Convolution that is implemented.
anthony930be612010-02-08 04:26:15 +00002114 **
2115 ** Correlation will have its kernel reflected before calling
2116 ** this function to do a Convolve.
2117 **
2118 ** For more details of Correlation vs Convolution see
2119 ** http://www.cs.umd.edu/~djacobs/CMSC426/Convolution.pdf
2120 */
anthony5ef8e942010-05-11 06:51:12 +00002121 if (((channel & SyncChannels) != 0 ) &&
2122 (image->matte == MagickTrue))
2123 { /* Channel has a 'Sync' Flag, and Alpha Channel enabled.
2124 ** Weight the color channels with Alpha Channel so that
2125 ** transparent pixels are not part of the results.
2126 */
anthony602ab9b2010-01-05 08:06:50 +00002127 MagickRealType
anthony5ef8e942010-05-11 06:51:12 +00002128 alpha, /* color channel weighting : kernel*alpha */
2129 gamma; /* divisor, sum of weighting values */
anthony602ab9b2010-01-05 08:06:50 +00002130
2131 gamma=0.0;
anthony29188a82010-01-22 10:12:34 +00002132 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00002133 k_pixels = p;
2134 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002135 for (v=0; v < (long) kernel->height; v++) {
2136 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00002137 if ( IsNan(*k) ) continue;
2138 alpha=(*k)*(QuantumScale*(QuantumRange-
2139 k_pixels[u].opacity));
2140 gamma += alpha;
2141 result.red += alpha*k_pixels[u].red;
2142 result.green += alpha*k_pixels[u].green;
2143 result.blue += alpha*k_pixels[u].blue;
anthony83ba99b2010-01-24 08:48:15 +00002144 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00002145 if ( image->colorspace == CMYKColorspace)
2146 result.index += alpha*k_indexes[u];
2147 }
2148 k_pixels += image->columns+kernel->width;
2149 k_indexes += image->columns+kernel->width;
2150 }
2151 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
anthony83ba99b2010-01-24 08:48:15 +00002152 result.red *= gamma;
2153 result.green *= gamma;
2154 result.blue *= gamma;
2155 result.opacity *= gamma;
2156 result.index *= gamma;
anthony602ab9b2010-01-05 08:06:50 +00002157 }
anthony5ef8e942010-05-11 06:51:12 +00002158 else
2159 {
2160 /* No 'Sync' flag, or no Alpha involved.
2161 ** Convolution is simple individual channel weigthed sum.
2162 */
2163 k = &kernel->values[ kernel->width*kernel->height-1 ];
2164 k_pixels = p;
2165 k_indexes = p_indexes;
2166 for (v=0; v < (long) kernel->height; v++) {
2167 for (u=0; u < (long) kernel->width; u++, k--) {
2168 if ( IsNan(*k) ) continue;
2169 result.red += (*k)*k_pixels[u].red;
2170 result.green += (*k)*k_pixels[u].green;
2171 result.blue += (*k)*k_pixels[u].blue;
2172 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
2173 if ( image->colorspace == CMYKColorspace)
2174 result.index += (*k)*k_indexes[u];
2175 }
2176 k_pixels += image->columns+kernel->width;
2177 k_indexes += image->columns+kernel->width;
2178 }
2179 }
anthony602ab9b2010-01-05 08:06:50 +00002180 break;
2181
anthony4fd27e22010-02-07 08:17:18 +00002182 case ErodeMorphology:
anthony5ef8e942010-05-11 06:51:12 +00002183 /* Minimum Value within kernel neighbourhood
anthony930be612010-02-08 04:26:15 +00002184 **
2185 ** NOTE that the kernel is not reflected for this operation!
2186 **
2187 ** NOTE: in normal Greyscale Morphology, the kernel value should
2188 ** be added to the real value, this is currently not done, due to
2189 ** the nature of the boolean kernels being used.
2190 */
anthony4fd27e22010-02-07 08:17:18 +00002191 k = kernel->values;
2192 k_pixels = p;
2193 k_indexes = p_indexes;
2194 for (v=0; v < (long) kernel->height; v++) {
2195 for (u=0; u < (long) kernel->width; u++, k++) {
2196 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony5ef8e942010-05-11 06:51:12 +00002197 Minimize(min.red, (double) k_pixels[u].red);
2198 Minimize(min.green, (double) k_pixels[u].green);
2199 Minimize(min.blue, (double) k_pixels[u].blue);
2200 Minimize(min.opacity,
anthonyd37a5cb2010-05-07 06:37:03 +00002201 QuantumRange-(double) k_pixels[u].opacity);
anthony4fd27e22010-02-07 08:17:18 +00002202 if ( image->colorspace == CMYKColorspace)
anthony5ef8e942010-05-11 06:51:12 +00002203 Minimize(min.index, (double) k_indexes[u]);
anthony4fd27e22010-02-07 08:17:18 +00002204 }
2205 k_pixels += image->columns+kernel->width;
2206 k_indexes += image->columns+kernel->width;
2207 }
2208 break;
2209
anthony5ef8e942010-05-11 06:51:12 +00002210
anthony83ba99b2010-01-24 08:48:15 +00002211 case DilateMorphology:
anthony5ef8e942010-05-11 06:51:12 +00002212 /* Maximum Value within kernel neighbourhood
anthony930be612010-02-08 04:26:15 +00002213 **
2214 ** NOTE for correct working of this operation for asymetrical
2215 ** kernels, the kernel needs to be applied in its reflected form.
2216 ** That is its values needs to be reversed.
2217 **
2218 ** NOTE: in normal Greyscale Morphology, the kernel value should
2219 ** be added to the real value, this is currently not done, due to
2220 ** the nature of the boolean kernels being used.
2221 **
2222 */
anthony29188a82010-01-22 10:12:34 +00002223 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00002224 k_pixels = p;
2225 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002226 for (v=0; v < (long) kernel->height; v++) {
2227 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00002228 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony5ef8e942010-05-11 06:51:12 +00002229 Maximize(max.red, (double) k_pixels[u].red);
2230 Maximize(max.green, (double) k_pixels[u].green);
2231 Maximize(max.blue, (double) k_pixels[u].blue);
2232 Maximize(max.opacity,
anthonyd37a5cb2010-05-07 06:37:03 +00002233 QuantumRange-(double) k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00002234 if ( image->colorspace == CMYKColorspace)
anthony5ef8e942010-05-11 06:51:12 +00002235 Maximize(max.index, (double) k_indexes[u]);
anthony602ab9b2010-01-05 08:06:50 +00002236 }
2237 k_pixels += image->columns+kernel->width;
2238 k_indexes += image->columns+kernel->width;
2239 }
anthony602ab9b2010-01-05 08:06:50 +00002240 break;
2241
anthony5ef8e942010-05-11 06:51:12 +00002242 case HitAndMissMorphology:
2243 case ThinningMorphology:
2244 case ThickenMorphology:
2245 /* Minimum of Foreground Pixel minus Maxumum of Background Pixels
2246 **
2247 ** NOTE that the kernel is not reflected for this operation,
2248 ** and consists of both foreground and background pixel
2249 ** neighbourhoods, 0.0 for background, and 1.0 for foreground
2250 ** with either Nan or 0.5 values for don't care.
2251 **
2252 ** Note that this can produce negative results, though really
2253 ** only a positive match has any real value.
2254 */
2255 k = kernel->values;
2256 k_pixels = p;
2257 k_indexes = p_indexes;
2258 for (v=0; v < (long) kernel->height; v++) {
2259 for (u=0; u < (long) kernel->width; u++, k++) {
2260 if ( IsNan(*k) ) continue;
2261 if ( (*k) > 0.7 )
2262 { /* minimim of foreground pixels */
2263 Minimize(min.red, (double) k_pixels[u].red);
2264 Minimize(min.green, (double) k_pixels[u].green);
2265 Minimize(min.blue, (double) k_pixels[u].blue);
2266 Minimize(min.opacity,
2267 QuantumRange-(double) k_pixels[u].opacity);
2268 if ( image->colorspace == CMYKColorspace)
2269 Minimize(min.index, (double) k_indexes[u]);
2270 }
2271 else if ( (*k) < 0.3 )
2272 { /* maximum of background pixels */
2273 Maximize(max.red, (double) k_pixels[u].red);
2274 Maximize(max.green, (double) k_pixels[u].green);
2275 Maximize(max.blue, (double) k_pixels[u].blue);
2276 Maximize(max.opacity,
2277 QuantumRange-(double) k_pixels[u].opacity);
2278 if ( image->colorspace == CMYKColorspace)
2279 Maximize(max.index, (double) k_indexes[u]);
2280 }
2281 }
2282 k_pixels += image->columns+kernel->width;
2283 k_indexes += image->columns+kernel->width;
2284 }
2285 /* Pattern Match only if min fg larger than min bg pixels */
2286 min.red -= max.red; Maximize( min.red, 0.0 );
2287 min.green -= max.green; Maximize( min.green, 0.0 );
2288 min.blue -= max.blue; Maximize( min.blue, 0.0 );
2289 min.opacity -= max.opacity; Maximize( min.opacity, 0.0 );
2290 min.index -= max.index; Maximize( min.index, 0.0 );
2291 break;
2292
anthony4fd27e22010-02-07 08:17:18 +00002293 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00002294 /* Select Pixel with Minimum Intensity within kernel neighbourhood
2295 **
2296 ** WARNING: the intensity test fails for CMYK and does not
2297 ** take into account the moderating effect of teh alpha channel
2298 ** on the intensity.
2299 **
2300 ** NOTE that the kernel is not reflected for this operation!
2301 */
anthony602ab9b2010-01-05 08:06:50 +00002302 k = kernel->values;
2303 k_pixels = p;
2304 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002305 for (v=0; v < (long) kernel->height; v++) {
2306 for (u=0; u < (long) kernel->width; u++, k++) {
anthony602ab9b2010-01-05 08:06:50 +00002307 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony4fd27e22010-02-07 08:17:18 +00002308 if ( result.red == 0.0 ||
2309 PixelIntensity(&(k_pixels[u])) < PixelIntensity(q) ) {
2310 /* copy the whole pixel - no channel selection */
2311 *q = k_pixels[u];
2312 if ( result.red > 0.0 ) changed++;
2313 result.red = 1.0;
2314 }
anthony602ab9b2010-01-05 08:06:50 +00002315 }
2316 k_pixels += image->columns+kernel->width;
2317 k_indexes += image->columns+kernel->width;
2318 }
anthony602ab9b2010-01-05 08:06:50 +00002319 break;
2320
anthony83ba99b2010-01-24 08:48:15 +00002321 case DilateIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00002322 /* Select Pixel with Maximum Intensity within kernel neighbourhood
2323 **
2324 ** WARNING: the intensity test fails for CMYK and does not
anthony9eb4f742010-05-18 02:45:54 +00002325 ** take into account the moderating effect of the alpha channel
2326 ** on the intensity (yet).
anthony930be612010-02-08 04:26:15 +00002327 **
2328 ** NOTE for correct working of this operation for asymetrical
2329 ** kernels, the kernel needs to be applied in its reflected form.
2330 ** That is its values needs to be reversed.
2331 */
anthony29188a82010-01-22 10:12:34 +00002332 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00002333 k_pixels = p;
2334 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002335 for (v=0; v < (long) kernel->height; v++) {
2336 for (u=0; u < (long) kernel->width; u++, k--) {
anthony29188a82010-01-22 10:12:34 +00002337 if ( IsNan(*k) || (*k) < 0.5 ) continue; /* boolean kernel */
2338 if ( result.red == 0.0 ||
2339 PixelIntensity(&(k_pixels[u])) > PixelIntensity(q) ) {
2340 /* copy the whole pixel - no channel selection */
2341 *q = k_pixels[u];
2342 if ( result.red > 0.0 ) changed++;
2343 result.red = 1.0;
2344 }
anthony602ab9b2010-01-05 08:06:50 +00002345 }
2346 k_pixels += image->columns+kernel->width;
2347 k_indexes += image->columns+kernel->width;
2348 }
anthony602ab9b2010-01-05 08:06:50 +00002349 break;
2350
anthony5ef8e942010-05-11 06:51:12 +00002351
anthony602ab9b2010-01-05 08:06:50 +00002352 case DistanceMorphology:
anthony930be612010-02-08 04:26:15 +00002353 /* Add kernel Value and select the minimum value found.
2354 ** The result is a iterative distance from edge of image shape.
2355 **
2356 ** All Distance Kernels are symetrical, but that may not always
2357 ** be the case. For example how about a distance from left edges?
2358 ** To work correctly with asymetrical kernels the reflected kernel
2359 ** needs to be applied.
anthony5ef8e942010-05-11 06:51:12 +00002360 **
2361 ** Actually this is really a GreyErode with a negative kernel!
2362 **
anthony930be612010-02-08 04:26:15 +00002363 */
anthony29188a82010-01-22 10:12:34 +00002364 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00002365 k_pixels = p;
2366 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002367 for (v=0; v < (long) kernel->height; v++) {
2368 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00002369 if ( IsNan(*k) ) continue;
2370 Minimize(result.red, (*k)+k_pixels[u].red);
2371 Minimize(result.green, (*k)+k_pixels[u].green);
2372 Minimize(result.blue, (*k)+k_pixels[u].blue);
2373 Minimize(result.opacity, (*k)+QuantumRange-k_pixels[u].opacity);
2374 if ( image->colorspace == CMYKColorspace)
2375 Minimize(result.index, (*k)+k_indexes[u]);
2376 }
2377 k_pixels += image->columns+kernel->width;
2378 k_indexes += image->columns+kernel->width;
2379 }
anthony602ab9b2010-01-05 08:06:50 +00002380 break;
2381
2382 case UndefinedMorphology:
2383 default:
2384 break; /* Do nothing */
anthony83ba99b2010-01-24 08:48:15 +00002385 }
anthony5ef8e942010-05-11 06:51:12 +00002386 /* Final mathematics of results (combine with original image?)
2387 **
2388 ** NOTE: Difference Morphology operators Edge* and *Hat could also
2389 ** be done here but works better with iteration as a image difference
2390 ** in the controling function (below). Thicken and Thinning however
2391 ** should be done here so thay can be iterated correctly.
2392 */
2393 switch ( method ) {
2394 case HitAndMissMorphology:
2395 case ErodeMorphology:
2396 result = min; /* minimum of neighbourhood */
2397 break;
2398 case DilateMorphology:
2399 result = max; /* maximum of neighbourhood */
2400 break;
2401 case ThinningMorphology:
2402 /* subtract pattern match from original */
2403 result.red -= min.red;
2404 result.green -= min.green;
2405 result.blue -= min.blue;
2406 result.opacity -= min.opacity;
2407 result.index -= min.index;
2408 break;
2409 case ThickenMorphology:
2410 /* Union with original image (maximize) - or should this be + */
2411 Maximize( result.red, min.red );
2412 Maximize( result.green, min.green );
2413 Maximize( result.blue, min.blue );
2414 Maximize( result.opacity, min.opacity );
2415 Maximize( result.index, min.index );
2416 break;
2417 default:
2418 /* result directly calculated or assigned */
2419 break;
2420 }
2421 /* Assign the resulting pixel values - Clamping Result */
anthony83ba99b2010-01-24 08:48:15 +00002422 switch ( method ) {
2423 case UndefinedMorphology:
2424 case DilateIntensityMorphology:
2425 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00002426 break; /* full pixel was directly assigned - not a channel method */
anthony83ba99b2010-01-24 08:48:15 +00002427 default:
anthony83ba99b2010-01-24 08:48:15 +00002428 if ((channel & RedChannel) != 0)
2429 q->red = ClampToQuantum(result.red);
2430 if ((channel & GreenChannel) != 0)
2431 q->green = ClampToQuantum(result.green);
2432 if ((channel & BlueChannel) != 0)
2433 q->blue = ClampToQuantum(result.blue);
2434 if ((channel & OpacityChannel) != 0
2435 && image->matte == MagickTrue )
2436 q->opacity = ClampToQuantum(QuantumRange-result.opacity);
2437 if ((channel & IndexChannel) != 0
2438 && image->colorspace == CMYKColorspace)
2439 q_indexes[x] = ClampToQuantum(result.index);
2440 break;
2441 }
anthony5ef8e942010-05-11 06:51:12 +00002442 /* Count up changed pixels */
anthony83ba99b2010-01-24 08:48:15 +00002443 if ( ( p[r].red != q->red )
2444 || ( p[r].green != q->green )
2445 || ( p[r].blue != q->blue )
2446 || ( p[r].opacity != q->opacity )
2447 || ( image->colorspace == CMYKColorspace &&
2448 p_indexes[r] != q_indexes[x] ) )
2449 changed++; /* The pixel had some value changed! */
anthony602ab9b2010-01-05 08:06:50 +00002450 p++;
2451 q++;
anthony83ba99b2010-01-24 08:48:15 +00002452 } /* x */
anthony602ab9b2010-01-05 08:06:50 +00002453 sync=SyncCacheViewAuthenticPixels(q_view,exception);
2454 if (sync == MagickFalse)
2455 status=MagickFalse;
2456 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2457 {
2458 MagickBooleanType
2459 proceed;
2460
2461#if defined(MAGICKCORE_OPENMP_SUPPORT)
2462 #pragma omp critical (MagickCore_MorphologyImage)
2463#endif
2464 proceed=SetImageProgress(image,MorphologyTag,progress++,image->rows);
2465 if (proceed == MagickFalse)
2466 status=MagickFalse;
2467 }
anthony83ba99b2010-01-24 08:48:15 +00002468 } /* y */
anthony602ab9b2010-01-05 08:06:50 +00002469 result_image->type=image->type;
2470 q_view=DestroyCacheView(q_view);
2471 p_view=DestroyCacheView(p_view);
cristy150989e2010-02-01 14:59:39 +00002472 return(status ? (unsigned long) changed : 0);
anthony602ab9b2010-01-05 08:06:50 +00002473}
2474
anthony4fd27e22010-02-07 08:17:18 +00002475
anthony9eb4f742010-05-18 02:45:54 +00002476MagickExport Image *MorphologyApply(const Image *image, const ChannelType
2477 channel,const MorphologyMethod method, const long iterations,
2478 const KernelInfo *kernel,const double bias,ExceptionInfo *exception)
cristy2be15382010-01-21 02:38:03 +00002479{
2480 Image
anthony9eb4f742010-05-18 02:45:54 +00002481 *curr_image, /* Image we are working with */
2482 *work_image, /* secondary working image */
2483 *save_image; /* save image for later use */
anthony602ab9b2010-01-05 08:06:50 +00002484
anthony4fd27e22010-02-07 08:17:18 +00002485 KernelInfo
anthony9eb4f742010-05-18 02:45:54 +00002486 *curr_kernel, /* current kernel list to apply */
2487 *this_kernel; /* current individual kernel to apply */
anthony4fd27e22010-02-07 08:17:18 +00002488
2489 MorphologyMethod
cristye96405a2010-05-19 02:24:31 +00002490 primitive; /* the current morphology primitive being applied */
anthony9eb4f742010-05-18 02:45:54 +00002491
2492 MagickBooleanType
2493 verbose; /* verbose output of results */
2494
2495 CompositeOperator
2496 kernel_compose; /* Handling the result of multiple kernels*/
anthony4fd27e22010-02-07 08:17:18 +00002497
anthony1b2bc0a2010-05-12 05:25:22 +00002498 unsigned long
cristye96405a2010-05-19 02:24:31 +00002499 count, /* count of primitive steps applied */
anthony9eb4f742010-05-18 02:45:54 +00002500 loop, /* number of times though kernel list (iterations) */
2501 loop_limit, /* finish looping after this many times */
2502 stage, /* stage number for compound morphology */
cristye96405a2010-05-19 02:24:31 +00002503 changed, /* number pixels changed by one primitive operation */
anthony9eb4f742010-05-18 02:45:54 +00002504 loop_changed, /* changes made over loop though of kernels */
2505 total_changed, /* total count of all changes to image */
2506 kernel_number; /* kernel number being applied */
anthony1b2bc0a2010-05-12 05:25:22 +00002507
anthony602ab9b2010-01-05 08:06:50 +00002508 assert(image != (Image *) NULL);
2509 assert(image->signature == MagickSignature);
anthony4fd27e22010-02-07 08:17:18 +00002510 assert(kernel != (KernelInfo *) NULL);
2511 assert(kernel->signature == MagickSignature);
anthony602ab9b2010-01-05 08:06:50 +00002512 assert(exception != (ExceptionInfo *) NULL);
2513 assert(exception->signature == MagickSignature);
2514
cristye96405a2010-05-19 02:24:31 +00002515 loop_limit = (unsigned long) iterations;
anthony9eb4f742010-05-18 02:45:54 +00002516 if ( iterations < 0 )
2517 loop_limit = image->columns > image->rows ? image->columns : image->rows;
anthony602ab9b2010-01-05 08:06:50 +00002518 if ( iterations == 0 )
2519 return((Image *)NULL); /* null operation - nothing to do! */
2520
2521 /* kernel must be valid at this point
2522 * (except maybe for posible future morphology methods like "Prune"
2523 */
cristy2be15382010-01-21 02:38:03 +00002524 assert(kernel != (KernelInfo *)NULL);
anthony602ab9b2010-01-05 08:06:50 +00002525
cristye96405a2010-05-19 02:24:31 +00002526 verbose = ( GetImageArtifact(image,"verbose") != (const char *) NULL ) ?
2527 MagickTrue : MagickFalse;
anthony4f1dcb72010-05-14 08:43:10 +00002528
anthony9eb4f742010-05-18 02:45:54 +00002529 /* initialise for cleanup */
cristye96405a2010-05-19 02:24:31 +00002530 curr_image = (Image *) image; /* result of morpholgy primitive */
anthony9eb4f742010-05-18 02:45:54 +00002531 work_image = (Image *) NULL; /* secondary working image */
2532 save_image = (Image *) NULL; /* save image for some compound methods */
2533 curr_kernel = (KernelInfo *)kernel; /* allow kernel list to be modified */
anthony4fd27e22010-02-07 08:17:18 +00002534
anthony9eb4f742010-05-18 02:45:54 +00002535 kernel_compose = NoCompositeOp; /* iterated over all kernels */
anthony602ab9b2010-01-05 08:06:50 +00002536
cristye96405a2010-05-19 02:24:31 +00002537 /* Select initial primitive morphology to apply */
2538 primitive = UndefinedMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002539 switch( method ) {
anthony930be612010-02-08 04:26:15 +00002540 case CorrelateMorphology:
2541 /* A Correlation is actually a Convolution with a reflected kernel.
anthony9eb4f742010-05-18 02:45:54 +00002542 ** However a Convolution is a weighted sum using a reflected kernel.
anthony930be612010-02-08 04:26:15 +00002543 ** It may seem stange to convert a Correlation into a Convolution
2544 ** as the Correleation is the simplier method, but Convolution is
2545 ** much more commonly used, and it makes sense to implement it directly
2546 ** so as to avoid the need to duplicate the kernel when it is not
2547 ** required (which is typically the default).
2548 */
anthony9eb4f742010-05-18 02:45:54 +00002549 curr_kernel = CloneKernelInfo(kernel);
anthony1b2bc0a2010-05-12 05:25:22 +00002550 if (curr_kernel == (KernelInfo *) NULL)
2551 goto error_cleanup;
anthony930be612010-02-08 04:26:15 +00002552 RotateKernelInfo(curr_kernel,180);
anthony9eb4f742010-05-18 02:45:54 +00002553 /* FALL THRU to Convolve */
anthonyc94cdb02010-01-06 08:15:29 +00002554 case ConvolveMorphology:
cristye96405a2010-05-19 02:24:31 +00002555 primitive = ConvolveMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002556 kernel_compose = NoCompositeOp;
2557 break;
2558 case ErodeMorphology: /* just erode */
2559 case OpenMorphology: /* erode then dialate */
2560 case EdgeInMorphology: /* erode and image difference */
2561 case TopHatMorphology: /* erode, dilate and image difference */
2562 case SmoothMorphology: /* erode, dilate, dilate, erode */
cristye96405a2010-05-19 02:24:31 +00002563 primitive = ErodeMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002564 break;
2565 case ErodeIntensityMorphology:
2566 case OpenIntensityMorphology:
cristye96405a2010-05-19 02:24:31 +00002567 primitive = ErodeIntensityMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002568 break;
2569 case DilateMorphology: /* just dilate */
2570 case EdgeOutMorphology: /* dilate and image difference */
2571 case EdgeMorphology: /* dilate and erode difference */
cristye96405a2010-05-19 02:24:31 +00002572 primitive = DilateMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002573 break;
2574 case CloseMorphology: /* dilate, then erode */
2575 case BottomHatMorphology: /* dilate and image difference */
2576 curr_kernel = CloneKernelInfo(kernel);
2577 if (curr_kernel == (KernelInfo *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00002578 goto error_cleanup;
anthony9eb4f742010-05-18 02:45:54 +00002579 RotateKernelInfo(curr_kernel,180);
cristye96405a2010-05-19 02:24:31 +00002580 primitive = DilateMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002581 break;
2582 case DilateIntensityMorphology:
2583 case CloseIntensityMorphology:
2584 curr_kernel = CloneKernelInfo(kernel);
2585 if (curr_kernel == (KernelInfo *) NULL)
2586 goto error_cleanup;
2587 RotateKernelInfo(curr_kernel,180);
cristye96405a2010-05-19 02:24:31 +00002588 primitive = DilateIntensityMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002589 break;
2590 case HitAndMissMorphology:
cristye96405a2010-05-19 02:24:31 +00002591 primitive = HitAndMissMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002592 loop_limit = 1; /* iterate only once */
2593 kernel_compose = LightenCompositeOp; /* Union of Hit-And-Miss */
2594 break;
2595 case ThinningMorphology: /* iterative morphology */
2596 case ThickenMorphology:
2597 case DistanceMorphology: /* Distance should never use multple kernels */
2598 case UndefinedMorphology:
cristye96405a2010-05-19 02:24:31 +00002599 primitive = method;
anthony930be612010-02-08 04:26:15 +00002600 break;
anthony602ab9b2010-01-05 08:06:50 +00002601 }
2602
anthony3c1cd552010-05-18 04:33:23 +00002603#if 0
2604 { /* User override of results handling -- Experimental */
anthony9eb4f742010-05-18 02:45:54 +00002605 const char
2606 *artifact = GetImageArtifact(image,"morphology:style");
2607 if ( artifact != (const char *) NULL ) {
2608 if (LocaleCompare("union",artifact) == 0)
2609 kernel_compose = LightenCompositeOp;
2610 if (LocaleCompare("iterate",artifact) == 0)
2611 kernel_compose = NoCompositeOp;
2612 else
2613 kernel_compose = (CompositeOperator) ParseMagickOption(
2614 MagickComposeOptions,MagickFalse,artifact);
2615 if ( kernel_compose == UndefinedCompositeOp )
2616 perror("Invalid \"morphology:compose\" setting\n");
2617 }
2618 }
anthony3c1cd552010-05-18 04:33:23 +00002619#endif
anthony7a01dcf2010-05-11 12:25:52 +00002620
anthony9eb4f742010-05-18 02:45:54 +00002621 /* Initialize compound morphology stages */
cristye96405a2010-05-19 02:24:31 +00002622 count = 0; /* number of low-level morphology primitives performed */
anthony9eb4f742010-05-18 02:45:54 +00002623 total_changed = 0; /* total number of pixels changed thoughout */
2624 stage = 1; /* the compound morphology stage number */
2625
2626 /* compount morphology staging loop */
2627 while ( 1 ) {
2628
2629#if 1
2630 /* Extra information for debugging compound operations */
cristye96405a2010-05-19 02:24:31 +00002631 if ( verbose == MagickTrue && primitive != method )
anthony9eb4f742010-05-18 02:45:54 +00002632 fprintf(stderr, "Morphology %s: Stage %lu %s%s (%s)\n",
2633 MagickOptionToMnemonic(MagickMorphologyOptions, method), stage,
cristye96405a2010-05-19 02:24:31 +00002634 MagickOptionToMnemonic(MagickMorphologyOptions, primitive),
anthony9eb4f742010-05-18 02:45:54 +00002635 ( curr_kernel == kernel) ? "" : "*",
2636 ( kernel_compose == NoCompositeOp ) ? "iterate"
2637 : MagickOptionToMnemonic(MagickComposeOptions, kernel_compose) );
2638#endif
2639
2640 if ( kernel_compose == NoCompositeOp ) {
2641 /******************************
2642 ** Iterate over all Kernels **
2643 ******************************/
2644 loop = 0;
2645 loop_changed = 1;
2646 while ( loop < loop_limit && loop_changed > 0 ) {
2647 loop++; /* the iteration of this kernel */
2648
2649 loop_changed = 0;
2650 this_kernel = curr_kernel;
2651 kernel_number = 0;
2652 while ( this_kernel != NULL ) {
2653
2654 /* Create a destination image, if not yet defined */
2655 if ( work_image == (Image *) NULL )
2656 {
2657 work_image=CloneImage(image,0,0,MagickTrue,exception);
2658 if (work_image == (Image *) NULL)
2659 goto error_cleanup;
2660 if (SetImageStorageClass(work_image,DirectClass) == MagickFalse)
2661 {
2662 InheritException(exception,&work_image->exception);
2663 goto error_cleanup;
2664 }
2665 }
2666
cristye96405a2010-05-19 02:24:31 +00002667 /* morphological primitive curr -> work */
anthony9eb4f742010-05-18 02:45:54 +00002668 count++;
anthony46a369d2010-05-19 02:41:48 +00002669 changed = MorphologyPrimitive(curr_image, work_image, primitive,
anthony9eb4f742010-05-18 02:45:54 +00002670 channel, this_kernel, bias, exception);
2671 loop_changed += changed;
2672 total_changed += changed;
2673
2674 if ( verbose == MagickTrue )
2675 fprintf(stderr, "Morphology %s:%lu.%lu #%lu => Changed %lu\n",
cristye96405a2010-05-19 02:24:31 +00002676 MagickOptionToMnemonic(MagickMorphologyOptions, primitive),
anthony9eb4f742010-05-18 02:45:54 +00002677 loop, kernel_number, count, changed);
2678
2679 /* prepare next loop */
2680 { Image *tmp = work_image; /* swap images for iteration */
2681 work_image = curr_image;
2682 curr_image = tmp;
2683 }
2684 if ( work_image == image )
2685 work_image = (Image *) NULL; /* never assign image to 'work' */
2686 this_kernel = this_kernel->next; /* prepare next kernel (if any) */
anthony1b2bc0a2010-05-12 05:25:22 +00002687 kernel_number++;
2688 }
anthony7a01dcf2010-05-11 12:25:52 +00002689
anthony9eb4f742010-05-18 02:45:54 +00002690 if ( verbose == MagickTrue && kernel->next != NULL )
2691 fprintf(stderr, "Morphology %s:%lu #%lu ===> Changed %lu Total %lu\n",
cristye96405a2010-05-19 02:24:31 +00002692 MagickOptionToMnemonic(MagickMorphologyOptions, primitive),
anthony9eb4f742010-05-18 02:45:54 +00002693 loop, count, loop_changed, total_changed );
anthony602ab9b2010-01-05 08:06:50 +00002694 }
anthony1b2bc0a2010-05-12 05:25:22 +00002695 }
2696
anthony9eb4f742010-05-18 02:45:54 +00002697 else {
2698 /*************************************
2699 ** Composition of Iterated Kernels **
2700 *************************************/
2701 Image
2702 *input_image, /* starting point for kernels */
2703 *union_image;
2704 input_image = curr_image;
2705 union_image = (Image *) NULL;
2706
2707 this_kernel = curr_kernel;
2708 kernel_number = 0;
2709 while ( this_kernel != NULL ) {
2710
2711 if( curr_image != (Image *) NULL && curr_image != input_image )
2712 curr_image=DestroyImage(curr_image);
2713 curr_image = input_image; /* always start with the original image */
2714
2715 loop = 0;
2716 changed = 1;
2717 loop_changed = 0;
2718 while ( loop < loop_limit && changed > 0 ) {
2719 loop++; /* the iteration of this kernel */
2720
2721 /* Create a destination image, if not defined */
2722 if ( work_image == (Image *) NULL )
2723 {
2724 work_image=CloneImage(image,0,0,MagickTrue,exception);
2725 if (work_image == (Image *) NULL)
2726 goto error_cleanup;
2727 if (SetImageStorageClass(work_image,DirectClass) == MagickFalse)
2728 {
2729 InheritException(exception,&curr_image->exception);
2730 if( union_image != (Image *) NULL )
2731 union_image=DestroyImage(union_image);
2732 if( curr_image != input_image )
2733 curr_image = DestroyImage(curr_image);
2734 curr_image = (Image *) input_image;
2735 goto error_cleanup;
2736 }
2737 }
2738
cristye96405a2010-05-19 02:24:31 +00002739 /* morphological primitive curr -> work */
anthony9eb4f742010-05-18 02:45:54 +00002740 count++;
anthony46a369d2010-05-19 02:41:48 +00002741 changed = MorphologyPrimitive(curr_image,work_image,primitive,
anthony9eb4f742010-05-18 02:45:54 +00002742 channel, this_kernel, bias, exception);
2743 loop_changed += changed;
2744 total_changed += changed;
2745
2746 if ( verbose == MagickTrue )
anthony4f1dcb72010-05-14 08:43:10 +00002747 fprintf(stderr, "Morphology %s:%lu.%lu #%lu => Changed %lu\n",
cristye96405a2010-05-19 02:24:31 +00002748 MagickOptionToMnemonic(MagickMorphologyOptions, primitive),
anthony9eb4f742010-05-18 02:45:54 +00002749 loop, kernel_number, count, changed);
2750
2751 /* prepare next loop */
2752 { Image *tmp = work_image; /* swap images for iteration */
2753 work_image = curr_image; /* curr_image is now the results */
2754 curr_image = tmp;
2755 }
2756 if ( work_image == input_image )
2757 work_image = (Image *) NULL; /* clear work of the input_image */
2758
2759 } /* end kernel iteration */
2760
2761 /* make a union of the iterated kernel */
2762 if ( union_image == (Image *) NULL) /* start the union? */
2763 union_image = curr_image, curr_image = (Image *)NULL;
2764 else
2765 (void) CompositeImageChannel(union_image,
2766 (ChannelType) (channel & ~SyncChannels), kernel_compose,
2767 curr_image, 0, 0);
2768
2769 this_kernel = this_kernel->next; /* next kernel (if any) */
anthony1b2bc0a2010-05-12 05:25:22 +00002770 kernel_number++;
anthony602ab9b2010-01-05 08:06:50 +00002771 }
anthony9eb4f742010-05-18 02:45:54 +00002772
2773 if ( verbose == MagickTrue && kernel->next != NULL && loop_limit > 1 )
2774 fprintf(stderr, "Morphology %s:%lu #%lu ===> Changed %lu Total %lu\n",
cristye96405a2010-05-19 02:24:31 +00002775 MagickOptionToMnemonic(MagickMorphologyOptions, primitive),
anthony9eb4f742010-05-18 02:45:54 +00002776 loop, count, loop_changed, total_changed );
2777
2778#if 0
2779fprintf(stderr, "--E-- image=0x%lx\n", (unsigned long)image);
2780fprintf(stderr, " input=0x%lx\n", (unsigned long)input_image);
2781fprintf(stderr, " union=0x%lx\n", (unsigned long)union_image);
2782fprintf(stderr, " curr =0x%lx\n", (unsigned long)curr_image);
2783fprintf(stderr, " work =0x%lx\n", (unsigned long)work_image);
2784fprintf(stderr, " save =0x%lx\n", (unsigned long)save_image);
2785#endif
2786
2787 /* Finish up - return the union of results */
2788 if( curr_image != (Image *) NULL && curr_image != input_image )
2789 curr_image=DestroyImage(curr_image);
2790 if( input_image != input_image )
2791 input_image = DestroyImage(input_image);
2792 curr_image = union_image;
anthony1b2bc0a2010-05-12 05:25:22 +00002793 }
anthony9eb4f742010-05-18 02:45:54 +00002794
2795 /* Compound Morphology Operations
cristye96405a2010-05-19 02:24:31 +00002796 * set next 'primitive' iteration, and continue
anthony9eb4f742010-05-18 02:45:54 +00002797 * or break when all operations are complete.
2798 */
2799 stage++; /* what is the next stage number to do */
2800 switch( method ) {
2801 case SmoothMorphology: /* open, close */
2802 switch ( stage ) {
2803 /* case 1: initialized above */
2804 case 2: /* open part 2 */
cristye96405a2010-05-19 02:24:31 +00002805 primitive = DilateMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002806 continue;
2807 case 3: /* close part 1 */
2808 curr_kernel = CloneKernelInfo(kernel);
2809 if (curr_kernel == (KernelInfo *) NULL)
2810 goto error_cleanup;
2811 RotateKernelInfo(curr_kernel,180);
2812 continue;
2813 case 4: /* close part 2 */
cristye96405a2010-05-19 02:24:31 +00002814 primitive = ErodeMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002815 continue;
2816 }
2817 break;
2818 case OpenMorphology: /* erode, dilate */
2819 case TopHatMorphology:
cristye96405a2010-05-19 02:24:31 +00002820 primitive = DilateMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002821 if ( stage <= 2 ) continue;
2822 break;
2823 case OpenIntensityMorphology:
cristye96405a2010-05-19 02:24:31 +00002824 primitive = DilateIntensityMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002825 if ( stage <= 2 ) continue;
2826 break;
2827 case CloseMorphology: /* dilate, erode */
2828 case BottomHatMorphology:
cristye96405a2010-05-19 02:24:31 +00002829 primitive = ErodeMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002830 if ( stage <= 2 ) continue;
2831 break;
2832 case CloseIntensityMorphology:
cristye96405a2010-05-19 02:24:31 +00002833 primitive = ErodeIntensityMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002834 if ( stage <= 2 ) continue;
2835 break;
2836 case EdgeMorphology: /* dilate and erode difference */
2837 if (stage <= 2) {
2838 save_image = curr_image;
2839 curr_image = (Image *) image;
cristye96405a2010-05-19 02:24:31 +00002840 primitive = ErodeMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002841 continue;
2842 }
2843 break;
2844 default: /* Primitive Morphology is just finished! */
2845 break;
2846 }
2847
2848 if ( verbose == MagickTrue && count > 1 )
2849 fprintf(stderr, "Morphology %s: ======> Total %lu\n",
2850 MagickOptionToMnemonic(MagickMorphologyOptions, method),
2851 total_changed );
2852
2853 /* If we reach this point we are finished! - Break the Loop */
2854 break;
anthony602ab9b2010-01-05 08:06:50 +00002855 }
anthony930be612010-02-08 04:26:15 +00002856
anthony9eb4f742010-05-18 02:45:54 +00002857 /* Final Post-processing for some Compound Methods
anthony7d10d742010-05-06 07:05:29 +00002858 **
2859 ** The removal of any 'Sync' channel flag in the Image Compositon below
2860 ** ensures the compose method is applied in a purely mathematical way, only
2861 ** the selected channels, without any normal 'alpha blending' normally
2862 ** associated with the compose method.
2863 **
2864 ** Note "method" here is the 'original' morphological method, and not the
2865 ** 'current' morphological method used above to generate "new_image".
2866 */
anthony4fd27e22010-02-07 08:17:18 +00002867 switch( method ) {
2868 case EdgeOutMorphology:
2869 case EdgeInMorphology:
2870 case TopHatMorphology:
2871 case BottomHatMorphology:
anthony9eb4f742010-05-18 02:45:54 +00002872 (void) CompositeImageChannel(curr_image,
2873 (ChannelType) (channel & ~SyncChannels), DifferenceCompositeOp,
2874 image, 0, 0);
anthony4fd27e22010-02-07 08:17:18 +00002875 break;
anthony7d10d742010-05-06 07:05:29 +00002876 case EdgeMorphology:
anthony9eb4f742010-05-18 02:45:54 +00002877 /* Difference the Eroded Image with a Dilate image */
2878 (void) CompositeImageChannel(curr_image,
2879 (ChannelType) (channel & ~SyncChannels), DifferenceCompositeOp,
2880 save_image, 0, 0);
anthony4fd27e22010-02-07 08:17:18 +00002881 break;
2882 default:
2883 break;
2884 }
anthony602ab9b2010-01-05 08:06:50 +00002885
anthony9eb4f742010-05-18 02:45:54 +00002886 goto exit_cleanup;
anthony1b2bc0a2010-05-12 05:25:22 +00002887
2888 /* Yes goto's are bad, but in this case it makes cleanup lot more efficient */
2889error_cleanup:
anthony9eb4f742010-05-18 02:45:54 +00002890 if ( curr_image != (Image *) NULL && curr_image != image )
cristye96405a2010-05-19 02:24:31 +00002891 (void) DestroyImage(curr_image);
anthony1b2bc0a2010-05-12 05:25:22 +00002892exit_cleanup:
anthony9eb4f742010-05-18 02:45:54 +00002893 if ( work_image != (Image *) NULL )
cristye96405a2010-05-19 02:24:31 +00002894 (void) DestroyImage(work_image);
anthony9eb4f742010-05-18 02:45:54 +00002895 if ( save_image != (Image *) NULL )
cristye96405a2010-05-19 02:24:31 +00002896 (void) DestroyImage(save_image);
anthony9eb4f742010-05-18 02:45:54 +00002897 return(curr_image);
2898}
2899
2900/*
2901%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2902% %
2903% %
2904% %
2905% M o r p h o l o g y I m a g e C h a n n e l %
2906% %
2907% %
2908% %
2909%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2910%
2911% MorphologyImageChannel() applies a user supplied kernel to the image
2912% according to the given mophology method.
2913%
2914% This function applies any and all user defined settings before calling
2915% the above internal function MorphologyApply().
2916%
2917% User defined settings include...
anthony46a369d2010-05-19 02:41:48 +00002918% * Output Bias for Convolution and correlation ("-bias")
2919% * Kernel Scale/normalize settings ("-set 'option:convolve:scale'")
2920% This can also includes the addition of a scaled unity kernel.
2921% * Show Kernel being applied ("-set option:showkernel 1")
anthony9eb4f742010-05-18 02:45:54 +00002922%
2923% The format of the MorphologyImage method is:
2924%
2925% Image *MorphologyImage(const Image *image,MorphologyMethod method,
2926% const long iterations,KernelInfo *kernel,ExceptionInfo *exception)
2927%
2928% Image *MorphologyImageChannel(const Image *image, const ChannelType
2929% channel,MorphologyMethod method,const long iterations,
2930% KernelInfo *kernel,ExceptionInfo *exception)
2931%
2932% A description of each parameter follows:
2933%
2934% o image: the image.
2935%
2936% o method: the morphology method to be applied.
2937%
2938% o iterations: apply the operation this many times (or no change).
2939% A value of -1 means loop until no change found.
2940% How this is applied may depend on the morphology method.
2941% Typically this is a value of 1.
2942%
2943% o channel: the channel type.
2944%
2945% o kernel: An array of double representing the morphology kernel.
2946% Warning: kernel may be normalized for the Convolve method.
2947%
2948% o exception: return any errors or warnings in this structure.
2949%
2950*/
2951
2952MagickExport Image *MorphologyImageChannel(const Image *image,
2953 const ChannelType channel,const MorphologyMethod method,
2954 const long iterations,const KernelInfo *kernel,ExceptionInfo *exception)
2955{
2956 const char
2957 *artifact;
2958
2959 KernelInfo
2960 *curr_kernel;
2961
2962 Image
2963 *morphology_image;
2964
2965
anthony46a369d2010-05-19 02:41:48 +00002966 /* Apply Convolve/Correlate Normalization and Scaling Factors.
2967 * This is done BEFORE the ShowKernelInfo() function is called so that
2968 * users can see the results of the 'option:convolve:scale' option.
anthony9eb4f742010-05-18 02:45:54 +00002969 */
2970 curr_kernel = (KernelInfo *) kernel;
anthonyf71ca292010-05-19 04:08:43 +00002971 if ( method == ConvolveMorphology || method == CorrelateMorphology )
anthony9eb4f742010-05-18 02:45:54 +00002972 {
2973 artifact = GetImageArtifact(image,"convolve:scale");
2974 if ( artifact != (char *)NULL ) {
anthony9eb4f742010-05-18 02:45:54 +00002975 if ( curr_kernel == kernel )
2976 curr_kernel = CloneKernelInfo(kernel);
2977 if (curr_kernel == (KernelInfo *) NULL) {
2978 curr_kernel=DestroyKernelInfo(curr_kernel);
2979 return((Image *) NULL);
2980 }
anthony46a369d2010-05-19 02:41:48 +00002981 ScaleGeometryKernelInfo(curr_kernel, artifact);
anthony9eb4f742010-05-18 02:45:54 +00002982 }
2983 }
2984
2985 /* display the (normalized) kernel via stderr */
2986 artifact = GetImageArtifact(image,"showkernel");
2987 if ( artifact != (const char *) NULL)
2988 ShowKernelInfo(curr_kernel);
2989
2990 /* Apply the Morphology */
2991 morphology_image = MorphologyApply(image, channel, method, iterations,
2992 curr_kernel, image->bias, exception);
2993
2994 /* Cleanup and Exit */
2995 if ( curr_kernel != kernel )
anthony1b2bc0a2010-05-12 05:25:22 +00002996 curr_kernel=DestroyKernelInfo(curr_kernel);
anthony9eb4f742010-05-18 02:45:54 +00002997 return(morphology_image);
2998}
2999
anthony46a369d2010-05-19 02:41:48 +00003000
anthony9eb4f742010-05-18 02:45:54 +00003001MagickExport Image *MorphologyImage(const Image *image, const MorphologyMethod
3002 method, const long iterations,const KernelInfo *kernel, ExceptionInfo
3003 *exception)
3004{
3005 Image
3006 *morphology_image;
3007
3008 morphology_image=MorphologyImageChannel(image,DefaultChannels,method,
3009 iterations,kernel,exception);
3010 return(morphology_image);
anthony602ab9b2010-01-05 08:06:50 +00003011}
anthony83ba99b2010-01-24 08:48:15 +00003012
3013/*
3014%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3015% %
3016% %
3017% %
anthony4fd27e22010-02-07 08:17:18 +00003018+ R o t a t e K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00003019% %
3020% %
3021% %
3022%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3023%
anthony46a369d2010-05-19 02:41:48 +00003024% RotateKernelInfo() rotates the kernel by the angle given.
3025%
3026% Currently it is restricted to 90 degree angles, of either 1D kernels
3027% or square kernels. And 'circular' rotations of 45 degrees for 3x3 kernels.
3028% It will ignore usless rotations for specific 'named' built-in kernels.
anthony83ba99b2010-01-24 08:48:15 +00003029%
anthony4fd27e22010-02-07 08:17:18 +00003030% The format of the RotateKernelInfo method is:
anthony83ba99b2010-01-24 08:48:15 +00003031%
anthony4fd27e22010-02-07 08:17:18 +00003032% void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00003033%
3034% A description of each parameter follows:
3035%
3036% o kernel: the Morphology/Convolution kernel
3037%
3038% o angle: angle to rotate in degrees
3039%
anthony46a369d2010-05-19 02:41:48 +00003040% This function is currently internal to this module only, but can be exported
3041% to other modules if needed.
anthony83ba99b2010-01-24 08:48:15 +00003042*/
anthony4fd27e22010-02-07 08:17:18 +00003043static void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00003044{
anthony1b2bc0a2010-05-12 05:25:22 +00003045 /* angle the lower kernels first */
3046 if ( kernel->next != (KernelInfo *) NULL)
3047 RotateKernelInfo(kernel->next, angle);
3048
anthony83ba99b2010-01-24 08:48:15 +00003049 /* WARNING: Currently assumes the kernel (rightly) is horizontally symetrical
3050 **
3051 ** TODO: expand beyond simple 90 degree rotates, flips and flops
3052 */
3053
3054 /* Modulus the angle */
3055 angle = fmod(angle, 360.0);
3056 if ( angle < 0 )
3057 angle += 360.0;
3058
anthony3c10fc82010-05-13 02:40:51 +00003059 if ( 337.5 < angle || angle <= 22.5 )
anthony43c49252010-05-18 10:59:50 +00003060 return; /* Near zero angle - no change! - At least not at this time */
anthony83ba99b2010-01-24 08:48:15 +00003061
anthony3dd0f622010-05-13 12:57:32 +00003062 /* Handle special cases */
anthony83ba99b2010-01-24 08:48:15 +00003063 switch (kernel->type) {
3064 /* These built-in kernels are cylindrical kernels, rotating is useless */
3065 case GaussianKernel:
anthony83ba99b2010-01-24 08:48:15 +00003066 case DOGKernel:
3067 case DiskKernel:
anthony3dd0f622010-05-13 12:57:32 +00003068 case PeaksKernel:
3069 case LaplacianKernel:
anthony83ba99b2010-01-24 08:48:15 +00003070 case ChebyshevKernel:
3071 case ManhattenKernel:
3072 case EuclideanKernel:
3073 return;
3074
3075 /* These may be rotatable at non-90 angles in the future */
3076 /* but simply rotating them in multiples of 90 degrees is useless */
3077 case SquareKernel:
3078 case DiamondKernel:
3079 case PlusKernel:
anthony3dd0f622010-05-13 12:57:32 +00003080 case CrossKernel:
anthony83ba99b2010-01-24 08:48:15 +00003081 return;
3082
3083 /* These only allows a +/-90 degree rotation (by transpose) */
3084 /* A 180 degree rotation is useless */
3085 case BlurKernel:
3086 case RectangleKernel:
3087 if ( 135.0 < angle && angle <= 225.0 )
3088 return;
3089 if ( 225.0 < angle && angle <= 315.0 )
3090 angle -= 180;
3091 break;
3092
anthony3dd0f622010-05-13 12:57:32 +00003093 default:
anthony83ba99b2010-01-24 08:48:15 +00003094 break;
3095 }
anthony3c10fc82010-05-13 02:40:51 +00003096 /* Attempt rotations by 45 degrees */
3097 if ( 22.5 < fmod(angle,90.0) && fmod(angle,90.0) <= 67.5 )
3098 {
3099 if ( kernel->width == 3 && kernel->height == 3 )
3100 { /* Rotate a 3x3 square by 45 degree angle */
3101 MagickRealType t = kernel->values[0];
anthony43c49252010-05-18 10:59:50 +00003102 kernel->values[0] = kernel->values[3];
3103 kernel->values[3] = kernel->values[6];
3104 kernel->values[6] = kernel->values[7];
3105 kernel->values[7] = kernel->values[8];
3106 kernel->values[8] = kernel->values[5];
3107 kernel->values[5] = kernel->values[2];
3108 kernel->values[2] = kernel->values[1];
3109 kernel->values[1] = t;
3110 /* NOT DONE - rotate a off-centered origin as well! */
3111 angle = fmod(angle+315.0, 360.0); /* angle reduced 45 degrees */
3112 kernel->angle = fmod(kernel->angle+45.0, 360.0);
anthony3c10fc82010-05-13 02:40:51 +00003113 }
3114 else
3115 perror("Unable to rotate non-3x3 kernel by 45 degrees");
3116 }
3117 if ( 45.0 < fmod(angle, 180.0) && fmod(angle,180.0) <= 135.0 )
3118 {
3119 if ( kernel->width == 1 || kernel->height == 1 )
3120 { /* Do a transpose of the image, which results in a 90
3121 ** degree rotation of a 1 dimentional kernel
3122 */
3123 long
3124 t;
3125 t = (long) kernel->width;
3126 kernel->width = kernel->height;
3127 kernel->height = (unsigned long) t;
3128 t = kernel->x;
3129 kernel->x = kernel->y;
3130 kernel->y = t;
anthony43c49252010-05-18 10:59:50 +00003131 if ( kernel->width == 1 ) {
3132 angle = fmod(angle+270.0, 360.0); /* angle reduced 90 degrees */
3133 kernel->angle = fmod(kernel->angle+90.0, 360.0);
3134 } else {
3135 angle = fmod(angle+90.0, 360.0); /* angle increased 90 degrees */
3136 kernel->angle = fmod(kernel->angle+270.0, 360.0);
3137 }
anthony3c10fc82010-05-13 02:40:51 +00003138 }
3139 else if ( kernel->width == kernel->height )
3140 { /* Rotate a square array of values by 90 degrees */
3141 register unsigned long
3142 i,j,x,y;
3143 register MagickRealType
3144 *k,t;
3145 k=kernel->values;
3146 for( i=0, x=kernel->width-1; i<=x; i++, x--)
3147 for( j=0, y=kernel->height-1; j<y; j++, y--)
3148 { t = k[i+j*kernel->width];
anthony43c49252010-05-18 10:59:50 +00003149 k[i+j*kernel->width] = k[j+x*kernel->width];
3150 k[j+x*kernel->width] = k[x+y*kernel->width];
3151 k[x+y*kernel->width] = k[y+i*kernel->width];
3152 k[y+i*kernel->width] = t;
anthony3c10fc82010-05-13 02:40:51 +00003153 }
anthony43c49252010-05-18 10:59:50 +00003154 /* NOT DONE - rotate a off-centered origin as well! */
3155 angle = fmod(angle+270.0, 360.0); /* angle reduced 90 degrees */
3156 kernel->angle = fmod(kernel->angle+90.0, 360.0);
anthony3c10fc82010-05-13 02:40:51 +00003157 }
3158 else
3159 perror("Unable to rotate a non-square, non-linear kernel 90 degrees");
3160 }
anthony83ba99b2010-01-24 08:48:15 +00003161 if ( 135.0 < angle && angle <= 225.0 )
3162 {
anthony43c49252010-05-18 10:59:50 +00003163 /* For a 180 degree rotation - also know as a reflection
3164 * This is actually a very very common operation!
3165 * Basically all that is needed is a reversal of the kernel data!
3166 * And a reflection of the origon
3167 */
anthony83ba99b2010-01-24 08:48:15 +00003168 unsigned long
3169 i,j;
3170 register double
3171 *k,t;
3172
3173 k=kernel->values;
3174 for ( i=0, j=kernel->width*kernel->height-1; i<j; i++, j--)
3175 t=k[i], k[i]=k[j], k[j]=t;
3176
anthony930be612010-02-08 04:26:15 +00003177 kernel->x = (long) kernel->width - kernel->x - 1;
3178 kernel->y = (long) kernel->height - kernel->y - 1;
anthony43c49252010-05-18 10:59:50 +00003179 angle = fmod(angle-180.0, 360.0); /* angle+180 degrees */
3180 kernel->angle = fmod(kernel->angle+180.0, 360.0);
anthony83ba99b2010-01-24 08:48:15 +00003181 }
anthony3c10fc82010-05-13 02:40:51 +00003182 /* At this point angle should at least between -45 (315) and +45 degrees
anthony83ba99b2010-01-24 08:48:15 +00003183 * In the future some form of non-orthogonal angled rotates could be
3184 * performed here, posibily with a linear kernel restriction.
3185 */
3186
3187#if 0
anthony3c10fc82010-05-13 02:40:51 +00003188 { /* Do a Flop by reversing each row.
anthony83ba99b2010-01-24 08:48:15 +00003189 */
3190 unsigned long
3191 y;
cristy150989e2010-02-01 14:59:39 +00003192 register long
anthony83ba99b2010-01-24 08:48:15 +00003193 x,r;
3194 register double
3195 *k,t;
3196
3197 for ( y=0, k=kernel->values; y < kernel->height; y++, k+=kernel->width)
3198 for ( x=0, r=kernel->width-1; x<kernel->width/2; x++, r--)
3199 t=k[x], k[x]=k[r], k[r]=t;
3200
cristyc99304f2010-02-01 15:26:27 +00003201 kernel->x = kernel->width - kernel->x - 1;
anthony83ba99b2010-01-24 08:48:15 +00003202 angle = fmod(angle+180.0, 360.0);
3203 }
3204#endif
3205 return;
3206}
3207
3208/*
3209%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3210% %
3211% %
3212% %
anthony46a369d2010-05-19 02:41:48 +00003213% S c a l e G e o m e t r y K e r n e l I n f o %
3214% %
3215% %
3216% %
3217%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3218%
3219% ScaleGeometryKernelInfo() takes a geometry argument string, typically
3220% provided as a "-set option:convolve:scale {geometry}" user setting,
3221% and modifies the kernel according to the parsed arguments of that setting.
3222%
3223% The first argument (and any normalization flags) are passed to
3224% ScaleKernelInfo() to scale/normalize the kernel. The second argument
3225% is then passed to UnityAddKernelInfo() to add a scled unity kernel
3226% into the scaled/normalized kernel.
3227%
3228% The format of the ScaleKernelInfo method is:
3229%
3230% void ScaleKernelInfo(KernelInfo *kernel, const double scaling_factor,
3231% const MagickStatusType normalize_flags )
3232%
3233% A description of each parameter follows:
3234%
3235% o kernel: the Morphology/Convolution kernel to modify
3236%
3237% o geometry:
3238% The geometry string to parse, typically from the user provided
3239% "-set option:convolve:scale {geometry}" setting.
3240%
3241*/
3242MagickExport void ScaleGeometryKernelInfo (KernelInfo *kernel,
3243 const char *geometry)
3244{
3245 GeometryFlags
3246 flags;
3247 GeometryInfo
3248 args;
3249
3250 SetGeometryInfo(&args);
3251 flags = (GeometryFlags) ParseGeometry(geometry, &args);
3252
3253#if 0
3254 /* For Debugging Geometry Input */
3255 fprintf(stderr, "Geometry = 0x%04X : %lg x %lg %+lg %+lg\n",
3256 flags, args.rho, args.sigma, args.xi, args.psi );
3257#endif
3258
3259 if ( (flags & PercentValue) != 0 ) /* Handle Percentage flag*/
3260 args.rho *= 0.01, args.sigma *= 0.01;
3261
3262 if ( (flags & RhoValue) == 0 ) /* Set Defaults for missing args */
3263 args.rho = 1.0;
3264 if ( (flags & SigmaValue) == 0 )
3265 args.sigma = 0.0;
3266
3267 /* Scale/Normalize the input kernel */
3268 ScaleKernelInfo(kernel, args.rho, flags);
3269
3270 /* Add Unity Kernel, for blending with original */
3271 if ( (flags & SigmaValue) != 0 )
3272 UnityAddKernelInfo(kernel, args.sigma);
3273
3274 return;
3275}
3276/*
3277%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3278% %
3279% %
3280% %
cristy6771f1e2010-03-05 19:43:39 +00003281% S c a l e K e r n e l I n f o %
anthonycc6c8362010-01-25 04:14:01 +00003282% %
3283% %
3284% %
3285%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3286%
anthony1b2bc0a2010-05-12 05:25:22 +00003287% ScaleKernelInfo() scales the given kernel list by the given amount, with or
3288% without normalization of the sum of the kernel values (as per given flags).
anthonycc6c8362010-01-25 04:14:01 +00003289%
anthony999bb2c2010-02-18 12:38:01 +00003290% By default (no flags given) the values within the kernel is scaled
anthony1b2bc0a2010-05-12 05:25:22 +00003291% directly using given scaling factor without change.
anthonycc6c8362010-01-25 04:14:01 +00003292%
anthony46a369d2010-05-19 02:41:48 +00003293% If either of the two 'normalize_flags' are given the kernel will first be
3294% normalized and then further scaled by the scaling factor value given.
anthony999bb2c2010-02-18 12:38:01 +00003295%
3296% Kernel normalization ('normalize_flags' given) is designed to ensure that
3297% any use of the kernel scaling factor with 'Convolve' or 'Correlate'
anthony1b2bc0a2010-05-12 05:25:22 +00003298% morphology methods will fall into -1.0 to +1.0 range. Note that for
3299% non-HDRI versions of IM this may cause images to have any negative results
3300% clipped, unless some 'bias' is used.
anthony999bb2c2010-02-18 12:38:01 +00003301%
3302% More specifically. Kernels which only contain positive values (such as a
3303% 'Gaussian' kernel) will be scaled so that those values sum to +1.0,
anthony1b2bc0a2010-05-12 05:25:22 +00003304% ensuring a 0.0 to +1.0 output range for non-HDRI images.
anthony999bb2c2010-02-18 12:38:01 +00003305%
3306% For Kernels that contain some negative values, (such as 'Sharpen' kernels)
3307% the kernel will be scaled by the absolute of the sum of kernel values, so
3308% that it will generally fall within the +/- 1.0 range.
3309%
3310% For kernels whose values sum to zero, (such as 'Laplician' kernels) kernel
3311% will be scaled by just the sum of the postive values, so that its output
3312% range will again fall into the +/- 1.0 range.
3313%
3314% For special kernels designed for locating shapes using 'Correlate', (often
3315% only containing +1 and -1 values, representing foreground/brackground
3316% matching) a special normalization method is provided to scale the positive
3317% values seperatally to those of the negative values, so the kernel will be
3318% forced to become a zero-sum kernel better suited to such searches.
3319%
anthony1b2bc0a2010-05-12 05:25:22 +00003320% WARNING: Correct normalization of the kernel assumes that the '*_range'
anthony999bb2c2010-02-18 12:38:01 +00003321% attributes within the kernel structure have been correctly set during the
3322% kernels creation.
3323%
3324% NOTE: The values used for 'normalize_flags' have been selected specifically
anthony46a369d2010-05-19 02:41:48 +00003325% to match the use of geometry options, so that '!' means NormalizeValue, '^'
3326% means CorrelateNormalizeValue. All other GeometryFlags values are ignored.
anthonycc6c8362010-01-25 04:14:01 +00003327%
anthony4fd27e22010-02-07 08:17:18 +00003328% The format of the ScaleKernelInfo method is:
anthonycc6c8362010-01-25 04:14:01 +00003329%
anthony999bb2c2010-02-18 12:38:01 +00003330% void ScaleKernelInfo(KernelInfo *kernel, const double scaling_factor,
3331% const MagickStatusType normalize_flags )
anthonycc6c8362010-01-25 04:14:01 +00003332%
3333% A description of each parameter follows:
3334%
3335% o kernel: the Morphology/Convolution kernel
3336%
anthony999bb2c2010-02-18 12:38:01 +00003337% o scaling_factor:
3338% multiply all values (after normalization) by this factor if not
3339% zero. If the kernel is normalized regardless of any flags.
3340%
3341% o normalize_flags:
3342% GeometryFlags defining normalization method to use.
3343% specifically: NormalizeValue, CorrelateNormalizeValue,
3344% and/or PercentValue
anthonycc6c8362010-01-25 04:14:01 +00003345%
3346*/
cristy6771f1e2010-03-05 19:43:39 +00003347MagickExport void ScaleKernelInfo(KernelInfo *kernel,
3348 const double scaling_factor,const GeometryFlags normalize_flags)
anthonycc6c8362010-01-25 04:14:01 +00003349{
cristy150989e2010-02-01 14:59:39 +00003350 register long
anthonycc6c8362010-01-25 04:14:01 +00003351 i;
3352
anthony999bb2c2010-02-18 12:38:01 +00003353 register double
3354 pos_scale,
3355 neg_scale;
3356
anthony46a369d2010-05-19 02:41:48 +00003357 /* do the other kernels in a multi-kernel list first */
anthony1b2bc0a2010-05-12 05:25:22 +00003358 if ( kernel->next != (KernelInfo *) NULL)
3359 ScaleKernelInfo(kernel->next, scaling_factor, normalize_flags);
3360
anthony46a369d2010-05-19 02:41:48 +00003361 /* Normalization of Kernel */
anthony999bb2c2010-02-18 12:38:01 +00003362 pos_scale = 1.0;
3363 if ( (normalize_flags&NormalizeValue) != 0 ) {
anthony46a369d2010-05-19 02:41:48 +00003364 /* non-zero and zero-summing kernels */
anthony999bb2c2010-02-18 12:38:01 +00003365 if ( fabs(kernel->positive_range + kernel->negative_range) > MagickEpsilon )
3366 pos_scale = fabs(kernel->positive_range + kernel->negative_range);
anthonycc6c8362010-01-25 04:14:01 +00003367 else
anthony999bb2c2010-02-18 12:38:01 +00003368 pos_scale = kernel->positive_range; /* special zero-summing kernel */
3369 }
anthony46a369d2010-05-19 02:41:48 +00003370 /* Force kernel into a normalized zero-summing kernel */
anthony999bb2c2010-02-18 12:38:01 +00003371 if ( (normalize_flags&CorrelateNormalizeValue) != 0 ) {
3372 pos_scale = ( fabs(kernel->positive_range) > MagickEpsilon )
3373 ? kernel->positive_range : 1.0;
3374 neg_scale = ( fabs(kernel->negative_range) > MagickEpsilon )
3375 ? -kernel->negative_range : 1.0;
3376 }
3377 else
3378 neg_scale = pos_scale;
3379
3380 /* finialize scaling_factor for positive and negative components */
3381 pos_scale = scaling_factor/pos_scale;
3382 neg_scale = scaling_factor/neg_scale;
anthonycc6c8362010-01-25 04:14:01 +00003383
cristy150989e2010-02-01 14:59:39 +00003384 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00003385 if ( ! IsNan(kernel->values[i]) )
anthony999bb2c2010-02-18 12:38:01 +00003386 kernel->values[i] *= (kernel->values[i] >= 0) ? pos_scale : neg_scale;
anthonycc6c8362010-01-25 04:14:01 +00003387
anthony999bb2c2010-02-18 12:38:01 +00003388 /* convolution output range */
3389 kernel->positive_range *= pos_scale;
3390 kernel->negative_range *= neg_scale;
3391 /* maximum and minimum values in kernel */
3392 kernel->maximum *= (kernel->maximum >= 0.0) ? pos_scale : neg_scale;
3393 kernel->minimum *= (kernel->minimum >= 0.0) ? pos_scale : neg_scale;
3394
anthony46a369d2010-05-19 02:41:48 +00003395 /* swap kernel settings if user's scaling factor is negative */
anthony999bb2c2010-02-18 12:38:01 +00003396 if ( scaling_factor < MagickEpsilon ) {
3397 double t;
3398 t = kernel->positive_range;
3399 kernel->positive_range = kernel->negative_range;
3400 kernel->negative_range = t;
3401 t = kernel->maximum;
3402 kernel->maximum = kernel->minimum;
3403 kernel->minimum = 1;
3404 }
anthonycc6c8362010-01-25 04:14:01 +00003405
3406 return;
3407}
3408
3409/*
3410%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3411% %
3412% %
3413% %
anthony46a369d2010-05-19 02:41:48 +00003414% S h o w K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00003415% %
3416% %
3417% %
3418%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3419%
anthony4fd27e22010-02-07 08:17:18 +00003420% ShowKernelInfo() outputs the details of the given kernel defination to
3421% standard error, generally due to a users 'showkernel' option request.
anthony83ba99b2010-01-24 08:48:15 +00003422%
3423% The format of the ShowKernel method is:
3424%
anthony4fd27e22010-02-07 08:17:18 +00003425% void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00003426%
3427% A description of each parameter follows:
3428%
3429% o kernel: the Morphology/Convolution kernel
3430%
anthony83ba99b2010-01-24 08:48:15 +00003431*/
anthony4fd27e22010-02-07 08:17:18 +00003432MagickExport void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00003433{
anthony7a01dcf2010-05-11 12:25:52 +00003434 KernelInfo
3435 *k;
anthony83ba99b2010-01-24 08:48:15 +00003436
anthony43c49252010-05-18 10:59:50 +00003437 unsigned long
anthony7a01dcf2010-05-11 12:25:52 +00003438 c, i, u, v;
3439
3440 for (c=0, k=kernel; k != (KernelInfo *) NULL; c++, k=k->next ) {
3441
anthony46a369d2010-05-19 02:41:48 +00003442 fprintf(stderr, "Kernel");
anthony7a01dcf2010-05-11 12:25:52 +00003443 if ( kernel->next != (KernelInfo *) NULL )
cristye96405a2010-05-19 02:24:31 +00003444 fprintf(stderr, " #%lu", c );
anthony43c49252010-05-18 10:59:50 +00003445 fprintf(stderr, " \"%s",
3446 MagickOptionToMnemonic(MagickKernelOptions, k->type) );
3447 if ( fabs(k->angle) > MagickEpsilon )
3448 fprintf(stderr, "@%lg", k->angle);
3449 fprintf(stderr, "\" of size %lux%lu%+ld%+ld ",
3450 k->width, k->height,
3451 k->x, k->y );
anthony7a01dcf2010-05-11 12:25:52 +00003452 fprintf(stderr,
3453 " with values from %.*lg to %.*lg\n",
3454 GetMagickPrecision(), k->minimum,
3455 GetMagickPrecision(), k->maximum);
anthony46a369d2010-05-19 02:41:48 +00003456 fprintf(stderr, "Forming a output range from %.*lg to %.*lg",
anthony7a01dcf2010-05-11 12:25:52 +00003457 GetMagickPrecision(), k->negative_range,
anthony46a369d2010-05-19 02:41:48 +00003458 GetMagickPrecision(), k->positive_range);
3459 if ( fabs(k->positive_range+k->negative_range) < MagickEpsilon )
3460 fprintf(stderr, " (Zero-Summing)\n");
3461 else if ( fabs(k->positive_range+k->negative_range-1.0) < MagickEpsilon )
3462 fprintf(stderr, " (Normalized)\n");
3463 else
3464 fprintf(stderr, " (Sum %.*lg)\n",
3465 GetMagickPrecision(), k->positive_range+k->negative_range);
anthony43c49252010-05-18 10:59:50 +00003466 for (i=v=0; v < k->height; v++) {
anthony46a369d2010-05-19 02:41:48 +00003467 fprintf(stderr, "%2lu:", v );
anthony43c49252010-05-18 10:59:50 +00003468 for (u=0; u < k->width; u++, i++)
anthony7a01dcf2010-05-11 12:25:52 +00003469 if ( IsNan(k->values[i]) )
3470 fprintf(stderr," %*s", GetMagickPrecision()+2, "nan");
3471 else
3472 fprintf(stderr," %*.*lg", GetMagickPrecision()+2,
3473 GetMagickPrecision(), k->values[i]);
3474 fprintf(stderr,"\n");
3475 }
anthony83ba99b2010-01-24 08:48:15 +00003476 }
3477}
anthonycc6c8362010-01-25 04:14:01 +00003478
3479/*
3480%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3481% %
3482% %
3483% %
anthony43c49252010-05-18 10:59:50 +00003484% U n i t y A d d K e r n a l I n f o %
3485% %
3486% %
3487% %
3488%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3489%
3490% UnityAddKernelInfo() Adds a given amount of the 'Unity' Convolution Kernel
3491% to the given pre-scaled and normalized Kernel. This in effect adds that
3492% amount of the original image into the resulting convolution kernel. This
3493% value is usually provided by the user as a percentage value in the
3494% 'convolve:scale' setting.
3495%
3496% The resulting effect is to either convert a 'zero-summing' edge detection
3497% kernel (such as a "Laplacian", "DOG" or a "LOG") into a 'sharpening'
3498% kernel.
3499%
3500% Alternativally by using a purely positive kernel, and using a negative
3501% post-normalizing scaling factor, you can convert a 'blurring' kernel (such
3502% as a "Gaussian") into a 'unsharp' kernel.
3503%
anthony46a369d2010-05-19 02:41:48 +00003504% The format of the UnityAdditionKernelInfo method is:
anthony43c49252010-05-18 10:59:50 +00003505%
3506% void UnityAdditionKernelInfo(KernelInfo *kernel, const double scale )
3507%
3508% A description of each parameter follows:
3509%
3510% o kernel: the Morphology/Convolution kernel
3511%
3512% o scale:
3513% scaling factor for the unity kernel to be added to
3514% the given kernel.
3515%
anthony43c49252010-05-18 10:59:50 +00003516*/
3517MagickExport void UnityAddKernelInfo(KernelInfo *kernel,
3518 const double scale)
3519{
anthony46a369d2010-05-19 02:41:48 +00003520 /* do the other kernels in a multi-kernel list first */
3521 if ( kernel->next != (KernelInfo *) NULL)
3522 UnityAddKernelInfo(kernel->next, scale);
anthony43c49252010-05-18 10:59:50 +00003523
anthony46a369d2010-05-19 02:41:48 +00003524 /* Add the scaled unity kernel to the existing kernel */
anthony43c49252010-05-18 10:59:50 +00003525 kernel->values[kernel->x+kernel->y*kernel->width] += scale;
anthony46a369d2010-05-19 02:41:48 +00003526 CalcKernelMetaData(kernel); /* recalculate the meta-data */
anthony43c49252010-05-18 10:59:50 +00003527
3528 return;
3529}
3530
3531/*
3532%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3533% %
3534% %
3535% %
3536% Z e r o K e r n e l N a n s %
anthonycc6c8362010-01-25 04:14:01 +00003537% %
3538% %
3539% %
3540%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3541%
3542% ZeroKernelNans() replaces any special 'nan' value that may be present in
3543% the kernel with a zero value. This is typically done when the kernel will
3544% be used in special hardware (GPU) convolution processors, to simply
3545% matters.
3546%
3547% The format of the ZeroKernelNans method is:
3548%
anthony46a369d2010-05-19 02:41:48 +00003549% void ZeroKernelNans (KernelInfo *kernel)
anthonycc6c8362010-01-25 04:14:01 +00003550%
3551% A description of each parameter follows:
3552%
3553% o kernel: the Morphology/Convolution kernel
3554%
anthonycc6c8362010-01-25 04:14:01 +00003555*/
anthonyc4c86e02010-01-27 09:30:32 +00003556MagickExport void ZeroKernelNans(KernelInfo *kernel)
anthonycc6c8362010-01-25 04:14:01 +00003557{
anthony43c49252010-05-18 10:59:50 +00003558 register unsigned long
anthonycc6c8362010-01-25 04:14:01 +00003559 i;
3560
anthony46a369d2010-05-19 02:41:48 +00003561 /* do the other kernels in a multi-kernel list first */
anthony1b2bc0a2010-05-12 05:25:22 +00003562 if ( kernel->next != (KernelInfo *) NULL)
3563 ZeroKernelNans(kernel->next);
3564
anthony43c49252010-05-18 10:59:50 +00003565 for (i=0; i < (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00003566 if ( IsNan(kernel->values[i]) )
3567 kernel->values[i] = 0.0;
3568
3569 return;
3570}