blob: 384a8903ea1047ff8feeae4eae8dc32911e8b393 [file] [log] [blame]
cristy701db312009-11-20 03:14:08 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% M M OOO RRRR PPPP H H OOO L OOO GGGG Y Y %
7% MM MM O O R R P P H H O O L O O G Y Y %
8% M M M O O RRRR PPPP HHHHH O O L O O G GGG Y %
9% M M O O R R P H H O O L O O G G Y %
10% M M OOO R R P H H OOO LLLLL OOO GGG Y %
11% %
12% %
13% MagickCore Morphology Methods %
14% %
15% Software Design %
16% Anthony Thyssen %
anthonyc94cdb02010-01-06 08:15:29 +000017% January 2010 %
cristy701db312009-11-20 03:14:08 +000018% %
19% %
cristy16af1cb2009-12-11 21:38:29 +000020% Copyright 1999-2010 ImageMagick Studio LLC, a non-profit organization %
cristy701db312009-11-20 03:14:08 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
anthony1b2bc0a2010-05-12 05:25:22 +000036% Morpology is the the application of various kernels, of any size and even
anthony602ab9b2010-01-05 08:06:50 +000037% shape, to a image in various ways (typically binary, but not always).
cristy701db312009-11-20 03:14:08 +000038%
anthony602ab9b2010-01-05 08:06:50 +000039% Convolution (weighted sum or average) is just one specific type of
40% morphology. Just one that is very common for image bluring and sharpening
41% effects. Not only 2D Gaussian blurring, but also 2-pass 1D Blurring.
42%
43% This module provides not only a general morphology function, and the ability
44% to apply more advanced or iterative morphologies, but also functions for the
45% generation of many different types of kernel arrays from user supplied
46% arguments. Prehaps even the generation of a kernel from a small image.
cristy701db312009-11-20 03:14:08 +000047*/
48
49/*
50 Include declarations.
51*/
52#include "magick/studio.h"
anthony602ab9b2010-01-05 08:06:50 +000053#include "magick/artifact.h"
cristy701db312009-11-20 03:14:08 +000054#include "magick/cache-view.h"
55#include "magick/color-private.h"
56#include "magick/enhance.h"
57#include "magick/exception.h"
58#include "magick/exception-private.h"
anthony602ab9b2010-01-05 08:06:50 +000059#include "magick/gem.h"
cristy701db312009-11-20 03:14:08 +000060#include "magick/hashmap.h"
61#include "magick/image.h"
cristybba804b2010-01-05 15:39:59 +000062#include "magick/image-private.h"
cristy701db312009-11-20 03:14:08 +000063#include "magick/list.h"
anthony29188a82010-01-22 10:12:34 +000064#include "magick/magick.h"
cristy701db312009-11-20 03:14:08 +000065#include "magick/memory_.h"
66#include "magick/monitor-private.h"
67#include "magick/morphology.h"
anthony46a369d2010-05-19 02:41:48 +000068#include "magick/morphology-private.h"
anthony602ab9b2010-01-05 08:06:50 +000069#include "magick/option.h"
cristy701db312009-11-20 03:14:08 +000070#include "magick/pixel-private.h"
71#include "magick/prepress.h"
72#include "magick/quantize.h"
73#include "magick/registry.h"
74#include "magick/semaphore.h"
75#include "magick/splay-tree.h"
76#include "magick/statistic.h"
77#include "magick/string_.h"
anthony602ab9b2010-01-05 08:06:50 +000078#include "magick/string-private.h"
79#include "magick/token.h"
cristya29d45f2010-03-05 21:14:54 +000080
anthony602ab9b2010-01-05 08:06:50 +000081/*
cristya29d45f2010-03-05 21:14:54 +000082 The following test is for special floating point numbers of value NaN (not
83 a number), that may be used within a Kernel Definition. NaN's are defined
84 as part of the IEEE standard for floating point number representation.
85
anthony1b2bc0a2010-05-12 05:25:22 +000086 These are used a Kernel value of NaN means that that kernel position is not
cristya29d45f2010-03-05 21:14:54 +000087 part of the normal convolution or morphology process, and thus allowing the
88 use of 'shaped' kernels.
89
90 Special properities two NaN's are never equal, even if they are from the
91 same variable That is the IsNaN() macro is only true if the value is NaN.
92*/
anthony602ab9b2010-01-05 08:06:50 +000093#define IsNan(a) ((a)!=(a))
94
anthony29188a82010-01-22 10:12:34 +000095/*
cristya29d45f2010-03-05 21:14:54 +000096 Other global definitions used by module.
97*/
anthony29188a82010-01-22 10:12:34 +000098static inline double MagickMin(const double x,const double y)
99{
100 return( x < y ? x : y);
101}
102static inline double MagickMax(const double x,const double y)
103{
104 return( x > y ? x : y);
105}
106#define Minimize(assign,value) assign=MagickMin(assign,value)
107#define Maximize(assign,value) assign=MagickMax(assign,value)
108
anthonyc4c86e02010-01-27 09:30:32 +0000109/* Currently these are only internal to this module */
110static void
anthony46a369d2010-05-19 02:41:48 +0000111 CalcKernelMetaData(KernelInfo *),
anthony3c10fc82010-05-13 02:40:51 +0000112 ExpandKernelInfo(KernelInfo *, double),
cristyef656912010-03-05 19:54:59 +0000113 RotateKernelInfo(KernelInfo *, double);
anthony602ab9b2010-01-05 08:06:50 +0000114
anthony3dd0f622010-05-13 12:57:32 +0000115
116/* Quick function to find last kernel in a kernel list */
117static inline KernelInfo *LastKernelInfo(KernelInfo *kernel)
118{
119 while (kernel->next != (KernelInfo *) NULL)
120 kernel = kernel->next;
121 return(kernel);
122}
123
124
anthony602ab9b2010-01-05 08:06:50 +0000125/*
126%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
127% %
128% %
129% %
anthony83ba99b2010-01-24 08:48:15 +0000130% A c q u i r e K e r n e l I n f o %
anthony602ab9b2010-01-05 08:06:50 +0000131% %
132% %
133% %
134%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
135%
cristy2be15382010-01-21 02:38:03 +0000136% AcquireKernelInfo() takes the given string (generally supplied by the
anthony602ab9b2010-01-05 08:06:50 +0000137% user) and converts it into a Morphology/Convolution Kernel. This allows
138% users to specify a kernel from a number of pre-defined kernels, or to fully
139% specify their own kernel for a specific Convolution or Morphology
140% Operation.
141%
142% The kernel so generated can be any rectangular array of floating point
143% values (doubles) with the 'control point' or 'pixel being affected'
144% anywhere within that array of values.
145%
anthony83ba99b2010-01-24 08:48:15 +0000146% Previously IM was restricted to a square of odd size using the exact
147% center as origin, this is no longer the case, and any rectangular kernel
148% with any value being declared the origin. This in turn allows the use of
149% highly asymmetrical kernels.
anthony602ab9b2010-01-05 08:06:50 +0000150%
151% The floating point values in the kernel can also include a special value
anthony83ba99b2010-01-24 08:48:15 +0000152% known as 'nan' or 'not a number' to indicate that this value is not part
153% of the kernel array. This allows you to shaped the kernel within its
154% rectangular area. That is 'nan' values provide a 'mask' for the kernel
155% shape. However at least one non-nan value must be provided for correct
156% working of a kernel.
anthony602ab9b2010-01-05 08:06:50 +0000157%
anthony7a01dcf2010-05-11 12:25:52 +0000158% The returned kernel should be freed using the DestroyKernelInfo() when you
159% are finished with it. Do not free this memory yourself.
anthony602ab9b2010-01-05 08:06:50 +0000160%
161% Input kernel defintion strings can consist of any of three types.
162%
anthony29188a82010-01-22 10:12:34 +0000163% "name:args"
164% Select from one of the built in kernels, using the name and
165% geometry arguments supplied. See AcquireKernelBuiltIn()
anthony602ab9b2010-01-05 08:06:50 +0000166%
anthony43c49252010-05-18 10:59:50 +0000167% "WxH[+X+Y][^@]:num, num, num ..."
anthony1b2bc0a2010-05-12 05:25:22 +0000168% a kernel of size W by H, with W*H floating point numbers following.
anthony602ab9b2010-01-05 08:06:50 +0000169% the 'center' can be optionally be defined at +X+Y (such that +0+0
anthony29188a82010-01-22 10:12:34 +0000170% is top left corner). If not defined the pixel in the center, for
171% odd sizes, or to the immediate top or left of center for even sizes
172% is automatically selected.
anthony602ab9b2010-01-05 08:06:50 +0000173%
anthony43c49252010-05-18 10:59:50 +0000174% If a '^' is included the kernel expanded with 90-degree rotations,
175% While a '@' will allow you to expand a 3x3 kernel using 45-degree
176% circular rotates.
177%
anthony29188a82010-01-22 10:12:34 +0000178% "num, num, num, num, ..."
179% list of floating point numbers defining an 'old style' odd sized
180% square kernel. At least 9 values should be provided for a 3x3
181% square kernel, 25 for a 5x5 square kernel, 49 for 7x7, etc.
182% Values can be space or comma separated. This is not recommended.
anthony602ab9b2010-01-05 08:06:50 +0000183%
anthony7a01dcf2010-05-11 12:25:52 +0000184% You can define a 'list of kernels' which can be used by some morphology
185% operators A list is defined as a semi-colon seperated list kernels.
186%
anthonydbc89892010-05-12 07:05:27 +0000187% " kernel ; kernel ; kernel ; "
anthony7a01dcf2010-05-11 12:25:52 +0000188%
anthony43c49252010-05-18 10:59:50 +0000189% Any extra ';' characters (at start, end or between kernel defintions are
190% simply ignored.
191%
192% Note that 'name' kernels will start with an alphabetic character while the
193% new kernel specification has a ':' character in its specification string.
194% If neither is the case, it is assumed an old style of a simple list of
195% numbers generating a odd-sized square kernel has been given.
anthony7a01dcf2010-05-11 12:25:52 +0000196%
anthony602ab9b2010-01-05 08:06:50 +0000197% The format of the AcquireKernal method is:
198%
cristy2be15382010-01-21 02:38:03 +0000199% KernelInfo *AcquireKernelInfo(const char *kernel_string)
anthony602ab9b2010-01-05 08:06:50 +0000200%
201% A description of each parameter follows:
202%
203% o kernel_string: the Morphology/Convolution kernel wanted.
204%
205*/
206
anthonyc84dce52010-05-07 05:42:23 +0000207/* This was separated so that it could be used as a separate
anthony5ef8e942010-05-11 06:51:12 +0000208** array input handling function, such as for -color-matrix
anthonyc84dce52010-05-07 05:42:23 +0000209*/
anthony5ef8e942010-05-11 06:51:12 +0000210static KernelInfo *ParseKernelArray(const char *kernel_string)
anthony602ab9b2010-01-05 08:06:50 +0000211{
cristy2be15382010-01-21 02:38:03 +0000212 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000213 *kernel;
214
215 char
216 token[MaxTextExtent];
217
anthony602ab9b2010-01-05 08:06:50 +0000218 const char
anthony5ef8e942010-05-11 06:51:12 +0000219 *p,
220 *end;
anthony602ab9b2010-01-05 08:06:50 +0000221
anthonyc84dce52010-05-07 05:42:23 +0000222 register long
223 i;
anthony602ab9b2010-01-05 08:06:50 +0000224
anthony29188a82010-01-22 10:12:34 +0000225 double
226 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
227
anthony43c49252010-05-18 10:59:50 +0000228 MagickStatusType
229 flags;
230
231 GeometryInfo
232 args;
233
cristy2be15382010-01-21 02:38:03 +0000234 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
235 if (kernel == (KernelInfo *)NULL)
anthony602ab9b2010-01-05 08:06:50 +0000236 return(kernel);
237 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
anthony43c49252010-05-18 10:59:50 +0000238 kernel->minimum = kernel->maximum = kernel->angle = 0.0;
anthony7a01dcf2010-05-11 12:25:52 +0000239 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000240 kernel->type = UserDefinedKernel;
anthony7a01dcf2010-05-11 12:25:52 +0000241 kernel->next = (KernelInfo *) NULL;
cristyd43a46b2010-01-21 02:13:41 +0000242 kernel->signature = MagickSignature;
anthony602ab9b2010-01-05 08:06:50 +0000243
anthony5ef8e942010-05-11 06:51:12 +0000244 /* find end of this specific kernel definition string */
245 end = strchr(kernel_string, ';');
246 if ( end == (char *) NULL )
247 end = strchr(kernel_string, '\0');
248
anthony43c49252010-05-18 10:59:50 +0000249 /* clear flags - for Expanding kernal lists thorugh rotations */
250 flags = NoValue;
251
anthony602ab9b2010-01-05 08:06:50 +0000252 /* Has a ':' in argument - New user kernel specification */
253 p = strchr(kernel_string, ':');
anthony5ef8e942010-05-11 06:51:12 +0000254 if ( p != (char *) NULL && p < end)
anthony602ab9b2010-01-05 08:06:50 +0000255 {
anthony602ab9b2010-01-05 08:06:50 +0000256 /* ParseGeometry() needs the geometry separated! -- Arrgghh */
cristy150989e2010-02-01 14:59:39 +0000257 memcpy(token, kernel_string, (size_t) (p-kernel_string));
anthony602ab9b2010-01-05 08:06:50 +0000258 token[p-kernel_string] = '\0';
anthonyc84dce52010-05-07 05:42:23 +0000259 SetGeometryInfo(&args);
anthony602ab9b2010-01-05 08:06:50 +0000260 flags = ParseGeometry(token, &args);
anthony602ab9b2010-01-05 08:06:50 +0000261
anthony29188a82010-01-22 10:12:34 +0000262 /* Size handling and checks of geometry settings */
anthony602ab9b2010-01-05 08:06:50 +0000263 if ( (flags & WidthValue) == 0 ) /* if no width then */
264 args.rho = args.sigma; /* then width = height */
265 if ( args.rho < 1.0 ) /* if width too small */
266 args.rho = 1.0; /* then width = 1 */
267 if ( args.sigma < 1.0 ) /* if height too small */
268 args.sigma = args.rho; /* then height = width */
269 kernel->width = (unsigned long)args.rho;
270 kernel->height = (unsigned long)args.sigma;
271
272 /* Offset Handling and Checks */
273 if ( args.xi < 0.0 || args.psi < 0.0 )
anthony83ba99b2010-01-24 08:48:15 +0000274 return(DestroyKernelInfo(kernel));
cristyc99304f2010-02-01 15:26:27 +0000275 kernel->x = ((flags & XValue)!=0) ? (long)args.xi
cristy150989e2010-02-01 14:59:39 +0000276 : (long) (kernel->width-1)/2;
cristyc99304f2010-02-01 15:26:27 +0000277 kernel->y = ((flags & YValue)!=0) ? (long)args.psi
cristy150989e2010-02-01 14:59:39 +0000278 : (long) (kernel->height-1)/2;
cristyc99304f2010-02-01 15:26:27 +0000279 if ( kernel->x >= (long) kernel->width ||
280 kernel->y >= (long) kernel->height )
anthony83ba99b2010-01-24 08:48:15 +0000281 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000282
283 p++; /* advance beyond the ':' */
284 }
285 else
anthonyc84dce52010-05-07 05:42:23 +0000286 { /* ELSE - Old old specification, forming odd-square kernel */
anthony602ab9b2010-01-05 08:06:50 +0000287 /* count up number of values given */
288 p=(const char *) kernel_string;
cristya699b172010-01-06 16:48:49 +0000289 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
anthony29188a82010-01-22 10:12:34 +0000290 p++; /* ignore "'" chars for convolve filter usage - Cristy */
anthony5ef8e942010-05-11 06:51:12 +0000291 for (i=0; p < end; i++)
anthony602ab9b2010-01-05 08:06:50 +0000292 {
293 GetMagickToken(p,&p,token);
294 if (*token == ',')
295 GetMagickToken(p,&p,token);
296 }
297 /* set the size of the kernel - old sized square */
298 kernel->width = kernel->height= (unsigned long) sqrt((double) i+1.0);
cristyc99304f2010-02-01 15:26:27 +0000299 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000300 p=(const char *) kernel_string;
anthony29188a82010-01-22 10:12:34 +0000301 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
302 p++; /* ignore "'" chars for convolve filter usage - Cristy */
anthony602ab9b2010-01-05 08:06:50 +0000303 }
304
305 /* Read in the kernel values from rest of input string argument */
306 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
307 kernel->height*sizeof(double));
308 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000309 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000310
cristyc99304f2010-02-01 15:26:27 +0000311 kernel->minimum = +MagickHuge;
312 kernel->maximum = -MagickHuge;
313 kernel->negative_range = kernel->positive_range = 0.0;
anthonyc84dce52010-05-07 05:42:23 +0000314
anthony5ef8e942010-05-11 06:51:12 +0000315 for (i=0; (i < (long) (kernel->width*kernel->height)) && (p < end); i++)
anthony602ab9b2010-01-05 08:06:50 +0000316 {
317 GetMagickToken(p,&p,token);
318 if (*token == ',')
319 GetMagickToken(p,&p,token);
anthony29188a82010-01-22 10:12:34 +0000320 if ( LocaleCompare("nan",token) == 0
anthonyc84dce52010-05-07 05:42:23 +0000321 || LocaleCompare("-",token) == 0 ) {
anthony29188a82010-01-22 10:12:34 +0000322 kernel->values[i] = nan; /* do not include this value in kernel */
323 }
324 else {
325 kernel->values[i] = StringToDouble(token);
326 ( kernel->values[i] < 0)
cristyc99304f2010-02-01 15:26:27 +0000327 ? ( kernel->negative_range += kernel->values[i] )
328 : ( kernel->positive_range += kernel->values[i] );
329 Minimize(kernel->minimum, kernel->values[i]);
330 Maximize(kernel->maximum, kernel->values[i]);
anthony29188a82010-01-22 10:12:34 +0000331 }
anthony602ab9b2010-01-05 08:06:50 +0000332 }
anthony29188a82010-01-22 10:12:34 +0000333
anthony5ef8e942010-05-11 06:51:12 +0000334 /* sanity check -- no more values in kernel definition */
335 GetMagickToken(p,&p,token);
336 if ( *token != '\0' && *token != ';' && *token != '\'' )
337 return(DestroyKernelInfo(kernel));
338
anthonyc84dce52010-05-07 05:42:23 +0000339#if 0
340 /* this was the old method of handling a incomplete kernel */
cristy150989e2010-02-01 14:59:39 +0000341 if ( i < (long) (kernel->width*kernel->height) ) {
cristyc99304f2010-02-01 15:26:27 +0000342 Minimize(kernel->minimum, kernel->values[i]);
343 Maximize(kernel->maximum, kernel->values[i]);
cristy150989e2010-02-01 14:59:39 +0000344 for ( ; i < (long) (kernel->width*kernel->height); i++)
anthony29188a82010-01-22 10:12:34 +0000345 kernel->values[i]=0.0;
346 }
anthonyc84dce52010-05-07 05:42:23 +0000347#else
348 /* Number of values for kernel was not enough - Report Error */
349 if ( i < (long) (kernel->width*kernel->height) )
350 return(DestroyKernelInfo(kernel));
351#endif
352
353 /* check that we recieved at least one real (non-nan) value! */
354 if ( kernel->minimum == MagickHuge )
355 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000356
anthony43c49252010-05-18 10:59:50 +0000357 if ( (flags & AreaValue) != 0 ) /* '@' symbol in kernel size */
358 ExpandKernelInfo(kernel, 45.0);
359 else if ( (flags & MinimumValue) != 0 ) /* '^' symbol in kernel size */
360 ExpandKernelInfo(kernel, 90.0);
361
anthony602ab9b2010-01-05 08:06:50 +0000362 return(kernel);
363}
anthonyc84dce52010-05-07 05:42:23 +0000364
anthony43c49252010-05-18 10:59:50 +0000365static KernelInfo *ParseKernelName(const char *kernel_string)
anthonyc84dce52010-05-07 05:42:23 +0000366{
367 char
368 token[MaxTextExtent];
369
anthony5ef8e942010-05-11 06:51:12 +0000370 long
371 type;
372
anthonyc84dce52010-05-07 05:42:23 +0000373 const char
anthony7a01dcf2010-05-11 12:25:52 +0000374 *p,
375 *end;
anthonyc84dce52010-05-07 05:42:23 +0000376
377 MagickStatusType
378 flags;
379
380 GeometryInfo
381 args;
382
anthonyc84dce52010-05-07 05:42:23 +0000383 /* Parse special 'named' kernel */
anthony5ef8e942010-05-11 06:51:12 +0000384 GetMagickToken(kernel_string,&p,token);
anthonyc84dce52010-05-07 05:42:23 +0000385 type=ParseMagickOption(MagickKernelOptions,MagickFalse,token);
386 if ( type < 0 || type == UserDefinedKernel )
anthony5ef8e942010-05-11 06:51:12 +0000387 return((KernelInfo *)NULL); /* not a valid named kernel */
anthonyc84dce52010-05-07 05:42:23 +0000388
389 while (((isspace((int) ((unsigned char) *p)) != 0) ||
anthony5ef8e942010-05-11 06:51:12 +0000390 (*p == ',') || (*p == ':' )) && (*p != '\0') && (*p != ';'))
anthonyc84dce52010-05-07 05:42:23 +0000391 p++;
anthony7a01dcf2010-05-11 12:25:52 +0000392
393 end = strchr(p, ';'); /* end of this kernel defintion */
394 if ( end == (char *) NULL )
395 end = strchr(p, '\0');
396
397 /* ParseGeometry() needs the geometry separated! -- Arrgghh */
398 memcpy(token, p, (size_t) (end-p));
399 token[end-p] = '\0';
anthonyc84dce52010-05-07 05:42:23 +0000400 SetGeometryInfo(&args);
anthony7a01dcf2010-05-11 12:25:52 +0000401 flags = ParseGeometry(token, &args);
anthonyc84dce52010-05-07 05:42:23 +0000402
anthony3c10fc82010-05-13 02:40:51 +0000403#if 0
404 /* For Debugging Geometry Input */
anthony46a369d2010-05-19 02:41:48 +0000405 fprintf(stderr, "Geometry = 0x%04X : %lg x %lg %+lg %+lg\n",
anthony3c10fc82010-05-13 02:40:51 +0000406 flags, args.rho, args.sigma, args.xi, args.psi );
407#endif
408
anthonyc84dce52010-05-07 05:42:23 +0000409 /* special handling of missing values in input string */
410 switch( type ) {
anthony5ef8e942010-05-11 06:51:12 +0000411 case RectangleKernel:
412 if ( (flags & WidthValue) == 0 ) /* if no width then */
413 args.rho = args.sigma; /* then width = height */
414 if ( args.rho < 1.0 ) /* if width too small */
415 args.rho = 3; /* then width = 3 */
416 if ( args.sigma < 1.0 ) /* if height too small */
417 args.sigma = args.rho; /* then height = width */
418 if ( (flags & XValue) == 0 ) /* center offset if not defined */
419 args.xi = (double)(((long)args.rho-1)/2);
420 if ( (flags & YValue) == 0 )
421 args.psi = (double)(((long)args.sigma-1)/2);
422 break;
423 case SquareKernel:
424 case DiamondKernel:
425 case DiskKernel:
426 case PlusKernel:
anthony3dd0f622010-05-13 12:57:32 +0000427 case CrossKernel:
anthony5ef8e942010-05-11 06:51:12 +0000428 /* If no scale given (a 0 scale is valid! - set it to 1.0 */
429 if ( (flags & HeightValue) == 0 )
430 args.sigma = 1.0;
431 break;
anthonyc1061722010-05-14 06:23:49 +0000432 case RingKernel:
433 if ( (flags & XValue) == 0 )
434 args.xi = 1.0;
435 break;
anthony5ef8e942010-05-11 06:51:12 +0000436 case ChebyshevKernel:
437 case ManhattenKernel:
438 case EuclideanKernel:
anthony43c49252010-05-18 10:59:50 +0000439 if ( (flags & HeightValue) == 0 ) /* no distance scale */
440 args.sigma = 100.0; /* default distance scaling */
441 else if ( (flags & AspectValue ) != 0 ) /* '!' flag */
442 args.sigma = QuantumRange/(args.sigma+1); /* maximum pixel distance */
443 else if ( (flags & PercentValue ) != 0 ) /* '%' flag */
444 args.sigma *= QuantumRange/100.0; /* percentage of color range */
anthony5ef8e942010-05-11 06:51:12 +0000445 break;
446 default:
447 break;
anthonyc84dce52010-05-07 05:42:23 +0000448 }
449
450 return(AcquireKernelBuiltIn((KernelInfoType)type, &args));
451}
452
anthony5ef8e942010-05-11 06:51:12 +0000453MagickExport KernelInfo *AcquireKernelInfo(const char *kernel_string)
454{
anthony7a01dcf2010-05-11 12:25:52 +0000455
456 KernelInfo
anthonydbc89892010-05-12 07:05:27 +0000457 *kernel,
anthony43c49252010-05-18 10:59:50 +0000458 *new_kernel;
anthony7a01dcf2010-05-11 12:25:52 +0000459
anthony5ef8e942010-05-11 06:51:12 +0000460 char
461 token[MaxTextExtent];
462
anthony7a01dcf2010-05-11 12:25:52 +0000463 const char
anthonydbc89892010-05-12 07:05:27 +0000464 *p;
anthony7a01dcf2010-05-11 12:25:52 +0000465
anthonye108a3f2010-05-12 07:24:03 +0000466 unsigned long
467 kernel_number;
468
anthonydbc89892010-05-12 07:05:27 +0000469 p = kernel_string;
anthony43c49252010-05-18 10:59:50 +0000470 kernel = NULL;
anthonye108a3f2010-05-12 07:24:03 +0000471 kernel_number = 0;
anthony5ef8e942010-05-11 06:51:12 +0000472
anthonydbc89892010-05-12 07:05:27 +0000473 while ( GetMagickToken(p,NULL,token), *token != '\0' ) {
anthony7a01dcf2010-05-11 12:25:52 +0000474
anthony43c49252010-05-18 10:59:50 +0000475 /* ignore extra or multiple ';' kernel seperators */
anthonydbc89892010-05-12 07:05:27 +0000476 if ( *token != ';' ) {
anthony7a01dcf2010-05-11 12:25:52 +0000477
anthonydbc89892010-05-12 07:05:27 +0000478 /* tokens starting with alpha is a Named kernel */
anthony43c49252010-05-18 10:59:50 +0000479 if (isalpha((int) *token) != 0)
480 new_kernel = ParseKernelName(p);
anthonydbc89892010-05-12 07:05:27 +0000481 else /* otherwise a user defined kernel array */
anthony43c49252010-05-18 10:59:50 +0000482 new_kernel = ParseKernelArray(p);
anthony7a01dcf2010-05-11 12:25:52 +0000483
anthonye108a3f2010-05-12 07:24:03 +0000484 /* Error handling -- this is not proper error handling! */
485 if ( new_kernel == (KernelInfo *) NULL ) {
486 fprintf(stderr, "Failed to parse kernel number #%lu\n", kernel_number);
487 if ( kernel != (KernelInfo *) NULL )
488 kernel=DestroyKernelInfo(kernel);
489 return((KernelInfo *) NULL);
anthonydbc89892010-05-12 07:05:27 +0000490 }
anthonye108a3f2010-05-12 07:24:03 +0000491
492 /* initialise or append the kernel list */
anthony3dd0f622010-05-13 12:57:32 +0000493 if ( kernel == (KernelInfo *) NULL )
494 kernel = new_kernel;
495 else
anthony43c49252010-05-18 10:59:50 +0000496 LastKernelInfo(kernel)->next = new_kernel;
anthonydbc89892010-05-12 07:05:27 +0000497 }
498
499 /* look for the next kernel in list */
500 p = strchr(p, ';');
501 if ( p == (char *) NULL )
502 break;
503 p++;
504
505 }
anthony7a01dcf2010-05-11 12:25:52 +0000506 return(kernel);
anthony5ef8e942010-05-11 06:51:12 +0000507}
508
anthony602ab9b2010-01-05 08:06:50 +0000509
510/*
511%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
512% %
513% %
514% %
515% A c q u i r e K e r n e l B u i l t I n %
516% %
517% %
518% %
519%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
520%
521% AcquireKernelBuiltIn() returned one of the 'named' built-in types of
522% kernels used for special purposes such as gaussian blurring, skeleton
523% pruning, and edge distance determination.
524%
525% They take a KernelType, and a set of geometry style arguments, which were
526% typically decoded from a user supplied string, or from a more complex
527% Morphology Method that was requested.
528%
529% The format of the AcquireKernalBuiltIn method is:
530%
cristy2be15382010-01-21 02:38:03 +0000531% KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000532% const GeometryInfo args)
533%
534% A description of each parameter follows:
535%
536% o type: the pre-defined type of kernel wanted
537%
538% o args: arguments defining or modifying the kernel
539%
540% Convolution Kernels
541%
anthony46a369d2010-05-19 02:41:48 +0000542% Unity
543% the No-Op kernel, also requivelent to Gaussian of sigma zero.
544% Basically a 3x3 kernel of a 1 surrounded by zeros.
545%
anthony3c10fc82010-05-13 02:40:51 +0000546% Gaussian:{radius},{sigma}
547% Generate a two-dimentional gaussian kernel, as used by -gaussian.
anthonyc1061722010-05-14 06:23:49 +0000548% The sigma for the curve is required. The resulting kernel is
549% normalized,
550%
551% If 'sigma' is zero, you get a single pixel on a field of zeros.
anthony602ab9b2010-01-05 08:06:50 +0000552%
553% NOTE: that the 'radius' is optional, but if provided can limit (clip)
554% the final size of the resulting kernel to a square 2*radius+1 in size.
555% The radius should be at least 2 times that of the sigma value, or
556% sever clipping and aliasing may result. If not given or set to 0 the
557% radius will be determined so as to produce the best minimal error
558% result, which is usally much larger than is normally needed.
559%
anthonyc1061722010-05-14 06:23:49 +0000560% DOG:{radius},{sigma1},{sigma2}
561% "Difference of Gaussians" Kernel.
562% As "Gaussian" but with a gaussian produced by 'sigma2' subtracted
563% from the gaussian produced by 'sigma1'. Typically sigma2 > sigma1.
564% The result is a zero-summing kernel.
anthony602ab9b2010-01-05 08:06:50 +0000565%
anthony9eb4f742010-05-18 02:45:54 +0000566% LOG:{radius},{sigma}
567% "Laplacian of a Gaussian" or "Mexician Hat" Kernel.
568% The supposed ideal edge detection, zero-summing kernel.
569%
570% An alturnative to this kernel is to use a "DOG" with a sigma ratio of
571% approx 1.6, which can also be applied as a 2 pass "DOB" (see below).
572%
anthonyc1061722010-05-14 06:23:49 +0000573% Blur:{radius},{sigma}[,{angle}]
574% Generates a 1 dimensional or linear gaussian blur, at the angle given
575% (current restricted to orthogonal angles). If a 'radius' is given the
576% kernel is clipped to a width of 2*radius+1. Kernel can be rotated
577% by a 90 degree angle.
578%
579% If 'sigma' is zero, you get a single pixel on a field of zeros.
580%
581% Note that two convolutions with two "Blur" kernels perpendicular to
582% each other, is equivelent to a far larger "Gaussian" kernel with the
583% same sigma value, However it is much faster to apply. This is how the
584% "-blur" operator actually works.
585%
586% DOB:{radius},{sigma1},{sigma2}[,{angle}]
587% "Difference of Blurs" Kernel.
588% As "Blur" but with the 1D gaussian produced by 'sigma2' subtracted
589% from thethe 1D gaussian produced by 'sigma1'.
590% The result is a zero-summing kernel.
591%
592% This can be used to generate a faster "DOG" convolution, in the same
593% way "Blur" can.
anthony602ab9b2010-01-05 08:06:50 +0000594%
anthony3c10fc82010-05-13 02:40:51 +0000595% Comet:{width},{sigma},{angle}
596% Blur in one direction only, much like how a bright object leaves
anthony602ab9b2010-01-05 08:06:50 +0000597% a comet like trail. The Kernel is actually half a gaussian curve,
anthony3c10fc82010-05-13 02:40:51 +0000598% Adding two such blurs in opposite directions produces a Blur Kernel.
599% Angle can be rotated in multiples of 90 degrees.
anthony602ab9b2010-01-05 08:06:50 +0000600%
anthony3c10fc82010-05-13 02:40:51 +0000601% Note that the first argument is the width of the kernel and not the
anthony602ab9b2010-01-05 08:06:50 +0000602% radius of the kernel.
603%
604% # Still to be implemented...
605% #
anthony4fd27e22010-02-07 08:17:18 +0000606% # Filter2D
607% # Filter1D
608% # Set kernel values using a resize filter, and given scale (sigma)
609% # Cylindrical or Linear. Is this posible with an image?
610% #
anthony602ab9b2010-01-05 08:06:50 +0000611%
anthony3c10fc82010-05-13 02:40:51 +0000612% Named Constant Convolution Kernels
613%
anthonyc1061722010-05-14 06:23:49 +0000614% All these are unscaled, zero-summing kernels by default. As such for
615% non-HDRI version of ImageMagick some form of normalization, user scaling,
616% and biasing the results is recommended, to prevent the resulting image
617% being 'clipped'.
618%
619% The 3x3 kernels (most of these) can be circularly rotated in multiples of
620% 45 degrees to generate the 8 angled varients of each of the kernels.
anthony3c10fc82010-05-13 02:40:51 +0000621%
622% Laplacian:{type}
anthony43c49252010-05-18 10:59:50 +0000623% Discrete Lapacian Kernels, (without normalization)
anthonyc1061722010-05-14 06:23:49 +0000624% Type 0 : 3x3 with center:8 surounded by -1 (8 neighbourhood)
625% Type 1 : 3x3 with center:4 edge:-1 corner:0 (4 neighbourhood)
anthony9eb4f742010-05-18 02:45:54 +0000626% Type 2 : 3x3 with center:4 edge:1 corner:-2
627% Type 3 : 3x3 with center:4 edge:-2 corner:1
628% Type 5 : 5x5 laplacian
629% Type 7 : 7x7 laplacian
anthony43c49252010-05-18 10:59:50 +0000630% Type 15 : 5x5 LOG (sigma approx 1.4)
631% Type 19 : 9x9 LOG (sigma approx 1.4)
anthonyc1061722010-05-14 06:23:49 +0000632%
633% Sobel:{angle}
anthony46a369d2010-05-19 02:41:48 +0000634% Sobel 'Edge' convolution kernel (3x3)
anthonyc1061722010-05-14 06:23:49 +0000635% -1, 0, 1
636% -2, 0,-2
637% -1, 0, 1
anthonye2a60ce2010-05-19 12:30:40 +0000638%
anthonyc1061722010-05-14 06:23:49 +0000639% Roberts:{angle}
anthony46a369d2010-05-19 02:41:48 +0000640% Roberts convolution kernel (3x3)
anthonyc1061722010-05-14 06:23:49 +0000641% 0, 0, 0
642% -1, 1, 0
643% 0, 0, 0
anthonyc1061722010-05-14 06:23:49 +0000644% Prewitt:{angle}
645% Prewitt Edge convolution kernel (3x3)
646% -1, 0, 1
647% -1, 0, 1
648% -1, 0, 1
anthony9eb4f742010-05-18 02:45:54 +0000649% Compass:{angle}
650% Prewitt's "Compass" convolution kernel (3x3)
651% -1, 1, 1
652% -1,-2, 1
653% -1, 1, 1
654% Kirsch:{angle}
655% Kirsch's "Compass" convolution kernel (3x3)
656% -3,-3, 5
657% -3, 0, 5
658% -3,-3, 5
anthony3c10fc82010-05-13 02:40:51 +0000659%
anthonye2a60ce2010-05-19 12:30:40 +0000660% FreiChen:{type},{angle}
661% Frei-Chen Edge Detector is a set of 9 unique convolution kernels that
anthony6915d062010-05-19 12:45:51 +0000662% are specially weighted. They should not be normalized. After applying
663% each to the original image, the results is then added together. The
664% square root of the resulting image is the cosine of the edge, and the
665% direction of the feature detection.
anthonye2a60ce2010-05-19 12:30:40 +0000666%
667% Type 1: | 1, sqrt(2), 1 |
668% | 0, 0, 0 | / 2*sqrt(2)
669% | -1, -sqrt(2), -1 |
670%
671% Type 2: | 1, 0, 1 |
672% | sqrt(2), 0, sqrt(2) | / 2*sqrt(2)
673% | 1, 0, 1 |
674%
675% Type 3: | 0, -1, sqrt(2) |
676% | 1, 0, -1 | / 2*sqrt(2)
677% | -sqrt(2), 1, 0 |
678%
anthony6915d062010-05-19 12:45:51 +0000679% Type 4: | sqrt(2), -1, 0 |
anthonye2a60ce2010-05-19 12:30:40 +0000680% | -1, 0, 1 | / 2*sqrt(2)
681% | 0, 1, -sqrt(2) |
682%
683% Type 5: | 0, 1, 0 |
684% | -1, 0, -1 | / 2
685% | 0, 1, 0 |
686%
687% Type 6: | -1, 0, 1 |
688% | 0, 0, 0 | / 2
689% | 1, 0, -1 |
690%
691% Type 7: | -1, -2, 1 |
692% | -2, 4, -2 | / 6
693% | 1, -2, 1 |
694%
695% Type 8: | -2, 1, -2 |
696% | 1, 4, 1 | / 6
697% | -2, 1, -2 |
698%
699% Type 9: | 1, 1, 1 |
700% | 1, 1, 1 | / 3
701% | 1, 1, 1 |
702%
703% The first 4 are for edge detection, the next 4 are for line detection
704% and the last is to add a average component to the results.
705%
anthonye2a60ce2010-05-19 12:30:40 +0000706%
anthony602ab9b2010-01-05 08:06:50 +0000707% Boolean Kernels
708%
anthony3c10fc82010-05-13 02:40:51 +0000709% Diamond:[{radius}[,{scale}]]
anthony1b2bc0a2010-05-12 05:25:22 +0000710% Generate a diamond shaped kernel with given radius to the points.
anthony602ab9b2010-01-05 08:06:50 +0000711% Kernel size will again be radius*2+1 square and defaults to radius 1,
712% generating a 3x3 kernel that is slightly larger than a square.
713%
anthony3c10fc82010-05-13 02:40:51 +0000714% Square:[{radius}[,{scale}]]
anthony602ab9b2010-01-05 08:06:50 +0000715% Generate a square shaped kernel of size radius*2+1, and defaulting
716% to a 3x3 (radius 1).
717%
anthonyc1061722010-05-14 06:23:49 +0000718% Note that using a larger radius for the "Square" or the "Diamond" is
719% also equivelent to iterating the basic morphological method that many
720% times. However iterating with the smaller radius is actually faster
721% than using a larger kernel radius.
722%
723% Rectangle:{geometry}
724% Simply generate a rectangle of 1's with the size given. You can also
725% specify the location of the 'control point', otherwise the closest
726% pixel to the center of the rectangle is selected.
727%
728% Properly centered and odd sized rectangles work the best.
anthony602ab9b2010-01-05 08:06:50 +0000729%
anthony3c10fc82010-05-13 02:40:51 +0000730% Disk:[{radius}[,{scale}]]
anthony602ab9b2010-01-05 08:06:50 +0000731% Generate a binary disk of the radius given, radius may be a float.
732% Kernel size will be ceil(radius)*2+1 square.
733% NOTE: Here are some disk shapes of specific interest
anthonyc1061722010-05-14 06:23:49 +0000734% "Disk:1" => "diamond" or "cross:1"
735% "Disk:1.5" => "square"
736% "Disk:2" => "diamond:2"
737% "Disk:2.5" => a general disk shape of radius 2
738% "Disk:2.9" => "square:2"
739% "Disk:3.5" => default - octagonal/disk shape of radius 3
740% "Disk:4.2" => roughly octagonal shape of radius 4
741% "Disk:4.3" => a general disk shape of radius 4
anthony602ab9b2010-01-05 08:06:50 +0000742% After this all the kernel shape becomes more and more circular.
743%
744% Because a "disk" is more circular when using a larger radius, using a
745% larger radius is preferred over iterating the morphological operation.
746%
anthonyc1061722010-05-14 06:23:49 +0000747% Symbol Dilation Kernels
748%
749% These kernel is not a good general morphological kernel, but is used
750% more for highlighting and marking any single pixels in an image using,
751% a "Dilate" method as appropriate.
752%
753% For the same reasons iterating these kernels does not produce the
754% same result as using a larger radius for the symbol.
755%
anthony3c10fc82010-05-13 02:40:51 +0000756% Plus:[{radius}[,{scale}]]
anthony3dd0f622010-05-13 12:57:32 +0000757% Cross:[{radius}[,{scale}]]
anthonyc1061722010-05-14 06:23:49 +0000758% Generate a kernel in the shape of a 'plus' or a 'cross' with
759% a each arm the length of the given radius (default 2).
anthony3dd0f622010-05-13 12:57:32 +0000760%
761% NOTE: "plus:1" is equivelent to a "Diamond" kernel.
anthony602ab9b2010-01-05 08:06:50 +0000762%
anthonyc1061722010-05-14 06:23:49 +0000763% Ring:{radius1},{radius2}[,{scale}]
764% A ring of the values given that falls between the two radii.
765% Defaults to a ring of approximataly 3 radius in a 7x7 kernel.
766% This is the 'edge' pixels of the default "Disk" kernel,
767% More specifically, "Ring" -> "Ring:2.5,3.5,1.0"
anthony602ab9b2010-01-05 08:06:50 +0000768%
anthony3dd0f622010-05-13 12:57:32 +0000769% Hit and Miss Kernels
770%
771% Peak:radius1,radius2
anthonyc1061722010-05-14 06:23:49 +0000772% Find any peak larger than the pixels the fall between the two radii.
773% The default ring of pixels is as per "Ring".
anthony43c49252010-05-18 10:59:50 +0000774% Edges
775% Find Edges of a binary shape
anthony3dd0f622010-05-13 12:57:32 +0000776% Corners
777% Find corners of a binary shape
778% LineEnds
779% Find end points of lines (for pruning a skeletion)
780% LineJunctions
anthony43c49252010-05-18 10:59:50 +0000781% Find three line junctions (within a skeletion)
anthony3dd0f622010-05-13 12:57:32 +0000782% ConvexHull
783% Octagonal thicken kernel, to generate convex hulls of 45 degrees
784% Skeleton
785% Thinning kernel, which leaves behind a skeletion of a shape
anthony602ab9b2010-01-05 08:06:50 +0000786%
787% Distance Measuring Kernels
788%
anthonyc1061722010-05-14 06:23:49 +0000789% Different types of distance measuring methods, which are used with the
790% a 'Distance' morphology method for generating a gradient based on
791% distance from an edge of a binary shape, though there is a technique
792% for handling a anti-aliased shape.
793%
794% See the 'Distance' Morphological Method, for information of how it is
795% applied.
796%
anthony3dd0f622010-05-13 12:57:32 +0000797% Chebyshev:[{radius}][x{scale}[%!]]
anthonyc94cdb02010-01-06 08:15:29 +0000798% Chebyshev Distance (also known as Tchebychev Distance) is a value of
799% one to any neighbour, orthogonal or diagonal. One why of thinking of
800% it is the number of squares a 'King' or 'Queen' in chess needs to
801% traverse reach any other position on a chess board. It results in a
802% 'square' like distance function, but one where diagonals are closer
803% than expected.
anthony602ab9b2010-01-05 08:06:50 +0000804%
anthonyc1061722010-05-14 06:23:49 +0000805% Manhatten:[{radius}][x{scale}[%!]]
anthonyc94cdb02010-01-06 08:15:29 +0000806% Manhatten Distance (also known as Rectilinear Distance, or the Taxi
807% Cab metric), is the distance needed when you can only travel in
808% orthogonal (horizontal or vertical) only. It is the distance a 'Rook'
809% in chess would travel. It results in a diamond like distances, where
810% diagonals are further than expected.
anthony602ab9b2010-01-05 08:06:50 +0000811%
anthonyc1061722010-05-14 06:23:49 +0000812% Euclidean:[{radius}][x{scale}[%!]]
anthonyc94cdb02010-01-06 08:15:29 +0000813% Euclidean Distance is the 'direct' or 'as the crow flys distance.
814% However by default the kernel size only has a radius of 1, which
815% limits the distance to 'Knight' like moves, with only orthogonal and
816% diagonal measurements being correct. As such for the default kernel
817% you will get octagonal like distance function, which is reasonally
818% accurate.
819%
820% However if you use a larger radius such as "Euclidean:4" you will
821% get a much smoother distance gradient from the edge of the shape.
822% Of course a larger kernel is slower to use, and generally not needed.
823%
824% To allow the use of fractional distances that you get with diagonals
825% the actual distance is scaled by a fixed value which the user can
826% provide. This is not actually nessary for either ""Chebyshev" or
827% "Manhatten" distance kernels, but is done for all three distance
828% kernels. If no scale is provided it is set to a value of 100,
829% allowing for a maximum distance measurement of 655 pixels using a Q16
830% version of IM, from any edge. However for small images this can
831% result in quite a dark gradient.
832%
anthony602ab9b2010-01-05 08:06:50 +0000833*/
834
cristy2be15382010-01-21 02:38:03 +0000835MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000836 const GeometryInfo *args)
837{
cristy2be15382010-01-21 02:38:03 +0000838 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000839 *kernel;
840
cristy150989e2010-02-01 14:59:39 +0000841 register long
anthony602ab9b2010-01-05 08:06:50 +0000842 i;
843
844 register long
845 u,
846 v;
847
848 double
849 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
850
anthonyc1061722010-05-14 06:23:49 +0000851 /* Generate a new empty kernel if needed */
cristye96405a2010-05-19 02:24:31 +0000852 kernel=(KernelInfo *) NULL;
anthonyc1061722010-05-14 06:23:49 +0000853 switch(type) {
anthony9eb4f742010-05-18 02:45:54 +0000854 case UndefinedKernel: /* These should not be used here */
855 case UserDefinedKernel:
856 break;
857 case LaplacianKernel: /* Named Descrete Convolution Kernels */
858 case SobelKernel:
859 case RobertsKernel:
860 case PrewittKernel:
861 case CompassKernel:
862 case KirschKernel:
863 case CornersKernel: /* Hit and Miss kernels */
864 case LineEndsKernel:
865 case LineJunctionsKernel:
anthony9eb4f742010-05-18 02:45:54 +0000866 case ThinningKernel:
867 case ConvexHullKernel:
868 case SkeletonKernel:
869 /* A pre-generated kernel is not needed */
870 break;
871#if 0
anthonyc1061722010-05-14 06:23:49 +0000872 case GaussianKernel:
873 case DOGKernel:
874 case BlurKernel:
875 case DOBKernel:
876 case CometKernel:
877 case DiamondKernel:
878 case SquareKernel:
879 case RectangleKernel:
880 case DiskKernel:
881 case PlusKernel:
882 case CrossKernel:
883 case RingKernel:
884 case PeaksKernel:
885 case ChebyshevKernel:
886 case ManhattenKernel:
887 case EuclideanKernel:
anthony9eb4f742010-05-18 02:45:54 +0000888#endif
889 default:
890 /* Generate the base Kernel Structure */
anthonyc1061722010-05-14 06:23:49 +0000891 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
892 if (kernel == (KernelInfo *) NULL)
893 return(kernel);
894 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
anthony43c49252010-05-18 10:59:50 +0000895 kernel->minimum = kernel->maximum = kernel->angle = 0.0;
anthonyc1061722010-05-14 06:23:49 +0000896 kernel->negative_range = kernel->positive_range = 0.0;
897 kernel->type = type;
898 kernel->next = (KernelInfo *) NULL;
899 kernel->signature = MagickSignature;
anthonyc1061722010-05-14 06:23:49 +0000900 break;
901 }
anthony602ab9b2010-01-05 08:06:50 +0000902
903 switch(type) {
904 /* Convolution Kernels */
905 case GaussianKernel:
anthonyc1061722010-05-14 06:23:49 +0000906 case DOGKernel:
anthony9eb4f742010-05-18 02:45:54 +0000907 case LOGKernel:
anthony602ab9b2010-01-05 08:06:50 +0000908 { double
anthonyc1061722010-05-14 06:23:49 +0000909 sigma = fabs(args->sigma),
910 sigma2 = fabs(args->xi),
anthony9eb4f742010-05-18 02:45:54 +0000911 A, B, R;
anthony602ab9b2010-01-05 08:06:50 +0000912
anthonyc1061722010-05-14 06:23:49 +0000913 if ( args->rho >= 1.0 )
914 kernel->width = (unsigned long)args->rho*2+1;
anthony9eb4f742010-05-18 02:45:54 +0000915 else if ( (type != DOGKernel) || (sigma >= sigma2) )
anthonyc1061722010-05-14 06:23:49 +0000916 kernel->width = GetOptimalKernelWidth2D(args->rho,sigma);
917 else
918 kernel->width = GetOptimalKernelWidth2D(args->rho,sigma2);
919 kernel->height = kernel->width;
cristyc99304f2010-02-01 15:26:27 +0000920 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000921 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
922 kernel->height*sizeof(double));
923 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000924 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000925
anthony46a369d2010-05-19 02:41:48 +0000926 /* WARNING: The following generates a 'sampled gaussian' kernel.
anthony9eb4f742010-05-18 02:45:54 +0000927 * What we really want is a 'discrete gaussian' kernel.
anthony46a369d2010-05-19 02:41:48 +0000928 *
929 * How to do this is currently not known, but appears to be
930 * basied on the Error Function 'erf()' (intergral of a gaussian)
anthony9eb4f742010-05-18 02:45:54 +0000931 */
932
933 if ( type == GaussianKernel || type == DOGKernel )
934 { /* Calculate a Gaussian, OR positive half of a DOG */
935 if ( sigma > MagickEpsilon )
936 { A = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
937 B = 1.0/(Magick2PI*sigma*sigma);
938 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
939 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
940 kernel->values[i] = exp(-((double)(u*u+v*v))*A)*B;
941 }
942 else /* limiting case - a unity (normalized Dirac) kernel */
943 { (void) ResetMagickMemory(kernel->values,0, (size_t)
944 kernel->width*kernel->height*sizeof(double));
945 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
946 }
anthonyc1061722010-05-14 06:23:49 +0000947 }
anthony9eb4f742010-05-18 02:45:54 +0000948
anthonyc1061722010-05-14 06:23:49 +0000949 if ( type == DOGKernel )
950 { /* Subtract a Negative Gaussian for "Difference of Gaussian" */
951 if ( sigma2 > MagickEpsilon )
952 { sigma = sigma2; /* simplify loop expressions */
anthony9eb4f742010-05-18 02:45:54 +0000953 A = 1.0/(2.0*sigma*sigma);
954 B = 1.0/(Magick2PI*sigma*sigma);
anthonyc1061722010-05-14 06:23:49 +0000955 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
956 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony9eb4f742010-05-18 02:45:54 +0000957 kernel->values[i] -= exp(-((double)(u*u+v*v))*A)*B;
anthonyc1061722010-05-14 06:23:49 +0000958 }
anthony9eb4f742010-05-18 02:45:54 +0000959 else /* limiting case - a unity (normalized Dirac) kernel */
anthonyc1061722010-05-14 06:23:49 +0000960 kernel->values[kernel->x+kernel->y*kernel->width] -= 1.0;
961 }
anthony9eb4f742010-05-18 02:45:54 +0000962
963 if ( type == LOGKernel )
964 { /* Calculate a Laplacian of a Gaussian - Or Mexician Hat */
965 if ( sigma > MagickEpsilon )
966 { A = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
967 B = 1.0/(MagickPI*sigma*sigma*sigma*sigma);
968 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
969 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
970 { R = ((double)(u*u+v*v))*A;
971 kernel->values[i] = (1-R)*exp(-R)*B;
972 }
973 }
974 else /* special case - generate a unity kernel */
975 { (void) ResetMagickMemory(kernel->values,0, (size_t)
976 kernel->width*kernel->height*sizeof(double));
977 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
978 }
979 }
980
981 /* Note the above kernels may have been 'clipped' by a user defined
anthonyc1061722010-05-14 06:23:49 +0000982 ** radius, producing a smaller (darker) kernel. Also for very small
983 ** sigma's (> 0.1) the central value becomes larger than one, and thus
984 ** producing a very bright kernel.
985 **
986 ** Normalization will still be needed.
987 */
anthony602ab9b2010-01-05 08:06:50 +0000988
anthony3dd0f622010-05-13 12:57:32 +0000989 /* Normalize the 2D Gaussian Kernel
990 **
anthonyc1061722010-05-14 06:23:49 +0000991 ** NB: a CorrelateNormalize performs a normal Normalize if
992 ** there are no negative values.
anthony3dd0f622010-05-13 12:57:32 +0000993 */
anthony46a369d2010-05-19 02:41:48 +0000994 CalcKernelMetaData(kernel); /* the other kernel meta-data */
anthonyc1061722010-05-14 06:23:49 +0000995 ScaleKernelInfo(kernel, 1.0, CorrelateNormalizeValue);
anthony602ab9b2010-01-05 08:06:50 +0000996
997 break;
998 }
999 case BlurKernel:
anthonyc1061722010-05-14 06:23:49 +00001000 case DOBKernel:
anthony602ab9b2010-01-05 08:06:50 +00001001 { double
anthonyc1061722010-05-14 06:23:49 +00001002 sigma = fabs(args->sigma),
1003 sigma2 = fabs(args->xi),
anthony9eb4f742010-05-18 02:45:54 +00001004 A, B;
anthony602ab9b2010-01-05 08:06:50 +00001005
anthonyc1061722010-05-14 06:23:49 +00001006 if ( args->rho >= 1.0 )
1007 kernel->width = (unsigned long)args->rho*2+1;
1008 else if ( (type == BlurKernel) || (sigma >= sigma2) )
1009 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
1010 else
1011 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma2);
anthony602ab9b2010-01-05 08:06:50 +00001012 kernel->height = 1;
anthonyc1061722010-05-14 06:23:49 +00001013 kernel->x = (long) (kernel->width-1)/2;
cristyc99304f2010-02-01 15:26:27 +00001014 kernel->y = 0;
1015 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +00001016 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1017 kernel->height*sizeof(double));
1018 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001019 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001020
1021#if 1
1022#define KernelRank 3
1023 /* Formula derived from GetBlurKernel() in "effect.c" (plus bug fix).
1024 ** It generates a gaussian 3 times the width, and compresses it into
1025 ** the expected range. This produces a closer normalization of the
1026 ** resulting kernel, especially for very low sigma values.
1027 ** As such while wierd it is prefered.
1028 **
1029 ** I am told this method originally came from Photoshop.
anthony9eb4f742010-05-18 02:45:54 +00001030 **
1031 ** A properly normalized curve is generated (apart from edge clipping)
1032 ** even though we later normalize the result (for edge clipping)
1033 ** to allow the correct generation of a "Difference of Blurs".
anthony602ab9b2010-01-05 08:06:50 +00001034 */
anthonyc1061722010-05-14 06:23:49 +00001035
1036 /* initialize */
cristy150989e2010-02-01 14:59:39 +00001037 v = (long) (kernel->width*KernelRank-1)/2; /* start/end points to fit range */
anthony9eb4f742010-05-18 02:45:54 +00001038 (void) ResetMagickMemory(kernel->values,0, (size_t)
1039 kernel->width*kernel->height*sizeof(double));
anthonyc1061722010-05-14 06:23:49 +00001040 /* Calculate a Positive 1D Gaussian */
1041 if ( sigma > MagickEpsilon )
1042 { sigma *= KernelRank; /* simplify loop expressions */
anthony9eb4f742010-05-18 02:45:54 +00001043 A = 1.0/(2.0*sigma*sigma);
1044 B = 1.0/(MagickSQ2PI*sigma );
anthonyc1061722010-05-14 06:23:49 +00001045 for ( u=-v; u <= v; u++) {
anthony9eb4f742010-05-18 02:45:54 +00001046 kernel->values[(u+v)/KernelRank] += exp(-((double)(u*u))*A)*B;
anthonyc1061722010-05-14 06:23:49 +00001047 }
1048 }
1049 else /* special case - generate a unity kernel */
1050 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
anthony9eb4f742010-05-18 02:45:54 +00001051
1052 /* Subtract a Second 1D Gaussian for "Difference of Blur" */
anthonyc1061722010-05-14 06:23:49 +00001053 if ( type == DOBKernel )
anthony9eb4f742010-05-18 02:45:54 +00001054 {
anthonyc1061722010-05-14 06:23:49 +00001055 if ( sigma2 > MagickEpsilon )
1056 { sigma = sigma2*KernelRank; /* simplify loop expressions */
anthony9eb4f742010-05-18 02:45:54 +00001057 A = 1.0/(2.0*sigma*sigma);
1058 B = 1.0/(MagickSQ2PI*sigma);
anthonyc1061722010-05-14 06:23:49 +00001059 for ( u=-v; u <= v; u++)
anthony9eb4f742010-05-18 02:45:54 +00001060 kernel->values[(u+v)/KernelRank] -= exp(-((double)(u*u))*A)*B;
anthonyc1061722010-05-14 06:23:49 +00001061 }
anthony9eb4f742010-05-18 02:45:54 +00001062 else /* limiting case - a unity (normalized Dirac) kernel */
anthonyc1061722010-05-14 06:23:49 +00001063 kernel->values[kernel->x+kernel->y*kernel->width] -= 1.0;
1064 }
anthony602ab9b2010-01-05 08:06:50 +00001065#else
anthonyc1061722010-05-14 06:23:49 +00001066 /* Direct calculation without curve averaging */
1067
1068 /* Calculate a Positive Gaussian */
1069 if ( sigma > MagickEpsilon )
anthony9eb4f742010-05-18 02:45:54 +00001070 { A = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
1071 B = 1.0/(MagickSQ2PI*sigma);
anthonyc1061722010-05-14 06:23:49 +00001072 for ( i=0, u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony9eb4f742010-05-18 02:45:54 +00001073 kernel->values[i] = exp(-((double)(u*u))*A)*B;
anthonyc1061722010-05-14 06:23:49 +00001074 }
1075 else /* special case - generate a unity kernel */
1076 { (void) ResetMagickMemory(kernel->values,0, (size_t)
1077 kernel->width*kernel->height*sizeof(double));
1078 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1079 }
anthony9eb4f742010-05-18 02:45:54 +00001080
1081 /* Subtract a Second 1D Gaussian for "Difference of Blur" */
anthonyc1061722010-05-14 06:23:49 +00001082 if ( type == DOBKernel )
anthony9eb4f742010-05-18 02:45:54 +00001083 {
anthonyc1061722010-05-14 06:23:49 +00001084 if ( sigma2 > MagickEpsilon )
1085 { sigma = sigma2; /* simplify loop expressions */
anthony9eb4f742010-05-18 02:45:54 +00001086 A = 1.0/(2.0*sigma*sigma);
1087 B = 1.0/(MagickSQ2PI*sigma);
anthonyc1061722010-05-14 06:23:49 +00001088 for ( i=0, u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony9eb4f742010-05-18 02:45:54 +00001089 kernel->values[i] -= exp(-((double)(u*u))*A)*B;
anthonyc1061722010-05-14 06:23:49 +00001090 }
anthony9eb4f742010-05-18 02:45:54 +00001091 else /* limiting case - a unity (normalized Dirac) kernel */
anthonyc1061722010-05-14 06:23:49 +00001092 kernel->values[kernel->x+kernel->y*kernel->width] -= 1.0;
1093 }
anthony602ab9b2010-01-05 08:06:50 +00001094#endif
anthonyc1061722010-05-14 06:23:49 +00001095 /* Note the above kernel may have been 'clipped' by a user defined
anthonycc6c8362010-01-25 04:14:01 +00001096 ** radius, producing a smaller (darker) kernel. Also for very small
1097 ** sigma's (> 0.1) the central value becomes larger than one, and thus
1098 ** producing a very bright kernel.
anthonyc1061722010-05-14 06:23:49 +00001099 **
1100 ** Normalization will still be needed.
anthony602ab9b2010-01-05 08:06:50 +00001101 */
anthonycc6c8362010-01-25 04:14:01 +00001102
anthony602ab9b2010-01-05 08:06:50 +00001103 /* Normalize the 1D Gaussian Kernel
1104 **
anthonyc1061722010-05-14 06:23:49 +00001105 ** NB: a CorrelateNormalize performs a normal Normalize if
1106 ** there are no negative values.
anthony602ab9b2010-01-05 08:06:50 +00001107 */
anthony46a369d2010-05-19 02:41:48 +00001108 CalcKernelMetaData(kernel); /* the other kernel meta-data */
1109 ScaleKernelInfo(kernel, 1.0, CorrelateNormalizeValue);
anthonycc6c8362010-01-25 04:14:01 +00001110
anthonyc1061722010-05-14 06:23:49 +00001111 /* rotate the 1D kernel by given angle */
1112 RotateKernelInfo(kernel, (type == BlurKernel) ? args->xi : args->psi );
anthony602ab9b2010-01-05 08:06:50 +00001113 break;
1114 }
1115 case CometKernel:
1116 { double
anthony9eb4f742010-05-18 02:45:54 +00001117 sigma = fabs(args->sigma),
1118 A;
anthony602ab9b2010-01-05 08:06:50 +00001119
anthony602ab9b2010-01-05 08:06:50 +00001120 if ( args->rho < 1.0 )
anthonye1cf9462010-05-19 03:50:26 +00001121 kernel->width = (GetOptimalKernelWidth1D(args->rho,sigma)-1)/2+1;
anthony602ab9b2010-01-05 08:06:50 +00001122 else
1123 kernel->width = (unsigned long)args->rho;
cristyc99304f2010-02-01 15:26:27 +00001124 kernel->x = kernel->y = 0;
anthony602ab9b2010-01-05 08:06:50 +00001125 kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +00001126 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +00001127 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1128 kernel->height*sizeof(double));
1129 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001130 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001131
anthonyc1061722010-05-14 06:23:49 +00001132 /* A comet blur is half a 1D gaussian curve, so that the object is
anthony602ab9b2010-01-05 08:06:50 +00001133 ** blurred in one direction only. This may not be quite the right
anthony3dd0f622010-05-13 12:57:32 +00001134 ** curve to use so may change in the future. The function must be
1135 ** normalised after generation, which also resolves any clipping.
anthonyc1061722010-05-14 06:23:49 +00001136 **
1137 ** As we are normalizing and not subtracting gaussians,
1138 ** there is no need for a divisor in the gaussian formula
1139 **
anthony43c49252010-05-18 10:59:50 +00001140 ** It is less comples
anthony602ab9b2010-01-05 08:06:50 +00001141 */
anthony9eb4f742010-05-18 02:45:54 +00001142 if ( sigma > MagickEpsilon )
1143 {
anthony602ab9b2010-01-05 08:06:50 +00001144#if 1
1145#define KernelRank 3
anthony9eb4f742010-05-18 02:45:54 +00001146 v = (long) kernel->width*KernelRank; /* start/end points */
1147 (void) ResetMagickMemory(kernel->values,0, (size_t)
1148 kernel->width*sizeof(double));
1149 sigma *= KernelRank; /* simplify the loop expression */
1150 A = 1.0/(2.0*sigma*sigma);
1151 /* B = 1.0/(MagickSQ2PI*sigma); */
1152 for ( u=0; u < v; u++) {
1153 kernel->values[u/KernelRank] +=
1154 exp(-((double)(u*u))*A);
1155 /* exp(-((double)(i*i))/2.0*sigma*sigma)/(MagickSQ2PI*sigma); */
1156 }
1157 for (i=0; i < (long) kernel->width; i++)
1158 kernel->positive_range += kernel->values[i];
anthony602ab9b2010-01-05 08:06:50 +00001159#else
anthony9eb4f742010-05-18 02:45:54 +00001160 A = 1.0/(2.0*sigma*sigma); /* simplify the loop expression */
1161 /* B = 1.0/(MagickSQ2PI*sigma); */
1162 for ( i=0; i < (long) kernel->width; i++)
1163 kernel->positive_range +=
1164 kernel->values[i] =
1165 exp(-((double)(i*i))*A);
1166 /* exp(-((double)(i*i))/2.0*sigma*sigma)/(MagickSQ2PI*sigma); */
anthony602ab9b2010-01-05 08:06:50 +00001167#endif
anthony9eb4f742010-05-18 02:45:54 +00001168 }
1169 else /* special case - generate a unity kernel */
1170 { (void) ResetMagickMemory(kernel->values,0, (size_t)
1171 kernel->width*kernel->height*sizeof(double));
1172 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1173 kernel->positive_range = 1.0;
1174 }
anthony46a369d2010-05-19 02:41:48 +00001175
1176 kernel->minimum = 0.0;
cristyc99304f2010-02-01 15:26:27 +00001177 kernel->maximum = kernel->values[0];
anthony46a369d2010-05-19 02:41:48 +00001178 kernel->negative_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +00001179
anthony999bb2c2010-02-18 12:38:01 +00001180 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
1181 RotateKernelInfo(kernel, args->xi); /* Rotate by angle */
anthony602ab9b2010-01-05 08:06:50 +00001182 break;
1183 }
anthonyc1061722010-05-14 06:23:49 +00001184
anthony3c10fc82010-05-13 02:40:51 +00001185 /* Convolution Kernels - Well Known Constants */
anthony3c10fc82010-05-13 02:40:51 +00001186 case LaplacianKernel:
anthonye2a60ce2010-05-19 12:30:40 +00001187 { switch ( (int) args->rho ) {
anthony3dd0f622010-05-13 12:57:32 +00001188 case 0:
anthony9eb4f742010-05-18 02:45:54 +00001189 default: /* laplacian square filter -- default */
anthonyc1061722010-05-14 06:23:49 +00001190 kernel=ParseKernelArray("3: -1,-1,-1 -1,8,-1 -1,-1,-1");
anthony3dd0f622010-05-13 12:57:32 +00001191 break;
anthony9eb4f742010-05-18 02:45:54 +00001192 case 1: /* laplacian diamond filter */
anthonyc1061722010-05-14 06:23:49 +00001193 kernel=ParseKernelArray("3: 0,-1,0 -1,4,-1 0,-1,0");
anthony3c10fc82010-05-13 02:40:51 +00001194 break;
1195 case 2:
anthony9eb4f742010-05-18 02:45:54 +00001196 kernel=ParseKernelArray("3: -2,1,-2 1,4,1 -2,1,-2");
1197 break;
1198 case 3:
anthonyc1061722010-05-14 06:23:49 +00001199 kernel=ParseKernelArray("3: 1,-2,1 -2,4,-2 1,-2,1");
anthony3c10fc82010-05-13 02:40:51 +00001200 break;
anthony9eb4f742010-05-18 02:45:54 +00001201 case 5: /* a 5x5 laplacian */
anthony3c10fc82010-05-13 02:40:51 +00001202 kernel=ParseKernelArray(
anthony9eb4f742010-05-18 02:45:54 +00001203 "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 +00001204 break;
anthony9eb4f742010-05-18 02:45:54 +00001205 case 7: /* a 7x7 laplacian */
anthony3c10fc82010-05-13 02:40:51 +00001206 kernel=ParseKernelArray(
anthonyc1061722010-05-14 06:23:49 +00001207 "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 +00001208 break;
anthony43c49252010-05-18 10:59:50 +00001209 case 15: /* a 5x5 LOG (sigma approx 1.4) */
anthony9eb4f742010-05-18 02:45:54 +00001210 kernel=ParseKernelArray(
1211 "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");
1212 break;
anthony43c49252010-05-18 10:59:50 +00001213 case 19: /* a 9x9 LOG (sigma approx 1.4) */
1214 /* http://www.cscjournals.org/csc/manuscript/Journals/IJIP/volume3/Issue1/IJIP-15.pdf */
1215 kernel=ParseKernelArray(
1216 "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");
1217 break;
anthony3c10fc82010-05-13 02:40:51 +00001218 }
1219 if (kernel == (KernelInfo *) NULL)
1220 return(kernel);
1221 kernel->type = type;
1222 break;
1223 }
anthonyc1061722010-05-14 06:23:49 +00001224 case SobelKernel:
anthony602ab9b2010-01-05 08:06:50 +00001225 {
anthonyc1061722010-05-14 06:23:49 +00001226 kernel=ParseKernelArray("3: -1,0,1 -2,0,2 -1,0,1");
1227 if (kernel == (KernelInfo *) NULL)
1228 return(kernel);
1229 kernel->type = type;
1230 RotateKernelInfo(kernel, args->rho); /* Rotate by angle */
1231 break;
1232 }
1233 case RobertsKernel:
1234 {
1235 kernel=ParseKernelArray("3: 0,0,0 -1,1,0 0,0,0");
1236 if (kernel == (KernelInfo *) NULL)
1237 return(kernel);
1238 kernel->type = type;
anthony46a369d2010-05-19 02:41:48 +00001239 RotateKernelInfo(kernel, args->rho);
anthonyc1061722010-05-14 06:23:49 +00001240 break;
1241 }
1242 case PrewittKernel:
1243 {
1244 kernel=ParseKernelArray("3: -1,1,1 0,0,0 -1,1,1");
1245 if (kernel == (KernelInfo *) NULL)
1246 return(kernel);
1247 kernel->type = type;
anthony46a369d2010-05-19 02:41:48 +00001248 RotateKernelInfo(kernel, args->rho);
anthonyc1061722010-05-14 06:23:49 +00001249 break;
1250 }
1251 case CompassKernel:
1252 {
1253 kernel=ParseKernelArray("3: -1,1,1 -1,-2,1 -1,1,1");
1254 if (kernel == (KernelInfo *) NULL)
1255 return(kernel);
1256 kernel->type = type;
anthony46a369d2010-05-19 02:41:48 +00001257 RotateKernelInfo(kernel, args->rho);
anthonyc1061722010-05-14 06:23:49 +00001258 break;
1259 }
anthony9eb4f742010-05-18 02:45:54 +00001260 case KirschKernel:
1261 {
1262 kernel=ParseKernelArray("3: -3,-3,5 -3,0,5 -3,-3,5");
1263 if (kernel == (KernelInfo *) NULL)
1264 return(kernel);
1265 kernel->type = type;
anthony46a369d2010-05-19 02:41:48 +00001266 RotateKernelInfo(kernel, args->rho);
anthony9eb4f742010-05-18 02:45:54 +00001267 break;
1268 }
anthonye2a60ce2010-05-19 12:30:40 +00001269 case FreiChenKernel:
anthony6915d062010-05-19 12:45:51 +00001270 /* http://www.math.tau.ac.il/~turkel/notes/edge_detectors.pdf */
1271 /* http://ltswww.epfl.ch/~courstiv/exos_labos/sol3.pdf */
anthonye2a60ce2010-05-19 12:30:40 +00001272 { switch ( (int) args->rho ) {
1273 default:
1274 case 1:
1275 kernel=ParseKernelArray("3: 1,2,1 0,0,0 -1,2,-1");
1276 if (kernel == (KernelInfo *) NULL)
1277 return(kernel);
1278 kernel->values[1] = +MagickSQ2;
1279 kernel->values[7] = -MagickSQ2;
1280 CalcKernelMetaData(kernel); /* recalculate meta-data */
1281 ScaleKernelInfo(kernel, 1.0/2.0*MagickSQ2, NoValue);
1282 break;
1283 case 2:
1284 kernel=ParseKernelArray("3: 1,0,1 2,0,2 1,0,1");
1285 if (kernel == (KernelInfo *) NULL)
1286 return(kernel);
1287 kernel->values[3] = +MagickSQ2;
1288 kernel->values[5] = +MagickSQ2;
1289 CalcKernelMetaData(kernel);
1290 ScaleKernelInfo(kernel, 1.0/2.0*MagickSQ2, NoValue);
1291 break;
1292 case 3:
1293 kernel=ParseKernelArray("3: 0,-1,2 1,0,-1 -2,1,0");
1294 if (kernel == (KernelInfo *) NULL)
1295 return(kernel);
1296 kernel->values[2] = +MagickSQ2;
1297 kernel->values[6] = -MagickSQ2;
1298 CalcKernelMetaData(kernel);
1299 ScaleKernelInfo(kernel, 1.0/2.0*MagickSQ2, NoValue);
1300 break;
1301 case 4:
anthony6915d062010-05-19 12:45:51 +00001302 kernel=ParseKernelArray("3: 2,-1,0 -1,0,1 0,1,-2");
anthonye2a60ce2010-05-19 12:30:40 +00001303 if (kernel == (KernelInfo *) NULL)
1304 return(kernel);
1305 kernel->values[0] = +MagickSQ2;
1306 kernel->values[8] = -MagickSQ2;
1307 CalcKernelMetaData(kernel);
1308 ScaleKernelInfo(kernel, 1.0/2.0*MagickSQ2, NoValue);
1309 break;
1310 case 5:
1311 kernel=ParseKernelArray("3: 0,1,0 -1,0,-1 0,1,0");
1312 if (kernel == (KernelInfo *) NULL)
1313 return(kernel);
1314 ScaleKernelInfo(kernel, 1.0/2.0, NoValue);
1315 break;
1316 case 6:
1317 kernel=ParseKernelArray("3: -1,0,1 0,0,0 1,0,-1");
1318 if (kernel == (KernelInfo *) NULL)
1319 return(kernel);
1320 ScaleKernelInfo(kernel, 1.0/2.0, NoValue);
1321 break;
1322 case 7:
1323 kernel=ParseKernelArray("3: -1,-2,1 -2,4,-2 1,-2,1");
1324 if (kernel == (KernelInfo *) NULL)
1325 return(kernel);
1326 ScaleKernelInfo(kernel, 1.0/6.0, NoValue);
1327 break;
1328 case 8:
1329 kernel=ParseKernelArray("3: -2,1,-2 1,4,1 -2,1,-2");
1330 if (kernel == (KernelInfo *) NULL)
1331 return(kernel);
1332 ScaleKernelInfo(kernel, 1.0/6.0, NoValue);
1333 break;
1334 case 9:
anthony6915d062010-05-19 12:45:51 +00001335 kernel=ParseKernelName("3: 1,1,1 1,1,1 1,1,1");
anthonye2a60ce2010-05-19 12:30:40 +00001336 if (kernel == (KernelInfo *) NULL)
1337 return(kernel);
1338 ScaleKernelInfo(kernel, 1.0/3.0, NoValue);
1339 break;
1340 }
1341 RotateKernelInfo(kernel, args->sigma); /* Rotate by angle */
1342 break;
1343 }
1344
anthonyc1061722010-05-14 06:23:49 +00001345 /* Boolean Kernels */
1346 case DiamondKernel:
1347 {
1348 if (args->rho < 1.0)
1349 kernel->width = kernel->height = 3; /* default radius = 1 */
1350 else
1351 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
1352 kernel->x = kernel->y = (long) (kernel->width-1)/2;
1353
1354 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1355 kernel->height*sizeof(double));
1356 if (kernel->values == (double *) NULL)
1357 return(DestroyKernelInfo(kernel));
1358
1359 /* set all kernel values within diamond area to scale given */
1360 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1361 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1362 if ((labs(u)+labs(v)) <= (long)kernel->x)
1363 kernel->positive_range += kernel->values[i] = args->sigma;
1364 else
1365 kernel->values[i] = nan;
1366 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1367 break;
1368 }
1369 case SquareKernel:
1370 case RectangleKernel:
1371 { double
1372 scale;
anthony602ab9b2010-01-05 08:06:50 +00001373 if ( type == SquareKernel )
1374 {
1375 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001376 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001377 else
cristy150989e2010-02-01 14:59:39 +00001378 kernel->width = kernel->height = (unsigned long) (2*args->rho+1);
cristyc99304f2010-02-01 15:26:27 +00001379 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony4fd27e22010-02-07 08:17:18 +00001380 scale = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +00001381 }
1382 else {
cristy2be15382010-01-21 02:38:03 +00001383 /* NOTE: user defaults set in "AcquireKernelInfo()" */
anthony602ab9b2010-01-05 08:06:50 +00001384 if ( args->rho < 1.0 || args->sigma < 1.0 )
anthony83ba99b2010-01-24 08:48:15 +00001385 return(DestroyKernelInfo(kernel)); /* invalid args given */
anthony602ab9b2010-01-05 08:06:50 +00001386 kernel->width = (unsigned long)args->rho;
1387 kernel->height = (unsigned long)args->sigma;
1388 if ( args->xi < 0.0 || args->xi > (double)kernel->width ||
1389 args->psi < 0.0 || args->psi > (double)kernel->height )
anthony83ba99b2010-01-24 08:48:15 +00001390 return(DestroyKernelInfo(kernel)); /* invalid args given */
cristyc99304f2010-02-01 15:26:27 +00001391 kernel->x = (long) args->xi;
1392 kernel->y = (long) args->psi;
anthony4fd27e22010-02-07 08:17:18 +00001393 scale = 1.0;
anthony602ab9b2010-01-05 08:06:50 +00001394 }
1395 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1396 kernel->height*sizeof(double));
1397 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001398 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001399
anthony3dd0f622010-05-13 12:57:32 +00001400 /* set all kernel values to scale given */
cristy150989e2010-02-01 14:59:39 +00001401 u=(long) kernel->width*kernel->height;
1402 for ( i=0; i < u; i++)
anthony4fd27e22010-02-07 08:17:18 +00001403 kernel->values[i] = scale;
1404 kernel->minimum = kernel->maximum = scale; /* a flat shape */
1405 kernel->positive_range = scale*u;
anthonycc6c8362010-01-25 04:14:01 +00001406 break;
anthony602ab9b2010-01-05 08:06:50 +00001407 }
anthony602ab9b2010-01-05 08:06:50 +00001408 case DiskKernel:
1409 {
anthonyc1061722010-05-14 06:23:49 +00001410 long limit = (long)(args->rho*args->rho);
anthony83ba99b2010-01-24 08:48:15 +00001411 if (args->rho < 0.1) /* default radius approx 3.5 */
1412 kernel->width = kernel->height = 7L, limit = 10L;
anthony602ab9b2010-01-05 08:06:50 +00001413 else
1414 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001415 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001416
1417 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1418 kernel->height*sizeof(double));
1419 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001420 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001421
anthony3dd0f622010-05-13 12:57:32 +00001422 /* set all kernel values within disk area to scale given */
1423 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
cristyc99304f2010-02-01 15:26:27 +00001424 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony602ab9b2010-01-05 08:06:50 +00001425 if ((u*u+v*v) <= limit)
anthony4fd27e22010-02-07 08:17:18 +00001426 kernel->positive_range += kernel->values[i] = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +00001427 else
1428 kernel->values[i] = nan;
anthony4fd27e22010-02-07 08:17:18 +00001429 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
anthony602ab9b2010-01-05 08:06:50 +00001430 break;
1431 }
1432 case PlusKernel:
1433 {
1434 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001435 kernel->width = kernel->height = 5; /* default radius 2 */
anthony602ab9b2010-01-05 08:06:50 +00001436 else
1437 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001438 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001439
1440 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1441 kernel->height*sizeof(double));
1442 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001443 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001444
anthony3dd0f622010-05-13 12:57:32 +00001445 /* set all kernel values along axises to given scale */
cristyc99304f2010-02-01 15:26:27 +00001446 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1447 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony4fd27e22010-02-07 08:17:18 +00001448 kernel->values[i] = (u == 0 || v == 0) ? args->sigma : nan;
1449 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1450 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
anthony602ab9b2010-01-05 08:06:50 +00001451 break;
1452 }
anthony3dd0f622010-05-13 12:57:32 +00001453 case CrossKernel:
1454 {
1455 if (args->rho < 1.0)
1456 kernel->width = kernel->height = 5; /* default radius 2 */
1457 else
1458 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
1459 kernel->x = kernel->y = (long) (kernel->width-1)/2;
1460
1461 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1462 kernel->height*sizeof(double));
1463 if (kernel->values == (double *) NULL)
1464 return(DestroyKernelInfo(kernel));
1465
1466 /* set all kernel values along axises to given scale */
1467 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1468 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1469 kernel->values[i] = (u == v || u == -v) ? args->sigma : nan;
1470 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1471 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
1472 break;
1473 }
1474 /* HitAndMiss Kernels */
anthonyc1061722010-05-14 06:23:49 +00001475 case RingKernel:
anthony3dd0f622010-05-13 12:57:32 +00001476 case PeaksKernel:
1477 {
1478 long
1479 limit1,
anthonyc1061722010-05-14 06:23:49 +00001480 limit2,
1481 scale;
anthony3dd0f622010-05-13 12:57:32 +00001482
1483 if (args->rho < args->sigma)
1484 {
1485 kernel->width = ((unsigned long)args->sigma)*2+1;
1486 limit1 = (long)args->rho*args->rho;
1487 limit2 = (long)args->sigma*args->sigma;
1488 }
1489 else
1490 {
1491 kernel->width = ((unsigned long)args->rho)*2+1;
1492 limit1 = (long)args->sigma*args->sigma;
1493 limit2 = (long)args->rho*args->rho;
1494 }
anthonyc1061722010-05-14 06:23:49 +00001495 if ( limit2 <= 0 )
1496 kernel->width = 7L, limit1 = 7L, limit2 = 11L;
1497
anthony3dd0f622010-05-13 12:57:32 +00001498 kernel->height = kernel->width;
1499 kernel->x = kernel->y = (long) (kernel->width-1)/2;
1500 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1501 kernel->height*sizeof(double));
1502 if (kernel->values == (double *) NULL)
1503 return(DestroyKernelInfo(kernel));
1504
anthonyc1061722010-05-14 06:23:49 +00001505 /* set a ring of points of 'scale' ( 0.0 for PeaksKernel ) */
cristye96405a2010-05-19 02:24:31 +00001506 scale = (long) (( type == PeaksKernel) ? 0.0 : args->xi);
anthony3dd0f622010-05-13 12:57:32 +00001507 for ( i=0, v= -kernel->y; v <= (long)kernel->y; v++)
1508 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1509 { long radius=u*u+v*v;
anthonyc1061722010-05-14 06:23:49 +00001510 if (limit1 < radius && radius <= limit2)
cristye96405a2010-05-19 02:24:31 +00001511 kernel->positive_range += kernel->values[i] = (double) scale;
anthony3dd0f622010-05-13 12:57:32 +00001512 else
1513 kernel->values[i] = nan;
1514 }
cristye96405a2010-05-19 02:24:31 +00001515 kernel->minimum = kernel->minimum = (double) scale;
anthonyc1061722010-05-14 06:23:49 +00001516 if ( type == PeaksKernel ) {
1517 /* set the central point in the middle */
1518 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1519 kernel->positive_range = 1.0;
1520 kernel->maximum = 1.0;
1521 }
anthony3dd0f622010-05-13 12:57:32 +00001522 break;
1523 }
anthony43c49252010-05-18 10:59:50 +00001524 case EdgesKernel:
1525 {
1526 kernel=ParseKernelArray("3: 0,0,0 -,1,- 1,1,1");
1527 if (kernel == (KernelInfo *) NULL)
1528 return(kernel);
1529 kernel->type = type;
1530 ExpandKernelInfo(kernel, 90.0); /* Create a list of 4 rotated kernels */
1531 break;
1532 }
anthony3dd0f622010-05-13 12:57:32 +00001533 case CornersKernel:
1534 {
anthony4f1dcb72010-05-14 08:43:10 +00001535 kernel=ParseKernelArray("3: 0,0,- 0,1,1 -,1,-");
anthony3dd0f622010-05-13 12:57:32 +00001536 if (kernel == (KernelInfo *) NULL)
1537 return(kernel);
1538 kernel->type = type;
1539 ExpandKernelInfo(kernel, 90.0); /* Create a list of 4 rotated kernels */
1540 break;
1541 }
1542 case LineEndsKernel:
1543 {
anthony43c49252010-05-18 10:59:50 +00001544 KernelInfo
1545 *new_kernel;
1546 kernel=ParseKernelArray("3: 0,0,0 0,1,0 -,1,-");
anthony3dd0f622010-05-13 12:57:32 +00001547 if (kernel == (KernelInfo *) NULL)
1548 return(kernel);
1549 kernel->type = type;
anthony43c49252010-05-18 10:59:50 +00001550 ExpandKernelInfo(kernel, 90.0);
1551 /* append second set of 4 kernels */
1552 new_kernel=ParseKernelArray("3: 0,0,0 0,1,0 0,0,1");
1553 if (new_kernel == (KernelInfo *) NULL)
1554 return(DestroyKernelInfo(kernel));
1555 new_kernel->type = type;
1556 ExpandKernelInfo(new_kernel, 90.0);
1557 LastKernelInfo(kernel)->next = new_kernel;
anthony3dd0f622010-05-13 12:57:32 +00001558 break;
1559 }
1560 case LineJunctionsKernel:
1561 {
1562 KernelInfo
1563 *new_kernel;
anthony3dd0f622010-05-13 12:57:32 +00001564 /* first set of 4 kernels */
anthony4f1dcb72010-05-14 08:43:10 +00001565 kernel=ParseKernelArray("3: -,1,- -,1,- 1,-,1");
anthony3dd0f622010-05-13 12:57:32 +00001566 if (kernel == (KernelInfo *) NULL)
1567 return(kernel);
1568 kernel->type = type;
anthony43c49252010-05-18 10:59:50 +00001569 ExpandKernelInfo(kernel, 45.0);
anthony3dd0f622010-05-13 12:57:32 +00001570 /* append second set of 4 kernels */
anthony4f1dcb72010-05-14 08:43:10 +00001571 new_kernel=ParseKernelArray("3: 1,-,- -,1,- 1,-,1");
anthony3dd0f622010-05-13 12:57:32 +00001572 if (new_kernel == (KernelInfo *) NULL)
1573 return(DestroyKernelInfo(kernel));
anthony43c49252010-05-18 10:59:50 +00001574 new_kernel->type = type;
anthony3dd0f622010-05-13 12:57:32 +00001575 ExpandKernelInfo(new_kernel, 90.0);
1576 LastKernelInfo(kernel)->next = new_kernel;
anthony4f1dcb72010-05-14 08:43:10 +00001577 break;
1578 }
anthony3dd0f622010-05-13 12:57:32 +00001579 case ConvexHullKernel:
1580 {
1581 KernelInfo
1582 *new_kernel;
anthony3dd0f622010-05-13 12:57:32 +00001583 /* first set of 4 kernels */
anthony4f1dcb72010-05-14 08:43:10 +00001584 kernel=ParseKernelArray("3: 1,1,- 1,0,- 1,-,0");
anthony3dd0f622010-05-13 12:57:32 +00001585 if (kernel == (KernelInfo *) NULL)
1586 return(kernel);
1587 kernel->type = type;
1588 ExpandKernelInfo(kernel, 90.0);
1589 /* append second set of 4 kernels */
anthony43c49252010-05-18 10:59:50 +00001590 new_kernel=ParseKernelArray("3: 1,1,1 1,0,0 -,-,0");
anthony3dd0f622010-05-13 12:57:32 +00001591 if (new_kernel == (KernelInfo *) NULL)
1592 return(DestroyKernelInfo(kernel));
anthony43c49252010-05-18 10:59:50 +00001593 new_kernel->type = type;
anthony3dd0f622010-05-13 12:57:32 +00001594 ExpandKernelInfo(new_kernel, 90.0);
1595 LastKernelInfo(kernel)->next = new_kernel;
1596 break;
1597 }
anthony43c49252010-05-18 10:59:50 +00001598 case ThinningKernel:
1599 { /* Thinning Kernel ?? -- filled corner and edge */
1600 kernel=ParseKernelArray("3: 0,0,- 0,1,1 -,1,1");
anthony3dd0f622010-05-13 12:57:32 +00001601 if (kernel == (KernelInfo *) NULL)
1602 return(kernel);
1603 kernel->type = type;
anthony43c49252010-05-18 10:59:50 +00001604 ExpandKernelInfo(kernel, 45);
1605 break;
1606 }
1607 case SkeletonKernel:
1608 {
1609 kernel=AcquireKernelInfo("Edges;Corners");
anthony3dd0f622010-05-13 12:57:32 +00001610 break;
1611 }
anthony602ab9b2010-01-05 08:06:50 +00001612 /* Distance Measuring Kernels */
1613 case ChebyshevKernel:
1614 {
anthony602ab9b2010-01-05 08:06:50 +00001615 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001616 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001617 else
1618 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001619 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001620
1621 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1622 kernel->height*sizeof(double));
1623 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001624 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001625
cristyc99304f2010-02-01 15:26:27 +00001626 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1627 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1628 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001629 args->sigma*((labs(u)>labs(v)) ? labs(u) : labs(v)) );
cristyc99304f2010-02-01 15:26:27 +00001630 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001631 break;
1632 }
1633 case ManhattenKernel:
1634 {
anthony602ab9b2010-01-05 08:06:50 +00001635 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001636 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001637 else
1638 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001639 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001640
1641 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1642 kernel->height*sizeof(double));
1643 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001644 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001645
cristyc99304f2010-02-01 15:26:27 +00001646 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1647 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1648 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001649 args->sigma*(labs(u)+labs(v)) );
cristyc99304f2010-02-01 15:26:27 +00001650 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001651 break;
1652 }
1653 case EuclideanKernel:
1654 {
anthony602ab9b2010-01-05 08:06:50 +00001655 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001656 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001657 else
1658 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001659 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001660
1661 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1662 kernel->height*sizeof(double));
1663 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001664 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001665
cristyc99304f2010-02-01 15:26:27 +00001666 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1667 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1668 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001669 args->sigma*sqrt((double)(u*u+v*v)) );
cristyc99304f2010-02-01 15:26:27 +00001670 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001671 break;
1672 }
anthony46a369d2010-05-19 02:41:48 +00001673 case UnityKernel:
anthony602ab9b2010-01-05 08:06:50 +00001674 default:
anthonyc1061722010-05-14 06:23:49 +00001675 {
anthony46a369d2010-05-19 02:41:48 +00001676 /* Unity or No-Op Kernel - 3x3 with 1 in center */
1677 kernel=ParseKernelArray("3:0,0,0,0,1,0,0,0,0");
anthonyc1061722010-05-14 06:23:49 +00001678 if (kernel == (KernelInfo *) NULL)
1679 return(kernel);
anthony46a369d2010-05-19 02:41:48 +00001680 kernel->type = ( type == UnityKernel ) ? UnityKernel : UndefinedKernel;
anthonyc1061722010-05-14 06:23:49 +00001681 break;
1682 }
anthony602ab9b2010-01-05 08:06:50 +00001683 break;
1684 }
1685
1686 return(kernel);
1687}
anthonyc94cdb02010-01-06 08:15:29 +00001688
anthony602ab9b2010-01-05 08:06:50 +00001689/*
1690%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1691% %
1692% %
1693% %
cristy6771f1e2010-03-05 19:43:39 +00001694% C l o n e K e r n e l I n f o %
anthony4fd27e22010-02-07 08:17:18 +00001695% %
1696% %
1697% %
1698%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1699%
anthony1b2bc0a2010-05-12 05:25:22 +00001700% CloneKernelInfo() creates a new clone of the given Kernel List so that its
1701% can be modified without effecting the original. The cloned kernel should
1702% be destroyed using DestoryKernelInfo() when no longer needed.
anthony7a01dcf2010-05-11 12:25:52 +00001703%
cristye6365592010-04-02 17:31:23 +00001704% The format of the CloneKernelInfo method is:
anthony4fd27e22010-02-07 08:17:18 +00001705%
anthony930be612010-02-08 04:26:15 +00001706% KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +00001707%
1708% A description of each parameter follows:
1709%
1710% o kernel: the Morphology/Convolution kernel to be cloned
1711%
1712*/
cristyef656912010-03-05 19:54:59 +00001713MagickExport KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +00001714{
1715 register long
1716 i;
1717
cristy19eb6412010-04-23 14:42:29 +00001718 KernelInfo
anthony7a01dcf2010-05-11 12:25:52 +00001719 *new_kernel;
anthony4fd27e22010-02-07 08:17:18 +00001720
1721 assert(kernel != (KernelInfo *) NULL);
anthony7a01dcf2010-05-11 12:25:52 +00001722 new_kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
1723 if (new_kernel == (KernelInfo *) NULL)
1724 return(new_kernel);
1725 *new_kernel=(*kernel); /* copy values in structure */
anthony7a01dcf2010-05-11 12:25:52 +00001726
1727 /* replace the values with a copy of the values */
1728 new_kernel->values=(double *) AcquireQuantumMemory(kernel->width,
cristy19eb6412010-04-23 14:42:29 +00001729 kernel->height*sizeof(double));
anthony7a01dcf2010-05-11 12:25:52 +00001730 if (new_kernel->values == (double *) NULL)
1731 return(DestroyKernelInfo(new_kernel));
anthony4fd27e22010-02-07 08:17:18 +00001732 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthony7a01dcf2010-05-11 12:25:52 +00001733 new_kernel->values[i]=kernel->values[i];
anthony1b2bc0a2010-05-12 05:25:22 +00001734
1735 /* Also clone the next kernel in the kernel list */
1736 if ( kernel->next != (KernelInfo *) NULL ) {
1737 new_kernel->next = CloneKernelInfo(kernel->next);
1738 if ( new_kernel->next == (KernelInfo *) NULL )
1739 return(DestroyKernelInfo(new_kernel));
1740 }
1741
anthony7a01dcf2010-05-11 12:25:52 +00001742 return(new_kernel);
anthony4fd27e22010-02-07 08:17:18 +00001743}
1744
1745/*
1746%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1747% %
1748% %
1749% %
anthony83ba99b2010-01-24 08:48:15 +00001750% D e s t r o y K e r n e l I n f o %
anthony602ab9b2010-01-05 08:06:50 +00001751% %
1752% %
1753% %
1754%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1755%
anthony83ba99b2010-01-24 08:48:15 +00001756% DestroyKernelInfo() frees the memory used by a Convolution/Morphology
1757% kernel.
anthony602ab9b2010-01-05 08:06:50 +00001758%
anthony83ba99b2010-01-24 08:48:15 +00001759% The format of the DestroyKernelInfo method is:
anthony602ab9b2010-01-05 08:06:50 +00001760%
anthony83ba99b2010-01-24 08:48:15 +00001761% KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +00001762%
1763% A description of each parameter follows:
1764%
1765% o kernel: the Morphology/Convolution kernel to be destroyed
1766%
1767*/
1768
anthony83ba99b2010-01-24 08:48:15 +00001769MagickExport KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +00001770{
cristy2be15382010-01-21 02:38:03 +00001771 assert(kernel != (KernelInfo *) NULL);
anthony4fd27e22010-02-07 08:17:18 +00001772
anthony7a01dcf2010-05-11 12:25:52 +00001773 if ( kernel->next != (KernelInfo *) NULL )
1774 kernel->next = DestroyKernelInfo(kernel->next);
1775
1776 kernel->values = (double *)RelinquishMagickMemory(kernel->values);
1777 kernel = (KernelInfo *) RelinquishMagickMemory(kernel);
anthony602ab9b2010-01-05 08:06:50 +00001778 return(kernel);
1779}
anthonyc94cdb02010-01-06 08:15:29 +00001780
1781/*
1782%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1783% %
1784% %
1785% %
anthony3c10fc82010-05-13 02:40:51 +00001786% E x p a n d K e r n e l I n f o %
1787% %
1788% %
1789% %
1790%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1791%
1792% ExpandKernelInfo() takes a single kernel, and expands it into a list
1793% of kernels each incrementally rotated the angle given.
1794%
1795% WARNING: 45 degree rotations only works for 3x3 kernels.
1796% While 90 degree roatations only works for linear and square kernels
1797%
1798% The format of the RotateKernelInfo method is:
1799%
1800% void ExpandKernelInfo(KernelInfo *kernel, double angle)
1801%
1802% A description of each parameter follows:
1803%
1804% o kernel: the Morphology/Convolution kernel
1805%
1806% o angle: angle to rotate in degrees
1807%
1808% This function is only internel to this module, as it is not finalized,
1809% especially with regard to non-orthogonal angles, and rotation of larger
1810% 2D kernels.
1811*/
1812static void ExpandKernelInfo(KernelInfo *kernel, double angle)
1813{
1814 KernelInfo
cristya9a61ad2010-05-13 12:47:41 +00001815 *clone,
anthony3c10fc82010-05-13 02:40:51 +00001816 *last;
cristya9a61ad2010-05-13 12:47:41 +00001817
anthony3c10fc82010-05-13 02:40:51 +00001818 double
1819 a;
1820
1821 last = kernel;
1822 for (a=angle; a<355.0; a+=angle) {
cristya9a61ad2010-05-13 12:47:41 +00001823 clone = CloneKernelInfo(last);
1824 RotateKernelInfo(clone, angle);
1825 last->next = clone;
1826 last = clone;
anthony3c10fc82010-05-13 02:40:51 +00001827 }
1828}
1829
1830/*
1831%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1832% %
1833% %
1834% %
anthony46a369d2010-05-19 02:41:48 +00001835+ C a l c M e t a K e r n a l I n f o %
1836% %
1837% %
1838% %
1839%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1840%
1841% CalcKernelMetaData() recalculate the KernelInfo meta-data of this kernel only,
1842% using the kernel values. This should only ne used if it is not posible to
1843% calculate that meta-data in some easier way.
1844%
1845% It is important that the meta-data is correct before ScaleKernelInfo() is
1846% used to perform kernel normalization.
1847%
1848% The format of the CalcKernelMetaData method is:
1849%
1850% void CalcKernelMetaData(KernelInfo *kernel, const double scale )
1851%
1852% A description of each parameter follows:
1853%
1854% o kernel: the Morphology/Convolution kernel to modify
1855%
1856% WARNING: Minimum and Maximum values are assumed to include zero, even if
1857% zero is not part of the kernel (as in Gaussian Derived kernels). This
1858% however is not true for flat-shaped morphological kernels.
1859%
1860% WARNING: Only the specific kernel pointed to is modified, not a list of
1861% multiple kernels.
1862%
1863% This is an internal function and not expected to be useful outside this
1864% module. This could change however.
1865*/
1866static void CalcKernelMetaData(KernelInfo *kernel)
1867{
1868 register unsigned long
1869 i;
1870
1871 kernel->minimum = kernel->maximum = 0.0;
1872 kernel->negative_range = kernel->positive_range = 0.0;
1873 for (i=0; i < (kernel->width*kernel->height); i++)
1874 {
1875 if ( fabs(kernel->values[i]) < MagickEpsilon )
1876 kernel->values[i] = 0.0;
1877 ( kernel->values[i] < 0)
1878 ? ( kernel->negative_range += kernel->values[i] )
1879 : ( kernel->positive_range += kernel->values[i] );
1880 Minimize(kernel->minimum, kernel->values[i]);
1881 Maximize(kernel->maximum, kernel->values[i]);
1882 }
1883
1884 return;
1885}
1886
1887/*
1888%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1889% %
1890% %
1891% %
anthony9eb4f742010-05-18 02:45:54 +00001892% M o r p h o l o g y A p p l y %
anthony602ab9b2010-01-05 08:06:50 +00001893% %
1894% %
1895% %
1896%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1897%
anthony9eb4f742010-05-18 02:45:54 +00001898% MorphologyApply() applies a morphological method, multiple times using
1899% a list of multiple kernels.
anthony602ab9b2010-01-05 08:06:50 +00001900%
anthony9eb4f742010-05-18 02:45:54 +00001901% It is basically equivelent to as MorphologyImageChannel() (see below) but
1902% without user controls, that that function extracts and applies to kernels
1903% and morphology methods.
1904%
1905% More specifically kernels are not normalized/scaled/blended by the
1906% 'convolve:scale' Image Artifact (-set setting), and the convolve bias
1907% (-bias setting or image->bias) is passed directly to this function,
1908% and not extracted from an image.
anthony602ab9b2010-01-05 08:06:50 +00001909%
1910% The format of the MorphologyImage method is:
1911%
anthony9eb4f742010-05-18 02:45:54 +00001912% Image *MorphologyApply(const Image *image,MorphologyMethod method,
1913% const long iterations,const KernelInfo *kernel,const double bias,
1914% ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001915%
1916% A description of each parameter follows:
1917%
1918% o image: the image.
1919%
1920% o method: the morphology method to be applied.
1921%
1922% o iterations: apply the operation this many times (or no change).
1923% A value of -1 means loop until no change found.
1924% How this is applied may depend on the morphology method.
1925% Typically this is a value of 1.
1926%
1927% o channel: the channel type.
1928%
1929% o kernel: An array of double representing the morphology kernel.
anthony29188a82010-01-22 10:12:34 +00001930% Warning: kernel may be normalized for the Convolve method.
anthony602ab9b2010-01-05 08:06:50 +00001931%
anthony9eb4f742010-05-18 02:45:54 +00001932% o bias: Convolution Bias to use.
1933%
anthony602ab9b2010-01-05 08:06:50 +00001934% o exception: return any errors or warnings in this structure.
1935%
anthony602ab9b2010-01-05 08:06:50 +00001936*/
1937
anthony930be612010-02-08 04:26:15 +00001938
anthony9eb4f742010-05-18 02:45:54 +00001939/* Apply a Morphology Primative to an image using the given kernel.
1940** Two pre-created images must be provided, no image is created.
1941** Returning the number of pixels that changed.
1942*/
anthony46a369d2010-05-19 02:41:48 +00001943static unsigned long MorphologyPrimitive(const Image *image, Image
anthony602ab9b2010-01-05 08:06:50 +00001944 *result_image, const MorphologyMethod method, const ChannelType channel,
anthony9eb4f742010-05-18 02:45:54 +00001945 const KernelInfo *kernel,const double bias,ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001946{
cristy2be15382010-01-21 02:38:03 +00001947#define MorphologyTag "Morphology/Image"
anthony602ab9b2010-01-05 08:06:50 +00001948
1949 long
cristy150989e2010-02-01 14:59:39 +00001950 progress,
anthony29188a82010-01-22 10:12:34 +00001951 y, offx, offy,
anthony602ab9b2010-01-05 08:06:50 +00001952 changed;
1953
1954 MagickBooleanType
1955 status;
1956
anthony602ab9b2010-01-05 08:06:50 +00001957 CacheView
1958 *p_view,
1959 *q_view;
1960
anthony602ab9b2010-01-05 08:06:50 +00001961 status=MagickTrue;
1962 changed=0;
1963 progress=0;
1964
anthony602ab9b2010-01-05 08:06:50 +00001965 p_view=AcquireCacheView(image);
1966 q_view=AcquireCacheView(result_image);
anthony29188a82010-01-22 10:12:34 +00001967
anthonycc6c8362010-01-25 04:14:01 +00001968 /* Some methods (including convolve) needs use a reflected kernel.
anthony9eb4f742010-05-18 02:45:54 +00001969 * Adjust 'origin' offsets to loop though kernel as a reflection.
anthony29188a82010-01-22 10:12:34 +00001970 */
cristyc99304f2010-02-01 15:26:27 +00001971 offx = kernel->x;
1972 offy = kernel->y;
anthony29188a82010-01-22 10:12:34 +00001973 switch(method) {
anthony930be612010-02-08 04:26:15 +00001974 case ConvolveMorphology:
1975 case DilateMorphology:
1976 case DilateIntensityMorphology:
1977 case DistanceMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001978 /* kernel needs to used with reflection about origin */
cristy150989e2010-02-01 14:59:39 +00001979 offx = (long) kernel->width-offx-1;
1980 offy = (long) kernel->height-offy-1;
anthony29188a82010-01-22 10:12:34 +00001981 break;
anthony5ef8e942010-05-11 06:51:12 +00001982 case ErodeMorphology:
1983 case ErodeIntensityMorphology:
1984 case HitAndMissMorphology:
1985 case ThinningMorphology:
1986 case ThickenMorphology:
1987 /* kernel is user as is, without reflection */
1988 break;
anthony930be612010-02-08 04:26:15 +00001989 default:
anthony9eb4f742010-05-18 02:45:54 +00001990 assert("Not a Primitive Morphology Method" != (char *) NULL);
anthony930be612010-02-08 04:26:15 +00001991 break;
anthony29188a82010-01-22 10:12:34 +00001992 }
1993
anthony602ab9b2010-01-05 08:06:50 +00001994#if defined(MAGICKCORE_OPENMP_SUPPORT)
1995 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1996#endif
cristy150989e2010-02-01 14:59:39 +00001997 for (y=0; y < (long) image->rows; y++)
anthony602ab9b2010-01-05 08:06:50 +00001998 {
1999 MagickBooleanType
2000 sync;
2001
2002 register const PixelPacket
2003 *restrict p;
2004
2005 register const IndexPacket
2006 *restrict p_indexes;
2007
2008 register PixelPacket
2009 *restrict q;
2010
2011 register IndexPacket
2012 *restrict q_indexes;
2013
cristy150989e2010-02-01 14:59:39 +00002014 register long
anthony602ab9b2010-01-05 08:06:50 +00002015 x;
2016
anthony29188a82010-01-22 10:12:34 +00002017 unsigned long
anthony602ab9b2010-01-05 08:06:50 +00002018 r;
2019
2020 if (status == MagickFalse)
2021 continue;
anthony29188a82010-01-22 10:12:34 +00002022 p=GetCacheViewVirtualPixels(p_view, -offx, y-offy,
2023 image->columns+kernel->width, kernel->height, exception);
anthony602ab9b2010-01-05 08:06:50 +00002024 q=GetCacheViewAuthenticPixels(q_view,0,y,result_image->columns,1,
2025 exception);
2026 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2027 {
2028 status=MagickFalse;
2029 continue;
2030 }
2031 p_indexes=GetCacheViewVirtualIndexQueue(p_view);
2032 q_indexes=GetCacheViewAuthenticIndexQueue(q_view);
anthony29188a82010-01-22 10:12:34 +00002033 r = (image->columns+kernel->width)*offy+offx; /* constant */
2034
cristy150989e2010-02-01 14:59:39 +00002035 for (x=0; x < (long) image->columns; x++)
anthony602ab9b2010-01-05 08:06:50 +00002036 {
cristy150989e2010-02-01 14:59:39 +00002037 long
anthony602ab9b2010-01-05 08:06:50 +00002038 v;
2039
cristy150989e2010-02-01 14:59:39 +00002040 register long
anthony602ab9b2010-01-05 08:06:50 +00002041 u;
2042
2043 register const double
2044 *restrict k;
2045
2046 register const PixelPacket
2047 *restrict k_pixels;
2048
2049 register const IndexPacket
2050 *restrict k_indexes;
2051
2052 MagickPixelPacket
anthony5ef8e942010-05-11 06:51:12 +00002053 result,
2054 min,
2055 max;
anthony602ab9b2010-01-05 08:06:50 +00002056
anthony29188a82010-01-22 10:12:34 +00002057 /* Copy input to ouput image for unused channels
anthony83ba99b2010-01-24 08:48:15 +00002058 * This removes need for 'cloning' a new image every iteration
anthony29188a82010-01-22 10:12:34 +00002059 */
anthony602ab9b2010-01-05 08:06:50 +00002060 *q = p[r];
2061 if (image->colorspace == CMYKColorspace)
2062 q_indexes[x] = p_indexes[r];
2063
anthony5ef8e942010-05-11 06:51:12 +00002064 /* Defaults */
2065 min.red =
2066 min.green =
2067 min.blue =
2068 min.opacity =
2069 min.index = (MagickRealType) QuantumRange;
2070 max.red =
2071 max.green =
2072 max.blue =
2073 max.opacity =
2074 max.index = (MagickRealType) 0;
anthony9eb4f742010-05-18 02:45:54 +00002075 /* default result is the original pixel value */
anthony5ef8e942010-05-11 06:51:12 +00002076 result.red = (MagickRealType) p[r].red;
2077 result.green = (MagickRealType) p[r].green;
2078 result.blue = (MagickRealType) p[r].blue;
2079 result.opacity = QuantumRange - (MagickRealType) p[r].opacity;
cristye96405a2010-05-19 02:24:31 +00002080 result.index = 0.0;
anthony5ef8e942010-05-11 06:51:12 +00002081 if ( image->colorspace == CMYKColorspace)
2082 result.index = (MagickRealType) p_indexes[r];
2083
anthony602ab9b2010-01-05 08:06:50 +00002084 switch (method) {
2085 case ConvolveMorphology:
anthony9eb4f742010-05-18 02:45:54 +00002086 /* Set the user defined bias of the weighted average output */
2087 result.red =
2088 result.green =
2089 result.blue =
2090 result.opacity =
2091 result.index = bias;
anthony930be612010-02-08 04:26:15 +00002092 break;
anthony4fd27e22010-02-07 08:17:18 +00002093 case DilateIntensityMorphology:
2094 case ErodeIntensityMorphology:
anthony9eb4f742010-05-18 02:45:54 +00002095 /* use a boolean flag indicating when first match found */
2096 result.red = 0.0; /* result is not used otherwise */
anthony4fd27e22010-02-07 08:17:18 +00002097 break;
anthony602ab9b2010-01-05 08:06:50 +00002098 default:
anthony602ab9b2010-01-05 08:06:50 +00002099 break;
2100 }
2101
2102 switch ( method ) {
2103 case ConvolveMorphology:
anthony930be612010-02-08 04:26:15 +00002104 /* Weighted Average of pixels using reflected kernel
2105 **
2106 ** NOTE for correct working of this operation for asymetrical
2107 ** kernels, the kernel needs to be applied in its reflected form.
2108 ** That is its values needs to be reversed.
2109 **
2110 ** Correlation is actually the same as this but without reflecting
2111 ** the kernel, and thus 'lower-level' that Convolution. However
2112 ** as Convolution is the more common method used, and it does not
2113 ** really cost us much in terms of processing to use a reflected
anthony5ef8e942010-05-11 06:51:12 +00002114 ** kernel, so it is Convolution that is implemented.
anthony930be612010-02-08 04:26:15 +00002115 **
2116 ** Correlation will have its kernel reflected before calling
2117 ** this function to do a Convolve.
2118 **
2119 ** For more details of Correlation vs Convolution see
2120 ** http://www.cs.umd.edu/~djacobs/CMSC426/Convolution.pdf
2121 */
anthony5ef8e942010-05-11 06:51:12 +00002122 if (((channel & SyncChannels) != 0 ) &&
2123 (image->matte == MagickTrue))
2124 { /* Channel has a 'Sync' Flag, and Alpha Channel enabled.
2125 ** Weight the color channels with Alpha Channel so that
2126 ** transparent pixels are not part of the results.
2127 */
anthony602ab9b2010-01-05 08:06:50 +00002128 MagickRealType
anthony5ef8e942010-05-11 06:51:12 +00002129 alpha, /* color channel weighting : kernel*alpha */
2130 gamma; /* divisor, sum of weighting values */
anthony602ab9b2010-01-05 08:06:50 +00002131
2132 gamma=0.0;
anthony29188a82010-01-22 10:12:34 +00002133 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00002134 k_pixels = p;
2135 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002136 for (v=0; v < (long) kernel->height; v++) {
2137 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00002138 if ( IsNan(*k) ) continue;
2139 alpha=(*k)*(QuantumScale*(QuantumRange-
2140 k_pixels[u].opacity));
2141 gamma += alpha;
2142 result.red += alpha*k_pixels[u].red;
2143 result.green += alpha*k_pixels[u].green;
2144 result.blue += alpha*k_pixels[u].blue;
anthony83ba99b2010-01-24 08:48:15 +00002145 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00002146 if ( image->colorspace == CMYKColorspace)
2147 result.index += alpha*k_indexes[u];
2148 }
2149 k_pixels += image->columns+kernel->width;
2150 k_indexes += image->columns+kernel->width;
2151 }
2152 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
anthony83ba99b2010-01-24 08:48:15 +00002153 result.red *= gamma;
2154 result.green *= gamma;
2155 result.blue *= gamma;
2156 result.opacity *= gamma;
2157 result.index *= gamma;
anthony602ab9b2010-01-05 08:06:50 +00002158 }
anthony5ef8e942010-05-11 06:51:12 +00002159 else
2160 {
2161 /* No 'Sync' flag, or no Alpha involved.
2162 ** Convolution is simple individual channel weigthed sum.
2163 */
2164 k = &kernel->values[ kernel->width*kernel->height-1 ];
2165 k_pixels = p;
2166 k_indexes = p_indexes;
2167 for (v=0; v < (long) kernel->height; v++) {
2168 for (u=0; u < (long) kernel->width; u++, k--) {
2169 if ( IsNan(*k) ) continue;
2170 result.red += (*k)*k_pixels[u].red;
2171 result.green += (*k)*k_pixels[u].green;
2172 result.blue += (*k)*k_pixels[u].blue;
2173 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
2174 if ( image->colorspace == CMYKColorspace)
2175 result.index += (*k)*k_indexes[u];
2176 }
2177 k_pixels += image->columns+kernel->width;
2178 k_indexes += image->columns+kernel->width;
2179 }
2180 }
anthony602ab9b2010-01-05 08:06:50 +00002181 break;
2182
anthony4fd27e22010-02-07 08:17:18 +00002183 case ErodeMorphology:
anthony5ef8e942010-05-11 06:51:12 +00002184 /* Minimum Value within kernel neighbourhood
anthony930be612010-02-08 04:26:15 +00002185 **
2186 ** NOTE that the kernel is not reflected for this operation!
2187 **
2188 ** NOTE: in normal Greyscale Morphology, the kernel value should
2189 ** be added to the real value, this is currently not done, due to
2190 ** the nature of the boolean kernels being used.
2191 */
anthony4fd27e22010-02-07 08:17:18 +00002192 k = kernel->values;
2193 k_pixels = p;
2194 k_indexes = p_indexes;
2195 for (v=0; v < (long) kernel->height; v++) {
2196 for (u=0; u < (long) kernel->width; u++, k++) {
2197 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony5ef8e942010-05-11 06:51:12 +00002198 Minimize(min.red, (double) k_pixels[u].red);
2199 Minimize(min.green, (double) k_pixels[u].green);
2200 Minimize(min.blue, (double) k_pixels[u].blue);
2201 Minimize(min.opacity,
anthonyd37a5cb2010-05-07 06:37:03 +00002202 QuantumRange-(double) k_pixels[u].opacity);
anthony4fd27e22010-02-07 08:17:18 +00002203 if ( image->colorspace == CMYKColorspace)
anthony5ef8e942010-05-11 06:51:12 +00002204 Minimize(min.index, (double) k_indexes[u]);
anthony4fd27e22010-02-07 08:17:18 +00002205 }
2206 k_pixels += image->columns+kernel->width;
2207 k_indexes += image->columns+kernel->width;
2208 }
2209 break;
2210
anthony5ef8e942010-05-11 06:51:12 +00002211
anthony83ba99b2010-01-24 08:48:15 +00002212 case DilateMorphology:
anthony5ef8e942010-05-11 06:51:12 +00002213 /* Maximum Value within kernel neighbourhood
anthony930be612010-02-08 04:26:15 +00002214 **
2215 ** NOTE for correct working of this operation for asymetrical
2216 ** kernels, the kernel needs to be applied in its reflected form.
2217 ** That is its values needs to be reversed.
2218 **
2219 ** NOTE: in normal Greyscale Morphology, the kernel value should
2220 ** be added to the real value, this is currently not done, due to
2221 ** the nature of the boolean kernels being used.
2222 **
2223 */
anthony29188a82010-01-22 10:12:34 +00002224 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00002225 k_pixels = p;
2226 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002227 for (v=0; v < (long) kernel->height; v++) {
2228 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00002229 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony5ef8e942010-05-11 06:51:12 +00002230 Maximize(max.red, (double) k_pixels[u].red);
2231 Maximize(max.green, (double) k_pixels[u].green);
2232 Maximize(max.blue, (double) k_pixels[u].blue);
2233 Maximize(max.opacity,
anthonyd37a5cb2010-05-07 06:37:03 +00002234 QuantumRange-(double) k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00002235 if ( image->colorspace == CMYKColorspace)
anthony5ef8e942010-05-11 06:51:12 +00002236 Maximize(max.index, (double) k_indexes[u]);
anthony602ab9b2010-01-05 08:06:50 +00002237 }
2238 k_pixels += image->columns+kernel->width;
2239 k_indexes += image->columns+kernel->width;
2240 }
anthony602ab9b2010-01-05 08:06:50 +00002241 break;
2242
anthony5ef8e942010-05-11 06:51:12 +00002243 case HitAndMissMorphology:
2244 case ThinningMorphology:
2245 case ThickenMorphology:
2246 /* Minimum of Foreground Pixel minus Maxumum of Background Pixels
2247 **
2248 ** NOTE that the kernel is not reflected for this operation,
2249 ** and consists of both foreground and background pixel
2250 ** neighbourhoods, 0.0 for background, and 1.0 for foreground
2251 ** with either Nan or 0.5 values for don't care.
2252 **
2253 ** Note that this can produce negative results, though really
2254 ** only a positive match has any real value.
2255 */
2256 k = kernel->values;
2257 k_pixels = p;
2258 k_indexes = p_indexes;
2259 for (v=0; v < (long) kernel->height; v++) {
2260 for (u=0; u < (long) kernel->width; u++, k++) {
2261 if ( IsNan(*k) ) continue;
2262 if ( (*k) > 0.7 )
2263 { /* minimim of foreground pixels */
2264 Minimize(min.red, (double) k_pixels[u].red);
2265 Minimize(min.green, (double) k_pixels[u].green);
2266 Minimize(min.blue, (double) k_pixels[u].blue);
2267 Minimize(min.opacity,
2268 QuantumRange-(double) k_pixels[u].opacity);
2269 if ( image->colorspace == CMYKColorspace)
2270 Minimize(min.index, (double) k_indexes[u]);
2271 }
2272 else if ( (*k) < 0.3 )
2273 { /* maximum of background pixels */
2274 Maximize(max.red, (double) k_pixels[u].red);
2275 Maximize(max.green, (double) k_pixels[u].green);
2276 Maximize(max.blue, (double) k_pixels[u].blue);
2277 Maximize(max.opacity,
2278 QuantumRange-(double) k_pixels[u].opacity);
2279 if ( image->colorspace == CMYKColorspace)
2280 Maximize(max.index, (double) k_indexes[u]);
2281 }
2282 }
2283 k_pixels += image->columns+kernel->width;
2284 k_indexes += image->columns+kernel->width;
2285 }
2286 /* Pattern Match only if min fg larger than min bg pixels */
2287 min.red -= max.red; Maximize( min.red, 0.0 );
2288 min.green -= max.green; Maximize( min.green, 0.0 );
2289 min.blue -= max.blue; Maximize( min.blue, 0.0 );
2290 min.opacity -= max.opacity; Maximize( min.opacity, 0.0 );
2291 min.index -= max.index; Maximize( min.index, 0.0 );
2292 break;
2293
anthony4fd27e22010-02-07 08:17:18 +00002294 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00002295 /* Select Pixel with Minimum Intensity within kernel neighbourhood
2296 **
2297 ** WARNING: the intensity test fails for CMYK and does not
2298 ** take into account the moderating effect of teh alpha channel
2299 ** on the intensity.
2300 **
2301 ** NOTE that the kernel is not reflected for this operation!
2302 */
anthony602ab9b2010-01-05 08:06:50 +00002303 k = kernel->values;
2304 k_pixels = p;
2305 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002306 for (v=0; v < (long) kernel->height; v++) {
2307 for (u=0; u < (long) kernel->width; u++, k++) {
anthony602ab9b2010-01-05 08:06:50 +00002308 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony4fd27e22010-02-07 08:17:18 +00002309 if ( result.red == 0.0 ||
2310 PixelIntensity(&(k_pixels[u])) < PixelIntensity(q) ) {
2311 /* copy the whole pixel - no channel selection */
2312 *q = k_pixels[u];
2313 if ( result.red > 0.0 ) changed++;
2314 result.red = 1.0;
2315 }
anthony602ab9b2010-01-05 08:06:50 +00002316 }
2317 k_pixels += image->columns+kernel->width;
2318 k_indexes += image->columns+kernel->width;
2319 }
anthony602ab9b2010-01-05 08:06:50 +00002320 break;
2321
anthony83ba99b2010-01-24 08:48:15 +00002322 case DilateIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00002323 /* Select Pixel with Maximum Intensity within kernel neighbourhood
2324 **
2325 ** WARNING: the intensity test fails for CMYK and does not
anthony9eb4f742010-05-18 02:45:54 +00002326 ** take into account the moderating effect of the alpha channel
2327 ** on the intensity (yet).
anthony930be612010-02-08 04:26:15 +00002328 **
2329 ** NOTE for correct working of this operation for asymetrical
2330 ** kernels, the kernel needs to be applied in its reflected form.
2331 ** That is its values needs to be reversed.
2332 */
anthony29188a82010-01-22 10:12:34 +00002333 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00002334 k_pixels = p;
2335 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002336 for (v=0; v < (long) kernel->height; v++) {
2337 for (u=0; u < (long) kernel->width; u++, k--) {
anthony29188a82010-01-22 10:12:34 +00002338 if ( IsNan(*k) || (*k) < 0.5 ) continue; /* boolean kernel */
2339 if ( result.red == 0.0 ||
2340 PixelIntensity(&(k_pixels[u])) > PixelIntensity(q) ) {
2341 /* copy the whole pixel - no channel selection */
2342 *q = k_pixels[u];
2343 if ( result.red > 0.0 ) changed++;
2344 result.red = 1.0;
2345 }
anthony602ab9b2010-01-05 08:06:50 +00002346 }
2347 k_pixels += image->columns+kernel->width;
2348 k_indexes += image->columns+kernel->width;
2349 }
anthony602ab9b2010-01-05 08:06:50 +00002350 break;
2351
anthony5ef8e942010-05-11 06:51:12 +00002352
anthony602ab9b2010-01-05 08:06:50 +00002353 case DistanceMorphology:
anthony930be612010-02-08 04:26:15 +00002354 /* Add kernel Value and select the minimum value found.
2355 ** The result is a iterative distance from edge of image shape.
2356 **
2357 ** All Distance Kernels are symetrical, but that may not always
2358 ** be the case. For example how about a distance from left edges?
2359 ** To work correctly with asymetrical kernels the reflected kernel
2360 ** needs to be applied.
anthony5ef8e942010-05-11 06:51:12 +00002361 **
2362 ** Actually this is really a GreyErode with a negative kernel!
2363 **
anthony930be612010-02-08 04:26:15 +00002364 */
anthony29188a82010-01-22 10:12:34 +00002365 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00002366 k_pixels = p;
2367 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002368 for (v=0; v < (long) kernel->height; v++) {
2369 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00002370 if ( IsNan(*k) ) continue;
2371 Minimize(result.red, (*k)+k_pixels[u].red);
2372 Minimize(result.green, (*k)+k_pixels[u].green);
2373 Minimize(result.blue, (*k)+k_pixels[u].blue);
2374 Minimize(result.opacity, (*k)+QuantumRange-k_pixels[u].opacity);
2375 if ( image->colorspace == CMYKColorspace)
2376 Minimize(result.index, (*k)+k_indexes[u]);
2377 }
2378 k_pixels += image->columns+kernel->width;
2379 k_indexes += image->columns+kernel->width;
2380 }
anthony602ab9b2010-01-05 08:06:50 +00002381 break;
2382
2383 case UndefinedMorphology:
2384 default:
2385 break; /* Do nothing */
anthony83ba99b2010-01-24 08:48:15 +00002386 }
anthony5ef8e942010-05-11 06:51:12 +00002387 /* Final mathematics of results (combine with original image?)
2388 **
2389 ** NOTE: Difference Morphology operators Edge* and *Hat could also
2390 ** be done here but works better with iteration as a image difference
2391 ** in the controling function (below). Thicken and Thinning however
2392 ** should be done here so thay can be iterated correctly.
2393 */
2394 switch ( method ) {
2395 case HitAndMissMorphology:
2396 case ErodeMorphology:
2397 result = min; /* minimum of neighbourhood */
2398 break;
2399 case DilateMorphology:
2400 result = max; /* maximum of neighbourhood */
2401 break;
2402 case ThinningMorphology:
2403 /* subtract pattern match from original */
2404 result.red -= min.red;
2405 result.green -= min.green;
2406 result.blue -= min.blue;
2407 result.opacity -= min.opacity;
2408 result.index -= min.index;
2409 break;
2410 case ThickenMorphology:
2411 /* Union with original image (maximize) - or should this be + */
2412 Maximize( result.red, min.red );
2413 Maximize( result.green, min.green );
2414 Maximize( result.blue, min.blue );
2415 Maximize( result.opacity, min.opacity );
2416 Maximize( result.index, min.index );
2417 break;
2418 default:
2419 /* result directly calculated or assigned */
2420 break;
2421 }
2422 /* Assign the resulting pixel values - Clamping Result */
anthony83ba99b2010-01-24 08:48:15 +00002423 switch ( method ) {
2424 case UndefinedMorphology:
2425 case DilateIntensityMorphology:
2426 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00002427 break; /* full pixel was directly assigned - not a channel method */
anthony83ba99b2010-01-24 08:48:15 +00002428 default:
anthony83ba99b2010-01-24 08:48:15 +00002429 if ((channel & RedChannel) != 0)
2430 q->red = ClampToQuantum(result.red);
2431 if ((channel & GreenChannel) != 0)
2432 q->green = ClampToQuantum(result.green);
2433 if ((channel & BlueChannel) != 0)
2434 q->blue = ClampToQuantum(result.blue);
2435 if ((channel & OpacityChannel) != 0
2436 && image->matte == MagickTrue )
2437 q->opacity = ClampToQuantum(QuantumRange-result.opacity);
2438 if ((channel & IndexChannel) != 0
2439 && image->colorspace == CMYKColorspace)
2440 q_indexes[x] = ClampToQuantum(result.index);
2441 break;
2442 }
anthony5ef8e942010-05-11 06:51:12 +00002443 /* Count up changed pixels */
anthony83ba99b2010-01-24 08:48:15 +00002444 if ( ( p[r].red != q->red )
2445 || ( p[r].green != q->green )
2446 || ( p[r].blue != q->blue )
2447 || ( p[r].opacity != q->opacity )
2448 || ( image->colorspace == CMYKColorspace &&
2449 p_indexes[r] != q_indexes[x] ) )
2450 changed++; /* The pixel had some value changed! */
anthony602ab9b2010-01-05 08:06:50 +00002451 p++;
2452 q++;
anthony83ba99b2010-01-24 08:48:15 +00002453 } /* x */
anthony602ab9b2010-01-05 08:06:50 +00002454 sync=SyncCacheViewAuthenticPixels(q_view,exception);
2455 if (sync == MagickFalse)
2456 status=MagickFalse;
2457 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2458 {
2459 MagickBooleanType
2460 proceed;
2461
2462#if defined(MAGICKCORE_OPENMP_SUPPORT)
2463 #pragma omp critical (MagickCore_MorphologyImage)
2464#endif
2465 proceed=SetImageProgress(image,MorphologyTag,progress++,image->rows);
2466 if (proceed == MagickFalse)
2467 status=MagickFalse;
2468 }
anthony83ba99b2010-01-24 08:48:15 +00002469 } /* y */
anthony602ab9b2010-01-05 08:06:50 +00002470 result_image->type=image->type;
2471 q_view=DestroyCacheView(q_view);
2472 p_view=DestroyCacheView(p_view);
cristy150989e2010-02-01 14:59:39 +00002473 return(status ? (unsigned long) changed : 0);
anthony602ab9b2010-01-05 08:06:50 +00002474}
2475
anthony4fd27e22010-02-07 08:17:18 +00002476
anthony9eb4f742010-05-18 02:45:54 +00002477MagickExport Image *MorphologyApply(const Image *image, const ChannelType
2478 channel,const MorphologyMethod method, const long iterations,
2479 const KernelInfo *kernel,const double bias,ExceptionInfo *exception)
cristy2be15382010-01-21 02:38:03 +00002480{
2481 Image
anthony9eb4f742010-05-18 02:45:54 +00002482 *curr_image, /* Image we are working with */
2483 *work_image, /* secondary working image */
2484 *save_image; /* save image for later use */
anthony602ab9b2010-01-05 08:06:50 +00002485
anthony4fd27e22010-02-07 08:17:18 +00002486 KernelInfo
anthony9eb4f742010-05-18 02:45:54 +00002487 *curr_kernel, /* current kernel list to apply */
2488 *this_kernel; /* current individual kernel to apply */
anthony4fd27e22010-02-07 08:17:18 +00002489
2490 MorphologyMethod
cristye96405a2010-05-19 02:24:31 +00002491 primitive; /* the current morphology primitive being applied */
anthony9eb4f742010-05-18 02:45:54 +00002492
2493 MagickBooleanType
2494 verbose; /* verbose output of results */
2495
2496 CompositeOperator
2497 kernel_compose; /* Handling the result of multiple kernels*/
anthony4fd27e22010-02-07 08:17:18 +00002498
anthony1b2bc0a2010-05-12 05:25:22 +00002499 unsigned long
cristye96405a2010-05-19 02:24:31 +00002500 count, /* count of primitive steps applied */
anthony9eb4f742010-05-18 02:45:54 +00002501 loop, /* number of times though kernel list (iterations) */
2502 loop_limit, /* finish looping after this many times */
2503 stage, /* stage number for compound morphology */
cristye96405a2010-05-19 02:24:31 +00002504 changed, /* number pixels changed by one primitive operation */
anthony9eb4f742010-05-18 02:45:54 +00002505 loop_changed, /* changes made over loop though of kernels */
2506 total_changed, /* total count of all changes to image */
2507 kernel_number; /* kernel number being applied */
anthony1b2bc0a2010-05-12 05:25:22 +00002508
anthony602ab9b2010-01-05 08:06:50 +00002509 assert(image != (Image *) NULL);
2510 assert(image->signature == MagickSignature);
anthony4fd27e22010-02-07 08:17:18 +00002511 assert(kernel != (KernelInfo *) NULL);
2512 assert(kernel->signature == MagickSignature);
anthony602ab9b2010-01-05 08:06:50 +00002513 assert(exception != (ExceptionInfo *) NULL);
2514 assert(exception->signature == MagickSignature);
2515
cristye96405a2010-05-19 02:24:31 +00002516 loop_limit = (unsigned long) iterations;
anthony9eb4f742010-05-18 02:45:54 +00002517 if ( iterations < 0 )
2518 loop_limit = image->columns > image->rows ? image->columns : image->rows;
anthony602ab9b2010-01-05 08:06:50 +00002519 if ( iterations == 0 )
2520 return((Image *)NULL); /* null operation - nothing to do! */
2521
2522 /* kernel must be valid at this point
2523 * (except maybe for posible future morphology methods like "Prune"
2524 */
cristy2be15382010-01-21 02:38:03 +00002525 assert(kernel != (KernelInfo *)NULL);
anthony602ab9b2010-01-05 08:06:50 +00002526
cristye96405a2010-05-19 02:24:31 +00002527 verbose = ( GetImageArtifact(image,"verbose") != (const char *) NULL ) ?
2528 MagickTrue : MagickFalse;
anthony4f1dcb72010-05-14 08:43:10 +00002529
anthony9eb4f742010-05-18 02:45:54 +00002530 /* initialise for cleanup */
cristye96405a2010-05-19 02:24:31 +00002531 curr_image = (Image *) image; /* result of morpholgy primitive */
anthony9eb4f742010-05-18 02:45:54 +00002532 work_image = (Image *) NULL; /* secondary working image */
2533 save_image = (Image *) NULL; /* save image for some compound methods */
2534 curr_kernel = (KernelInfo *)kernel; /* allow kernel list to be modified */
anthony4fd27e22010-02-07 08:17:18 +00002535
anthony9eb4f742010-05-18 02:45:54 +00002536 kernel_compose = NoCompositeOp; /* iterated over all kernels */
anthony602ab9b2010-01-05 08:06:50 +00002537
cristye96405a2010-05-19 02:24:31 +00002538 /* Select initial primitive morphology to apply */
2539 primitive = UndefinedMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002540 switch( method ) {
anthony930be612010-02-08 04:26:15 +00002541 case CorrelateMorphology:
2542 /* A Correlation is actually a Convolution with a reflected kernel.
anthony9eb4f742010-05-18 02:45:54 +00002543 ** However a Convolution is a weighted sum using a reflected kernel.
anthony930be612010-02-08 04:26:15 +00002544 ** It may seem stange to convert a Correlation into a Convolution
2545 ** as the Correleation is the simplier method, but Convolution is
2546 ** much more commonly used, and it makes sense to implement it directly
2547 ** so as to avoid the need to duplicate the kernel when it is not
2548 ** required (which is typically the default).
2549 */
anthony9eb4f742010-05-18 02:45:54 +00002550 curr_kernel = CloneKernelInfo(kernel);
anthony1b2bc0a2010-05-12 05:25:22 +00002551 if (curr_kernel == (KernelInfo *) NULL)
2552 goto error_cleanup;
anthony930be612010-02-08 04:26:15 +00002553 RotateKernelInfo(curr_kernel,180);
anthony9eb4f742010-05-18 02:45:54 +00002554 /* FALL THRU to Convolve */
anthonyc94cdb02010-01-06 08:15:29 +00002555 case ConvolveMorphology:
cristye96405a2010-05-19 02:24:31 +00002556 primitive = ConvolveMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002557 kernel_compose = NoCompositeOp;
2558 break;
2559 case ErodeMorphology: /* just erode */
2560 case OpenMorphology: /* erode then dialate */
2561 case EdgeInMorphology: /* erode and image difference */
2562 case TopHatMorphology: /* erode, dilate and image difference */
2563 case SmoothMorphology: /* erode, dilate, dilate, erode */
cristye96405a2010-05-19 02:24:31 +00002564 primitive = ErodeMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002565 break;
2566 case ErodeIntensityMorphology:
2567 case OpenIntensityMorphology:
cristye96405a2010-05-19 02:24:31 +00002568 primitive = ErodeIntensityMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002569 break;
2570 case DilateMorphology: /* just dilate */
2571 case EdgeOutMorphology: /* dilate and image difference */
2572 case EdgeMorphology: /* dilate and erode difference */
cristye96405a2010-05-19 02:24:31 +00002573 primitive = DilateMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002574 break;
2575 case CloseMorphology: /* dilate, then erode */
2576 case BottomHatMorphology: /* dilate and image difference */
2577 curr_kernel = CloneKernelInfo(kernel);
2578 if (curr_kernel == (KernelInfo *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00002579 goto error_cleanup;
anthony9eb4f742010-05-18 02:45:54 +00002580 RotateKernelInfo(curr_kernel,180);
cristye96405a2010-05-19 02:24:31 +00002581 primitive = DilateMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002582 break;
2583 case DilateIntensityMorphology:
2584 case CloseIntensityMorphology:
2585 curr_kernel = CloneKernelInfo(kernel);
2586 if (curr_kernel == (KernelInfo *) NULL)
2587 goto error_cleanup;
2588 RotateKernelInfo(curr_kernel,180);
cristye96405a2010-05-19 02:24:31 +00002589 primitive = DilateIntensityMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002590 break;
2591 case HitAndMissMorphology:
cristye96405a2010-05-19 02:24:31 +00002592 primitive = HitAndMissMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002593 loop_limit = 1; /* iterate only once */
2594 kernel_compose = LightenCompositeOp; /* Union of Hit-And-Miss */
2595 break;
2596 case ThinningMorphology: /* iterative morphology */
2597 case ThickenMorphology:
2598 case DistanceMorphology: /* Distance should never use multple kernels */
2599 case UndefinedMorphology:
cristye96405a2010-05-19 02:24:31 +00002600 primitive = method;
anthony930be612010-02-08 04:26:15 +00002601 break;
anthony602ab9b2010-01-05 08:06:50 +00002602 }
2603
anthony3c1cd552010-05-18 04:33:23 +00002604#if 0
2605 { /* User override of results handling -- Experimental */
anthony9eb4f742010-05-18 02:45:54 +00002606 const char
2607 *artifact = GetImageArtifact(image,"morphology:style");
2608 if ( artifact != (const char *) NULL ) {
2609 if (LocaleCompare("union",artifact) == 0)
2610 kernel_compose = LightenCompositeOp;
2611 if (LocaleCompare("iterate",artifact) == 0)
2612 kernel_compose = NoCompositeOp;
2613 else
2614 kernel_compose = (CompositeOperator) ParseMagickOption(
2615 MagickComposeOptions,MagickFalse,artifact);
2616 if ( kernel_compose == UndefinedCompositeOp )
2617 perror("Invalid \"morphology:compose\" setting\n");
2618 }
2619 }
anthony3c1cd552010-05-18 04:33:23 +00002620#endif
anthony7a01dcf2010-05-11 12:25:52 +00002621
anthony9eb4f742010-05-18 02:45:54 +00002622 /* Initialize compound morphology stages */
cristye96405a2010-05-19 02:24:31 +00002623 count = 0; /* number of low-level morphology primitives performed */
anthony9eb4f742010-05-18 02:45:54 +00002624 total_changed = 0; /* total number of pixels changed thoughout */
2625 stage = 1; /* the compound morphology stage number */
2626
2627 /* compount morphology staging loop */
2628 while ( 1 ) {
2629
2630#if 1
2631 /* Extra information for debugging compound operations */
cristye96405a2010-05-19 02:24:31 +00002632 if ( verbose == MagickTrue && primitive != method )
anthony9eb4f742010-05-18 02:45:54 +00002633 fprintf(stderr, "Morphology %s: Stage %lu %s%s (%s)\n",
2634 MagickOptionToMnemonic(MagickMorphologyOptions, method), stage,
cristye96405a2010-05-19 02:24:31 +00002635 MagickOptionToMnemonic(MagickMorphologyOptions, primitive),
anthony9eb4f742010-05-18 02:45:54 +00002636 ( curr_kernel == kernel) ? "" : "*",
2637 ( kernel_compose == NoCompositeOp ) ? "iterate"
2638 : MagickOptionToMnemonic(MagickComposeOptions, kernel_compose) );
2639#endif
2640
2641 if ( kernel_compose == NoCompositeOp ) {
2642 /******************************
2643 ** Iterate over all Kernels **
2644 ******************************/
2645 loop = 0;
2646 loop_changed = 1;
2647 while ( loop < loop_limit && loop_changed > 0 ) {
2648 loop++; /* the iteration of this kernel */
2649
2650 loop_changed = 0;
2651 this_kernel = curr_kernel;
2652 kernel_number = 0;
2653 while ( this_kernel != NULL ) {
2654
2655 /* Create a destination image, if not yet defined */
2656 if ( work_image == (Image *) NULL )
2657 {
2658 work_image=CloneImage(image,0,0,MagickTrue,exception);
2659 if (work_image == (Image *) NULL)
2660 goto error_cleanup;
2661 if (SetImageStorageClass(work_image,DirectClass) == MagickFalse)
2662 {
2663 InheritException(exception,&work_image->exception);
2664 goto error_cleanup;
2665 }
2666 }
2667
cristye96405a2010-05-19 02:24:31 +00002668 /* morphological primitive curr -> work */
anthony9eb4f742010-05-18 02:45:54 +00002669 count++;
anthony46a369d2010-05-19 02:41:48 +00002670 changed = MorphologyPrimitive(curr_image, work_image, primitive,
anthony9eb4f742010-05-18 02:45:54 +00002671 channel, this_kernel, bias, exception);
2672 loop_changed += changed;
2673 total_changed += changed;
2674
2675 if ( verbose == MagickTrue )
2676 fprintf(stderr, "Morphology %s:%lu.%lu #%lu => Changed %lu\n",
cristye96405a2010-05-19 02:24:31 +00002677 MagickOptionToMnemonic(MagickMorphologyOptions, primitive),
anthony9eb4f742010-05-18 02:45:54 +00002678 loop, kernel_number, count, changed);
2679
2680 /* prepare next loop */
2681 { Image *tmp = work_image; /* swap images for iteration */
2682 work_image = curr_image;
2683 curr_image = tmp;
2684 }
2685 if ( work_image == image )
2686 work_image = (Image *) NULL; /* never assign image to 'work' */
2687 this_kernel = this_kernel->next; /* prepare next kernel (if any) */
anthony1b2bc0a2010-05-12 05:25:22 +00002688 kernel_number++;
2689 }
anthony7a01dcf2010-05-11 12:25:52 +00002690
anthony9eb4f742010-05-18 02:45:54 +00002691 if ( verbose == MagickTrue && kernel->next != NULL )
2692 fprintf(stderr, "Morphology %s:%lu #%lu ===> Changed %lu Total %lu\n",
cristye96405a2010-05-19 02:24:31 +00002693 MagickOptionToMnemonic(MagickMorphologyOptions, primitive),
anthony9eb4f742010-05-18 02:45:54 +00002694 loop, count, loop_changed, total_changed );
anthony602ab9b2010-01-05 08:06:50 +00002695 }
anthony1b2bc0a2010-05-12 05:25:22 +00002696 }
2697
anthony9eb4f742010-05-18 02:45:54 +00002698 else {
2699 /*************************************
2700 ** Composition of Iterated Kernels **
2701 *************************************/
2702 Image
2703 *input_image, /* starting point for kernels */
2704 *union_image;
2705 input_image = curr_image;
2706 union_image = (Image *) NULL;
2707
2708 this_kernel = curr_kernel;
2709 kernel_number = 0;
2710 while ( this_kernel != NULL ) {
2711
2712 if( curr_image != (Image *) NULL && curr_image != input_image )
2713 curr_image=DestroyImage(curr_image);
2714 curr_image = input_image; /* always start with the original image */
2715
2716 loop = 0;
2717 changed = 1;
2718 loop_changed = 0;
2719 while ( loop < loop_limit && changed > 0 ) {
2720 loop++; /* the iteration of this kernel */
2721
2722 /* Create a destination image, if not defined */
2723 if ( work_image == (Image *) NULL )
2724 {
2725 work_image=CloneImage(image,0,0,MagickTrue,exception);
2726 if (work_image == (Image *) NULL)
2727 goto error_cleanup;
2728 if (SetImageStorageClass(work_image,DirectClass) == MagickFalse)
2729 {
2730 InheritException(exception,&curr_image->exception);
2731 if( union_image != (Image *) NULL )
2732 union_image=DestroyImage(union_image);
2733 if( curr_image != input_image )
2734 curr_image = DestroyImage(curr_image);
2735 curr_image = (Image *) input_image;
2736 goto error_cleanup;
2737 }
2738 }
2739
cristye96405a2010-05-19 02:24:31 +00002740 /* morphological primitive curr -> work */
anthony9eb4f742010-05-18 02:45:54 +00002741 count++;
anthony46a369d2010-05-19 02:41:48 +00002742 changed = MorphologyPrimitive(curr_image,work_image,primitive,
anthony9eb4f742010-05-18 02:45:54 +00002743 channel, this_kernel, bias, exception);
2744 loop_changed += changed;
2745 total_changed += changed;
2746
2747 if ( verbose == MagickTrue )
anthony4f1dcb72010-05-14 08:43:10 +00002748 fprintf(stderr, "Morphology %s:%lu.%lu #%lu => Changed %lu\n",
cristye96405a2010-05-19 02:24:31 +00002749 MagickOptionToMnemonic(MagickMorphologyOptions, primitive),
anthony9eb4f742010-05-18 02:45:54 +00002750 loop, kernel_number, count, changed);
2751
2752 /* prepare next loop */
2753 { Image *tmp = work_image; /* swap images for iteration */
2754 work_image = curr_image; /* curr_image is now the results */
2755 curr_image = tmp;
2756 }
2757 if ( work_image == input_image )
2758 work_image = (Image *) NULL; /* clear work of the input_image */
2759
2760 } /* end kernel iteration */
2761
2762 /* make a union of the iterated kernel */
2763 if ( union_image == (Image *) NULL) /* start the union? */
2764 union_image = curr_image, curr_image = (Image *)NULL;
2765 else
2766 (void) CompositeImageChannel(union_image,
2767 (ChannelType) (channel & ~SyncChannels), kernel_compose,
2768 curr_image, 0, 0);
2769
2770 this_kernel = this_kernel->next; /* next kernel (if any) */
anthony1b2bc0a2010-05-12 05:25:22 +00002771 kernel_number++;
anthony602ab9b2010-01-05 08:06:50 +00002772 }
anthony9eb4f742010-05-18 02:45:54 +00002773
2774 if ( verbose == MagickTrue && kernel->next != NULL && loop_limit > 1 )
2775 fprintf(stderr, "Morphology %s:%lu #%lu ===> Changed %lu Total %lu\n",
cristye96405a2010-05-19 02:24:31 +00002776 MagickOptionToMnemonic(MagickMorphologyOptions, primitive),
anthony9eb4f742010-05-18 02:45:54 +00002777 loop, count, loop_changed, total_changed );
2778
2779#if 0
2780fprintf(stderr, "--E-- image=0x%lx\n", (unsigned long)image);
2781fprintf(stderr, " input=0x%lx\n", (unsigned long)input_image);
2782fprintf(stderr, " union=0x%lx\n", (unsigned long)union_image);
2783fprintf(stderr, " curr =0x%lx\n", (unsigned long)curr_image);
2784fprintf(stderr, " work =0x%lx\n", (unsigned long)work_image);
2785fprintf(stderr, " save =0x%lx\n", (unsigned long)save_image);
2786#endif
2787
2788 /* Finish up - return the union of results */
2789 if( curr_image != (Image *) NULL && curr_image != input_image )
2790 curr_image=DestroyImage(curr_image);
2791 if( input_image != input_image )
2792 input_image = DestroyImage(input_image);
2793 curr_image = union_image;
anthony1b2bc0a2010-05-12 05:25:22 +00002794 }
anthony9eb4f742010-05-18 02:45:54 +00002795
2796 /* Compound Morphology Operations
cristye96405a2010-05-19 02:24:31 +00002797 * set next 'primitive' iteration, and continue
anthony9eb4f742010-05-18 02:45:54 +00002798 * or break when all operations are complete.
2799 */
2800 stage++; /* what is the next stage number to do */
2801 switch( method ) {
2802 case SmoothMorphology: /* open, close */
2803 switch ( stage ) {
2804 /* case 1: initialized above */
2805 case 2: /* open part 2 */
cristye96405a2010-05-19 02:24:31 +00002806 primitive = DilateMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002807 continue;
2808 case 3: /* close part 1 */
2809 curr_kernel = CloneKernelInfo(kernel);
2810 if (curr_kernel == (KernelInfo *) NULL)
2811 goto error_cleanup;
2812 RotateKernelInfo(curr_kernel,180);
2813 continue;
2814 case 4: /* close part 2 */
cristye96405a2010-05-19 02:24:31 +00002815 primitive = ErodeMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002816 continue;
2817 }
2818 break;
2819 case OpenMorphology: /* erode, dilate */
2820 case TopHatMorphology:
cristye96405a2010-05-19 02:24:31 +00002821 primitive = DilateMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002822 if ( stage <= 2 ) continue;
2823 break;
2824 case OpenIntensityMorphology:
cristye96405a2010-05-19 02:24:31 +00002825 primitive = DilateIntensityMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002826 if ( stage <= 2 ) continue;
2827 break;
2828 case CloseMorphology: /* dilate, erode */
2829 case BottomHatMorphology:
cristye96405a2010-05-19 02:24:31 +00002830 primitive = ErodeMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002831 if ( stage <= 2 ) continue;
2832 break;
2833 case CloseIntensityMorphology:
cristye96405a2010-05-19 02:24:31 +00002834 primitive = ErodeIntensityMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002835 if ( stage <= 2 ) continue;
2836 break;
2837 case EdgeMorphology: /* dilate and erode difference */
2838 if (stage <= 2) {
2839 save_image = curr_image;
2840 curr_image = (Image *) image;
cristye96405a2010-05-19 02:24:31 +00002841 primitive = ErodeMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002842 continue;
2843 }
2844 break;
2845 default: /* Primitive Morphology is just finished! */
2846 break;
2847 }
2848
2849 if ( verbose == MagickTrue && count > 1 )
2850 fprintf(stderr, "Morphology %s: ======> Total %lu\n",
2851 MagickOptionToMnemonic(MagickMorphologyOptions, method),
2852 total_changed );
2853
2854 /* If we reach this point we are finished! - Break the Loop */
2855 break;
anthony602ab9b2010-01-05 08:06:50 +00002856 }
anthony930be612010-02-08 04:26:15 +00002857
anthony9eb4f742010-05-18 02:45:54 +00002858 /* Final Post-processing for some Compound Methods
anthony7d10d742010-05-06 07:05:29 +00002859 **
2860 ** The removal of any 'Sync' channel flag in the Image Compositon below
2861 ** ensures the compose method is applied in a purely mathematical way, only
2862 ** the selected channels, without any normal 'alpha blending' normally
2863 ** associated with the compose method.
2864 **
2865 ** Note "method" here is the 'original' morphological method, and not the
2866 ** 'current' morphological method used above to generate "new_image".
2867 */
anthony4fd27e22010-02-07 08:17:18 +00002868 switch( method ) {
2869 case EdgeOutMorphology:
2870 case EdgeInMorphology:
2871 case TopHatMorphology:
2872 case BottomHatMorphology:
anthony9eb4f742010-05-18 02:45:54 +00002873 (void) CompositeImageChannel(curr_image,
2874 (ChannelType) (channel & ~SyncChannels), DifferenceCompositeOp,
2875 image, 0, 0);
anthony4fd27e22010-02-07 08:17:18 +00002876 break;
anthony7d10d742010-05-06 07:05:29 +00002877 case EdgeMorphology:
anthony9eb4f742010-05-18 02:45:54 +00002878 /* Difference the Eroded Image with a Dilate image */
2879 (void) CompositeImageChannel(curr_image,
2880 (ChannelType) (channel & ~SyncChannels), DifferenceCompositeOp,
2881 save_image, 0, 0);
anthony4fd27e22010-02-07 08:17:18 +00002882 break;
2883 default:
2884 break;
2885 }
anthony602ab9b2010-01-05 08:06:50 +00002886
anthony9eb4f742010-05-18 02:45:54 +00002887 goto exit_cleanup;
anthony1b2bc0a2010-05-12 05:25:22 +00002888
2889 /* Yes goto's are bad, but in this case it makes cleanup lot more efficient */
2890error_cleanup:
anthony9eb4f742010-05-18 02:45:54 +00002891 if ( curr_image != (Image *) NULL && curr_image != image )
cristye96405a2010-05-19 02:24:31 +00002892 (void) DestroyImage(curr_image);
anthony1b2bc0a2010-05-12 05:25:22 +00002893exit_cleanup:
anthony9eb4f742010-05-18 02:45:54 +00002894 if ( work_image != (Image *) NULL )
cristye96405a2010-05-19 02:24:31 +00002895 (void) DestroyImage(work_image);
anthony9eb4f742010-05-18 02:45:54 +00002896 if ( save_image != (Image *) NULL )
cristye96405a2010-05-19 02:24:31 +00002897 (void) DestroyImage(save_image);
anthony9eb4f742010-05-18 02:45:54 +00002898 return(curr_image);
2899}
2900
2901/*
2902%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2903% %
2904% %
2905% %
2906% M o r p h o l o g y I m a g e C h a n n e l %
2907% %
2908% %
2909% %
2910%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2911%
2912% MorphologyImageChannel() applies a user supplied kernel to the image
2913% according to the given mophology method.
2914%
2915% This function applies any and all user defined settings before calling
2916% the above internal function MorphologyApply().
2917%
2918% User defined settings include...
anthony46a369d2010-05-19 02:41:48 +00002919% * Output Bias for Convolution and correlation ("-bias")
2920% * Kernel Scale/normalize settings ("-set 'option:convolve:scale'")
2921% This can also includes the addition of a scaled unity kernel.
2922% * Show Kernel being applied ("-set option:showkernel 1")
anthony9eb4f742010-05-18 02:45:54 +00002923%
2924% The format of the MorphologyImage method is:
2925%
2926% Image *MorphologyImage(const Image *image,MorphologyMethod method,
2927% const long iterations,KernelInfo *kernel,ExceptionInfo *exception)
2928%
2929% Image *MorphologyImageChannel(const Image *image, const ChannelType
2930% channel,MorphologyMethod method,const long iterations,
2931% KernelInfo *kernel,ExceptionInfo *exception)
2932%
2933% A description of each parameter follows:
2934%
2935% o image: the image.
2936%
2937% o method: the morphology method to be applied.
2938%
2939% o iterations: apply the operation this many times (or no change).
2940% A value of -1 means loop until no change found.
2941% How this is applied may depend on the morphology method.
2942% Typically this is a value of 1.
2943%
2944% o channel: the channel type.
2945%
2946% o kernel: An array of double representing the morphology kernel.
2947% Warning: kernel may be normalized for the Convolve method.
2948%
2949% o exception: return any errors or warnings in this structure.
2950%
2951*/
2952
2953MagickExport Image *MorphologyImageChannel(const Image *image,
2954 const ChannelType channel,const MorphologyMethod method,
2955 const long iterations,const KernelInfo *kernel,ExceptionInfo *exception)
2956{
2957 const char
2958 *artifact;
2959
2960 KernelInfo
2961 *curr_kernel;
2962
2963 Image
2964 *morphology_image;
2965
2966
anthony46a369d2010-05-19 02:41:48 +00002967 /* Apply Convolve/Correlate Normalization and Scaling Factors.
2968 * This is done BEFORE the ShowKernelInfo() function is called so that
2969 * users can see the results of the 'option:convolve:scale' option.
anthony9eb4f742010-05-18 02:45:54 +00002970 */
2971 curr_kernel = (KernelInfo *) kernel;
anthonyf71ca292010-05-19 04:08:43 +00002972 if ( method == ConvolveMorphology || method == CorrelateMorphology )
anthony9eb4f742010-05-18 02:45:54 +00002973 {
2974 artifact = GetImageArtifact(image,"convolve:scale");
2975 if ( artifact != (char *)NULL ) {
anthony9eb4f742010-05-18 02:45:54 +00002976 if ( curr_kernel == kernel )
2977 curr_kernel = CloneKernelInfo(kernel);
2978 if (curr_kernel == (KernelInfo *) NULL) {
2979 curr_kernel=DestroyKernelInfo(curr_kernel);
2980 return((Image *) NULL);
2981 }
anthony46a369d2010-05-19 02:41:48 +00002982 ScaleGeometryKernelInfo(curr_kernel, artifact);
anthony9eb4f742010-05-18 02:45:54 +00002983 }
2984 }
2985
2986 /* display the (normalized) kernel via stderr */
2987 artifact = GetImageArtifact(image,"showkernel");
2988 if ( artifact != (const char *) NULL)
2989 ShowKernelInfo(curr_kernel);
2990
2991 /* Apply the Morphology */
2992 morphology_image = MorphologyApply(image, channel, method, iterations,
2993 curr_kernel, image->bias, exception);
2994
2995 /* Cleanup and Exit */
2996 if ( curr_kernel != kernel )
anthony1b2bc0a2010-05-12 05:25:22 +00002997 curr_kernel=DestroyKernelInfo(curr_kernel);
anthony9eb4f742010-05-18 02:45:54 +00002998 return(morphology_image);
2999}
3000
anthony46a369d2010-05-19 02:41:48 +00003001
anthony9eb4f742010-05-18 02:45:54 +00003002MagickExport Image *MorphologyImage(const Image *image, const MorphologyMethod
3003 method, const long iterations,const KernelInfo *kernel, ExceptionInfo
3004 *exception)
3005{
3006 Image
3007 *morphology_image;
3008
3009 morphology_image=MorphologyImageChannel(image,DefaultChannels,method,
3010 iterations,kernel,exception);
3011 return(morphology_image);
anthony602ab9b2010-01-05 08:06:50 +00003012}
anthony83ba99b2010-01-24 08:48:15 +00003013
3014/*
3015%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3016% %
3017% %
3018% %
anthony4fd27e22010-02-07 08:17:18 +00003019+ R o t a t e K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00003020% %
3021% %
3022% %
3023%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3024%
anthony46a369d2010-05-19 02:41:48 +00003025% RotateKernelInfo() rotates the kernel by the angle given.
3026%
3027% Currently it is restricted to 90 degree angles, of either 1D kernels
3028% or square kernels. And 'circular' rotations of 45 degrees for 3x3 kernels.
3029% It will ignore usless rotations for specific 'named' built-in kernels.
anthony83ba99b2010-01-24 08:48:15 +00003030%
anthony4fd27e22010-02-07 08:17:18 +00003031% The format of the RotateKernelInfo method is:
anthony83ba99b2010-01-24 08:48:15 +00003032%
anthony4fd27e22010-02-07 08:17:18 +00003033% void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00003034%
3035% A description of each parameter follows:
3036%
3037% o kernel: the Morphology/Convolution kernel
3038%
3039% o angle: angle to rotate in degrees
3040%
anthony46a369d2010-05-19 02:41:48 +00003041% This function is currently internal to this module only, but can be exported
3042% to other modules if needed.
anthony83ba99b2010-01-24 08:48:15 +00003043*/
anthony4fd27e22010-02-07 08:17:18 +00003044static void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00003045{
anthony1b2bc0a2010-05-12 05:25:22 +00003046 /* angle the lower kernels first */
3047 if ( kernel->next != (KernelInfo *) NULL)
3048 RotateKernelInfo(kernel->next, angle);
3049
anthony83ba99b2010-01-24 08:48:15 +00003050 /* WARNING: Currently assumes the kernel (rightly) is horizontally symetrical
3051 **
3052 ** TODO: expand beyond simple 90 degree rotates, flips and flops
3053 */
3054
3055 /* Modulus the angle */
3056 angle = fmod(angle, 360.0);
3057 if ( angle < 0 )
3058 angle += 360.0;
3059
anthony3c10fc82010-05-13 02:40:51 +00003060 if ( 337.5 < angle || angle <= 22.5 )
anthony43c49252010-05-18 10:59:50 +00003061 return; /* Near zero angle - no change! - At least not at this time */
anthony83ba99b2010-01-24 08:48:15 +00003062
anthony3dd0f622010-05-13 12:57:32 +00003063 /* Handle special cases */
anthony83ba99b2010-01-24 08:48:15 +00003064 switch (kernel->type) {
3065 /* These built-in kernels are cylindrical kernels, rotating is useless */
3066 case GaussianKernel:
anthony83ba99b2010-01-24 08:48:15 +00003067 case DOGKernel:
3068 case DiskKernel:
anthony3dd0f622010-05-13 12:57:32 +00003069 case PeaksKernel:
3070 case LaplacianKernel:
anthony83ba99b2010-01-24 08:48:15 +00003071 case ChebyshevKernel:
3072 case ManhattenKernel:
3073 case EuclideanKernel:
3074 return;
3075
3076 /* These may be rotatable at non-90 angles in the future */
3077 /* but simply rotating them in multiples of 90 degrees is useless */
3078 case SquareKernel:
3079 case DiamondKernel:
3080 case PlusKernel:
anthony3dd0f622010-05-13 12:57:32 +00003081 case CrossKernel:
anthony83ba99b2010-01-24 08:48:15 +00003082 return;
3083
3084 /* These only allows a +/-90 degree rotation (by transpose) */
3085 /* A 180 degree rotation is useless */
3086 case BlurKernel:
3087 case RectangleKernel:
3088 if ( 135.0 < angle && angle <= 225.0 )
3089 return;
3090 if ( 225.0 < angle && angle <= 315.0 )
3091 angle -= 180;
3092 break;
3093
anthony3dd0f622010-05-13 12:57:32 +00003094 default:
anthony83ba99b2010-01-24 08:48:15 +00003095 break;
3096 }
anthony3c10fc82010-05-13 02:40:51 +00003097 /* Attempt rotations by 45 degrees */
3098 if ( 22.5 < fmod(angle,90.0) && fmod(angle,90.0) <= 67.5 )
3099 {
3100 if ( kernel->width == 3 && kernel->height == 3 )
3101 { /* Rotate a 3x3 square by 45 degree angle */
3102 MagickRealType t = kernel->values[0];
anthony43c49252010-05-18 10:59:50 +00003103 kernel->values[0] = kernel->values[3];
3104 kernel->values[3] = kernel->values[6];
3105 kernel->values[6] = kernel->values[7];
3106 kernel->values[7] = kernel->values[8];
3107 kernel->values[8] = kernel->values[5];
3108 kernel->values[5] = kernel->values[2];
3109 kernel->values[2] = kernel->values[1];
3110 kernel->values[1] = t;
3111 /* NOT DONE - rotate a off-centered origin as well! */
3112 angle = fmod(angle+315.0, 360.0); /* angle reduced 45 degrees */
3113 kernel->angle = fmod(kernel->angle+45.0, 360.0);
anthony3c10fc82010-05-13 02:40:51 +00003114 }
3115 else
3116 perror("Unable to rotate non-3x3 kernel by 45 degrees");
3117 }
3118 if ( 45.0 < fmod(angle, 180.0) && fmod(angle,180.0) <= 135.0 )
3119 {
3120 if ( kernel->width == 1 || kernel->height == 1 )
3121 { /* Do a transpose of the image, which results in a 90
3122 ** degree rotation of a 1 dimentional kernel
3123 */
3124 long
3125 t;
3126 t = (long) kernel->width;
3127 kernel->width = kernel->height;
3128 kernel->height = (unsigned long) t;
3129 t = kernel->x;
3130 kernel->x = kernel->y;
3131 kernel->y = t;
anthony43c49252010-05-18 10:59:50 +00003132 if ( kernel->width == 1 ) {
3133 angle = fmod(angle+270.0, 360.0); /* angle reduced 90 degrees */
3134 kernel->angle = fmod(kernel->angle+90.0, 360.0);
3135 } else {
3136 angle = fmod(angle+90.0, 360.0); /* angle increased 90 degrees */
3137 kernel->angle = fmod(kernel->angle+270.0, 360.0);
3138 }
anthony3c10fc82010-05-13 02:40:51 +00003139 }
3140 else if ( kernel->width == kernel->height )
3141 { /* Rotate a square array of values by 90 degrees */
3142 register unsigned long
3143 i,j,x,y;
3144 register MagickRealType
3145 *k,t;
3146 k=kernel->values;
3147 for( i=0, x=kernel->width-1; i<=x; i++, x--)
3148 for( j=0, y=kernel->height-1; j<y; j++, y--)
3149 { t = k[i+j*kernel->width];
anthony43c49252010-05-18 10:59:50 +00003150 k[i+j*kernel->width] = k[j+x*kernel->width];
3151 k[j+x*kernel->width] = k[x+y*kernel->width];
3152 k[x+y*kernel->width] = k[y+i*kernel->width];
3153 k[y+i*kernel->width] = t;
anthony3c10fc82010-05-13 02:40:51 +00003154 }
anthony43c49252010-05-18 10:59:50 +00003155 /* NOT DONE - rotate a off-centered origin as well! */
3156 angle = fmod(angle+270.0, 360.0); /* angle reduced 90 degrees */
3157 kernel->angle = fmod(kernel->angle+90.0, 360.0);
anthony3c10fc82010-05-13 02:40:51 +00003158 }
3159 else
3160 perror("Unable to rotate a non-square, non-linear kernel 90 degrees");
3161 }
anthony83ba99b2010-01-24 08:48:15 +00003162 if ( 135.0 < angle && angle <= 225.0 )
3163 {
anthony43c49252010-05-18 10:59:50 +00003164 /* For a 180 degree rotation - also know as a reflection
3165 * This is actually a very very common operation!
3166 * Basically all that is needed is a reversal of the kernel data!
3167 * And a reflection of the origon
3168 */
anthony83ba99b2010-01-24 08:48:15 +00003169 unsigned long
3170 i,j;
3171 register double
3172 *k,t;
3173
3174 k=kernel->values;
3175 for ( i=0, j=kernel->width*kernel->height-1; i<j; i++, j--)
3176 t=k[i], k[i]=k[j], k[j]=t;
3177
anthony930be612010-02-08 04:26:15 +00003178 kernel->x = (long) kernel->width - kernel->x - 1;
3179 kernel->y = (long) kernel->height - kernel->y - 1;
anthony43c49252010-05-18 10:59:50 +00003180 angle = fmod(angle-180.0, 360.0); /* angle+180 degrees */
3181 kernel->angle = fmod(kernel->angle+180.0, 360.0);
anthony83ba99b2010-01-24 08:48:15 +00003182 }
anthony3c10fc82010-05-13 02:40:51 +00003183 /* At this point angle should at least between -45 (315) and +45 degrees
anthony83ba99b2010-01-24 08:48:15 +00003184 * In the future some form of non-orthogonal angled rotates could be
3185 * performed here, posibily with a linear kernel restriction.
3186 */
3187
3188#if 0
anthony3c10fc82010-05-13 02:40:51 +00003189 { /* Do a Flop by reversing each row.
anthony83ba99b2010-01-24 08:48:15 +00003190 */
3191 unsigned long
3192 y;
cristy150989e2010-02-01 14:59:39 +00003193 register long
anthony83ba99b2010-01-24 08:48:15 +00003194 x,r;
3195 register double
3196 *k,t;
3197
3198 for ( y=0, k=kernel->values; y < kernel->height; y++, k+=kernel->width)
3199 for ( x=0, r=kernel->width-1; x<kernel->width/2; x++, r--)
3200 t=k[x], k[x]=k[r], k[r]=t;
3201
cristyc99304f2010-02-01 15:26:27 +00003202 kernel->x = kernel->width - kernel->x - 1;
anthony83ba99b2010-01-24 08:48:15 +00003203 angle = fmod(angle+180.0, 360.0);
3204 }
3205#endif
3206 return;
3207}
3208
3209/*
3210%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3211% %
3212% %
3213% %
anthony46a369d2010-05-19 02:41:48 +00003214% S c a l e G e o m e t r y K e r n e l I n f o %
3215% %
3216% %
3217% %
3218%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3219%
3220% ScaleGeometryKernelInfo() takes a geometry argument string, typically
3221% provided as a "-set option:convolve:scale {geometry}" user setting,
3222% and modifies the kernel according to the parsed arguments of that setting.
3223%
3224% The first argument (and any normalization flags) are passed to
3225% ScaleKernelInfo() to scale/normalize the kernel. The second argument
3226% is then passed to UnityAddKernelInfo() to add a scled unity kernel
3227% into the scaled/normalized kernel.
3228%
3229% The format of the ScaleKernelInfo method is:
3230%
3231% void ScaleKernelInfo(KernelInfo *kernel, const double scaling_factor,
3232% const MagickStatusType normalize_flags )
3233%
3234% A description of each parameter follows:
3235%
3236% o kernel: the Morphology/Convolution kernel to modify
3237%
3238% o geometry:
3239% The geometry string to parse, typically from the user provided
3240% "-set option:convolve:scale {geometry}" setting.
3241%
3242*/
3243MagickExport void ScaleGeometryKernelInfo (KernelInfo *kernel,
3244 const char *geometry)
3245{
3246 GeometryFlags
3247 flags;
3248 GeometryInfo
3249 args;
3250
3251 SetGeometryInfo(&args);
3252 flags = (GeometryFlags) ParseGeometry(geometry, &args);
3253
3254#if 0
3255 /* For Debugging Geometry Input */
3256 fprintf(stderr, "Geometry = 0x%04X : %lg x %lg %+lg %+lg\n",
3257 flags, args.rho, args.sigma, args.xi, args.psi );
3258#endif
3259
3260 if ( (flags & PercentValue) != 0 ) /* Handle Percentage flag*/
3261 args.rho *= 0.01, args.sigma *= 0.01;
3262
3263 if ( (flags & RhoValue) == 0 ) /* Set Defaults for missing args */
3264 args.rho = 1.0;
3265 if ( (flags & SigmaValue) == 0 )
3266 args.sigma = 0.0;
3267
3268 /* Scale/Normalize the input kernel */
3269 ScaleKernelInfo(kernel, args.rho, flags);
3270
3271 /* Add Unity Kernel, for blending with original */
3272 if ( (flags & SigmaValue) != 0 )
3273 UnityAddKernelInfo(kernel, args.sigma);
3274
3275 return;
3276}
3277/*
3278%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3279% %
3280% %
3281% %
cristy6771f1e2010-03-05 19:43:39 +00003282% S c a l e K e r n e l I n f o %
anthonycc6c8362010-01-25 04:14:01 +00003283% %
3284% %
3285% %
3286%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3287%
anthony1b2bc0a2010-05-12 05:25:22 +00003288% ScaleKernelInfo() scales the given kernel list by the given amount, with or
3289% without normalization of the sum of the kernel values (as per given flags).
anthonycc6c8362010-01-25 04:14:01 +00003290%
anthony999bb2c2010-02-18 12:38:01 +00003291% By default (no flags given) the values within the kernel is scaled
anthony1b2bc0a2010-05-12 05:25:22 +00003292% directly using given scaling factor without change.
anthonycc6c8362010-01-25 04:14:01 +00003293%
anthony46a369d2010-05-19 02:41:48 +00003294% If either of the two 'normalize_flags' are given the kernel will first be
3295% normalized and then further scaled by the scaling factor value given.
anthony999bb2c2010-02-18 12:38:01 +00003296%
3297% Kernel normalization ('normalize_flags' given) is designed to ensure that
3298% any use of the kernel scaling factor with 'Convolve' or 'Correlate'
anthony1b2bc0a2010-05-12 05:25:22 +00003299% morphology methods will fall into -1.0 to +1.0 range. Note that for
3300% non-HDRI versions of IM this may cause images to have any negative results
3301% clipped, unless some 'bias' is used.
anthony999bb2c2010-02-18 12:38:01 +00003302%
3303% More specifically. Kernels which only contain positive values (such as a
3304% 'Gaussian' kernel) will be scaled so that those values sum to +1.0,
anthony1b2bc0a2010-05-12 05:25:22 +00003305% ensuring a 0.0 to +1.0 output range for non-HDRI images.
anthony999bb2c2010-02-18 12:38:01 +00003306%
3307% For Kernels that contain some negative values, (such as 'Sharpen' kernels)
3308% the kernel will be scaled by the absolute of the sum of kernel values, so
3309% that it will generally fall within the +/- 1.0 range.
3310%
3311% For kernels whose values sum to zero, (such as 'Laplician' kernels) kernel
3312% will be scaled by just the sum of the postive values, so that its output
3313% range will again fall into the +/- 1.0 range.
3314%
3315% For special kernels designed for locating shapes using 'Correlate', (often
3316% only containing +1 and -1 values, representing foreground/brackground
3317% matching) a special normalization method is provided to scale the positive
3318% values seperatally to those of the negative values, so the kernel will be
3319% forced to become a zero-sum kernel better suited to such searches.
3320%
anthony1b2bc0a2010-05-12 05:25:22 +00003321% WARNING: Correct normalization of the kernel assumes that the '*_range'
anthony999bb2c2010-02-18 12:38:01 +00003322% attributes within the kernel structure have been correctly set during the
3323% kernels creation.
3324%
3325% NOTE: The values used for 'normalize_flags' have been selected specifically
anthony46a369d2010-05-19 02:41:48 +00003326% to match the use of geometry options, so that '!' means NormalizeValue, '^'
3327% means CorrelateNormalizeValue. All other GeometryFlags values are ignored.
anthonycc6c8362010-01-25 04:14:01 +00003328%
anthony4fd27e22010-02-07 08:17:18 +00003329% The format of the ScaleKernelInfo method is:
anthonycc6c8362010-01-25 04:14:01 +00003330%
anthony999bb2c2010-02-18 12:38:01 +00003331% void ScaleKernelInfo(KernelInfo *kernel, const double scaling_factor,
3332% const MagickStatusType normalize_flags )
anthonycc6c8362010-01-25 04:14:01 +00003333%
3334% A description of each parameter follows:
3335%
3336% o kernel: the Morphology/Convolution kernel
3337%
anthony999bb2c2010-02-18 12:38:01 +00003338% o scaling_factor:
3339% multiply all values (after normalization) by this factor if not
3340% zero. If the kernel is normalized regardless of any flags.
3341%
3342% o normalize_flags:
3343% GeometryFlags defining normalization method to use.
3344% specifically: NormalizeValue, CorrelateNormalizeValue,
3345% and/or PercentValue
anthonycc6c8362010-01-25 04:14:01 +00003346%
3347*/
cristy6771f1e2010-03-05 19:43:39 +00003348MagickExport void ScaleKernelInfo(KernelInfo *kernel,
3349 const double scaling_factor,const GeometryFlags normalize_flags)
anthonycc6c8362010-01-25 04:14:01 +00003350{
cristy150989e2010-02-01 14:59:39 +00003351 register long
anthonycc6c8362010-01-25 04:14:01 +00003352 i;
3353
anthony999bb2c2010-02-18 12:38:01 +00003354 register double
3355 pos_scale,
3356 neg_scale;
3357
anthony46a369d2010-05-19 02:41:48 +00003358 /* do the other kernels in a multi-kernel list first */
anthony1b2bc0a2010-05-12 05:25:22 +00003359 if ( kernel->next != (KernelInfo *) NULL)
3360 ScaleKernelInfo(kernel->next, scaling_factor, normalize_flags);
3361
anthony46a369d2010-05-19 02:41:48 +00003362 /* Normalization of Kernel */
anthony999bb2c2010-02-18 12:38:01 +00003363 pos_scale = 1.0;
3364 if ( (normalize_flags&NormalizeValue) != 0 ) {
anthony46a369d2010-05-19 02:41:48 +00003365 /* non-zero and zero-summing kernels */
anthony999bb2c2010-02-18 12:38:01 +00003366 if ( fabs(kernel->positive_range + kernel->negative_range) > MagickEpsilon )
3367 pos_scale = fabs(kernel->positive_range + kernel->negative_range);
anthonycc6c8362010-01-25 04:14:01 +00003368 else
anthony999bb2c2010-02-18 12:38:01 +00003369 pos_scale = kernel->positive_range; /* special zero-summing kernel */
3370 }
anthony46a369d2010-05-19 02:41:48 +00003371 /* Force kernel into a normalized zero-summing kernel */
anthony999bb2c2010-02-18 12:38:01 +00003372 if ( (normalize_flags&CorrelateNormalizeValue) != 0 ) {
3373 pos_scale = ( fabs(kernel->positive_range) > MagickEpsilon )
3374 ? kernel->positive_range : 1.0;
3375 neg_scale = ( fabs(kernel->negative_range) > MagickEpsilon )
3376 ? -kernel->negative_range : 1.0;
3377 }
3378 else
3379 neg_scale = pos_scale;
3380
3381 /* finialize scaling_factor for positive and negative components */
3382 pos_scale = scaling_factor/pos_scale;
3383 neg_scale = scaling_factor/neg_scale;
anthonycc6c8362010-01-25 04:14:01 +00003384
cristy150989e2010-02-01 14:59:39 +00003385 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00003386 if ( ! IsNan(kernel->values[i]) )
anthony999bb2c2010-02-18 12:38:01 +00003387 kernel->values[i] *= (kernel->values[i] >= 0) ? pos_scale : neg_scale;
anthonycc6c8362010-01-25 04:14:01 +00003388
anthony999bb2c2010-02-18 12:38:01 +00003389 /* convolution output range */
3390 kernel->positive_range *= pos_scale;
3391 kernel->negative_range *= neg_scale;
3392 /* maximum and minimum values in kernel */
3393 kernel->maximum *= (kernel->maximum >= 0.0) ? pos_scale : neg_scale;
3394 kernel->minimum *= (kernel->minimum >= 0.0) ? pos_scale : neg_scale;
3395
anthony46a369d2010-05-19 02:41:48 +00003396 /* swap kernel settings if user's scaling factor is negative */
anthony999bb2c2010-02-18 12:38:01 +00003397 if ( scaling_factor < MagickEpsilon ) {
3398 double t;
3399 t = kernel->positive_range;
3400 kernel->positive_range = kernel->negative_range;
3401 kernel->negative_range = t;
3402 t = kernel->maximum;
3403 kernel->maximum = kernel->minimum;
3404 kernel->minimum = 1;
3405 }
anthonycc6c8362010-01-25 04:14:01 +00003406
3407 return;
3408}
3409
3410/*
3411%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3412% %
3413% %
3414% %
anthony46a369d2010-05-19 02:41:48 +00003415% S h o w K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00003416% %
3417% %
3418% %
3419%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3420%
anthony4fd27e22010-02-07 08:17:18 +00003421% ShowKernelInfo() outputs the details of the given kernel defination to
3422% standard error, generally due to a users 'showkernel' option request.
anthony83ba99b2010-01-24 08:48:15 +00003423%
3424% The format of the ShowKernel method is:
3425%
anthony4fd27e22010-02-07 08:17:18 +00003426% void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00003427%
3428% A description of each parameter follows:
3429%
3430% o kernel: the Morphology/Convolution kernel
3431%
anthony83ba99b2010-01-24 08:48:15 +00003432*/
anthony4fd27e22010-02-07 08:17:18 +00003433MagickExport void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00003434{
anthony7a01dcf2010-05-11 12:25:52 +00003435 KernelInfo
3436 *k;
anthony83ba99b2010-01-24 08:48:15 +00003437
anthony43c49252010-05-18 10:59:50 +00003438 unsigned long
anthony7a01dcf2010-05-11 12:25:52 +00003439 c, i, u, v;
3440
3441 for (c=0, k=kernel; k != (KernelInfo *) NULL; c++, k=k->next ) {
3442
anthony46a369d2010-05-19 02:41:48 +00003443 fprintf(stderr, "Kernel");
anthony7a01dcf2010-05-11 12:25:52 +00003444 if ( kernel->next != (KernelInfo *) NULL )
cristye96405a2010-05-19 02:24:31 +00003445 fprintf(stderr, " #%lu", c );
anthony43c49252010-05-18 10:59:50 +00003446 fprintf(stderr, " \"%s",
3447 MagickOptionToMnemonic(MagickKernelOptions, k->type) );
3448 if ( fabs(k->angle) > MagickEpsilon )
3449 fprintf(stderr, "@%lg", k->angle);
3450 fprintf(stderr, "\" of size %lux%lu%+ld%+ld ",
3451 k->width, k->height,
3452 k->x, k->y );
anthony7a01dcf2010-05-11 12:25:52 +00003453 fprintf(stderr,
3454 " with values from %.*lg to %.*lg\n",
3455 GetMagickPrecision(), k->minimum,
3456 GetMagickPrecision(), k->maximum);
anthony46a369d2010-05-19 02:41:48 +00003457 fprintf(stderr, "Forming a output range from %.*lg to %.*lg",
anthony7a01dcf2010-05-11 12:25:52 +00003458 GetMagickPrecision(), k->negative_range,
anthony46a369d2010-05-19 02:41:48 +00003459 GetMagickPrecision(), k->positive_range);
3460 if ( fabs(k->positive_range+k->negative_range) < MagickEpsilon )
3461 fprintf(stderr, " (Zero-Summing)\n");
3462 else if ( fabs(k->positive_range+k->negative_range-1.0) < MagickEpsilon )
3463 fprintf(stderr, " (Normalized)\n");
3464 else
3465 fprintf(stderr, " (Sum %.*lg)\n",
3466 GetMagickPrecision(), k->positive_range+k->negative_range);
anthony43c49252010-05-18 10:59:50 +00003467 for (i=v=0; v < k->height; v++) {
anthony46a369d2010-05-19 02:41:48 +00003468 fprintf(stderr, "%2lu:", v );
anthony43c49252010-05-18 10:59:50 +00003469 for (u=0; u < k->width; u++, i++)
anthony7a01dcf2010-05-11 12:25:52 +00003470 if ( IsNan(k->values[i]) )
3471 fprintf(stderr," %*s", GetMagickPrecision()+2, "nan");
3472 else
3473 fprintf(stderr," %*.*lg", GetMagickPrecision()+2,
3474 GetMagickPrecision(), k->values[i]);
3475 fprintf(stderr,"\n");
3476 }
anthony83ba99b2010-01-24 08:48:15 +00003477 }
3478}
anthonycc6c8362010-01-25 04:14:01 +00003479
3480/*
3481%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3482% %
3483% %
3484% %
anthony43c49252010-05-18 10:59:50 +00003485% U n i t y A d d K e r n a l I n f o %
3486% %
3487% %
3488% %
3489%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3490%
3491% UnityAddKernelInfo() Adds a given amount of the 'Unity' Convolution Kernel
3492% to the given pre-scaled and normalized Kernel. This in effect adds that
3493% amount of the original image into the resulting convolution kernel. This
3494% value is usually provided by the user as a percentage value in the
3495% 'convolve:scale' setting.
3496%
3497% The resulting effect is to either convert a 'zero-summing' edge detection
3498% kernel (such as a "Laplacian", "DOG" or a "LOG") into a 'sharpening'
3499% kernel.
3500%
3501% Alternativally by using a purely positive kernel, and using a negative
3502% post-normalizing scaling factor, you can convert a 'blurring' kernel (such
3503% as a "Gaussian") into a 'unsharp' kernel.
3504%
anthony46a369d2010-05-19 02:41:48 +00003505% The format of the UnityAdditionKernelInfo method is:
anthony43c49252010-05-18 10:59:50 +00003506%
3507% void UnityAdditionKernelInfo(KernelInfo *kernel, const double scale )
3508%
3509% A description of each parameter follows:
3510%
3511% o kernel: the Morphology/Convolution kernel
3512%
3513% o scale:
3514% scaling factor for the unity kernel to be added to
3515% the given kernel.
3516%
anthony43c49252010-05-18 10:59:50 +00003517*/
3518MagickExport void UnityAddKernelInfo(KernelInfo *kernel,
3519 const double scale)
3520{
anthony46a369d2010-05-19 02:41:48 +00003521 /* do the other kernels in a multi-kernel list first */
3522 if ( kernel->next != (KernelInfo *) NULL)
3523 UnityAddKernelInfo(kernel->next, scale);
anthony43c49252010-05-18 10:59:50 +00003524
anthony46a369d2010-05-19 02:41:48 +00003525 /* Add the scaled unity kernel to the existing kernel */
anthony43c49252010-05-18 10:59:50 +00003526 kernel->values[kernel->x+kernel->y*kernel->width] += scale;
anthony46a369d2010-05-19 02:41:48 +00003527 CalcKernelMetaData(kernel); /* recalculate the meta-data */
anthony43c49252010-05-18 10:59:50 +00003528
3529 return;
3530}
3531
3532/*
3533%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3534% %
3535% %
3536% %
3537% Z e r o K e r n e l N a n s %
anthonycc6c8362010-01-25 04:14:01 +00003538% %
3539% %
3540% %
3541%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3542%
3543% ZeroKernelNans() replaces any special 'nan' value that may be present in
3544% the kernel with a zero value. This is typically done when the kernel will
3545% be used in special hardware (GPU) convolution processors, to simply
3546% matters.
3547%
3548% The format of the ZeroKernelNans method is:
3549%
anthony46a369d2010-05-19 02:41:48 +00003550% void ZeroKernelNans (KernelInfo *kernel)
anthonycc6c8362010-01-25 04:14:01 +00003551%
3552% A description of each parameter follows:
3553%
3554% o kernel: the Morphology/Convolution kernel
3555%
anthonycc6c8362010-01-25 04:14:01 +00003556*/
anthonyc4c86e02010-01-27 09:30:32 +00003557MagickExport void ZeroKernelNans(KernelInfo *kernel)
anthonycc6c8362010-01-25 04:14:01 +00003558{
anthony43c49252010-05-18 10:59:50 +00003559 register unsigned long
anthonycc6c8362010-01-25 04:14:01 +00003560 i;
3561
anthony46a369d2010-05-19 02:41:48 +00003562 /* do the other kernels in a multi-kernel list first */
anthony1b2bc0a2010-05-12 05:25:22 +00003563 if ( kernel->next != (KernelInfo *) NULL)
3564 ZeroKernelNans(kernel->next);
3565
anthony43c49252010-05-18 10:59:50 +00003566 for (i=0; i < (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00003567 if ( IsNan(kernel->values[i]) )
3568 kernel->values[i] = 0.0;
3569
3570 return;
3571}