blob: e7427a5f20583f634e84b1bc605dc082ae61aea0 [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 *),
anthonyc3e48252010-05-24 12:43:11 +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{
anthonyf0176c32010-05-23 23:08:57 +0000367 KernelInfo
368 *kernel;
369
anthonyc84dce52010-05-07 05:42:23 +0000370 char
371 token[MaxTextExtent];
372
anthony5ef8e942010-05-11 06:51:12 +0000373 long
374 type;
375
anthonyc84dce52010-05-07 05:42:23 +0000376 const char
anthony7a01dcf2010-05-11 12:25:52 +0000377 *p,
378 *end;
anthonyc84dce52010-05-07 05:42:23 +0000379
380 MagickStatusType
381 flags;
382
383 GeometryInfo
384 args;
385
anthonyc84dce52010-05-07 05:42:23 +0000386 /* Parse special 'named' kernel */
anthony5ef8e942010-05-11 06:51:12 +0000387 GetMagickToken(kernel_string,&p,token);
anthonyc84dce52010-05-07 05:42:23 +0000388 type=ParseMagickOption(MagickKernelOptions,MagickFalse,token);
389 if ( type < 0 || type == UserDefinedKernel )
anthony5ef8e942010-05-11 06:51:12 +0000390 return((KernelInfo *)NULL); /* not a valid named kernel */
anthonyc84dce52010-05-07 05:42:23 +0000391
392 while (((isspace((int) ((unsigned char) *p)) != 0) ||
anthony5ef8e942010-05-11 06:51:12 +0000393 (*p == ',') || (*p == ':' )) && (*p != '\0') && (*p != ';'))
anthonyc84dce52010-05-07 05:42:23 +0000394 p++;
anthony7a01dcf2010-05-11 12:25:52 +0000395
396 end = strchr(p, ';'); /* end of this kernel defintion */
397 if ( end == (char *) NULL )
398 end = strchr(p, '\0');
399
400 /* ParseGeometry() needs the geometry separated! -- Arrgghh */
401 memcpy(token, p, (size_t) (end-p));
402 token[end-p] = '\0';
anthonyc84dce52010-05-07 05:42:23 +0000403 SetGeometryInfo(&args);
anthony7a01dcf2010-05-11 12:25:52 +0000404 flags = ParseGeometry(token, &args);
anthonyc84dce52010-05-07 05:42:23 +0000405
anthony3c10fc82010-05-13 02:40:51 +0000406#if 0
407 /* For Debugging Geometry Input */
anthony46a369d2010-05-19 02:41:48 +0000408 fprintf(stderr, "Geometry = 0x%04X : %lg x %lg %+lg %+lg\n",
anthony3c10fc82010-05-13 02:40:51 +0000409 flags, args.rho, args.sigma, args.xi, args.psi );
410#endif
411
anthonyc84dce52010-05-07 05:42:23 +0000412 /* special handling of missing values in input string */
413 switch( type ) {
anthony5ef8e942010-05-11 06:51:12 +0000414 case RectangleKernel:
415 if ( (flags & WidthValue) == 0 ) /* if no width then */
416 args.rho = args.sigma; /* then width = height */
417 if ( args.rho < 1.0 ) /* if width too small */
418 args.rho = 3; /* then width = 3 */
419 if ( args.sigma < 1.0 ) /* if height too small */
420 args.sigma = args.rho; /* then height = width */
421 if ( (flags & XValue) == 0 ) /* center offset if not defined */
422 args.xi = (double)(((long)args.rho-1)/2);
423 if ( (flags & YValue) == 0 )
424 args.psi = (double)(((long)args.sigma-1)/2);
425 break;
426 case SquareKernel:
427 case DiamondKernel:
428 case DiskKernel:
429 case PlusKernel:
anthony3dd0f622010-05-13 12:57:32 +0000430 case CrossKernel:
anthony5ef8e942010-05-11 06:51:12 +0000431 /* If no scale given (a 0 scale is valid! - set it to 1.0 */
432 if ( (flags & HeightValue) == 0 )
433 args.sigma = 1.0;
434 break;
anthonyc1061722010-05-14 06:23:49 +0000435 case RingKernel:
436 if ( (flags & XValue) == 0 )
437 args.xi = 1.0;
438 break;
anthony5ef8e942010-05-11 06:51:12 +0000439 case ChebyshevKernel:
440 case ManhattenKernel:
441 case EuclideanKernel:
anthony43c49252010-05-18 10:59:50 +0000442 if ( (flags & HeightValue) == 0 ) /* no distance scale */
443 args.sigma = 100.0; /* default distance scaling */
444 else if ( (flags & AspectValue ) != 0 ) /* '!' flag */
445 args.sigma = QuantumRange/(args.sigma+1); /* maximum pixel distance */
446 else if ( (flags & PercentValue ) != 0 ) /* '%' flag */
447 args.sigma *= QuantumRange/100.0; /* percentage of color range */
anthony5ef8e942010-05-11 06:51:12 +0000448 break;
449 default:
450 break;
anthonyc84dce52010-05-07 05:42:23 +0000451 }
452
anthonyf0176c32010-05-23 23:08:57 +0000453 kernel = AcquireKernelBuiltIn((KernelInfoType)type, &args);
454
455 /* global expand to rotated kernel list - only for single kernels */
456 if ( kernel->next == (KernelInfo *) NULL ) {
457 if ( (flags & AreaValue) != 0 ) /* '@' symbol in kernel args */
458 ExpandKernelInfo(kernel, 45.0);
459 else if ( (flags & MinimumValue) != 0 ) /* '^' symbol in kernel args */
460 ExpandKernelInfo(kernel, 90.0);
461 }
462
463 return(kernel);
anthonyc84dce52010-05-07 05:42:23 +0000464}
465
anthony5ef8e942010-05-11 06:51:12 +0000466MagickExport KernelInfo *AcquireKernelInfo(const char *kernel_string)
467{
anthony7a01dcf2010-05-11 12:25:52 +0000468
469 KernelInfo
anthonydbc89892010-05-12 07:05:27 +0000470 *kernel,
anthony43c49252010-05-18 10:59:50 +0000471 *new_kernel;
anthony7a01dcf2010-05-11 12:25:52 +0000472
anthony5ef8e942010-05-11 06:51:12 +0000473 char
474 token[MaxTextExtent];
475
anthony7a01dcf2010-05-11 12:25:52 +0000476 const char
anthonydbc89892010-05-12 07:05:27 +0000477 *p;
anthony7a01dcf2010-05-11 12:25:52 +0000478
anthonye108a3f2010-05-12 07:24:03 +0000479 unsigned long
480 kernel_number;
481
anthonydbc89892010-05-12 07:05:27 +0000482 p = kernel_string;
anthony43c49252010-05-18 10:59:50 +0000483 kernel = NULL;
anthonye108a3f2010-05-12 07:24:03 +0000484 kernel_number = 0;
anthony5ef8e942010-05-11 06:51:12 +0000485
anthonydbc89892010-05-12 07:05:27 +0000486 while ( GetMagickToken(p,NULL,token), *token != '\0' ) {
anthony7a01dcf2010-05-11 12:25:52 +0000487
anthony43c49252010-05-18 10:59:50 +0000488 /* ignore extra or multiple ';' kernel seperators */
anthonydbc89892010-05-12 07:05:27 +0000489 if ( *token != ';' ) {
anthony7a01dcf2010-05-11 12:25:52 +0000490
anthonydbc89892010-05-12 07:05:27 +0000491 /* tokens starting with alpha is a Named kernel */
anthony43c49252010-05-18 10:59:50 +0000492 if (isalpha((int) *token) != 0)
493 new_kernel = ParseKernelName(p);
anthonydbc89892010-05-12 07:05:27 +0000494 else /* otherwise a user defined kernel array */
anthony43c49252010-05-18 10:59:50 +0000495 new_kernel = ParseKernelArray(p);
anthony7a01dcf2010-05-11 12:25:52 +0000496
anthonye108a3f2010-05-12 07:24:03 +0000497 /* Error handling -- this is not proper error handling! */
498 if ( new_kernel == (KernelInfo *) NULL ) {
499 fprintf(stderr, "Failed to parse kernel number #%lu\n", kernel_number);
500 if ( kernel != (KernelInfo *) NULL )
501 kernel=DestroyKernelInfo(kernel);
502 return((KernelInfo *) NULL);
anthonydbc89892010-05-12 07:05:27 +0000503 }
anthonye108a3f2010-05-12 07:24:03 +0000504
505 /* initialise or append the kernel list */
anthony3dd0f622010-05-13 12:57:32 +0000506 if ( kernel == (KernelInfo *) NULL )
507 kernel = new_kernel;
508 else
anthony43c49252010-05-18 10:59:50 +0000509 LastKernelInfo(kernel)->next = new_kernel;
anthonydbc89892010-05-12 07:05:27 +0000510 }
511
512 /* look for the next kernel in list */
513 p = strchr(p, ';');
514 if ( p == (char *) NULL )
515 break;
516 p++;
517
518 }
anthony7a01dcf2010-05-11 12:25:52 +0000519 return(kernel);
anthony5ef8e942010-05-11 06:51:12 +0000520}
521
anthony602ab9b2010-01-05 08:06:50 +0000522
523/*
524%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
525% %
526% %
527% %
528% A c q u i r e K e r n e l B u i l t I n %
529% %
530% %
531% %
532%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
533%
534% AcquireKernelBuiltIn() returned one of the 'named' built-in types of
535% kernels used for special purposes such as gaussian blurring, skeleton
536% pruning, and edge distance determination.
537%
538% They take a KernelType, and a set of geometry style arguments, which were
539% typically decoded from a user supplied string, or from a more complex
540% Morphology Method that was requested.
541%
542% The format of the AcquireKernalBuiltIn method is:
543%
cristy2be15382010-01-21 02:38:03 +0000544% KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000545% const GeometryInfo args)
546%
547% A description of each parameter follows:
548%
549% o type: the pre-defined type of kernel wanted
550%
551% o args: arguments defining or modifying the kernel
552%
553% Convolution Kernels
554%
anthony46a369d2010-05-19 02:41:48 +0000555% Unity
556% the No-Op kernel, also requivelent to Gaussian of sigma zero.
557% Basically a 3x3 kernel of a 1 surrounded by zeros.
558%
anthony3c10fc82010-05-13 02:40:51 +0000559% Gaussian:{radius},{sigma}
560% Generate a two-dimentional gaussian kernel, as used by -gaussian.
anthonyc1061722010-05-14 06:23:49 +0000561% The sigma for the curve is required. The resulting kernel is
562% normalized,
563%
564% If 'sigma' is zero, you get a single pixel on a field of zeros.
anthony602ab9b2010-01-05 08:06:50 +0000565%
566% NOTE: that the 'radius' is optional, but if provided can limit (clip)
567% the final size of the resulting kernel to a square 2*radius+1 in size.
568% The radius should be at least 2 times that of the sigma value, or
569% sever clipping and aliasing may result. If not given or set to 0 the
570% radius will be determined so as to produce the best minimal error
571% result, which is usally much larger than is normally needed.
572%
anthonyc1061722010-05-14 06:23:49 +0000573% DOG:{radius},{sigma1},{sigma2}
574% "Difference of Gaussians" Kernel.
575% As "Gaussian" but with a gaussian produced by 'sigma2' subtracted
576% from the gaussian produced by 'sigma1'. Typically sigma2 > sigma1.
577% The result is a zero-summing kernel.
anthony602ab9b2010-01-05 08:06:50 +0000578%
anthony9eb4f742010-05-18 02:45:54 +0000579% LOG:{radius},{sigma}
580% "Laplacian of a Gaussian" or "Mexician Hat" Kernel.
581% The supposed ideal edge detection, zero-summing kernel.
582%
583% An alturnative to this kernel is to use a "DOG" with a sigma ratio of
584% approx 1.6, which can also be applied as a 2 pass "DOB" (see below).
585%
anthonyc1061722010-05-14 06:23:49 +0000586% Blur:{radius},{sigma}[,{angle}]
587% Generates a 1 dimensional or linear gaussian blur, at the angle given
588% (current restricted to orthogonal angles). If a 'radius' is given the
589% kernel is clipped to a width of 2*radius+1. Kernel can be rotated
590% by a 90 degree angle.
591%
592% If 'sigma' is zero, you get a single pixel on a field of zeros.
593%
594% Note that two convolutions with two "Blur" kernels perpendicular to
595% each other, is equivelent to a far larger "Gaussian" kernel with the
596% same sigma value, However it is much faster to apply. This is how the
597% "-blur" operator actually works.
598%
599% DOB:{radius},{sigma1},{sigma2}[,{angle}]
600% "Difference of Blurs" Kernel.
601% As "Blur" but with the 1D gaussian produced by 'sigma2' subtracted
602% from thethe 1D gaussian produced by 'sigma1'.
603% The result is a zero-summing kernel.
604%
605% This can be used to generate a faster "DOG" convolution, in the same
606% way "Blur" can.
anthony602ab9b2010-01-05 08:06:50 +0000607%
anthony3c10fc82010-05-13 02:40:51 +0000608% Comet:{width},{sigma},{angle}
609% Blur in one direction only, much like how a bright object leaves
anthony602ab9b2010-01-05 08:06:50 +0000610% a comet like trail. The Kernel is actually half a gaussian curve,
anthony3c10fc82010-05-13 02:40:51 +0000611% Adding two such blurs in opposite directions produces a Blur Kernel.
612% Angle can be rotated in multiples of 90 degrees.
anthony602ab9b2010-01-05 08:06:50 +0000613%
anthony3c10fc82010-05-13 02:40:51 +0000614% Note that the first argument is the width of the kernel and not the
anthony602ab9b2010-01-05 08:06:50 +0000615% radius of the kernel.
616%
617% # Still to be implemented...
618% #
anthony4fd27e22010-02-07 08:17:18 +0000619% # Filter2D
620% # Filter1D
621% # Set kernel values using a resize filter, and given scale (sigma)
622% # Cylindrical or Linear. Is this posible with an image?
623% #
anthony602ab9b2010-01-05 08:06:50 +0000624%
anthony3c10fc82010-05-13 02:40:51 +0000625% Named Constant Convolution Kernels
626%
anthonyc1061722010-05-14 06:23:49 +0000627% All these are unscaled, zero-summing kernels by default. As such for
628% non-HDRI version of ImageMagick some form of normalization, user scaling,
629% and biasing the results is recommended, to prevent the resulting image
630% being 'clipped'.
631%
632% The 3x3 kernels (most of these) can be circularly rotated in multiples of
633% 45 degrees to generate the 8 angled varients of each of the kernels.
anthony3c10fc82010-05-13 02:40:51 +0000634%
635% Laplacian:{type}
anthony43c49252010-05-18 10:59:50 +0000636% Discrete Lapacian Kernels, (without normalization)
anthonyc1061722010-05-14 06:23:49 +0000637% Type 0 : 3x3 with center:8 surounded by -1 (8 neighbourhood)
638% Type 1 : 3x3 with center:4 edge:-1 corner:0 (4 neighbourhood)
anthony9eb4f742010-05-18 02:45:54 +0000639% Type 2 : 3x3 with center:4 edge:1 corner:-2
640% Type 3 : 3x3 with center:4 edge:-2 corner:1
641% Type 5 : 5x5 laplacian
642% Type 7 : 7x7 laplacian
anthony43c49252010-05-18 10:59:50 +0000643% Type 15 : 5x5 LOG (sigma approx 1.4)
644% Type 19 : 9x9 LOG (sigma approx 1.4)
anthonyc1061722010-05-14 06:23:49 +0000645%
646% Sobel:{angle}
anthony46a369d2010-05-19 02:41:48 +0000647% Sobel 'Edge' convolution kernel (3x3)
anthonyc1061722010-05-14 06:23:49 +0000648% -1, 0, 1
649% -2, 0,-2
650% -1, 0, 1
anthonye2a60ce2010-05-19 12:30:40 +0000651%
anthonyc1061722010-05-14 06:23:49 +0000652% Roberts:{angle}
anthony46a369d2010-05-19 02:41:48 +0000653% Roberts convolution kernel (3x3)
anthonyc1061722010-05-14 06:23:49 +0000654% 0, 0, 0
655% -1, 1, 0
656% 0, 0, 0
anthonyc1061722010-05-14 06:23:49 +0000657% Prewitt:{angle}
658% Prewitt Edge convolution kernel (3x3)
659% -1, 0, 1
660% -1, 0, 1
661% -1, 0, 1
anthony9eb4f742010-05-18 02:45:54 +0000662% Compass:{angle}
663% Prewitt's "Compass" convolution kernel (3x3)
664% -1, 1, 1
665% -1,-2, 1
666% -1, 1, 1
667% Kirsch:{angle}
668% Kirsch's "Compass" convolution kernel (3x3)
669% -3,-3, 5
670% -3, 0, 5
671% -3,-3, 5
anthony3c10fc82010-05-13 02:40:51 +0000672%
anthonye2a60ce2010-05-19 12:30:40 +0000673% FreiChen:{type},{angle}
674% Frei-Chen Edge Detector is a set of 9 unique convolution kernels that
anthony6915d062010-05-19 12:45:51 +0000675% are specially weighted. They should not be normalized. After applying
676% each to the original image, the results is then added together. The
677% square root of the resulting image is the cosine of the edge, and the
678% direction of the feature detection.
anthonye2a60ce2010-05-19 12:30:40 +0000679%
680% Type 1: | 1, sqrt(2), 1 |
681% | 0, 0, 0 | / 2*sqrt(2)
682% | -1, -sqrt(2), -1 |
683%
684% Type 2: | 1, 0, 1 |
685% | sqrt(2), 0, sqrt(2) | / 2*sqrt(2)
686% | 1, 0, 1 |
687%
688% Type 3: | 0, -1, sqrt(2) |
689% | 1, 0, -1 | / 2*sqrt(2)
690% | -sqrt(2), 1, 0 |
691%
anthony6915d062010-05-19 12:45:51 +0000692% Type 4: | sqrt(2), -1, 0 |
anthonye2a60ce2010-05-19 12:30:40 +0000693% | -1, 0, 1 | / 2*sqrt(2)
694% | 0, 1, -sqrt(2) |
695%
696% Type 5: | 0, 1, 0 |
697% | -1, 0, -1 | / 2
698% | 0, 1, 0 |
699%
700% Type 6: | -1, 0, 1 |
701% | 0, 0, 0 | / 2
702% | 1, 0, -1 |
703%
anthonyf4e00312010-05-20 12:06:35 +0000704% Type 7: | 1, -2, 1 |
anthonye2a60ce2010-05-19 12:30:40 +0000705% | -2, 4, -2 | / 6
706% | 1, -2, 1 |
707%
anthonyf4e00312010-05-20 12:06:35 +0000708% Type 8: | -2, 1, -2 |
709% | 1, 4, 1 | / 6
710% | -2, 1, -2 |
anthonye2a60ce2010-05-19 12:30:40 +0000711%
anthonyf4e00312010-05-20 12:06:35 +0000712% Type 9: | 1, 1, 1 |
713% | 1, 1, 1 | / 3
714% | 1, 1, 1 |
anthonye2a60ce2010-05-19 12:30:40 +0000715%
716% The first 4 are for edge detection, the next 4 are for line detection
717% and the last is to add a average component to the results.
718%
anthonye2a60ce2010-05-19 12:30:40 +0000719%
anthony602ab9b2010-01-05 08:06:50 +0000720% Boolean Kernels
721%
anthony3c10fc82010-05-13 02:40:51 +0000722% Diamond:[{radius}[,{scale}]]
anthony1b2bc0a2010-05-12 05:25:22 +0000723% Generate a diamond shaped kernel with given radius to the points.
anthony602ab9b2010-01-05 08:06:50 +0000724% Kernel size will again be radius*2+1 square and defaults to radius 1,
725% generating a 3x3 kernel that is slightly larger than a square.
726%
anthony3c10fc82010-05-13 02:40:51 +0000727% Square:[{radius}[,{scale}]]
anthony602ab9b2010-01-05 08:06:50 +0000728% Generate a square shaped kernel of size radius*2+1, and defaulting
729% to a 3x3 (radius 1).
730%
anthonyc1061722010-05-14 06:23:49 +0000731% Note that using a larger radius for the "Square" or the "Diamond" is
732% also equivelent to iterating the basic morphological method that many
733% times. However iterating with the smaller radius is actually faster
734% than using a larger kernel radius.
735%
736% Rectangle:{geometry}
737% Simply generate a rectangle of 1's with the size given. You can also
738% specify the location of the 'control point', otherwise the closest
739% pixel to the center of the rectangle is selected.
740%
741% Properly centered and odd sized rectangles work the best.
anthony602ab9b2010-01-05 08:06:50 +0000742%
anthony3c10fc82010-05-13 02:40:51 +0000743% Disk:[{radius}[,{scale}]]
anthony602ab9b2010-01-05 08:06:50 +0000744% Generate a binary disk of the radius given, radius may be a float.
745% Kernel size will be ceil(radius)*2+1 square.
746% NOTE: Here are some disk shapes of specific interest
anthonyc1061722010-05-14 06:23:49 +0000747% "Disk:1" => "diamond" or "cross:1"
748% "Disk:1.5" => "square"
749% "Disk:2" => "diamond:2"
750% "Disk:2.5" => a general disk shape of radius 2
751% "Disk:2.9" => "square:2"
752% "Disk:3.5" => default - octagonal/disk shape of radius 3
753% "Disk:4.2" => roughly octagonal shape of radius 4
754% "Disk:4.3" => a general disk shape of radius 4
anthony602ab9b2010-01-05 08:06:50 +0000755% After this all the kernel shape becomes more and more circular.
756%
757% Because a "disk" is more circular when using a larger radius, using a
758% larger radius is preferred over iterating the morphological operation.
759%
anthonyc1061722010-05-14 06:23:49 +0000760% Symbol Dilation Kernels
761%
762% These kernel is not a good general morphological kernel, but is used
763% more for highlighting and marking any single pixels in an image using,
764% a "Dilate" method as appropriate.
765%
766% For the same reasons iterating these kernels does not produce the
767% same result as using a larger radius for the symbol.
768%
anthony3c10fc82010-05-13 02:40:51 +0000769% Plus:[{radius}[,{scale}]]
anthony3dd0f622010-05-13 12:57:32 +0000770% Cross:[{radius}[,{scale}]]
anthonyc1061722010-05-14 06:23:49 +0000771% Generate a kernel in the shape of a 'plus' or a 'cross' with
772% a each arm the length of the given radius (default 2).
anthony3dd0f622010-05-13 12:57:32 +0000773%
774% NOTE: "plus:1" is equivelent to a "Diamond" kernel.
anthony602ab9b2010-01-05 08:06:50 +0000775%
anthonyc1061722010-05-14 06:23:49 +0000776% Ring:{radius1},{radius2}[,{scale}]
777% A ring of the values given that falls between the two radii.
778% Defaults to a ring of approximataly 3 radius in a 7x7 kernel.
779% This is the 'edge' pixels of the default "Disk" kernel,
780% More specifically, "Ring" -> "Ring:2.5,3.5,1.0"
anthony602ab9b2010-01-05 08:06:50 +0000781%
anthony3dd0f622010-05-13 12:57:32 +0000782% Hit and Miss Kernels
783%
784% Peak:radius1,radius2
anthonyc1061722010-05-14 06:23:49 +0000785% Find any peak larger than the pixels the fall between the two radii.
786% The default ring of pixels is as per "Ring".
anthony43c49252010-05-18 10:59:50 +0000787% Edges
788% Find Edges of a binary shape
anthony3dd0f622010-05-13 12:57:32 +0000789% Corners
790% Find corners of a binary shape
anthony47f5d062010-05-23 07:47:50 +0000791% Ridges
792% Find Ridges or Thin lines
anthony3dd0f622010-05-13 12:57:32 +0000793% LineEnds
794% Find end points of lines (for pruning a skeletion)
795% LineJunctions
anthony43c49252010-05-18 10:59:50 +0000796% Find three line junctions (within a skeletion)
anthony3dd0f622010-05-13 12:57:32 +0000797% ConvexHull
798% Octagonal thicken kernel, to generate convex hulls of 45 degrees
799% Skeleton
800% Thinning kernel, which leaves behind a skeletion of a shape
anthony602ab9b2010-01-05 08:06:50 +0000801%
802% Distance Measuring Kernels
803%
anthonyc1061722010-05-14 06:23:49 +0000804% Different types of distance measuring methods, which are used with the
805% a 'Distance' morphology method for generating a gradient based on
806% distance from an edge of a binary shape, though there is a technique
807% for handling a anti-aliased shape.
808%
809% See the 'Distance' Morphological Method, for information of how it is
810% applied.
811%
anthony3dd0f622010-05-13 12:57:32 +0000812% Chebyshev:[{radius}][x{scale}[%!]]
anthonyc94cdb02010-01-06 08:15:29 +0000813% Chebyshev Distance (also known as Tchebychev Distance) is a value of
814% one to any neighbour, orthogonal or diagonal. One why of thinking of
815% it is the number of squares a 'King' or 'Queen' in chess needs to
816% traverse reach any other position on a chess board. It results in a
817% 'square' like distance function, but one where diagonals are closer
818% than expected.
anthony602ab9b2010-01-05 08:06:50 +0000819%
anthonyc1061722010-05-14 06:23:49 +0000820% Manhatten:[{radius}][x{scale}[%!]]
anthonyc94cdb02010-01-06 08:15:29 +0000821% Manhatten Distance (also known as Rectilinear Distance, or the Taxi
822% Cab metric), is the distance needed when you can only travel in
823% orthogonal (horizontal or vertical) only. It is the distance a 'Rook'
824% in chess would travel. It results in a diamond like distances, where
825% diagonals are further than expected.
anthony602ab9b2010-01-05 08:06:50 +0000826%
anthonyc1061722010-05-14 06:23:49 +0000827% Euclidean:[{radius}][x{scale}[%!]]
anthonyc94cdb02010-01-06 08:15:29 +0000828% Euclidean Distance is the 'direct' or 'as the crow flys distance.
829% However by default the kernel size only has a radius of 1, which
830% limits the distance to 'Knight' like moves, with only orthogonal and
831% diagonal measurements being correct. As such for the default kernel
832% you will get octagonal like distance function, which is reasonally
833% accurate.
834%
835% However if you use a larger radius such as "Euclidean:4" you will
836% get a much smoother distance gradient from the edge of the shape.
837% Of course a larger kernel is slower to use, and generally not needed.
838%
839% To allow the use of fractional distances that you get with diagonals
840% the actual distance is scaled by a fixed value which the user can
841% provide. This is not actually nessary for either ""Chebyshev" or
842% "Manhatten" distance kernels, but is done for all three distance
843% kernels. If no scale is provided it is set to a value of 100,
844% allowing for a maximum distance measurement of 655 pixels using a Q16
845% version of IM, from any edge. However for small images this can
846% result in quite a dark gradient.
847%
anthony602ab9b2010-01-05 08:06:50 +0000848*/
849
cristy2be15382010-01-21 02:38:03 +0000850MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000851 const GeometryInfo *args)
852{
cristy2be15382010-01-21 02:38:03 +0000853 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000854 *kernel;
855
cristy150989e2010-02-01 14:59:39 +0000856 register long
anthony602ab9b2010-01-05 08:06:50 +0000857 i;
858
859 register long
860 u,
861 v;
862
863 double
864 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
865
anthonyc1061722010-05-14 06:23:49 +0000866 /* Generate a new empty kernel if needed */
cristye96405a2010-05-19 02:24:31 +0000867 kernel=(KernelInfo *) NULL;
anthonyc1061722010-05-14 06:23:49 +0000868 switch(type) {
anthony9eb4f742010-05-18 02:45:54 +0000869 case UndefinedKernel: /* These should not be used here */
870 case UserDefinedKernel:
871 break;
872 case LaplacianKernel: /* Named Descrete Convolution Kernels */
873 case SobelKernel:
874 case RobertsKernel:
875 case PrewittKernel:
876 case CompassKernel:
877 case KirschKernel:
878 case CornersKernel: /* Hit and Miss kernels */
879 case LineEndsKernel:
880 case LineJunctionsKernel:
anthony9eb4f742010-05-18 02:45:54 +0000881 case ConvexHullKernel:
882 case SkeletonKernel:
883 /* A pre-generated kernel is not needed */
884 break;
885#if 0
anthonyc1061722010-05-14 06:23:49 +0000886 case GaussianKernel:
887 case DOGKernel:
888 case BlurKernel:
889 case DOBKernel:
890 case CometKernel:
891 case DiamondKernel:
892 case SquareKernel:
893 case RectangleKernel:
894 case DiskKernel:
895 case PlusKernel:
896 case CrossKernel:
897 case RingKernel:
898 case PeaksKernel:
899 case ChebyshevKernel:
900 case ManhattenKernel:
901 case EuclideanKernel:
anthony9eb4f742010-05-18 02:45:54 +0000902#endif
903 default:
904 /* Generate the base Kernel Structure */
anthonyc1061722010-05-14 06:23:49 +0000905 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
906 if (kernel == (KernelInfo *) NULL)
907 return(kernel);
908 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
anthony43c49252010-05-18 10:59:50 +0000909 kernel->minimum = kernel->maximum = kernel->angle = 0.0;
anthonyc1061722010-05-14 06:23:49 +0000910 kernel->negative_range = kernel->positive_range = 0.0;
911 kernel->type = type;
912 kernel->next = (KernelInfo *) NULL;
913 kernel->signature = MagickSignature;
anthonyc1061722010-05-14 06:23:49 +0000914 break;
915 }
anthony602ab9b2010-01-05 08:06:50 +0000916
917 switch(type) {
918 /* Convolution Kernels */
919 case GaussianKernel:
anthonyc1061722010-05-14 06:23:49 +0000920 case DOGKernel:
anthony9eb4f742010-05-18 02:45:54 +0000921 case LOGKernel:
anthony602ab9b2010-01-05 08:06:50 +0000922 { double
anthonyc1061722010-05-14 06:23:49 +0000923 sigma = fabs(args->sigma),
924 sigma2 = fabs(args->xi),
anthony9eb4f742010-05-18 02:45:54 +0000925 A, B, R;
anthony602ab9b2010-01-05 08:06:50 +0000926
anthonyc1061722010-05-14 06:23:49 +0000927 if ( args->rho >= 1.0 )
928 kernel->width = (unsigned long)args->rho*2+1;
anthony9eb4f742010-05-18 02:45:54 +0000929 else if ( (type != DOGKernel) || (sigma >= sigma2) )
anthonyc1061722010-05-14 06:23:49 +0000930 kernel->width = GetOptimalKernelWidth2D(args->rho,sigma);
931 else
932 kernel->width = GetOptimalKernelWidth2D(args->rho,sigma2);
933 kernel->height = kernel->width;
cristyc99304f2010-02-01 15:26:27 +0000934 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000935 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
936 kernel->height*sizeof(double));
937 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000938 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000939
anthony46a369d2010-05-19 02:41:48 +0000940 /* WARNING: The following generates a 'sampled gaussian' kernel.
anthony9eb4f742010-05-18 02:45:54 +0000941 * What we really want is a 'discrete gaussian' kernel.
anthony46a369d2010-05-19 02:41:48 +0000942 *
943 * How to do this is currently not known, but appears to be
944 * basied on the Error Function 'erf()' (intergral of a gaussian)
anthony9eb4f742010-05-18 02:45:54 +0000945 */
946
947 if ( type == GaussianKernel || type == DOGKernel )
948 { /* Calculate a Gaussian, OR positive half of a DOG */
949 if ( sigma > MagickEpsilon )
950 { A = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
951 B = 1.0/(Magick2PI*sigma*sigma);
952 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
953 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
954 kernel->values[i] = exp(-((double)(u*u+v*v))*A)*B;
955 }
956 else /* limiting case - a unity (normalized Dirac) kernel */
957 { (void) ResetMagickMemory(kernel->values,0, (size_t)
958 kernel->width*kernel->height*sizeof(double));
959 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
960 }
anthonyc1061722010-05-14 06:23:49 +0000961 }
anthony9eb4f742010-05-18 02:45:54 +0000962
anthonyc1061722010-05-14 06:23:49 +0000963 if ( type == DOGKernel )
964 { /* Subtract a Negative Gaussian for "Difference of Gaussian" */
965 if ( sigma2 > MagickEpsilon )
966 { sigma = sigma2; /* simplify loop expressions */
anthony9eb4f742010-05-18 02:45:54 +0000967 A = 1.0/(2.0*sigma*sigma);
968 B = 1.0/(Magick2PI*sigma*sigma);
anthonyc1061722010-05-14 06:23:49 +0000969 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
970 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony9eb4f742010-05-18 02:45:54 +0000971 kernel->values[i] -= exp(-((double)(u*u+v*v))*A)*B;
anthonyc1061722010-05-14 06:23:49 +0000972 }
anthony9eb4f742010-05-18 02:45:54 +0000973 else /* limiting case - a unity (normalized Dirac) kernel */
anthonyc1061722010-05-14 06:23:49 +0000974 kernel->values[kernel->x+kernel->y*kernel->width] -= 1.0;
975 }
anthony9eb4f742010-05-18 02:45:54 +0000976
977 if ( type == LOGKernel )
978 { /* Calculate a Laplacian of a Gaussian - Or Mexician Hat */
979 if ( sigma > MagickEpsilon )
980 { A = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
981 B = 1.0/(MagickPI*sigma*sigma*sigma*sigma);
982 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
983 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
984 { R = ((double)(u*u+v*v))*A;
985 kernel->values[i] = (1-R)*exp(-R)*B;
986 }
987 }
988 else /* special case - generate a unity kernel */
989 { (void) ResetMagickMemory(kernel->values,0, (size_t)
990 kernel->width*kernel->height*sizeof(double));
991 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
992 }
993 }
994
995 /* Note the above kernels may have been 'clipped' by a user defined
anthonyc1061722010-05-14 06:23:49 +0000996 ** radius, producing a smaller (darker) kernel. Also for very small
997 ** sigma's (> 0.1) the central value becomes larger than one, and thus
998 ** producing a very bright kernel.
999 **
1000 ** Normalization will still be needed.
1001 */
anthony602ab9b2010-01-05 08:06:50 +00001002
anthony3dd0f622010-05-13 12:57:32 +00001003 /* Normalize the 2D Gaussian Kernel
1004 **
anthonyc1061722010-05-14 06:23:49 +00001005 ** NB: a CorrelateNormalize performs a normal Normalize if
1006 ** there are no negative values.
anthony3dd0f622010-05-13 12:57:32 +00001007 */
anthony46a369d2010-05-19 02:41:48 +00001008 CalcKernelMetaData(kernel); /* the other kernel meta-data */
anthonyc1061722010-05-14 06:23:49 +00001009 ScaleKernelInfo(kernel, 1.0, CorrelateNormalizeValue);
anthony602ab9b2010-01-05 08:06:50 +00001010
1011 break;
1012 }
1013 case BlurKernel:
anthonyc1061722010-05-14 06:23:49 +00001014 case DOBKernel:
anthony602ab9b2010-01-05 08:06:50 +00001015 { double
anthonyc1061722010-05-14 06:23:49 +00001016 sigma = fabs(args->sigma),
1017 sigma2 = fabs(args->xi),
anthony9eb4f742010-05-18 02:45:54 +00001018 A, B;
anthony602ab9b2010-01-05 08:06:50 +00001019
anthonyc1061722010-05-14 06:23:49 +00001020 if ( args->rho >= 1.0 )
1021 kernel->width = (unsigned long)args->rho*2+1;
1022 else if ( (type == BlurKernel) || (sigma >= sigma2) )
1023 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
1024 else
1025 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma2);
anthony602ab9b2010-01-05 08:06:50 +00001026 kernel->height = 1;
anthonyc1061722010-05-14 06:23:49 +00001027 kernel->x = (long) (kernel->width-1)/2;
cristyc99304f2010-02-01 15:26:27 +00001028 kernel->y = 0;
1029 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +00001030 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1031 kernel->height*sizeof(double));
1032 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001033 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001034
1035#if 1
1036#define KernelRank 3
1037 /* Formula derived from GetBlurKernel() in "effect.c" (plus bug fix).
1038 ** It generates a gaussian 3 times the width, and compresses it into
1039 ** the expected range. This produces a closer normalization of the
1040 ** resulting kernel, especially for very low sigma values.
1041 ** As such while wierd it is prefered.
1042 **
1043 ** I am told this method originally came from Photoshop.
anthony9eb4f742010-05-18 02:45:54 +00001044 **
1045 ** A properly normalized curve is generated (apart from edge clipping)
1046 ** even though we later normalize the result (for edge clipping)
1047 ** to allow the correct generation of a "Difference of Blurs".
anthony602ab9b2010-01-05 08:06:50 +00001048 */
anthonyc1061722010-05-14 06:23:49 +00001049
1050 /* initialize */
cristy150989e2010-02-01 14:59:39 +00001051 v = (long) (kernel->width*KernelRank-1)/2; /* start/end points to fit range */
anthony9eb4f742010-05-18 02:45:54 +00001052 (void) ResetMagickMemory(kernel->values,0, (size_t)
1053 kernel->width*kernel->height*sizeof(double));
anthonyc1061722010-05-14 06:23:49 +00001054 /* Calculate a Positive 1D Gaussian */
1055 if ( sigma > MagickEpsilon )
1056 { sigma *= 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 }
1062 }
1063 else /* special case - generate a unity kernel */
1064 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
anthony9eb4f742010-05-18 02:45:54 +00001065
1066 /* Subtract a Second 1D Gaussian for "Difference of Blur" */
anthonyc1061722010-05-14 06:23:49 +00001067 if ( type == DOBKernel )
anthony9eb4f742010-05-18 02:45:54 +00001068 {
anthonyc1061722010-05-14 06:23:49 +00001069 if ( sigma2 > MagickEpsilon )
1070 { sigma = sigma2*KernelRank; /* simplify loop expressions */
anthony9eb4f742010-05-18 02:45:54 +00001071 A = 1.0/(2.0*sigma*sigma);
1072 B = 1.0/(MagickSQ2PI*sigma);
anthonyc1061722010-05-14 06:23:49 +00001073 for ( u=-v; u <= v; u++)
anthony9eb4f742010-05-18 02:45:54 +00001074 kernel->values[(u+v)/KernelRank] -= exp(-((double)(u*u))*A)*B;
anthonyc1061722010-05-14 06:23:49 +00001075 }
anthony9eb4f742010-05-18 02:45:54 +00001076 else /* limiting case - a unity (normalized Dirac) kernel */
anthonyc1061722010-05-14 06:23:49 +00001077 kernel->values[kernel->x+kernel->y*kernel->width] -= 1.0;
1078 }
anthony602ab9b2010-01-05 08:06:50 +00001079#else
anthonyc1061722010-05-14 06:23:49 +00001080 /* Direct calculation without curve averaging */
1081
1082 /* Calculate a Positive Gaussian */
1083 if ( sigma > MagickEpsilon )
anthony9eb4f742010-05-18 02:45:54 +00001084 { A = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
1085 B = 1.0/(MagickSQ2PI*sigma);
anthonyc1061722010-05-14 06:23:49 +00001086 for ( i=0, u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony9eb4f742010-05-18 02:45:54 +00001087 kernel->values[i] = exp(-((double)(u*u))*A)*B;
anthonyc1061722010-05-14 06:23:49 +00001088 }
1089 else /* special case - generate a unity kernel */
1090 { (void) ResetMagickMemory(kernel->values,0, (size_t)
1091 kernel->width*kernel->height*sizeof(double));
1092 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1093 }
anthony9eb4f742010-05-18 02:45:54 +00001094
1095 /* Subtract a Second 1D Gaussian for "Difference of Blur" */
anthonyc1061722010-05-14 06:23:49 +00001096 if ( type == DOBKernel )
anthony9eb4f742010-05-18 02:45:54 +00001097 {
anthonyc1061722010-05-14 06:23:49 +00001098 if ( sigma2 > MagickEpsilon )
1099 { sigma = sigma2; /* simplify loop expressions */
anthony9eb4f742010-05-18 02:45:54 +00001100 A = 1.0/(2.0*sigma*sigma);
1101 B = 1.0/(MagickSQ2PI*sigma);
anthonyc1061722010-05-14 06:23:49 +00001102 for ( i=0, u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony9eb4f742010-05-18 02:45:54 +00001103 kernel->values[i] -= exp(-((double)(u*u))*A)*B;
anthonyc1061722010-05-14 06:23:49 +00001104 }
anthony9eb4f742010-05-18 02:45:54 +00001105 else /* limiting case - a unity (normalized Dirac) kernel */
anthonyc1061722010-05-14 06:23:49 +00001106 kernel->values[kernel->x+kernel->y*kernel->width] -= 1.0;
1107 }
anthony602ab9b2010-01-05 08:06:50 +00001108#endif
anthonyc1061722010-05-14 06:23:49 +00001109 /* Note the above kernel may have been 'clipped' by a user defined
anthonycc6c8362010-01-25 04:14:01 +00001110 ** radius, producing a smaller (darker) kernel. Also for very small
1111 ** sigma's (> 0.1) the central value becomes larger than one, and thus
1112 ** producing a very bright kernel.
anthonyc1061722010-05-14 06:23:49 +00001113 **
1114 ** Normalization will still be needed.
anthony602ab9b2010-01-05 08:06:50 +00001115 */
anthonycc6c8362010-01-25 04:14:01 +00001116
anthony602ab9b2010-01-05 08:06:50 +00001117 /* Normalize the 1D Gaussian Kernel
1118 **
anthonyc1061722010-05-14 06:23:49 +00001119 ** NB: a CorrelateNormalize performs a normal Normalize if
1120 ** there are no negative values.
anthony602ab9b2010-01-05 08:06:50 +00001121 */
anthony46a369d2010-05-19 02:41:48 +00001122 CalcKernelMetaData(kernel); /* the other kernel meta-data */
1123 ScaleKernelInfo(kernel, 1.0, CorrelateNormalizeValue);
anthonycc6c8362010-01-25 04:14:01 +00001124
anthonyc1061722010-05-14 06:23:49 +00001125 /* rotate the 1D kernel by given angle */
1126 RotateKernelInfo(kernel, (type == BlurKernel) ? args->xi : args->psi );
anthony602ab9b2010-01-05 08:06:50 +00001127 break;
1128 }
1129 case CometKernel:
1130 { double
anthony9eb4f742010-05-18 02:45:54 +00001131 sigma = fabs(args->sigma),
1132 A;
anthony602ab9b2010-01-05 08:06:50 +00001133
anthony602ab9b2010-01-05 08:06:50 +00001134 if ( args->rho < 1.0 )
anthonye1cf9462010-05-19 03:50:26 +00001135 kernel->width = (GetOptimalKernelWidth1D(args->rho,sigma)-1)/2+1;
anthony602ab9b2010-01-05 08:06:50 +00001136 else
1137 kernel->width = (unsigned long)args->rho;
cristyc99304f2010-02-01 15:26:27 +00001138 kernel->x = kernel->y = 0;
anthony602ab9b2010-01-05 08:06:50 +00001139 kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +00001140 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +00001141 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1142 kernel->height*sizeof(double));
1143 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001144 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001145
anthonyc1061722010-05-14 06:23:49 +00001146 /* A comet blur is half a 1D gaussian curve, so that the object is
anthony602ab9b2010-01-05 08:06:50 +00001147 ** blurred in one direction only. This may not be quite the right
anthony3dd0f622010-05-13 12:57:32 +00001148 ** curve to use so may change in the future. The function must be
1149 ** normalised after generation, which also resolves any clipping.
anthonyc1061722010-05-14 06:23:49 +00001150 **
1151 ** As we are normalizing and not subtracting gaussians,
1152 ** there is no need for a divisor in the gaussian formula
1153 **
anthony43c49252010-05-18 10:59:50 +00001154 ** It is less comples
anthony602ab9b2010-01-05 08:06:50 +00001155 */
anthony9eb4f742010-05-18 02:45:54 +00001156 if ( sigma > MagickEpsilon )
1157 {
anthony602ab9b2010-01-05 08:06:50 +00001158#if 1
1159#define KernelRank 3
anthony9eb4f742010-05-18 02:45:54 +00001160 v = (long) kernel->width*KernelRank; /* start/end points */
1161 (void) ResetMagickMemory(kernel->values,0, (size_t)
1162 kernel->width*sizeof(double));
1163 sigma *= KernelRank; /* simplify the loop expression */
1164 A = 1.0/(2.0*sigma*sigma);
1165 /* B = 1.0/(MagickSQ2PI*sigma); */
1166 for ( u=0; u < v; u++) {
1167 kernel->values[u/KernelRank] +=
1168 exp(-((double)(u*u))*A);
1169 /* exp(-((double)(i*i))/2.0*sigma*sigma)/(MagickSQ2PI*sigma); */
1170 }
1171 for (i=0; i < (long) kernel->width; i++)
1172 kernel->positive_range += kernel->values[i];
anthony602ab9b2010-01-05 08:06:50 +00001173#else
anthony9eb4f742010-05-18 02:45:54 +00001174 A = 1.0/(2.0*sigma*sigma); /* simplify the loop expression */
1175 /* B = 1.0/(MagickSQ2PI*sigma); */
1176 for ( i=0; i < (long) kernel->width; i++)
1177 kernel->positive_range +=
1178 kernel->values[i] =
1179 exp(-((double)(i*i))*A);
1180 /* exp(-((double)(i*i))/2.0*sigma*sigma)/(MagickSQ2PI*sigma); */
anthony602ab9b2010-01-05 08:06:50 +00001181#endif
anthony9eb4f742010-05-18 02:45:54 +00001182 }
1183 else /* special case - generate a unity kernel */
1184 { (void) ResetMagickMemory(kernel->values,0, (size_t)
1185 kernel->width*kernel->height*sizeof(double));
1186 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1187 kernel->positive_range = 1.0;
1188 }
anthony46a369d2010-05-19 02:41:48 +00001189
1190 kernel->minimum = 0.0;
cristyc99304f2010-02-01 15:26:27 +00001191 kernel->maximum = kernel->values[0];
anthony46a369d2010-05-19 02:41:48 +00001192 kernel->negative_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +00001193
anthony999bb2c2010-02-18 12:38:01 +00001194 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
1195 RotateKernelInfo(kernel, args->xi); /* Rotate by angle */
anthony602ab9b2010-01-05 08:06:50 +00001196 break;
1197 }
anthonyc1061722010-05-14 06:23:49 +00001198
anthony3c10fc82010-05-13 02:40:51 +00001199 /* Convolution Kernels - Well Known Constants */
anthony3c10fc82010-05-13 02:40:51 +00001200 case LaplacianKernel:
anthonye2a60ce2010-05-19 12:30:40 +00001201 { switch ( (int) args->rho ) {
anthony3dd0f622010-05-13 12:57:32 +00001202 case 0:
anthony9eb4f742010-05-18 02:45:54 +00001203 default: /* laplacian square filter -- default */
anthonyc1061722010-05-14 06:23:49 +00001204 kernel=ParseKernelArray("3: -1,-1,-1 -1,8,-1 -1,-1,-1");
anthony3dd0f622010-05-13 12:57:32 +00001205 break;
anthony9eb4f742010-05-18 02:45:54 +00001206 case 1: /* laplacian diamond filter */
anthonyc1061722010-05-14 06:23:49 +00001207 kernel=ParseKernelArray("3: 0,-1,0 -1,4,-1 0,-1,0");
anthony3c10fc82010-05-13 02:40:51 +00001208 break;
1209 case 2:
anthony9eb4f742010-05-18 02:45:54 +00001210 kernel=ParseKernelArray("3: -2,1,-2 1,4,1 -2,1,-2");
1211 break;
1212 case 3:
anthonyc1061722010-05-14 06:23:49 +00001213 kernel=ParseKernelArray("3: 1,-2,1 -2,4,-2 1,-2,1");
anthony3c10fc82010-05-13 02:40:51 +00001214 break;
anthony9eb4f742010-05-18 02:45:54 +00001215 case 5: /* a 5x5 laplacian */
anthony3c10fc82010-05-13 02:40:51 +00001216 kernel=ParseKernelArray(
anthony9eb4f742010-05-18 02:45:54 +00001217 "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 +00001218 break;
anthony9eb4f742010-05-18 02:45:54 +00001219 case 7: /* a 7x7 laplacian */
anthony3c10fc82010-05-13 02:40:51 +00001220 kernel=ParseKernelArray(
anthonyc1061722010-05-14 06:23:49 +00001221 "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 +00001222 break;
anthony43c49252010-05-18 10:59:50 +00001223 case 15: /* a 5x5 LOG (sigma approx 1.4) */
anthony9eb4f742010-05-18 02:45:54 +00001224 kernel=ParseKernelArray(
1225 "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");
1226 break;
anthony43c49252010-05-18 10:59:50 +00001227 case 19: /* a 9x9 LOG (sigma approx 1.4) */
1228 /* http://www.cscjournals.org/csc/manuscript/Journals/IJIP/volume3/Issue1/IJIP-15.pdf */
1229 kernel=ParseKernelArray(
1230 "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");
1231 break;
anthony3c10fc82010-05-13 02:40:51 +00001232 }
1233 if (kernel == (KernelInfo *) NULL)
1234 return(kernel);
1235 kernel->type = type;
1236 break;
1237 }
anthonyc1061722010-05-14 06:23:49 +00001238 case SobelKernel:
anthony602ab9b2010-01-05 08:06:50 +00001239 {
anthonyc1061722010-05-14 06:23:49 +00001240 kernel=ParseKernelArray("3: -1,0,1 -2,0,2 -1,0,1");
1241 if (kernel == (KernelInfo *) NULL)
1242 return(kernel);
1243 kernel->type = type;
1244 RotateKernelInfo(kernel, args->rho); /* Rotate by angle */
1245 break;
1246 }
1247 case RobertsKernel:
1248 {
1249 kernel=ParseKernelArray("3: 0,0,0 -1,1,0 0,0,0");
1250 if (kernel == (KernelInfo *) NULL)
1251 return(kernel);
1252 kernel->type = type;
anthony46a369d2010-05-19 02:41:48 +00001253 RotateKernelInfo(kernel, args->rho);
anthonyc1061722010-05-14 06:23:49 +00001254 break;
1255 }
1256 case PrewittKernel:
1257 {
1258 kernel=ParseKernelArray("3: -1,1,1 0,0,0 -1,1,1");
1259 if (kernel == (KernelInfo *) NULL)
1260 return(kernel);
1261 kernel->type = type;
anthony46a369d2010-05-19 02:41:48 +00001262 RotateKernelInfo(kernel, args->rho);
anthonyc1061722010-05-14 06:23:49 +00001263 break;
1264 }
1265 case CompassKernel:
1266 {
1267 kernel=ParseKernelArray("3: -1,1,1 -1,-2,1 -1,1,1");
1268 if (kernel == (KernelInfo *) NULL)
1269 return(kernel);
1270 kernel->type = type;
anthony46a369d2010-05-19 02:41:48 +00001271 RotateKernelInfo(kernel, args->rho);
anthonyc1061722010-05-14 06:23:49 +00001272 break;
1273 }
anthony9eb4f742010-05-18 02:45:54 +00001274 case KirschKernel:
1275 {
1276 kernel=ParseKernelArray("3: -3,-3,5 -3,0,5 -3,-3,5");
1277 if (kernel == (KernelInfo *) NULL)
1278 return(kernel);
1279 kernel->type = type;
anthony46a369d2010-05-19 02:41:48 +00001280 RotateKernelInfo(kernel, args->rho);
anthony9eb4f742010-05-18 02:45:54 +00001281 break;
1282 }
anthonye2a60ce2010-05-19 12:30:40 +00001283 case FreiChenKernel:
anthony6915d062010-05-19 12:45:51 +00001284 /* http://www.math.tau.ac.il/~turkel/notes/edge_detectors.pdf */
1285 /* http://ltswww.epfl.ch/~courstiv/exos_labos/sol3.pdf */
anthonye2a60ce2010-05-19 12:30:40 +00001286 { switch ( (int) args->rho ) {
1287 default:
1288 case 1:
1289 kernel=ParseKernelArray("3: 1,2,1 0,0,0 -1,2,-1");
1290 if (kernel == (KernelInfo *) NULL)
1291 return(kernel);
1292 kernel->values[1] = +MagickSQ2;
1293 kernel->values[7] = -MagickSQ2;
1294 CalcKernelMetaData(kernel); /* recalculate meta-data */
1295 ScaleKernelInfo(kernel, 1.0/2.0*MagickSQ2, NoValue);
1296 break;
1297 case 2:
1298 kernel=ParseKernelArray("3: 1,0,1 2,0,2 1,0,1");
1299 if (kernel == (KernelInfo *) NULL)
1300 return(kernel);
1301 kernel->values[3] = +MagickSQ2;
1302 kernel->values[5] = +MagickSQ2;
1303 CalcKernelMetaData(kernel);
1304 ScaleKernelInfo(kernel, 1.0/2.0*MagickSQ2, NoValue);
1305 break;
1306 case 3:
1307 kernel=ParseKernelArray("3: 0,-1,2 1,0,-1 -2,1,0");
1308 if (kernel == (KernelInfo *) NULL)
1309 return(kernel);
1310 kernel->values[2] = +MagickSQ2;
1311 kernel->values[6] = -MagickSQ2;
1312 CalcKernelMetaData(kernel);
1313 ScaleKernelInfo(kernel, 1.0/2.0*MagickSQ2, NoValue);
1314 break;
1315 case 4:
anthony6915d062010-05-19 12:45:51 +00001316 kernel=ParseKernelArray("3: 2,-1,0 -1,0,1 0,1,-2");
anthonye2a60ce2010-05-19 12:30:40 +00001317 if (kernel == (KernelInfo *) NULL)
1318 return(kernel);
1319 kernel->values[0] = +MagickSQ2;
1320 kernel->values[8] = -MagickSQ2;
1321 CalcKernelMetaData(kernel);
1322 ScaleKernelInfo(kernel, 1.0/2.0*MagickSQ2, NoValue);
1323 break;
1324 case 5:
1325 kernel=ParseKernelArray("3: 0,1,0 -1,0,-1 0,1,0");
1326 if (kernel == (KernelInfo *) NULL)
1327 return(kernel);
1328 ScaleKernelInfo(kernel, 1.0/2.0, NoValue);
1329 break;
1330 case 6:
1331 kernel=ParseKernelArray("3: -1,0,1 0,0,0 1,0,-1");
1332 if (kernel == (KernelInfo *) NULL)
1333 return(kernel);
1334 ScaleKernelInfo(kernel, 1.0/2.0, NoValue);
1335 break;
1336 case 7:
anthonyf4e00312010-05-20 12:06:35 +00001337 kernel=ParseKernelArray("3: 1,-2,1 -2,4,-2 1,-2,1");
anthonye2a60ce2010-05-19 12:30:40 +00001338 if (kernel == (KernelInfo *) NULL)
1339 return(kernel);
1340 ScaleKernelInfo(kernel, 1.0/6.0, NoValue);
1341 break;
1342 case 8:
1343 kernel=ParseKernelArray("3: -2,1,-2 1,4,1 -2,1,-2");
1344 if (kernel == (KernelInfo *) NULL)
1345 return(kernel);
1346 ScaleKernelInfo(kernel, 1.0/6.0, NoValue);
1347 break;
1348 case 9:
anthony6915d062010-05-19 12:45:51 +00001349 kernel=ParseKernelName("3: 1,1,1 1,1,1 1,1,1");
anthonye2a60ce2010-05-19 12:30:40 +00001350 if (kernel == (KernelInfo *) NULL)
1351 return(kernel);
1352 ScaleKernelInfo(kernel, 1.0/3.0, NoValue);
1353 break;
1354 }
1355 RotateKernelInfo(kernel, args->sigma); /* Rotate by angle */
1356 break;
1357 }
1358
anthonyc1061722010-05-14 06:23:49 +00001359 /* Boolean Kernels */
1360 case DiamondKernel:
1361 {
1362 if (args->rho < 1.0)
1363 kernel->width = kernel->height = 3; /* default radius = 1 */
1364 else
1365 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
1366 kernel->x = kernel->y = (long) (kernel->width-1)/2;
1367
1368 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1369 kernel->height*sizeof(double));
1370 if (kernel->values == (double *) NULL)
1371 return(DestroyKernelInfo(kernel));
1372
1373 /* set all kernel values within diamond area to scale given */
1374 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1375 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1376 if ((labs(u)+labs(v)) <= (long)kernel->x)
1377 kernel->positive_range += kernel->values[i] = args->sigma;
1378 else
1379 kernel->values[i] = nan;
1380 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1381 break;
1382 }
1383 case SquareKernel:
1384 case RectangleKernel:
1385 { double
1386 scale;
anthony602ab9b2010-01-05 08:06:50 +00001387 if ( type == SquareKernel )
1388 {
1389 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001390 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001391 else
cristy150989e2010-02-01 14:59:39 +00001392 kernel->width = kernel->height = (unsigned long) (2*args->rho+1);
cristyc99304f2010-02-01 15:26:27 +00001393 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony4fd27e22010-02-07 08:17:18 +00001394 scale = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +00001395 }
1396 else {
cristy2be15382010-01-21 02:38:03 +00001397 /* NOTE: user defaults set in "AcquireKernelInfo()" */
anthony602ab9b2010-01-05 08:06:50 +00001398 if ( args->rho < 1.0 || args->sigma < 1.0 )
anthony83ba99b2010-01-24 08:48:15 +00001399 return(DestroyKernelInfo(kernel)); /* invalid args given */
anthony602ab9b2010-01-05 08:06:50 +00001400 kernel->width = (unsigned long)args->rho;
1401 kernel->height = (unsigned long)args->sigma;
1402 if ( args->xi < 0.0 || args->xi > (double)kernel->width ||
1403 args->psi < 0.0 || args->psi > (double)kernel->height )
anthony83ba99b2010-01-24 08:48:15 +00001404 return(DestroyKernelInfo(kernel)); /* invalid args given */
cristyc99304f2010-02-01 15:26:27 +00001405 kernel->x = (long) args->xi;
1406 kernel->y = (long) args->psi;
anthony4fd27e22010-02-07 08:17:18 +00001407 scale = 1.0;
anthony602ab9b2010-01-05 08:06:50 +00001408 }
1409 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1410 kernel->height*sizeof(double));
1411 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001412 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001413
anthony3dd0f622010-05-13 12:57:32 +00001414 /* set all kernel values to scale given */
cristy150989e2010-02-01 14:59:39 +00001415 u=(long) kernel->width*kernel->height;
1416 for ( i=0; i < u; i++)
anthony4fd27e22010-02-07 08:17:18 +00001417 kernel->values[i] = scale;
1418 kernel->minimum = kernel->maximum = scale; /* a flat shape */
1419 kernel->positive_range = scale*u;
anthonycc6c8362010-01-25 04:14:01 +00001420 break;
anthony602ab9b2010-01-05 08:06:50 +00001421 }
anthony602ab9b2010-01-05 08:06:50 +00001422 case DiskKernel:
1423 {
anthonyc1061722010-05-14 06:23:49 +00001424 long limit = (long)(args->rho*args->rho);
anthony83ba99b2010-01-24 08:48:15 +00001425 if (args->rho < 0.1) /* default radius approx 3.5 */
1426 kernel->width = kernel->height = 7L, limit = 10L;
anthony602ab9b2010-01-05 08:06:50 +00001427 else
1428 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001429 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001430
1431 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1432 kernel->height*sizeof(double));
1433 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001434 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001435
anthony3dd0f622010-05-13 12:57:32 +00001436 /* set all kernel values within disk area to scale given */
1437 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
cristyc99304f2010-02-01 15:26:27 +00001438 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony602ab9b2010-01-05 08:06:50 +00001439 if ((u*u+v*v) <= limit)
anthony4fd27e22010-02-07 08:17:18 +00001440 kernel->positive_range += kernel->values[i] = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +00001441 else
1442 kernel->values[i] = nan;
anthony4fd27e22010-02-07 08:17:18 +00001443 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
anthony602ab9b2010-01-05 08:06:50 +00001444 break;
1445 }
1446 case PlusKernel:
1447 {
1448 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001449 kernel->width = kernel->height = 5; /* default radius 2 */
anthony602ab9b2010-01-05 08:06:50 +00001450 else
1451 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001452 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001453
1454 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1455 kernel->height*sizeof(double));
1456 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001457 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001458
anthony3dd0f622010-05-13 12:57:32 +00001459 /* set all kernel values along axises to given scale */
cristyc99304f2010-02-01 15:26:27 +00001460 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1461 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony4fd27e22010-02-07 08:17:18 +00001462 kernel->values[i] = (u == 0 || v == 0) ? args->sigma : nan;
1463 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1464 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
anthony602ab9b2010-01-05 08:06:50 +00001465 break;
1466 }
anthony3dd0f622010-05-13 12:57:32 +00001467 case CrossKernel:
1468 {
1469 if (args->rho < 1.0)
1470 kernel->width = kernel->height = 5; /* default radius 2 */
1471 else
1472 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
1473 kernel->x = kernel->y = (long) (kernel->width-1)/2;
1474
1475 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1476 kernel->height*sizeof(double));
1477 if (kernel->values == (double *) NULL)
1478 return(DestroyKernelInfo(kernel));
1479
1480 /* set all kernel values along axises to given scale */
1481 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1482 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1483 kernel->values[i] = (u == v || u == -v) ? args->sigma : nan;
1484 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1485 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
1486 break;
1487 }
1488 /* HitAndMiss Kernels */
anthonyc1061722010-05-14 06:23:49 +00001489 case RingKernel:
anthony3dd0f622010-05-13 12:57:32 +00001490 case PeaksKernel:
1491 {
1492 long
1493 limit1,
anthonyc1061722010-05-14 06:23:49 +00001494 limit2,
1495 scale;
anthony3dd0f622010-05-13 12:57:32 +00001496
1497 if (args->rho < args->sigma)
1498 {
1499 kernel->width = ((unsigned long)args->sigma)*2+1;
1500 limit1 = (long)args->rho*args->rho;
1501 limit2 = (long)args->sigma*args->sigma;
1502 }
1503 else
1504 {
1505 kernel->width = ((unsigned long)args->rho)*2+1;
1506 limit1 = (long)args->sigma*args->sigma;
1507 limit2 = (long)args->rho*args->rho;
1508 }
anthonyc1061722010-05-14 06:23:49 +00001509 if ( limit2 <= 0 )
1510 kernel->width = 7L, limit1 = 7L, limit2 = 11L;
1511
anthony3dd0f622010-05-13 12:57:32 +00001512 kernel->height = kernel->width;
1513 kernel->x = kernel->y = (long) (kernel->width-1)/2;
1514 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1515 kernel->height*sizeof(double));
1516 if (kernel->values == (double *) NULL)
1517 return(DestroyKernelInfo(kernel));
1518
anthonyc1061722010-05-14 06:23:49 +00001519 /* set a ring of points of 'scale' ( 0.0 for PeaksKernel ) */
cristye96405a2010-05-19 02:24:31 +00001520 scale = (long) (( type == PeaksKernel) ? 0.0 : args->xi);
anthony3dd0f622010-05-13 12:57:32 +00001521 for ( i=0, v= -kernel->y; v <= (long)kernel->y; v++)
1522 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1523 { long radius=u*u+v*v;
anthonyc1061722010-05-14 06:23:49 +00001524 if (limit1 < radius && radius <= limit2)
cristye96405a2010-05-19 02:24:31 +00001525 kernel->positive_range += kernel->values[i] = (double) scale;
anthony3dd0f622010-05-13 12:57:32 +00001526 else
1527 kernel->values[i] = nan;
1528 }
cristye96405a2010-05-19 02:24:31 +00001529 kernel->minimum = kernel->minimum = (double) scale;
anthonyc1061722010-05-14 06:23:49 +00001530 if ( type == PeaksKernel ) {
1531 /* set the central point in the middle */
1532 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1533 kernel->positive_range = 1.0;
1534 kernel->maximum = 1.0;
1535 }
anthony3dd0f622010-05-13 12:57:32 +00001536 break;
1537 }
anthony43c49252010-05-18 10:59:50 +00001538 case EdgesKernel:
1539 {
1540 kernel=ParseKernelArray("3: 0,0,0 -,1,- 1,1,1");
1541 if (kernel == (KernelInfo *) NULL)
1542 return(kernel);
1543 kernel->type = type;
1544 ExpandKernelInfo(kernel, 90.0); /* Create a list of 4 rotated kernels */
1545 break;
1546 }
anthony3dd0f622010-05-13 12:57:32 +00001547 case CornersKernel:
1548 {
anthony4f1dcb72010-05-14 08:43:10 +00001549 kernel=ParseKernelArray("3: 0,0,- 0,1,1 -,1,-");
anthony3dd0f622010-05-13 12:57:32 +00001550 if (kernel == (KernelInfo *) NULL)
1551 return(kernel);
1552 kernel->type = type;
1553 ExpandKernelInfo(kernel, 90.0); /* Create a list of 4 rotated kernels */
1554 break;
1555 }
anthony47f5d062010-05-23 07:47:50 +00001556 case RidgesKernel:
1557 {
1558 kernel=ParseKernelArray("3: -,-,- 0,1,0 -,-,-");
1559 if (kernel == (KernelInfo *) NULL)
1560 return(kernel);
1561 kernel->type = type;
1562 ExpandKernelInfo(kernel, 45.0); /* 4 rotated kernels (symmetrical) */
1563 break;
1564 }
anthony3dd0f622010-05-13 12:57:32 +00001565 case LineEndsKernel:
1566 {
anthony43c49252010-05-18 10:59:50 +00001567 KernelInfo
1568 *new_kernel;
1569 kernel=ParseKernelArray("3: 0,0,0 0,1,0 -,1,-");
anthony3dd0f622010-05-13 12:57:32 +00001570 if (kernel == (KernelInfo *) NULL)
1571 return(kernel);
1572 kernel->type = type;
anthony43c49252010-05-18 10:59:50 +00001573 ExpandKernelInfo(kernel, 90.0);
1574 /* append second set of 4 kernels */
1575 new_kernel=ParseKernelArray("3: 0,0,0 0,1,0 0,0,1");
1576 if (new_kernel == (KernelInfo *) NULL)
1577 return(DestroyKernelInfo(kernel));
1578 new_kernel->type = type;
1579 ExpandKernelInfo(new_kernel, 90.0);
1580 LastKernelInfo(kernel)->next = new_kernel;
anthony3dd0f622010-05-13 12:57:32 +00001581 break;
1582 }
1583 case LineJunctionsKernel:
1584 {
1585 KernelInfo
1586 *new_kernel;
anthony3dd0f622010-05-13 12:57:32 +00001587 /* first set of 4 kernels */
anthony4f1dcb72010-05-14 08:43:10 +00001588 kernel=ParseKernelArray("3: -,1,- -,1,- 1,-,1");
anthony3dd0f622010-05-13 12:57:32 +00001589 if (kernel == (KernelInfo *) NULL)
1590 return(kernel);
1591 kernel->type = type;
anthony43c49252010-05-18 10:59:50 +00001592 ExpandKernelInfo(kernel, 45.0);
anthony3dd0f622010-05-13 12:57:32 +00001593 /* append second set of 4 kernels */
anthony4f1dcb72010-05-14 08:43:10 +00001594 new_kernel=ParseKernelArray("3: 1,-,- -,1,- 1,-,1");
anthony3dd0f622010-05-13 12:57:32 +00001595 if (new_kernel == (KernelInfo *) NULL)
1596 return(DestroyKernelInfo(kernel));
anthony43c49252010-05-18 10:59:50 +00001597 new_kernel->type = type;
anthony3dd0f622010-05-13 12:57:32 +00001598 ExpandKernelInfo(new_kernel, 90.0);
1599 LastKernelInfo(kernel)->next = new_kernel;
anthony4f1dcb72010-05-14 08:43:10 +00001600 break;
1601 }
anthony3dd0f622010-05-13 12:57:32 +00001602 case ConvexHullKernel:
1603 {
1604 KernelInfo
1605 *new_kernel;
anthony3dd0f622010-05-13 12:57:32 +00001606 /* first set of 4 kernels */
anthony4f1dcb72010-05-14 08:43:10 +00001607 kernel=ParseKernelArray("3: 1,1,- 1,0,- 1,-,0");
anthony3dd0f622010-05-13 12:57:32 +00001608 if (kernel == (KernelInfo *) NULL)
1609 return(kernel);
1610 kernel->type = type;
1611 ExpandKernelInfo(kernel, 90.0);
1612 /* append second set of 4 kernels */
anthony43c49252010-05-18 10:59:50 +00001613 new_kernel=ParseKernelArray("3: 1,1,1 1,0,0 -,-,0");
anthony3dd0f622010-05-13 12:57:32 +00001614 if (new_kernel == (KernelInfo *) NULL)
1615 return(DestroyKernelInfo(kernel));
anthony43c49252010-05-18 10:59:50 +00001616 new_kernel->type = type;
anthony3dd0f622010-05-13 12:57:32 +00001617 ExpandKernelInfo(new_kernel, 90.0);
1618 LastKernelInfo(kernel)->next = new_kernel;
1619 break;
1620 }
anthony47f5d062010-05-23 07:47:50 +00001621 case SkeletonKernel:
1622 { /* what is the best form for medial axis skeletonization? */
1623#if 0
1624# if 0
1625 kernel=AcquireKernelInfo("Corners;Edges");
1626# else
1627 kernel=AcquireKernelInfo("Edges;Corners");
1628# endif
1629#else
anthony43c49252010-05-18 10:59:50 +00001630 kernel=ParseKernelArray("3: 0,0,- 0,1,1 -,1,1");
anthony3dd0f622010-05-13 12:57:32 +00001631 if (kernel == (KernelInfo *) NULL)
1632 return(kernel);
1633 kernel->type = type;
anthony43c49252010-05-18 10:59:50 +00001634 ExpandKernelInfo(kernel, 45);
1635 break;
anthony47f5d062010-05-23 07:47:50 +00001636#endif
anthony3dd0f622010-05-13 12:57:32 +00001637 break;
1638 }
anthony602ab9b2010-01-05 08:06:50 +00001639 /* Distance Measuring Kernels */
1640 case ChebyshevKernel:
1641 {
anthony602ab9b2010-01-05 08:06:50 +00001642 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001643 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001644 else
1645 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001646 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001647
1648 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1649 kernel->height*sizeof(double));
1650 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001651 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001652
cristyc99304f2010-02-01 15:26:27 +00001653 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1654 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1655 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001656 args->sigma*((labs(u)>labs(v)) ? labs(u) : labs(v)) );
cristyc99304f2010-02-01 15:26:27 +00001657 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001658 break;
1659 }
1660 case ManhattenKernel:
1661 {
anthony602ab9b2010-01-05 08:06:50 +00001662 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001663 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001664 else
1665 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001666 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001667
1668 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1669 kernel->height*sizeof(double));
1670 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001671 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001672
cristyc99304f2010-02-01 15:26:27 +00001673 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1674 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1675 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001676 args->sigma*(labs(u)+labs(v)) );
cristyc99304f2010-02-01 15:26:27 +00001677 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001678 break;
1679 }
1680 case EuclideanKernel:
1681 {
anthony602ab9b2010-01-05 08:06:50 +00001682 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001683 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001684 else
1685 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001686 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001687
1688 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1689 kernel->height*sizeof(double));
1690 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001691 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001692
cristyc99304f2010-02-01 15:26:27 +00001693 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1694 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1695 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001696 args->sigma*sqrt((double)(u*u+v*v)) );
cristyc99304f2010-02-01 15:26:27 +00001697 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001698 break;
1699 }
anthony46a369d2010-05-19 02:41:48 +00001700 case UnityKernel:
anthony602ab9b2010-01-05 08:06:50 +00001701 default:
anthonyc1061722010-05-14 06:23:49 +00001702 {
anthony46a369d2010-05-19 02:41:48 +00001703 /* Unity or No-Op Kernel - 3x3 with 1 in center */
1704 kernel=ParseKernelArray("3:0,0,0,0,1,0,0,0,0");
anthonyc1061722010-05-14 06:23:49 +00001705 if (kernel == (KernelInfo *) NULL)
1706 return(kernel);
anthony46a369d2010-05-19 02:41:48 +00001707 kernel->type = ( type == UnityKernel ) ? UnityKernel : UndefinedKernel;
anthonyc1061722010-05-14 06:23:49 +00001708 break;
1709 }
anthony602ab9b2010-01-05 08:06:50 +00001710 break;
1711 }
1712
1713 return(kernel);
1714}
anthonyc94cdb02010-01-06 08:15:29 +00001715
anthony602ab9b2010-01-05 08:06:50 +00001716/*
1717%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1718% %
1719% %
1720% %
cristy6771f1e2010-03-05 19:43:39 +00001721% C l o n e K e r n e l I n f o %
anthony4fd27e22010-02-07 08:17:18 +00001722% %
1723% %
1724% %
1725%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1726%
anthony1b2bc0a2010-05-12 05:25:22 +00001727% CloneKernelInfo() creates a new clone of the given Kernel List so that its
1728% can be modified without effecting the original. The cloned kernel should
1729% be destroyed using DestoryKernelInfo() when no longer needed.
anthony7a01dcf2010-05-11 12:25:52 +00001730%
cristye6365592010-04-02 17:31:23 +00001731% The format of the CloneKernelInfo method is:
anthony4fd27e22010-02-07 08:17:18 +00001732%
anthony930be612010-02-08 04:26:15 +00001733% KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +00001734%
1735% A description of each parameter follows:
1736%
1737% o kernel: the Morphology/Convolution kernel to be cloned
1738%
1739*/
cristyef656912010-03-05 19:54:59 +00001740MagickExport KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +00001741{
1742 register long
1743 i;
1744
cristy19eb6412010-04-23 14:42:29 +00001745 KernelInfo
anthony7a01dcf2010-05-11 12:25:52 +00001746 *new_kernel;
anthony4fd27e22010-02-07 08:17:18 +00001747
1748 assert(kernel != (KernelInfo *) NULL);
anthony7a01dcf2010-05-11 12:25:52 +00001749 new_kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
1750 if (new_kernel == (KernelInfo *) NULL)
1751 return(new_kernel);
1752 *new_kernel=(*kernel); /* copy values in structure */
anthony7a01dcf2010-05-11 12:25:52 +00001753
1754 /* replace the values with a copy of the values */
1755 new_kernel->values=(double *) AcquireQuantumMemory(kernel->width,
cristy19eb6412010-04-23 14:42:29 +00001756 kernel->height*sizeof(double));
anthony7a01dcf2010-05-11 12:25:52 +00001757 if (new_kernel->values == (double *) NULL)
1758 return(DestroyKernelInfo(new_kernel));
anthony4fd27e22010-02-07 08:17:18 +00001759 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthony7a01dcf2010-05-11 12:25:52 +00001760 new_kernel->values[i]=kernel->values[i];
anthony1b2bc0a2010-05-12 05:25:22 +00001761
1762 /* Also clone the next kernel in the kernel list */
1763 if ( kernel->next != (KernelInfo *) NULL ) {
1764 new_kernel->next = CloneKernelInfo(kernel->next);
1765 if ( new_kernel->next == (KernelInfo *) NULL )
1766 return(DestroyKernelInfo(new_kernel));
1767 }
1768
anthony7a01dcf2010-05-11 12:25:52 +00001769 return(new_kernel);
anthony4fd27e22010-02-07 08:17:18 +00001770}
1771
1772/*
1773%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1774% %
1775% %
1776% %
anthony83ba99b2010-01-24 08:48:15 +00001777% D e s t r o y K e r n e l I n f o %
anthony602ab9b2010-01-05 08:06:50 +00001778% %
1779% %
1780% %
1781%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1782%
anthony83ba99b2010-01-24 08:48:15 +00001783% DestroyKernelInfo() frees the memory used by a Convolution/Morphology
1784% kernel.
anthony602ab9b2010-01-05 08:06:50 +00001785%
anthony83ba99b2010-01-24 08:48:15 +00001786% The format of the DestroyKernelInfo method is:
anthony602ab9b2010-01-05 08:06:50 +00001787%
anthony83ba99b2010-01-24 08:48:15 +00001788% KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +00001789%
1790% A description of each parameter follows:
1791%
1792% o kernel: the Morphology/Convolution kernel to be destroyed
1793%
1794*/
1795
anthony83ba99b2010-01-24 08:48:15 +00001796MagickExport KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +00001797{
cristy2be15382010-01-21 02:38:03 +00001798 assert(kernel != (KernelInfo *) NULL);
anthony4fd27e22010-02-07 08:17:18 +00001799
anthony7a01dcf2010-05-11 12:25:52 +00001800 if ( kernel->next != (KernelInfo *) NULL )
1801 kernel->next = DestroyKernelInfo(kernel->next);
1802
1803 kernel->values = (double *)RelinquishMagickMemory(kernel->values);
1804 kernel = (KernelInfo *) RelinquishMagickMemory(kernel);
anthony602ab9b2010-01-05 08:06:50 +00001805 return(kernel);
1806}
anthonyc94cdb02010-01-06 08:15:29 +00001807
1808/*
1809%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1810% %
1811% %
1812% %
anthony3c10fc82010-05-13 02:40:51 +00001813% E x p a n d K e r n e l I n f o %
1814% %
1815% %
1816% %
1817%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1818%
1819% ExpandKernelInfo() takes a single kernel, and expands it into a list
1820% of kernels each incrementally rotated the angle given.
1821%
1822% WARNING: 45 degree rotations only works for 3x3 kernels.
1823% While 90 degree roatations only works for linear and square kernels
1824%
1825% The format of the RotateKernelInfo method is:
1826%
1827% void ExpandKernelInfo(KernelInfo *kernel, double angle)
1828%
1829% A description of each parameter follows:
1830%
1831% o kernel: the Morphology/Convolution kernel
1832%
1833% o angle: angle to rotate in degrees
1834%
1835% This function is only internel to this module, as it is not finalized,
1836% especially with regard to non-orthogonal angles, and rotation of larger
1837% 2D kernels.
1838*/
anthony47f5d062010-05-23 07:47:50 +00001839
1840/* Internal Routine - Return true if two kernels are the same */
1841static MagickBooleanType SameKernelInfo(const KernelInfo *kernel1,
1842 const KernelInfo *kernel2)
1843{
1844 register unsigned long
1845 i;
1846 if ( kernel1->width != kernel2->width )
1847 return MagickFalse;
1848 if ( kernel1->height != kernel2->height )
1849 return MagickFalse;
1850 for (i=0; i < (kernel1->width*kernel1->height); i++) {
1851 /* Test Nan */
1852 if ( IsNan(kernel1->values[i]) && !IsNan(kernel2->values[i]) )
1853 return MagickFalse;
1854 if ( IsNan(kernel2->values[i]) && !IsNan(kernel1->values[i]) )
1855 return MagickFalse;
1856 /* Test actual value */
1857 if ( fabs(kernel1->values[i] - kernel2->values[i]) > MagickEpsilon )
1858 return MagickFalse;
1859 }
1860 return MagickTrue;
1861}
1862
1863static void ExpandKernelInfo(KernelInfo *kernel, const double angle)
anthony3c10fc82010-05-13 02:40:51 +00001864{
1865 KernelInfo
anthonyc3e48252010-05-24 12:43:11 +00001866 *new,
anthony3c10fc82010-05-13 02:40:51 +00001867 *last;
cristya9a61ad2010-05-13 12:47:41 +00001868
anthony3c10fc82010-05-13 02:40:51 +00001869 last = kernel;
anthony47f5d062010-05-23 07:47:50 +00001870 while(1) {
anthonyc3e48252010-05-24 12:43:11 +00001871 new = CloneKernelInfo(last);
1872 RotateKernelInfo(new, angle);
1873 if ( SameKernelInfo(kernel, new) == MagickTrue )
anthony47f5d062010-05-23 07:47:50 +00001874 break;
anthonyc3e48252010-05-24 12:43:11 +00001875 last->next = new;
1876 last = new;
anthony3c10fc82010-05-13 02:40:51 +00001877 }
anthonyc3e48252010-05-24 12:43:11 +00001878 new = DestroyKernelInfo(new); /* This was the same as the first - junk */
anthony47f5d062010-05-23 07:47:50 +00001879 return;
anthony3c10fc82010-05-13 02:40:51 +00001880}
1881
1882/*
1883%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1884% %
1885% %
1886% %
anthony46a369d2010-05-19 02:41:48 +00001887+ C a l c M e t a K e r n a l I n f o %
1888% %
1889% %
1890% %
1891%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1892%
1893% CalcKernelMetaData() recalculate the KernelInfo meta-data of this kernel only,
1894% using the kernel values. This should only ne used if it is not posible to
1895% calculate that meta-data in some easier way.
1896%
1897% It is important that the meta-data is correct before ScaleKernelInfo() is
1898% used to perform kernel normalization.
1899%
1900% The format of the CalcKernelMetaData method is:
1901%
1902% void CalcKernelMetaData(KernelInfo *kernel, const double scale )
1903%
1904% A description of each parameter follows:
1905%
1906% o kernel: the Morphology/Convolution kernel to modify
1907%
1908% WARNING: Minimum and Maximum values are assumed to include zero, even if
1909% zero is not part of the kernel (as in Gaussian Derived kernels). This
1910% however is not true for flat-shaped morphological kernels.
1911%
1912% WARNING: Only the specific kernel pointed to is modified, not a list of
1913% multiple kernels.
1914%
1915% This is an internal function and not expected to be useful outside this
1916% module. This could change however.
1917*/
1918static void CalcKernelMetaData(KernelInfo *kernel)
1919{
1920 register unsigned long
1921 i;
1922
1923 kernel->minimum = kernel->maximum = 0.0;
1924 kernel->negative_range = kernel->positive_range = 0.0;
1925 for (i=0; i < (kernel->width*kernel->height); i++)
1926 {
1927 if ( fabs(kernel->values[i]) < MagickEpsilon )
1928 kernel->values[i] = 0.0;
1929 ( kernel->values[i] < 0)
1930 ? ( kernel->negative_range += kernel->values[i] )
1931 : ( kernel->positive_range += kernel->values[i] );
1932 Minimize(kernel->minimum, kernel->values[i]);
1933 Maximize(kernel->maximum, kernel->values[i]);
1934 }
1935
1936 return;
1937}
1938
1939/*
1940%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1941% %
1942% %
1943% %
anthony9eb4f742010-05-18 02:45:54 +00001944% M o r p h o l o g y A p p l y %
anthony602ab9b2010-01-05 08:06:50 +00001945% %
1946% %
1947% %
1948%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1949%
anthony9eb4f742010-05-18 02:45:54 +00001950% MorphologyApply() applies a morphological method, multiple times using
1951% a list of multiple kernels.
anthony602ab9b2010-01-05 08:06:50 +00001952%
anthony9eb4f742010-05-18 02:45:54 +00001953% It is basically equivelent to as MorphologyImageChannel() (see below) but
1954% without user controls, that that function extracts and applies to kernels
1955% and morphology methods.
1956%
1957% More specifically kernels are not normalized/scaled/blended by the
1958% 'convolve:scale' Image Artifact (-set setting), and the convolve bias
1959% (-bias setting or image->bias) is passed directly to this function,
1960% and not extracted from an image.
anthony602ab9b2010-01-05 08:06:50 +00001961%
anthony47f5d062010-05-23 07:47:50 +00001962% The format of the MorphologyApply method is:
anthony602ab9b2010-01-05 08:06:50 +00001963%
anthony9eb4f742010-05-18 02:45:54 +00001964% Image *MorphologyApply(const Image *image,MorphologyMethod method,
anthony47f5d062010-05-23 07:47:50 +00001965% const long iterations,const KernelInfo *kernel,
1966% const CompositeMethod compose, const double bias,
anthony9eb4f742010-05-18 02:45:54 +00001967% ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001968%
1969% A description of each parameter follows:
1970%
1971% o image: the image.
1972%
1973% o method: the morphology method to be applied.
1974%
1975% o iterations: apply the operation this many times (or no change).
1976% A value of -1 means loop until no change found.
1977% How this is applied may depend on the morphology method.
1978% Typically this is a value of 1.
1979%
1980% o channel: the channel type.
1981%
1982% o kernel: An array of double representing the morphology kernel.
anthony29188a82010-01-22 10:12:34 +00001983% Warning: kernel may be normalized for the Convolve method.
anthony602ab9b2010-01-05 08:06:50 +00001984%
anthony47f5d062010-05-23 07:47:50 +00001985% o compose: How to handle or merge multi-kernel results.
1986% If 'Undefined' use default of the Morphology method.
1987% If 'No' force image to be re-iterated by each kernel.
1988% Otherwise merge the results using the mathematical compose
1989% method given.
1990%
1991% o bias: Convolution Output Bias.
anthony9eb4f742010-05-18 02:45:54 +00001992%
anthony602ab9b2010-01-05 08:06:50 +00001993% o exception: return any errors or warnings in this structure.
1994%
anthony602ab9b2010-01-05 08:06:50 +00001995*/
1996
anthony930be612010-02-08 04:26:15 +00001997
anthony9eb4f742010-05-18 02:45:54 +00001998/* Apply a Morphology Primative to an image using the given kernel.
1999** Two pre-created images must be provided, no image is created.
2000** Returning the number of pixels that changed.
2001*/
anthony46a369d2010-05-19 02:41:48 +00002002static unsigned long MorphologyPrimitive(const Image *image, Image
anthony602ab9b2010-01-05 08:06:50 +00002003 *result_image, const MorphologyMethod method, const ChannelType channel,
anthony9eb4f742010-05-18 02:45:54 +00002004 const KernelInfo *kernel,const double bias,ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00002005{
cristy2be15382010-01-21 02:38:03 +00002006#define MorphologyTag "Morphology/Image"
anthony602ab9b2010-01-05 08:06:50 +00002007
2008 long
cristy150989e2010-02-01 14:59:39 +00002009 progress,
anthony29188a82010-01-22 10:12:34 +00002010 y, offx, offy,
anthony602ab9b2010-01-05 08:06:50 +00002011 changed;
2012
2013 MagickBooleanType
2014 status;
2015
anthony602ab9b2010-01-05 08:06:50 +00002016 CacheView
2017 *p_view,
2018 *q_view;
2019
anthony602ab9b2010-01-05 08:06:50 +00002020 status=MagickTrue;
2021 changed=0;
2022 progress=0;
2023
anthony602ab9b2010-01-05 08:06:50 +00002024 p_view=AcquireCacheView(image);
2025 q_view=AcquireCacheView(result_image);
anthony29188a82010-01-22 10:12:34 +00002026
anthonycc6c8362010-01-25 04:14:01 +00002027 /* Some methods (including convolve) needs use a reflected kernel.
anthony9eb4f742010-05-18 02:45:54 +00002028 * Adjust 'origin' offsets to loop though kernel as a reflection.
anthony29188a82010-01-22 10:12:34 +00002029 */
cristyc99304f2010-02-01 15:26:27 +00002030 offx = kernel->x;
2031 offy = kernel->y;
anthony29188a82010-01-22 10:12:34 +00002032 switch(method) {
anthony930be612010-02-08 04:26:15 +00002033 case ConvolveMorphology:
2034 case DilateMorphology:
2035 case DilateIntensityMorphology:
2036 case DistanceMorphology:
anthony5ef8e942010-05-11 06:51:12 +00002037 /* kernel needs to used with reflection about origin */
cristy150989e2010-02-01 14:59:39 +00002038 offx = (long) kernel->width-offx-1;
2039 offy = (long) kernel->height-offy-1;
anthony29188a82010-01-22 10:12:34 +00002040 break;
anthony5ef8e942010-05-11 06:51:12 +00002041 case ErodeMorphology:
2042 case ErodeIntensityMorphology:
2043 case HitAndMissMorphology:
2044 case ThinningMorphology:
2045 case ThickenMorphology:
2046 /* kernel is user as is, without reflection */
2047 break;
anthony930be612010-02-08 04:26:15 +00002048 default:
anthony9eb4f742010-05-18 02:45:54 +00002049 assert("Not a Primitive Morphology Method" != (char *) NULL);
anthony930be612010-02-08 04:26:15 +00002050 break;
anthony29188a82010-01-22 10:12:34 +00002051 }
2052
anthony602ab9b2010-01-05 08:06:50 +00002053#if defined(MAGICKCORE_OPENMP_SUPPORT)
2054 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2055#endif
cristy150989e2010-02-01 14:59:39 +00002056 for (y=0; y < (long) image->rows; y++)
anthony602ab9b2010-01-05 08:06:50 +00002057 {
2058 MagickBooleanType
2059 sync;
2060
2061 register const PixelPacket
2062 *restrict p;
2063
2064 register const IndexPacket
2065 *restrict p_indexes;
2066
2067 register PixelPacket
2068 *restrict q;
2069
2070 register IndexPacket
2071 *restrict q_indexes;
2072
cristy150989e2010-02-01 14:59:39 +00002073 register long
anthony602ab9b2010-01-05 08:06:50 +00002074 x;
2075
anthony29188a82010-01-22 10:12:34 +00002076 unsigned long
anthony602ab9b2010-01-05 08:06:50 +00002077 r;
2078
2079 if (status == MagickFalse)
2080 continue;
anthony29188a82010-01-22 10:12:34 +00002081 p=GetCacheViewVirtualPixels(p_view, -offx, y-offy,
2082 image->columns+kernel->width, kernel->height, exception);
anthony602ab9b2010-01-05 08:06:50 +00002083 q=GetCacheViewAuthenticPixels(q_view,0,y,result_image->columns,1,
2084 exception);
2085 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2086 {
2087 status=MagickFalse;
2088 continue;
2089 }
2090 p_indexes=GetCacheViewVirtualIndexQueue(p_view);
2091 q_indexes=GetCacheViewAuthenticIndexQueue(q_view);
anthony29188a82010-01-22 10:12:34 +00002092 r = (image->columns+kernel->width)*offy+offx; /* constant */
2093
cristy150989e2010-02-01 14:59:39 +00002094 for (x=0; x < (long) image->columns; x++)
anthony602ab9b2010-01-05 08:06:50 +00002095 {
cristy150989e2010-02-01 14:59:39 +00002096 long
anthony602ab9b2010-01-05 08:06:50 +00002097 v;
2098
cristy150989e2010-02-01 14:59:39 +00002099 register long
anthony602ab9b2010-01-05 08:06:50 +00002100 u;
2101
2102 register const double
2103 *restrict k;
2104
2105 register const PixelPacket
2106 *restrict k_pixels;
2107
2108 register const IndexPacket
2109 *restrict k_indexes;
2110
2111 MagickPixelPacket
anthony5ef8e942010-05-11 06:51:12 +00002112 result,
2113 min,
2114 max;
anthony602ab9b2010-01-05 08:06:50 +00002115
anthony29188a82010-01-22 10:12:34 +00002116 /* Copy input to ouput image for unused channels
anthony83ba99b2010-01-24 08:48:15 +00002117 * This removes need for 'cloning' a new image every iteration
anthony29188a82010-01-22 10:12:34 +00002118 */
anthony602ab9b2010-01-05 08:06:50 +00002119 *q = p[r];
2120 if (image->colorspace == CMYKColorspace)
2121 q_indexes[x] = p_indexes[r];
2122
anthony5ef8e942010-05-11 06:51:12 +00002123 /* Defaults */
2124 min.red =
2125 min.green =
2126 min.blue =
2127 min.opacity =
2128 min.index = (MagickRealType) QuantumRange;
2129 max.red =
2130 max.green =
2131 max.blue =
2132 max.opacity =
2133 max.index = (MagickRealType) 0;
anthony9eb4f742010-05-18 02:45:54 +00002134 /* default result is the original pixel value */
anthony5ef8e942010-05-11 06:51:12 +00002135 result.red = (MagickRealType) p[r].red;
2136 result.green = (MagickRealType) p[r].green;
2137 result.blue = (MagickRealType) p[r].blue;
2138 result.opacity = QuantumRange - (MagickRealType) p[r].opacity;
cristye96405a2010-05-19 02:24:31 +00002139 result.index = 0.0;
anthony5ef8e942010-05-11 06:51:12 +00002140 if ( image->colorspace == CMYKColorspace)
2141 result.index = (MagickRealType) p_indexes[r];
2142
anthony602ab9b2010-01-05 08:06:50 +00002143 switch (method) {
2144 case ConvolveMorphology:
anthony9eb4f742010-05-18 02:45:54 +00002145 /* Set the user defined bias of the weighted average output */
2146 result.red =
2147 result.green =
2148 result.blue =
2149 result.opacity =
2150 result.index = bias;
anthony930be612010-02-08 04:26:15 +00002151 break;
anthony4fd27e22010-02-07 08:17:18 +00002152 case DilateIntensityMorphology:
2153 case ErodeIntensityMorphology:
anthony9eb4f742010-05-18 02:45:54 +00002154 /* use a boolean flag indicating when first match found */
2155 result.red = 0.0; /* result is not used otherwise */
anthony4fd27e22010-02-07 08:17:18 +00002156 break;
anthony602ab9b2010-01-05 08:06:50 +00002157 default:
anthony602ab9b2010-01-05 08:06:50 +00002158 break;
2159 }
2160
2161 switch ( method ) {
2162 case ConvolveMorphology:
anthony930be612010-02-08 04:26:15 +00002163 /* Weighted Average of pixels using reflected kernel
2164 **
2165 ** NOTE for correct working of this operation for asymetrical
2166 ** kernels, the kernel needs to be applied in its reflected form.
2167 ** That is its values needs to be reversed.
2168 **
2169 ** Correlation is actually the same as this but without reflecting
2170 ** the kernel, and thus 'lower-level' that Convolution. However
2171 ** as Convolution is the more common method used, and it does not
2172 ** really cost us much in terms of processing to use a reflected
anthony5ef8e942010-05-11 06:51:12 +00002173 ** kernel, so it is Convolution that is implemented.
anthony930be612010-02-08 04:26:15 +00002174 **
2175 ** Correlation will have its kernel reflected before calling
2176 ** this function to do a Convolve.
2177 **
2178 ** For more details of Correlation vs Convolution see
2179 ** http://www.cs.umd.edu/~djacobs/CMSC426/Convolution.pdf
2180 */
anthony5ef8e942010-05-11 06:51:12 +00002181 if (((channel & SyncChannels) != 0 ) &&
2182 (image->matte == MagickTrue))
2183 { /* Channel has a 'Sync' Flag, and Alpha Channel enabled.
2184 ** Weight the color channels with Alpha Channel so that
2185 ** transparent pixels are not part of the results.
2186 */
anthony602ab9b2010-01-05 08:06:50 +00002187 MagickRealType
anthony5ef8e942010-05-11 06:51:12 +00002188 alpha, /* color channel weighting : kernel*alpha */
2189 gamma; /* divisor, sum of weighting values */
anthony602ab9b2010-01-05 08:06:50 +00002190
2191 gamma=0.0;
anthony29188a82010-01-22 10:12:34 +00002192 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00002193 k_pixels = p;
2194 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002195 for (v=0; v < (long) kernel->height; v++) {
2196 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00002197 if ( IsNan(*k) ) continue;
2198 alpha=(*k)*(QuantumScale*(QuantumRange-
2199 k_pixels[u].opacity));
2200 gamma += alpha;
2201 result.red += alpha*k_pixels[u].red;
2202 result.green += alpha*k_pixels[u].green;
2203 result.blue += alpha*k_pixels[u].blue;
anthony83ba99b2010-01-24 08:48:15 +00002204 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00002205 if ( image->colorspace == CMYKColorspace)
2206 result.index += alpha*k_indexes[u];
2207 }
2208 k_pixels += image->columns+kernel->width;
2209 k_indexes += image->columns+kernel->width;
2210 }
2211 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
anthony83ba99b2010-01-24 08:48:15 +00002212 result.red *= gamma;
2213 result.green *= gamma;
2214 result.blue *= gamma;
2215 result.opacity *= gamma;
2216 result.index *= gamma;
anthony602ab9b2010-01-05 08:06:50 +00002217 }
anthony5ef8e942010-05-11 06:51:12 +00002218 else
2219 {
2220 /* No 'Sync' flag, or no Alpha involved.
2221 ** Convolution is simple individual channel weigthed sum.
2222 */
2223 k = &kernel->values[ kernel->width*kernel->height-1 ];
2224 k_pixels = p;
2225 k_indexes = p_indexes;
2226 for (v=0; v < (long) kernel->height; v++) {
2227 for (u=0; u < (long) kernel->width; u++, k--) {
2228 if ( IsNan(*k) ) continue;
2229 result.red += (*k)*k_pixels[u].red;
2230 result.green += (*k)*k_pixels[u].green;
2231 result.blue += (*k)*k_pixels[u].blue;
2232 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
2233 if ( image->colorspace == CMYKColorspace)
2234 result.index += (*k)*k_indexes[u];
2235 }
2236 k_pixels += image->columns+kernel->width;
2237 k_indexes += image->columns+kernel->width;
2238 }
2239 }
anthony602ab9b2010-01-05 08:06:50 +00002240 break;
2241
anthony4fd27e22010-02-07 08:17:18 +00002242 case ErodeMorphology:
anthony5ef8e942010-05-11 06:51:12 +00002243 /* Minimum Value within kernel neighbourhood
anthony930be612010-02-08 04:26:15 +00002244 **
2245 ** NOTE that the kernel is not reflected for this operation!
2246 **
2247 ** NOTE: in normal Greyscale Morphology, the kernel value should
2248 ** be added to the real value, this is currently not done, due to
2249 ** the nature of the boolean kernels being used.
2250 */
anthony4fd27e22010-02-07 08:17:18 +00002251 k = kernel->values;
2252 k_pixels = p;
2253 k_indexes = p_indexes;
2254 for (v=0; v < (long) kernel->height; v++) {
2255 for (u=0; u < (long) kernel->width; u++, k++) {
2256 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony5ef8e942010-05-11 06:51:12 +00002257 Minimize(min.red, (double) k_pixels[u].red);
2258 Minimize(min.green, (double) k_pixels[u].green);
2259 Minimize(min.blue, (double) k_pixels[u].blue);
2260 Minimize(min.opacity,
anthonyd37a5cb2010-05-07 06:37:03 +00002261 QuantumRange-(double) k_pixels[u].opacity);
anthony4fd27e22010-02-07 08:17:18 +00002262 if ( image->colorspace == CMYKColorspace)
anthony5ef8e942010-05-11 06:51:12 +00002263 Minimize(min.index, (double) k_indexes[u]);
anthony4fd27e22010-02-07 08:17:18 +00002264 }
2265 k_pixels += image->columns+kernel->width;
2266 k_indexes += image->columns+kernel->width;
2267 }
2268 break;
2269
anthony5ef8e942010-05-11 06:51:12 +00002270
anthony83ba99b2010-01-24 08:48:15 +00002271 case DilateMorphology:
anthony5ef8e942010-05-11 06:51:12 +00002272 /* Maximum Value within kernel neighbourhood
anthony930be612010-02-08 04:26:15 +00002273 **
2274 ** NOTE for correct working of this operation for asymetrical
2275 ** kernels, the kernel needs to be applied in its reflected form.
2276 ** That is its values needs to be reversed.
2277 **
2278 ** NOTE: in normal Greyscale Morphology, the kernel value should
2279 ** be added to the real value, this is currently not done, due to
2280 ** the nature of the boolean kernels being used.
2281 **
2282 */
anthony29188a82010-01-22 10:12:34 +00002283 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00002284 k_pixels = p;
2285 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002286 for (v=0; v < (long) kernel->height; v++) {
2287 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00002288 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony5ef8e942010-05-11 06:51:12 +00002289 Maximize(max.red, (double) k_pixels[u].red);
2290 Maximize(max.green, (double) k_pixels[u].green);
2291 Maximize(max.blue, (double) k_pixels[u].blue);
2292 Maximize(max.opacity,
anthonyd37a5cb2010-05-07 06:37:03 +00002293 QuantumRange-(double) k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00002294 if ( image->colorspace == CMYKColorspace)
anthony5ef8e942010-05-11 06:51:12 +00002295 Maximize(max.index, (double) k_indexes[u]);
anthony602ab9b2010-01-05 08:06:50 +00002296 }
2297 k_pixels += image->columns+kernel->width;
2298 k_indexes += image->columns+kernel->width;
2299 }
anthony602ab9b2010-01-05 08:06:50 +00002300 break;
2301
anthony5ef8e942010-05-11 06:51:12 +00002302 case HitAndMissMorphology:
2303 case ThinningMorphology:
2304 case ThickenMorphology:
2305 /* Minimum of Foreground Pixel minus Maxumum of Background Pixels
2306 **
2307 ** NOTE that the kernel is not reflected for this operation,
2308 ** and consists of both foreground and background pixel
2309 ** neighbourhoods, 0.0 for background, and 1.0 for foreground
2310 ** with either Nan or 0.5 values for don't care.
2311 **
2312 ** Note that this can produce negative results, though really
2313 ** only a positive match has any real value.
2314 */
2315 k = kernel->values;
2316 k_pixels = p;
2317 k_indexes = p_indexes;
2318 for (v=0; v < (long) kernel->height; v++) {
2319 for (u=0; u < (long) kernel->width; u++, k++) {
2320 if ( IsNan(*k) ) continue;
2321 if ( (*k) > 0.7 )
2322 { /* minimim of foreground pixels */
2323 Minimize(min.red, (double) k_pixels[u].red);
2324 Minimize(min.green, (double) k_pixels[u].green);
2325 Minimize(min.blue, (double) k_pixels[u].blue);
2326 Minimize(min.opacity,
2327 QuantumRange-(double) k_pixels[u].opacity);
2328 if ( image->colorspace == CMYKColorspace)
2329 Minimize(min.index, (double) k_indexes[u]);
2330 }
2331 else if ( (*k) < 0.3 )
2332 { /* maximum of background pixels */
2333 Maximize(max.red, (double) k_pixels[u].red);
2334 Maximize(max.green, (double) k_pixels[u].green);
2335 Maximize(max.blue, (double) k_pixels[u].blue);
2336 Maximize(max.opacity,
2337 QuantumRange-(double) k_pixels[u].opacity);
2338 if ( image->colorspace == CMYKColorspace)
2339 Maximize(max.index, (double) k_indexes[u]);
2340 }
2341 }
2342 k_pixels += image->columns+kernel->width;
2343 k_indexes += image->columns+kernel->width;
2344 }
2345 /* Pattern Match only if min fg larger than min bg pixels */
2346 min.red -= max.red; Maximize( min.red, 0.0 );
2347 min.green -= max.green; Maximize( min.green, 0.0 );
2348 min.blue -= max.blue; Maximize( min.blue, 0.0 );
2349 min.opacity -= max.opacity; Maximize( min.opacity, 0.0 );
2350 min.index -= max.index; Maximize( min.index, 0.0 );
2351 break;
2352
anthony4fd27e22010-02-07 08:17:18 +00002353 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00002354 /* Select Pixel with Minimum Intensity within kernel neighbourhood
2355 **
2356 ** WARNING: the intensity test fails for CMYK and does not
2357 ** take into account the moderating effect of teh alpha channel
2358 ** on the intensity.
2359 **
2360 ** NOTE that the kernel is not reflected for this operation!
2361 */
anthony602ab9b2010-01-05 08:06:50 +00002362 k = kernel->values;
2363 k_pixels = p;
2364 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002365 for (v=0; v < (long) kernel->height; v++) {
2366 for (u=0; u < (long) kernel->width; u++, k++) {
anthony602ab9b2010-01-05 08:06:50 +00002367 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony4fd27e22010-02-07 08:17:18 +00002368 if ( result.red == 0.0 ||
2369 PixelIntensity(&(k_pixels[u])) < PixelIntensity(q) ) {
2370 /* copy the whole pixel - no channel selection */
2371 *q = k_pixels[u];
2372 if ( result.red > 0.0 ) changed++;
2373 result.red = 1.0;
2374 }
anthony602ab9b2010-01-05 08:06:50 +00002375 }
2376 k_pixels += image->columns+kernel->width;
2377 k_indexes += image->columns+kernel->width;
2378 }
anthony602ab9b2010-01-05 08:06:50 +00002379 break;
2380
anthony83ba99b2010-01-24 08:48:15 +00002381 case DilateIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00002382 /* Select Pixel with Maximum Intensity within kernel neighbourhood
2383 **
2384 ** WARNING: the intensity test fails for CMYK and does not
anthony9eb4f742010-05-18 02:45:54 +00002385 ** take into account the moderating effect of the alpha channel
2386 ** on the intensity (yet).
anthony930be612010-02-08 04:26:15 +00002387 **
2388 ** NOTE for correct working of this operation for asymetrical
2389 ** kernels, the kernel needs to be applied in its reflected form.
2390 ** That is its values needs to be reversed.
2391 */
anthony29188a82010-01-22 10:12:34 +00002392 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00002393 k_pixels = p;
2394 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002395 for (v=0; v < (long) kernel->height; v++) {
2396 for (u=0; u < (long) kernel->width; u++, k--) {
anthony29188a82010-01-22 10:12:34 +00002397 if ( IsNan(*k) || (*k) < 0.5 ) continue; /* boolean kernel */
2398 if ( result.red == 0.0 ||
2399 PixelIntensity(&(k_pixels[u])) > PixelIntensity(q) ) {
2400 /* copy the whole pixel - no channel selection */
2401 *q = k_pixels[u];
2402 if ( result.red > 0.0 ) changed++;
2403 result.red = 1.0;
2404 }
anthony602ab9b2010-01-05 08:06:50 +00002405 }
2406 k_pixels += image->columns+kernel->width;
2407 k_indexes += image->columns+kernel->width;
2408 }
anthony602ab9b2010-01-05 08:06:50 +00002409 break;
2410
anthony5ef8e942010-05-11 06:51:12 +00002411
anthony602ab9b2010-01-05 08:06:50 +00002412 case DistanceMorphology:
anthony930be612010-02-08 04:26:15 +00002413 /* Add kernel Value and select the minimum value found.
2414 ** The result is a iterative distance from edge of image shape.
2415 **
2416 ** All Distance Kernels are symetrical, but that may not always
2417 ** be the case. For example how about a distance from left edges?
2418 ** To work correctly with asymetrical kernels the reflected kernel
2419 ** needs to be applied.
anthony5ef8e942010-05-11 06:51:12 +00002420 **
2421 ** Actually this is really a GreyErode with a negative kernel!
2422 **
anthony930be612010-02-08 04:26:15 +00002423 */
anthony29188a82010-01-22 10:12:34 +00002424 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00002425 k_pixels = p;
2426 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002427 for (v=0; v < (long) kernel->height; v++) {
2428 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00002429 if ( IsNan(*k) ) continue;
2430 Minimize(result.red, (*k)+k_pixels[u].red);
2431 Minimize(result.green, (*k)+k_pixels[u].green);
2432 Minimize(result.blue, (*k)+k_pixels[u].blue);
2433 Minimize(result.opacity, (*k)+QuantumRange-k_pixels[u].opacity);
2434 if ( image->colorspace == CMYKColorspace)
2435 Minimize(result.index, (*k)+k_indexes[u]);
2436 }
2437 k_pixels += image->columns+kernel->width;
2438 k_indexes += image->columns+kernel->width;
2439 }
anthony602ab9b2010-01-05 08:06:50 +00002440 break;
2441
2442 case UndefinedMorphology:
2443 default:
2444 break; /* Do nothing */
anthony83ba99b2010-01-24 08:48:15 +00002445 }
anthony5ef8e942010-05-11 06:51:12 +00002446 /* Final mathematics of results (combine with original image?)
2447 **
2448 ** NOTE: Difference Morphology operators Edge* and *Hat could also
2449 ** be done here but works better with iteration as a image difference
2450 ** in the controling function (below). Thicken and Thinning however
2451 ** should be done here so thay can be iterated correctly.
2452 */
2453 switch ( method ) {
2454 case HitAndMissMorphology:
2455 case ErodeMorphology:
2456 result = min; /* minimum of neighbourhood */
2457 break;
2458 case DilateMorphology:
2459 result = max; /* maximum of neighbourhood */
2460 break;
2461 case ThinningMorphology:
2462 /* subtract pattern match from original */
2463 result.red -= min.red;
2464 result.green -= min.green;
2465 result.blue -= min.blue;
2466 result.opacity -= min.opacity;
2467 result.index -= min.index;
2468 break;
2469 case ThickenMorphology:
2470 /* Union with original image (maximize) - or should this be + */
2471 Maximize( result.red, min.red );
2472 Maximize( result.green, min.green );
2473 Maximize( result.blue, min.blue );
2474 Maximize( result.opacity, min.opacity );
2475 Maximize( result.index, min.index );
2476 break;
2477 default:
2478 /* result directly calculated or assigned */
2479 break;
2480 }
2481 /* Assign the resulting pixel values - Clamping Result */
anthony83ba99b2010-01-24 08:48:15 +00002482 switch ( method ) {
2483 case UndefinedMorphology:
2484 case DilateIntensityMorphology:
2485 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00002486 break; /* full pixel was directly assigned - not a channel method */
anthony83ba99b2010-01-24 08:48:15 +00002487 default:
anthony83ba99b2010-01-24 08:48:15 +00002488 if ((channel & RedChannel) != 0)
2489 q->red = ClampToQuantum(result.red);
2490 if ((channel & GreenChannel) != 0)
2491 q->green = ClampToQuantum(result.green);
2492 if ((channel & BlueChannel) != 0)
2493 q->blue = ClampToQuantum(result.blue);
2494 if ((channel & OpacityChannel) != 0
2495 && image->matte == MagickTrue )
2496 q->opacity = ClampToQuantum(QuantumRange-result.opacity);
2497 if ((channel & IndexChannel) != 0
2498 && image->colorspace == CMYKColorspace)
2499 q_indexes[x] = ClampToQuantum(result.index);
2500 break;
2501 }
anthony5ef8e942010-05-11 06:51:12 +00002502 /* Count up changed pixels */
anthony83ba99b2010-01-24 08:48:15 +00002503 if ( ( p[r].red != q->red )
2504 || ( p[r].green != q->green )
2505 || ( p[r].blue != q->blue )
2506 || ( p[r].opacity != q->opacity )
2507 || ( image->colorspace == CMYKColorspace &&
2508 p_indexes[r] != q_indexes[x] ) )
2509 changed++; /* The pixel had some value changed! */
anthony602ab9b2010-01-05 08:06:50 +00002510 p++;
2511 q++;
anthony83ba99b2010-01-24 08:48:15 +00002512 } /* x */
anthony602ab9b2010-01-05 08:06:50 +00002513 sync=SyncCacheViewAuthenticPixels(q_view,exception);
2514 if (sync == MagickFalse)
2515 status=MagickFalse;
2516 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2517 {
2518 MagickBooleanType
2519 proceed;
2520
2521#if defined(MAGICKCORE_OPENMP_SUPPORT)
2522 #pragma omp critical (MagickCore_MorphologyImage)
2523#endif
2524 proceed=SetImageProgress(image,MorphologyTag,progress++,image->rows);
2525 if (proceed == MagickFalse)
2526 status=MagickFalse;
2527 }
anthony83ba99b2010-01-24 08:48:15 +00002528 } /* y */
anthony602ab9b2010-01-05 08:06:50 +00002529 result_image->type=image->type;
2530 q_view=DestroyCacheView(q_view);
2531 p_view=DestroyCacheView(p_view);
cristy150989e2010-02-01 14:59:39 +00002532 return(status ? (unsigned long) changed : 0);
anthony602ab9b2010-01-05 08:06:50 +00002533}
2534
anthony4fd27e22010-02-07 08:17:18 +00002535
anthony9eb4f742010-05-18 02:45:54 +00002536MagickExport Image *MorphologyApply(const Image *image, const ChannelType
2537 channel,const MorphologyMethod method, const long iterations,
anthony47f5d062010-05-23 07:47:50 +00002538 const KernelInfo *kernel, const CompositeOperator compose,
2539 const double bias, ExceptionInfo *exception)
cristy2be15382010-01-21 02:38:03 +00002540{
2541 Image
anthony47f5d062010-05-23 07:47:50 +00002542 *curr_image, /* Image we are working with or iterating */
2543 *work_image, /* secondary image for primative iteration */
2544 *save_image, /* saved image - for 'edge' method only */
2545 *rslt_image; /* resultant image - after multi-kernel handling */
anthony602ab9b2010-01-05 08:06:50 +00002546
anthony4fd27e22010-02-07 08:17:18 +00002547 KernelInfo
anthony47f5d062010-05-23 07:47:50 +00002548 *reflected_kernel, /* A reflected copy of the kernel (if needed) */
2549 *norm_kernel, /* the current normal un-reflected kernel */
2550 *rflt_kernel, /* the current reflected kernel (if needed) */
2551 *this_kernel; /* the kernel being applied */
anthony4fd27e22010-02-07 08:17:18 +00002552
2553 MorphologyMethod
anthony47f5d062010-05-23 07:47:50 +00002554 primative; /* the current morphology primative being applied */
anthony9eb4f742010-05-18 02:45:54 +00002555
2556 CompositeOperator
anthony47f5d062010-05-23 07:47:50 +00002557 rslt_compose; /* multi-kernel compose method for results to use */
2558
2559 MagickBooleanType
2560 verbose; /* verbose output of results */
anthony4fd27e22010-02-07 08:17:18 +00002561
anthony1b2bc0a2010-05-12 05:25:22 +00002562 unsigned long
anthony47f5d062010-05-23 07:47:50 +00002563 method_loop, /* Loop 1: number of compound method iterations */
2564 method_limit, /* maximum number of compound method iterations */
2565 kernel_number, /* Loop 2: the kernel number being applied */
2566 stage_loop, /* Loop 3: primative loop for compound morphology */
2567 stage_limit, /* how many primatives in this compound */
2568 kernel_loop, /* Loop 4: iterate the kernel (basic morphology) */
2569 kernel_limit, /* number of times to iterate kernel */
2570 count, /* total count of primative steps applied */
2571 changed, /* number pixels changed by last primative operation */
2572 kernel_changed, /* total count of changed using iterated kernel */
2573 method_changed; /* total count of changed over method iteration */
2574
2575 char
2576 v_info[80];
anthony1b2bc0a2010-05-12 05:25:22 +00002577
anthony602ab9b2010-01-05 08:06:50 +00002578 assert(image != (Image *) NULL);
2579 assert(image->signature == MagickSignature);
anthony4fd27e22010-02-07 08:17:18 +00002580 assert(kernel != (KernelInfo *) NULL);
2581 assert(kernel->signature == MagickSignature);
anthony602ab9b2010-01-05 08:06:50 +00002582 assert(exception != (ExceptionInfo *) NULL);
2583 assert(exception->signature == MagickSignature);
2584
anthonyc3e48252010-05-24 12:43:11 +00002585 count = 0; /* number of low-level morphology primatives performed */
anthony602ab9b2010-01-05 08:06:50 +00002586 if ( iterations == 0 )
anthony47f5d062010-05-23 07:47:50 +00002587 return((Image *)NULL); /* null operation - nothing to do! */
anthony602ab9b2010-01-05 08:06:50 +00002588
anthony47f5d062010-05-23 07:47:50 +00002589 kernel_limit = (unsigned long) iterations;
2590 if ( iterations < 0 ) /* negative interations = infinite (well alomst) */
2591 kernel_limit = image->columns > image->rows ? image->columns : image->rows;
anthony602ab9b2010-01-05 08:06:50 +00002592
cristye96405a2010-05-19 02:24:31 +00002593 verbose = ( GetImageArtifact(image,"verbose") != (const char *) NULL ) ?
2594 MagickTrue : MagickFalse;
anthony4f1dcb72010-05-14 08:43:10 +00002595
anthony9eb4f742010-05-18 02:45:54 +00002596 /* initialise for cleanup */
anthony47f5d062010-05-23 07:47:50 +00002597 curr_image = (Image *) image;
2598 work_image = save_image = rslt_image = (Image *) NULL;
2599 reflected_kernel = (KernelInfo *) NULL;
anthony4fd27e22010-02-07 08:17:18 +00002600
anthony47f5d062010-05-23 07:47:50 +00002601 /* Initialize specific methods
2602 * + which loop should use the given iteratations
2603 * + how many primatives make up the compound morphology
2604 * + multi-kernel compose method to use (by default)
2605 */
2606 method_limit = 1; /* just do method once, unless otherwise set */
2607 stage_limit = 1; /* assume method is not a compount */
2608 rslt_compose = compose; /* and we are composing multi-kernels as given */
anthony9eb4f742010-05-18 02:45:54 +00002609 switch( method ) {
anthony47f5d062010-05-23 07:47:50 +00002610 case SmoothMorphology: /* 4 primative compound morphology */
2611 stage_limit = 4;
anthony9eb4f742010-05-18 02:45:54 +00002612 break;
anthony47f5d062010-05-23 07:47:50 +00002613 case OpenMorphology: /* 2 primative compound morphology */
anthony9eb4f742010-05-18 02:45:54 +00002614 case OpenIntensityMorphology:
anthony47f5d062010-05-23 07:47:50 +00002615 case TopHatMorphology:
2616 case CloseMorphology:
anthony9eb4f742010-05-18 02:45:54 +00002617 case CloseIntensityMorphology:
anthony47f5d062010-05-23 07:47:50 +00002618 case BottomHatMorphology:
2619 case EdgeMorphology:
2620 stage_limit = 2;
anthony9eb4f742010-05-18 02:45:54 +00002621 break;
2622 case HitAndMissMorphology:
anthonyc3e48252010-05-24 12:43:11 +00002623 kernel_limit = 1; /* no method or kernel iteration */
anthony47f5d062010-05-23 07:47:50 +00002624 rslt_compose = LightenCompositeOp; /* Union of multi-kernel results */
anthony9eb4f742010-05-18 02:45:54 +00002625 break;
anthonyc3e48252010-05-24 12:43:11 +00002626 case ThinningMorphology:
anthony9eb4f742010-05-18 02:45:54 +00002627 case ThickenMorphology:
anthonyc3e48252010-05-24 12:43:11 +00002628 case DistanceMorphology:
2629 method_limit = kernel_limit; /* iterate method with each kernel */
2630 kernel_limit = 1; /* do not do kernel iteration */
2631 rslt_compose = NoCompositeOp; /* Re-iterate with multiple kernels */
anthony47f5d062010-05-23 07:47:50 +00002632 break;
2633 default:
anthony930be612010-02-08 04:26:15 +00002634 break;
anthony602ab9b2010-01-05 08:06:50 +00002635 }
2636
anthonyc3e48252010-05-24 12:43:11 +00002637 /* Handle user (caller) specified multi-kernel composition method */
anthony47f5d062010-05-23 07:47:50 +00002638 if ( compose != UndefinedCompositeOp )
2639 rslt_compose = compose; /* override default composition for method */
2640 if ( rslt_compose == UndefinedCompositeOp )
2641 rslt_compose = NoCompositeOp; /* still not defined! Then re-iterate */
2642
anthonyc3e48252010-05-24 12:43:11 +00002643 /* Some methods require a reflected kernel to use with primatives.
2644 * Create the reflected kernel for those methods. */
anthony47f5d062010-05-23 07:47:50 +00002645 switch ( method ) {
2646 case CorrelateMorphology:
2647 case CloseMorphology:
2648 case CloseIntensityMorphology:
2649 case BottomHatMorphology:
2650 case SmoothMorphology:
2651 reflected_kernel = CloneKernelInfo(kernel);
2652 if (reflected_kernel == (KernelInfo *) NULL)
2653 goto error_cleanup;
2654 RotateKernelInfo(reflected_kernel,180);
2655 break;
2656 default:
2657 break;
anthony9eb4f742010-05-18 02:45:54 +00002658 }
anthony7a01dcf2010-05-11 12:25:52 +00002659
anthony47f5d062010-05-23 07:47:50 +00002660 /* Loop 1: iterate the compound method */
2661 method_loop = 0;
2662 method_changed = 1;
2663 while ( method_loop < method_limit && method_changed > 0 ) {
2664 method_loop++;
2665 method_changed = 0;
anthony9eb4f742010-05-18 02:45:54 +00002666
anthony47f5d062010-05-23 07:47:50 +00002667 /* Loop 2: iterate over each kernel in a multi-kernel list */
2668 norm_kernel = (KernelInfo *) kernel;
2669 rflt_kernel = reflected_kernel;
2670 kernel_number = 0;
2671 while ( norm_kernel != NULL ) {
anthony9eb4f742010-05-18 02:45:54 +00002672
anthony47f5d062010-05-23 07:47:50 +00002673 /* Loop 3: Compound Morphology Staging - Select Primative to apply */
2674 stage_loop = 0; /* the compound morphology stage number */
2675 while ( stage_loop < stage_limit ) {
2676 stage_loop++; /* The stage of the compound morphology */
anthony9eb4f742010-05-18 02:45:54 +00002677
anthony47f5d062010-05-23 07:47:50 +00002678 /* Select primative morphology for this stage of compound method */
2679 this_kernel = norm_kernel; /* default use unreflected kernel */
anthonybd0f5562010-05-24 13:05:02 +00002680 primative = method; /* Assume method is a primative */
anthony47f5d062010-05-23 07:47:50 +00002681 switch( method ) {
2682 case ErodeMorphology: /* just erode */
2683 case EdgeInMorphology: /* erode and image difference */
2684 primative = ErodeMorphology;
2685 break;
2686 case DilateMorphology: /* just dilate */
2687 case EdgeOutMorphology: /* dilate and image difference */
2688 primative = DilateMorphology;
2689 break;
2690 case OpenMorphology: /* erode then dialate */
2691 case TopHatMorphology: /* open and image difference */
2692 primative = ErodeMorphology;
2693 if ( stage_loop == 2 )
2694 primative = DilateMorphology;
2695 break;
2696 case OpenIntensityMorphology:
2697 primative = ErodeIntensityMorphology;
2698 if ( stage_loop == 2 )
2699 primative = DilateIntensityMorphology;
2700 case CloseMorphology: /* dilate, then erode */
2701 case BottomHatMorphology: /* close and image difference */
2702 this_kernel = rflt_kernel; /* use the reflected kernel */
2703 primative = DilateMorphology;
2704 if ( stage_loop == 2 )
2705 primative = ErodeMorphology;
2706 break;
2707 case CloseIntensityMorphology:
2708 this_kernel = rflt_kernel; /* use the reflected kernel */
2709 primative = DilateIntensityMorphology;
2710 if ( stage_loop == 2 )
2711 primative = ErodeIntensityMorphology;
2712 break;
2713 case SmoothMorphology: /* open, close */
2714 switch ( stage_loop ) {
2715 case 1: /* start an open method, which starts with Erode */
2716 primative = ErodeMorphology;
2717 break;
2718 case 2: /* now Dilate the Erode */
2719 primative = DilateMorphology;
2720 break;
2721 case 3: /* Reflect kernel a close */
2722 this_kernel = rflt_kernel; /* use the reflected kernel */
2723 primative = DilateMorphology;
2724 break;
2725 case 4: /* Finish the Close */
2726 this_kernel = rflt_kernel; /* use the reflected kernel */
2727 primative = ErodeMorphology;
2728 break;
2729 }
2730 break;
2731 case EdgeMorphology: /* dilate and erode difference */
2732 primative = DilateMorphology;
2733 if ( stage_loop == 2 ) {
2734 save_image = curr_image; /* save the image difference */
2735 curr_image = (Image *) image;
2736 primative = ErodeMorphology;
2737 }
2738 break;
2739 case CorrelateMorphology:
2740 /* A Correlation is a Convolution with a reflected kernel.
2741 ** However a Convolution is a weighted sum using a reflected
2742 ** kernel. It may seem stange to convert a Correlation into a
2743 ** Convolution as the Correlation is the simplier method, but
2744 ** Convolution is much more commonly used, and it makes sense to
2745 ** implement it directly so as to avoid the need to duplicate the
2746 ** kernel when it is not required (which is typically the
2747 ** default).
2748 */
2749 this_kernel = rflt_kernel; /* use the reflected kernel */
2750 primative = ConvolveMorphology;
2751 break;
2752 default:
anthony47f5d062010-05-23 07:47:50 +00002753 break;
2754 }
anthony9eb4f742010-05-18 02:45:54 +00002755
anthony47f5d062010-05-23 07:47:50 +00002756 /* Extra information for debugging compound operations */
2757 if ( verbose == MagickTrue ) {
2758 if ( stage_limit > 1 )
cristydc1c30b2010-05-23 14:23:12 +00002759 (void) FormatMagickString(v_info, MaxTextExtent, "%s:%lu.%lu -> ",
anthony47f5d062010-05-23 07:47:50 +00002760 MagickOptionToMnemonic(MagickMorphologyOptions, method),
2761 method_loop, stage_loop );
2762 else if ( primative != method )
cristydc1c30b2010-05-23 14:23:12 +00002763 (void) FormatMagickString(v_info, MaxTextExtent, "%s:%lu -> ",
anthony47f5d062010-05-23 07:47:50 +00002764 MagickOptionToMnemonic(MagickMorphologyOptions, method),
2765 method_loop );
2766 else
2767 v_info[0] = '\0';
2768 }
2769
2770 /* Loop 4: Iterate the kernel with primative */
2771 kernel_loop = 0;
2772 kernel_changed = 0;
2773 changed = 1;
2774 while ( kernel_loop < kernel_limit && changed > 0 ) {
2775 kernel_loop++; /* the iteration of this kernel */
anthony9eb4f742010-05-18 02:45:54 +00002776
2777 /* Create a destination image, if not yet defined */
2778 if ( work_image == (Image *) NULL )
2779 {
2780 work_image=CloneImage(image,0,0,MagickTrue,exception);
2781 if (work_image == (Image *) NULL)
2782 goto error_cleanup;
2783 if (SetImageStorageClass(work_image,DirectClass) == MagickFalse)
2784 {
2785 InheritException(exception,&work_image->exception);
2786 goto error_cleanup;
2787 }
2788 }
2789
anthony47f5d062010-05-23 07:47:50 +00002790 /* APPLY THE MORPHOLOGICAL PRIMITIVE (curr -> work) */
anthony9eb4f742010-05-18 02:45:54 +00002791 count++;
anthony47f5d062010-05-23 07:47:50 +00002792 changed = MorphologyPrimitive(curr_image, work_image, primative,
anthony9eb4f742010-05-18 02:45:54 +00002793 channel, this_kernel, bias, exception);
anthony47f5d062010-05-23 07:47:50 +00002794 kernel_changed += changed;
2795 method_changed += changed;
anthony9eb4f742010-05-18 02:45:54 +00002796
anthony47f5d062010-05-23 07:47:50 +00002797 if ( verbose == MagickTrue ) {
2798 if ( kernel_loop > 1 )
2799 fprintf(stderr, "\n"); /* add end-of-line from previous */
2800 fprintf(stderr, "%s%s%s:%lu.%lu #%lu => Changed %lu", v_info,
2801 MagickOptionToMnemonic(MagickMorphologyOptions, primative),
2802 ( this_kernel == rflt_kernel ) ? "*" : "",
2803 method_loop+kernel_loop-1, kernel_number, count, changed);
2804 }
anthony9eb4f742010-05-18 02:45:54 +00002805 /* prepare next loop */
2806 { Image *tmp = work_image; /* swap images for iteration */
2807 work_image = curr_image;
2808 curr_image = tmp;
2809 }
2810 if ( work_image == image )
anthony47f5d062010-05-23 07:47:50 +00002811 work_image = (Image *) NULL; /* replace input 'image' */
anthony7a01dcf2010-05-11 12:25:52 +00002812
anthony47f5d062010-05-23 07:47:50 +00002813 } /* End Loop 4: Iterate the kernel with primative */
anthony1b2bc0a2010-05-12 05:25:22 +00002814
anthony47f5d062010-05-23 07:47:50 +00002815 if ( verbose == MagickTrue && kernel_changed != changed )
2816 fprintf(stderr, " Total %lu", kernel_changed);
2817 if ( verbose == MagickTrue && stage_loop < stage_limit )
2818 fprintf(stderr, "\n"); /* add end-of-line before looping */
anthony9eb4f742010-05-18 02:45:54 +00002819
2820#if 0
anthony47f5d062010-05-23 07:47:50 +00002821 fprintf(stderr, "--E-- image=0x%lx\n", (unsigned long)image);
2822 fprintf(stderr, " curr =0x%lx\n", (unsigned long)curr_image);
2823 fprintf(stderr, " work =0x%lx\n", (unsigned long)work_image);
2824 fprintf(stderr, " save =0x%lx\n", (unsigned long)save_image);
2825 fprintf(stderr, " union=0x%lx\n", (unsigned long)rslt_image);
anthony9eb4f742010-05-18 02:45:54 +00002826#endif
2827
anthony47f5d062010-05-23 07:47:50 +00002828 } /* End Loop 3: Primative (staging) Loop for Coumpound Methods */
anthony9eb4f742010-05-18 02:45:54 +00002829
anthony47f5d062010-05-23 07:47:50 +00002830 /* Final Post-processing for some Compound Methods
2831 **
2832 ** The removal of any 'Sync' channel flag in the Image Compositon
2833 ** below ensures the methematical compose method is applied in a
2834 ** purely mathematical way, and only to the selected channels.
2835 ** Turn off SVG composition 'alpha blending'.
2836 */
2837 switch( method ) {
2838 case EdgeOutMorphology:
2839 case EdgeInMorphology:
2840 case TopHatMorphology:
2841 case BottomHatMorphology:
2842 if ( verbose == MagickTrue )
2843 fprintf(stderr, "\n%s: Difference with original image",
2844 MagickOptionToMnemonic(MagickMorphologyOptions, method) );
2845 (void) CompositeImageChannel(curr_image,
2846 (ChannelType) (channel & ~SyncChannels),
2847 DifferenceCompositeOp, image, 0, 0);
2848 break;
2849 case EdgeMorphology:
2850 if ( verbose == MagickTrue )
2851 fprintf(stderr, "\n%s: Difference of Dilate and Erode",
2852 MagickOptionToMnemonic(MagickMorphologyOptions, method) );
2853 (void) CompositeImageChannel(curr_image,
2854 (ChannelType) (channel & ~SyncChannels),
2855 DifferenceCompositeOp, save_image, 0, 0);
2856 save_image = DestroyImage(save_image); /* finished with save image */
2857 break;
2858 default:
2859 break;
2860 }
2861
2862 /* multi-kernel handling: re-iterate, or compose results */
2863 if ( kernel->next == (KernelInfo *) NULL )
anthonyc3e48252010-05-24 12:43:11 +00002864 rslt_image = curr_image; /* just return the resulting image */
anthony47f5d062010-05-23 07:47:50 +00002865 else if ( rslt_compose == NoCompositeOp )
anthonyc3e48252010-05-24 12:43:11 +00002866 { if ( verbose == MagickTrue ) {
2867 if ( this_kernel->next != (KernelInfo *) NULL )
2868 fprintf(stderr, " (re-iterate)");
2869 else
2870 fprintf(stderr, " (done)");
2871 }
2872 rslt_image = curr_image; /* return result, and re-iterate */
anthony9eb4f742010-05-18 02:45:54 +00002873 }
anthony47f5d062010-05-23 07:47:50 +00002874 else if ( rslt_image == (Image *) NULL)
2875 { if ( verbose == MagickTrue )
2876 fprintf(stderr, " (save for compose)");
2877 rslt_image = curr_image;
2878 curr_image = (Image *) image; /* continue with original image */
anthony9eb4f742010-05-18 02:45:54 +00002879 }
anthony47f5d062010-05-23 07:47:50 +00002880 else
2881 { /* add the new 'current' result to the composition
2882 **
2883 ** The removal of any 'Sync' channel flag in the Image Compositon
2884 ** below ensures the methematical compose method is applied in a
2885 ** purely mathematical way, and only to the selected channels.
2886 ** Turn off SVG composition 'alpha blending'.
2887 */
2888 if ( verbose == MagickTrue )
2889 fprintf(stderr, " (compose \"%s\")",
2890 MagickOptionToMnemonic(MagickComposeOptions, rslt_compose) );
2891 (void) CompositeImageChannel(rslt_image,
2892 (ChannelType) (channel & ~SyncChannels), rslt_compose,
2893 curr_image, 0, 0);
2894 curr_image = (Image *) image; /* continue with original image */
2895 }
2896 if ( verbose == MagickTrue )
2897 fprintf(stderr, "\n");
anthony9eb4f742010-05-18 02:45:54 +00002898
anthony47f5d062010-05-23 07:47:50 +00002899 /* loop to the next kernel in a multi-kernel list */
2900 norm_kernel = norm_kernel->next;
2901 if ( rflt_kernel != (KernelInfo *) NULL )
2902 rflt_kernel = rflt_kernel->next;
2903 kernel_number++;
2904 } /* End Loop 2: Loop over each kernel */
anthony9eb4f742010-05-18 02:45:54 +00002905
anthony47f5d062010-05-23 07:47:50 +00002906 } /* End Loop 1: compound method interation */
anthony602ab9b2010-01-05 08:06:50 +00002907
anthony9eb4f742010-05-18 02:45:54 +00002908 goto exit_cleanup;
anthony1b2bc0a2010-05-12 05:25:22 +00002909
anthony47f5d062010-05-23 07:47:50 +00002910 /* Yes goto's are bad, but it makes cleanup lot more efficient */
anthony1b2bc0a2010-05-12 05:25:22 +00002911error_cleanup:
anthony47f5d062010-05-23 07:47:50 +00002912 if ( curr_image != (Image *) NULL &&
2913 curr_image != rslt_image &&
2914 curr_image != image )
2915 curr_image = DestroyImage(curr_image);
2916 if ( rslt_image != (Image *) NULL )
2917 rslt_image = DestroyImage(rslt_image);
anthony1b2bc0a2010-05-12 05:25:22 +00002918exit_cleanup:
anthony47f5d062010-05-23 07:47:50 +00002919 if ( curr_image != (Image *) NULL &&
2920 curr_image != rslt_image &&
2921 curr_image != image )
2922 curr_image = DestroyImage(curr_image);
anthony9eb4f742010-05-18 02:45:54 +00002923 if ( work_image != (Image *) NULL )
anthony47f5d062010-05-23 07:47:50 +00002924 work_image = DestroyImage(work_image);
anthony9eb4f742010-05-18 02:45:54 +00002925 if ( save_image != (Image *) NULL )
anthony47f5d062010-05-23 07:47:50 +00002926 save_image = DestroyImage(save_image);
2927 if ( reflected_kernel != (KernelInfo *) NULL )
2928 reflected_kernel = DestroyKernelInfo(reflected_kernel);
2929 return(rslt_image);
anthony9eb4f742010-05-18 02:45:54 +00002930}
2931
2932/*
2933%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2934% %
2935% %
2936% %
2937% M o r p h o l o g y I m a g e C h a n n e l %
2938% %
2939% %
2940% %
2941%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2942%
2943% MorphologyImageChannel() applies a user supplied kernel to the image
2944% according to the given mophology method.
2945%
2946% This function applies any and all user defined settings before calling
2947% the above internal function MorphologyApply().
2948%
2949% User defined settings include...
anthony46a369d2010-05-19 02:41:48 +00002950% * Output Bias for Convolution and correlation ("-bias")
2951% * Kernel Scale/normalize settings ("-set 'option:convolve:scale'")
2952% This can also includes the addition of a scaled unity kernel.
2953% * Show Kernel being applied ("-set option:showkernel 1")
anthony9eb4f742010-05-18 02:45:54 +00002954%
2955% The format of the MorphologyImage method is:
2956%
2957% Image *MorphologyImage(const Image *image,MorphologyMethod method,
2958% const long iterations,KernelInfo *kernel,ExceptionInfo *exception)
2959%
2960% Image *MorphologyImageChannel(const Image *image, const ChannelType
2961% channel,MorphologyMethod method,const long iterations,
2962% KernelInfo *kernel,ExceptionInfo *exception)
2963%
2964% A description of each parameter follows:
2965%
2966% o image: the image.
2967%
2968% o method: the morphology method to be applied.
2969%
2970% o iterations: apply the operation this many times (or no change).
2971% A value of -1 means loop until no change found.
2972% How this is applied may depend on the morphology method.
2973% Typically this is a value of 1.
2974%
2975% o channel: the channel type.
2976%
2977% o kernel: An array of double representing the morphology kernel.
2978% Warning: kernel may be normalized for the Convolve method.
2979%
2980% o exception: return any errors or warnings in this structure.
2981%
2982*/
2983
2984MagickExport Image *MorphologyImageChannel(const Image *image,
2985 const ChannelType channel,const MorphologyMethod method,
2986 const long iterations,const KernelInfo *kernel,ExceptionInfo *exception)
2987{
2988 const char
2989 *artifact;
2990
2991 KernelInfo
2992 *curr_kernel;
2993
anthony47f5d062010-05-23 07:47:50 +00002994 CompositeOperator
2995 compose;
2996
anthony9eb4f742010-05-18 02:45:54 +00002997 Image
2998 *morphology_image;
2999
3000
anthony46a369d2010-05-19 02:41:48 +00003001 /* Apply Convolve/Correlate Normalization and Scaling Factors.
3002 * This is done BEFORE the ShowKernelInfo() function is called so that
3003 * users can see the results of the 'option:convolve:scale' option.
anthony9eb4f742010-05-18 02:45:54 +00003004 */
3005 curr_kernel = (KernelInfo *) kernel;
anthonyf71ca292010-05-19 04:08:43 +00003006 if ( method == ConvolveMorphology || method == CorrelateMorphology )
anthony9eb4f742010-05-18 02:45:54 +00003007 {
3008 artifact = GetImageArtifact(image,"convolve:scale");
3009 if ( artifact != (char *)NULL ) {
anthony9eb4f742010-05-18 02:45:54 +00003010 if ( curr_kernel == kernel )
3011 curr_kernel = CloneKernelInfo(kernel);
3012 if (curr_kernel == (KernelInfo *) NULL) {
3013 curr_kernel=DestroyKernelInfo(curr_kernel);
3014 return((Image *) NULL);
3015 }
anthony46a369d2010-05-19 02:41:48 +00003016 ScaleGeometryKernelInfo(curr_kernel, artifact);
anthony9eb4f742010-05-18 02:45:54 +00003017 }
3018 }
3019
3020 /* display the (normalized) kernel via stderr */
3021 artifact = GetImageArtifact(image,"showkernel");
anthony47f5d062010-05-23 07:47:50 +00003022 if ( artifact == (const char *) NULL)
3023 artifact = GetImageArtifact(image,"convolve:showkernel");
3024 if ( artifact == (const char *) NULL)
3025 artifact = GetImageArtifact(image,"morphology:showkernel");
anthony9eb4f742010-05-18 02:45:54 +00003026 if ( artifact != (const char *) NULL)
3027 ShowKernelInfo(curr_kernel);
3028
anthony47f5d062010-05-23 07:47:50 +00003029 /* override the default handling of multi-kernel morphology results
3030 * if 'Undefined' use the default method
3031 * if 'None' (default for 'Convolve') re-iterate previous result
3032 * otherwise merge resulting images using compose method given
3033 */
3034 compose = UndefinedCompositeOp; /* use default for method */
3035 artifact = GetImageArtifact(image,"morphology:compose");
3036 if ( artifact != (const char *) NULL)
3037 compose = (CompositeOperator) ParseMagickOption(
3038 MagickComposeOptions,MagickFalse,artifact);
3039
anthony9eb4f742010-05-18 02:45:54 +00003040 /* Apply the Morphology */
3041 morphology_image = MorphologyApply(image, channel, method, iterations,
anthony47f5d062010-05-23 07:47:50 +00003042 curr_kernel, compose, image->bias, exception);
anthony9eb4f742010-05-18 02:45:54 +00003043
3044 /* Cleanup and Exit */
3045 if ( curr_kernel != kernel )
anthony1b2bc0a2010-05-12 05:25:22 +00003046 curr_kernel=DestroyKernelInfo(curr_kernel);
anthony9eb4f742010-05-18 02:45:54 +00003047 return(morphology_image);
3048}
3049
3050MagickExport Image *MorphologyImage(const Image *image, const MorphologyMethod
3051 method, const long iterations,const KernelInfo *kernel, ExceptionInfo
3052 *exception)
3053{
3054 Image
3055 *morphology_image;
3056
3057 morphology_image=MorphologyImageChannel(image,DefaultChannels,method,
3058 iterations,kernel,exception);
3059 return(morphology_image);
anthony602ab9b2010-01-05 08:06:50 +00003060}
anthony83ba99b2010-01-24 08:48:15 +00003061
3062/*
3063%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3064% %
3065% %
3066% %
anthony4fd27e22010-02-07 08:17:18 +00003067+ R o t a t e K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00003068% %
3069% %
3070% %
3071%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3072%
anthony46a369d2010-05-19 02:41:48 +00003073% RotateKernelInfo() rotates the kernel by the angle given.
3074%
3075% Currently it is restricted to 90 degree angles, of either 1D kernels
3076% or square kernels. And 'circular' rotations of 45 degrees for 3x3 kernels.
3077% It will ignore usless rotations for specific 'named' built-in kernels.
anthony83ba99b2010-01-24 08:48:15 +00003078%
anthony4fd27e22010-02-07 08:17:18 +00003079% The format of the RotateKernelInfo method is:
anthony83ba99b2010-01-24 08:48:15 +00003080%
anthony4fd27e22010-02-07 08:17:18 +00003081% void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00003082%
3083% A description of each parameter follows:
3084%
3085% o kernel: the Morphology/Convolution kernel
3086%
3087% o angle: angle to rotate in degrees
3088%
anthony46a369d2010-05-19 02:41:48 +00003089% This function is currently internal to this module only, but can be exported
3090% to other modules if needed.
anthony83ba99b2010-01-24 08:48:15 +00003091*/
anthony4fd27e22010-02-07 08:17:18 +00003092static void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00003093{
anthony1b2bc0a2010-05-12 05:25:22 +00003094 /* angle the lower kernels first */
3095 if ( kernel->next != (KernelInfo *) NULL)
3096 RotateKernelInfo(kernel->next, angle);
3097
anthony83ba99b2010-01-24 08:48:15 +00003098 /* WARNING: Currently assumes the kernel (rightly) is horizontally symetrical
3099 **
3100 ** TODO: expand beyond simple 90 degree rotates, flips and flops
3101 */
3102
3103 /* Modulus the angle */
3104 angle = fmod(angle, 360.0);
3105 if ( angle < 0 )
3106 angle += 360.0;
3107
anthony3c10fc82010-05-13 02:40:51 +00003108 if ( 337.5 < angle || angle <= 22.5 )
anthony43c49252010-05-18 10:59:50 +00003109 return; /* Near zero angle - no change! - At least not at this time */
anthony83ba99b2010-01-24 08:48:15 +00003110
anthony3dd0f622010-05-13 12:57:32 +00003111 /* Handle special cases */
anthony83ba99b2010-01-24 08:48:15 +00003112 switch (kernel->type) {
3113 /* These built-in kernels are cylindrical kernels, rotating is useless */
3114 case GaussianKernel:
anthony83ba99b2010-01-24 08:48:15 +00003115 case DOGKernel:
3116 case DiskKernel:
anthony3dd0f622010-05-13 12:57:32 +00003117 case PeaksKernel:
3118 case LaplacianKernel:
anthony83ba99b2010-01-24 08:48:15 +00003119 case ChebyshevKernel:
3120 case ManhattenKernel:
3121 case EuclideanKernel:
3122 return;
3123
3124 /* These may be rotatable at non-90 angles in the future */
3125 /* but simply rotating them in multiples of 90 degrees is useless */
3126 case SquareKernel:
3127 case DiamondKernel:
3128 case PlusKernel:
anthony3dd0f622010-05-13 12:57:32 +00003129 case CrossKernel:
anthony83ba99b2010-01-24 08:48:15 +00003130 return;
3131
3132 /* These only allows a +/-90 degree rotation (by transpose) */
3133 /* A 180 degree rotation is useless */
3134 case BlurKernel:
3135 case RectangleKernel:
3136 if ( 135.0 < angle && angle <= 225.0 )
3137 return;
3138 if ( 225.0 < angle && angle <= 315.0 )
3139 angle -= 180;
3140 break;
3141
anthony3dd0f622010-05-13 12:57:32 +00003142 default:
anthony83ba99b2010-01-24 08:48:15 +00003143 break;
3144 }
anthony3c10fc82010-05-13 02:40:51 +00003145 /* Attempt rotations by 45 degrees */
3146 if ( 22.5 < fmod(angle,90.0) && fmod(angle,90.0) <= 67.5 )
3147 {
3148 if ( kernel->width == 3 && kernel->height == 3 )
3149 { /* Rotate a 3x3 square by 45 degree angle */
3150 MagickRealType t = kernel->values[0];
anthony43c49252010-05-18 10:59:50 +00003151 kernel->values[0] = kernel->values[3];
3152 kernel->values[3] = kernel->values[6];
3153 kernel->values[6] = kernel->values[7];
3154 kernel->values[7] = kernel->values[8];
3155 kernel->values[8] = kernel->values[5];
3156 kernel->values[5] = kernel->values[2];
3157 kernel->values[2] = kernel->values[1];
3158 kernel->values[1] = t;
3159 /* NOT DONE - rotate a off-centered origin as well! */
3160 angle = fmod(angle+315.0, 360.0); /* angle reduced 45 degrees */
3161 kernel->angle = fmod(kernel->angle+45.0, 360.0);
anthony3c10fc82010-05-13 02:40:51 +00003162 }
3163 else
3164 perror("Unable to rotate non-3x3 kernel by 45 degrees");
3165 }
3166 if ( 45.0 < fmod(angle, 180.0) && fmod(angle,180.0) <= 135.0 )
3167 {
3168 if ( kernel->width == 1 || kernel->height == 1 )
3169 { /* Do a transpose of the image, which results in a 90
3170 ** degree rotation of a 1 dimentional kernel
3171 */
3172 long
3173 t;
3174 t = (long) kernel->width;
3175 kernel->width = kernel->height;
3176 kernel->height = (unsigned long) t;
3177 t = kernel->x;
3178 kernel->x = kernel->y;
3179 kernel->y = t;
anthony43c49252010-05-18 10:59:50 +00003180 if ( kernel->width == 1 ) {
3181 angle = fmod(angle+270.0, 360.0); /* angle reduced 90 degrees */
3182 kernel->angle = fmod(kernel->angle+90.0, 360.0);
3183 } else {
3184 angle = fmod(angle+90.0, 360.0); /* angle increased 90 degrees */
3185 kernel->angle = fmod(kernel->angle+270.0, 360.0);
3186 }
anthony3c10fc82010-05-13 02:40:51 +00003187 }
3188 else if ( kernel->width == kernel->height )
3189 { /* Rotate a square array of values by 90 degrees */
3190 register unsigned long
3191 i,j,x,y;
3192 register MagickRealType
3193 *k,t;
3194 k=kernel->values;
3195 for( i=0, x=kernel->width-1; i<=x; i++, x--)
3196 for( j=0, y=kernel->height-1; j<y; j++, y--)
3197 { t = k[i+j*kernel->width];
anthony43c49252010-05-18 10:59:50 +00003198 k[i+j*kernel->width] = k[j+x*kernel->width];
3199 k[j+x*kernel->width] = k[x+y*kernel->width];
3200 k[x+y*kernel->width] = k[y+i*kernel->width];
3201 k[y+i*kernel->width] = t;
anthony3c10fc82010-05-13 02:40:51 +00003202 }
anthony43c49252010-05-18 10:59:50 +00003203 /* NOT DONE - rotate a off-centered origin as well! */
3204 angle = fmod(angle+270.0, 360.0); /* angle reduced 90 degrees */
3205 kernel->angle = fmod(kernel->angle+90.0, 360.0);
anthony3c10fc82010-05-13 02:40:51 +00003206 }
3207 else
3208 perror("Unable to rotate a non-square, non-linear kernel 90 degrees");
3209 }
anthony83ba99b2010-01-24 08:48:15 +00003210 if ( 135.0 < angle && angle <= 225.0 )
3211 {
anthony43c49252010-05-18 10:59:50 +00003212 /* For a 180 degree rotation - also know as a reflection
3213 * This is actually a very very common operation!
3214 * Basically all that is needed is a reversal of the kernel data!
3215 * And a reflection of the origon
3216 */
anthony83ba99b2010-01-24 08:48:15 +00003217 unsigned long
3218 i,j;
3219 register double
3220 *k,t;
3221
3222 k=kernel->values;
3223 for ( i=0, j=kernel->width*kernel->height-1; i<j; i++, j--)
3224 t=k[i], k[i]=k[j], k[j]=t;
3225
anthony930be612010-02-08 04:26:15 +00003226 kernel->x = (long) kernel->width - kernel->x - 1;
3227 kernel->y = (long) kernel->height - kernel->y - 1;
anthony43c49252010-05-18 10:59:50 +00003228 angle = fmod(angle-180.0, 360.0); /* angle+180 degrees */
3229 kernel->angle = fmod(kernel->angle+180.0, 360.0);
anthony83ba99b2010-01-24 08:48:15 +00003230 }
anthony3c10fc82010-05-13 02:40:51 +00003231 /* At this point angle should at least between -45 (315) and +45 degrees
anthony83ba99b2010-01-24 08:48:15 +00003232 * In the future some form of non-orthogonal angled rotates could be
3233 * performed here, posibily with a linear kernel restriction.
3234 */
3235
3236#if 0
anthony3c10fc82010-05-13 02:40:51 +00003237 { /* Do a Flop by reversing each row.
anthony83ba99b2010-01-24 08:48:15 +00003238 */
3239 unsigned long
3240 y;
cristy150989e2010-02-01 14:59:39 +00003241 register long
anthony83ba99b2010-01-24 08:48:15 +00003242 x,r;
3243 register double
3244 *k,t;
3245
3246 for ( y=0, k=kernel->values; y < kernel->height; y++, k+=kernel->width)
3247 for ( x=0, r=kernel->width-1; x<kernel->width/2; x++, r--)
3248 t=k[x], k[x]=k[r], k[r]=t;
3249
cristyc99304f2010-02-01 15:26:27 +00003250 kernel->x = kernel->width - kernel->x - 1;
anthony83ba99b2010-01-24 08:48:15 +00003251 angle = fmod(angle+180.0, 360.0);
3252 }
3253#endif
3254 return;
3255}
3256
3257/*
3258%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3259% %
3260% %
3261% %
anthony46a369d2010-05-19 02:41:48 +00003262% S c a l e G e o m e t r y K e r n e l I n f o %
3263% %
3264% %
3265% %
3266%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3267%
3268% ScaleGeometryKernelInfo() takes a geometry argument string, typically
3269% provided as a "-set option:convolve:scale {geometry}" user setting,
3270% and modifies the kernel according to the parsed arguments of that setting.
3271%
3272% The first argument (and any normalization flags) are passed to
3273% ScaleKernelInfo() to scale/normalize the kernel. The second argument
3274% is then passed to UnityAddKernelInfo() to add a scled unity kernel
3275% into the scaled/normalized kernel.
3276%
3277% The format of the ScaleKernelInfo method is:
3278%
3279% void ScaleKernelInfo(KernelInfo *kernel, const double scaling_factor,
3280% const MagickStatusType normalize_flags )
3281%
3282% A description of each parameter follows:
3283%
3284% o kernel: the Morphology/Convolution kernel to modify
3285%
3286% o geometry:
3287% The geometry string to parse, typically from the user provided
3288% "-set option:convolve:scale {geometry}" setting.
3289%
3290*/
3291MagickExport void ScaleGeometryKernelInfo (KernelInfo *kernel,
3292 const char *geometry)
3293{
3294 GeometryFlags
3295 flags;
3296 GeometryInfo
3297 args;
3298
3299 SetGeometryInfo(&args);
3300 flags = (GeometryFlags) ParseGeometry(geometry, &args);
3301
3302#if 0
3303 /* For Debugging Geometry Input */
3304 fprintf(stderr, "Geometry = 0x%04X : %lg x %lg %+lg %+lg\n",
3305 flags, args.rho, args.sigma, args.xi, args.psi );
3306#endif
3307
3308 if ( (flags & PercentValue) != 0 ) /* Handle Percentage flag*/
3309 args.rho *= 0.01, args.sigma *= 0.01;
3310
3311 if ( (flags & RhoValue) == 0 ) /* Set Defaults for missing args */
3312 args.rho = 1.0;
3313 if ( (flags & SigmaValue) == 0 )
3314 args.sigma = 0.0;
3315
3316 /* Scale/Normalize the input kernel */
3317 ScaleKernelInfo(kernel, args.rho, flags);
3318
3319 /* Add Unity Kernel, for blending with original */
3320 if ( (flags & SigmaValue) != 0 )
3321 UnityAddKernelInfo(kernel, args.sigma);
3322
3323 return;
3324}
3325/*
3326%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3327% %
3328% %
3329% %
cristy6771f1e2010-03-05 19:43:39 +00003330% S c a l e K e r n e l I n f o %
anthonycc6c8362010-01-25 04:14:01 +00003331% %
3332% %
3333% %
3334%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3335%
anthony1b2bc0a2010-05-12 05:25:22 +00003336% ScaleKernelInfo() scales the given kernel list by the given amount, with or
3337% without normalization of the sum of the kernel values (as per given flags).
anthonycc6c8362010-01-25 04:14:01 +00003338%
anthony999bb2c2010-02-18 12:38:01 +00003339% By default (no flags given) the values within the kernel is scaled
anthony1b2bc0a2010-05-12 05:25:22 +00003340% directly using given scaling factor without change.
anthonycc6c8362010-01-25 04:14:01 +00003341%
anthony46a369d2010-05-19 02:41:48 +00003342% If either of the two 'normalize_flags' are given the kernel will first be
3343% normalized and then further scaled by the scaling factor value given.
anthony999bb2c2010-02-18 12:38:01 +00003344%
3345% Kernel normalization ('normalize_flags' given) is designed to ensure that
3346% any use of the kernel scaling factor with 'Convolve' or 'Correlate'
anthony1b2bc0a2010-05-12 05:25:22 +00003347% morphology methods will fall into -1.0 to +1.0 range. Note that for
3348% non-HDRI versions of IM this may cause images to have any negative results
3349% clipped, unless some 'bias' is used.
anthony999bb2c2010-02-18 12:38:01 +00003350%
3351% More specifically. Kernels which only contain positive values (such as a
3352% 'Gaussian' kernel) will be scaled so that those values sum to +1.0,
anthony1b2bc0a2010-05-12 05:25:22 +00003353% ensuring a 0.0 to +1.0 output range for non-HDRI images.
anthony999bb2c2010-02-18 12:38:01 +00003354%
3355% For Kernels that contain some negative values, (such as 'Sharpen' kernels)
3356% the kernel will be scaled by the absolute of the sum of kernel values, so
3357% that it will generally fall within the +/- 1.0 range.
3358%
3359% For kernels whose values sum to zero, (such as 'Laplician' kernels) kernel
3360% will be scaled by just the sum of the postive values, so that its output
3361% range will again fall into the +/- 1.0 range.
3362%
3363% For special kernels designed for locating shapes using 'Correlate', (often
3364% only containing +1 and -1 values, representing foreground/brackground
3365% matching) a special normalization method is provided to scale the positive
3366% values seperatally to those of the negative values, so the kernel will be
3367% forced to become a zero-sum kernel better suited to such searches.
3368%
anthony1b2bc0a2010-05-12 05:25:22 +00003369% WARNING: Correct normalization of the kernel assumes that the '*_range'
anthony999bb2c2010-02-18 12:38:01 +00003370% attributes within the kernel structure have been correctly set during the
3371% kernels creation.
3372%
3373% NOTE: The values used for 'normalize_flags' have been selected specifically
anthony46a369d2010-05-19 02:41:48 +00003374% to match the use of geometry options, so that '!' means NormalizeValue, '^'
3375% means CorrelateNormalizeValue. All other GeometryFlags values are ignored.
anthonycc6c8362010-01-25 04:14:01 +00003376%
anthony4fd27e22010-02-07 08:17:18 +00003377% The format of the ScaleKernelInfo method is:
anthonycc6c8362010-01-25 04:14:01 +00003378%
anthony999bb2c2010-02-18 12:38:01 +00003379% void ScaleKernelInfo(KernelInfo *kernel, const double scaling_factor,
3380% const MagickStatusType normalize_flags )
anthonycc6c8362010-01-25 04:14:01 +00003381%
3382% A description of each parameter follows:
3383%
3384% o kernel: the Morphology/Convolution kernel
3385%
anthony999bb2c2010-02-18 12:38:01 +00003386% o scaling_factor:
3387% multiply all values (after normalization) by this factor if not
3388% zero. If the kernel is normalized regardless of any flags.
3389%
3390% o normalize_flags:
3391% GeometryFlags defining normalization method to use.
3392% specifically: NormalizeValue, CorrelateNormalizeValue,
3393% and/or PercentValue
anthonycc6c8362010-01-25 04:14:01 +00003394%
3395*/
cristy6771f1e2010-03-05 19:43:39 +00003396MagickExport void ScaleKernelInfo(KernelInfo *kernel,
3397 const double scaling_factor,const GeometryFlags normalize_flags)
anthonycc6c8362010-01-25 04:14:01 +00003398{
cristy150989e2010-02-01 14:59:39 +00003399 register long
anthonycc6c8362010-01-25 04:14:01 +00003400 i;
3401
anthony999bb2c2010-02-18 12:38:01 +00003402 register double
3403 pos_scale,
3404 neg_scale;
3405
anthony46a369d2010-05-19 02:41:48 +00003406 /* do the other kernels in a multi-kernel list first */
anthony1b2bc0a2010-05-12 05:25:22 +00003407 if ( kernel->next != (KernelInfo *) NULL)
3408 ScaleKernelInfo(kernel->next, scaling_factor, normalize_flags);
3409
anthony46a369d2010-05-19 02:41:48 +00003410 /* Normalization of Kernel */
anthony999bb2c2010-02-18 12:38:01 +00003411 pos_scale = 1.0;
3412 if ( (normalize_flags&NormalizeValue) != 0 ) {
anthony999bb2c2010-02-18 12:38:01 +00003413 if ( fabs(kernel->positive_range + kernel->negative_range) > MagickEpsilon )
anthonyf4e00312010-05-20 12:06:35 +00003414 /* non-zero-summing kernel (generally positive) */
anthony999bb2c2010-02-18 12:38:01 +00003415 pos_scale = fabs(kernel->positive_range + kernel->negative_range);
anthonycc6c8362010-01-25 04:14:01 +00003416 else
anthonyf4e00312010-05-20 12:06:35 +00003417 /* zero-summing kernel */
3418 pos_scale = kernel->positive_range;
anthony999bb2c2010-02-18 12:38:01 +00003419 }
anthony46a369d2010-05-19 02:41:48 +00003420 /* Force kernel into a normalized zero-summing kernel */
anthony999bb2c2010-02-18 12:38:01 +00003421 if ( (normalize_flags&CorrelateNormalizeValue) != 0 ) {
3422 pos_scale = ( fabs(kernel->positive_range) > MagickEpsilon )
3423 ? kernel->positive_range : 1.0;
3424 neg_scale = ( fabs(kernel->negative_range) > MagickEpsilon )
3425 ? -kernel->negative_range : 1.0;
3426 }
3427 else
3428 neg_scale = pos_scale;
3429
3430 /* finialize scaling_factor for positive and negative components */
3431 pos_scale = scaling_factor/pos_scale;
3432 neg_scale = scaling_factor/neg_scale;
anthonycc6c8362010-01-25 04:14:01 +00003433
cristy150989e2010-02-01 14:59:39 +00003434 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00003435 if ( ! IsNan(kernel->values[i]) )
anthony999bb2c2010-02-18 12:38:01 +00003436 kernel->values[i] *= (kernel->values[i] >= 0) ? pos_scale : neg_scale;
anthonycc6c8362010-01-25 04:14:01 +00003437
anthony999bb2c2010-02-18 12:38:01 +00003438 /* convolution output range */
3439 kernel->positive_range *= pos_scale;
3440 kernel->negative_range *= neg_scale;
3441 /* maximum and minimum values in kernel */
3442 kernel->maximum *= (kernel->maximum >= 0.0) ? pos_scale : neg_scale;
3443 kernel->minimum *= (kernel->minimum >= 0.0) ? pos_scale : neg_scale;
3444
anthony46a369d2010-05-19 02:41:48 +00003445 /* swap kernel settings if user's scaling factor is negative */
anthony999bb2c2010-02-18 12:38:01 +00003446 if ( scaling_factor < MagickEpsilon ) {
3447 double t;
3448 t = kernel->positive_range;
3449 kernel->positive_range = kernel->negative_range;
3450 kernel->negative_range = t;
3451 t = kernel->maximum;
3452 kernel->maximum = kernel->minimum;
3453 kernel->minimum = 1;
3454 }
anthonycc6c8362010-01-25 04:14:01 +00003455
3456 return;
3457}
3458
3459/*
3460%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3461% %
3462% %
3463% %
anthony46a369d2010-05-19 02:41:48 +00003464% S h o w K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00003465% %
3466% %
3467% %
3468%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3469%
anthony4fd27e22010-02-07 08:17:18 +00003470% ShowKernelInfo() outputs the details of the given kernel defination to
3471% standard error, generally due to a users 'showkernel' option request.
anthony83ba99b2010-01-24 08:48:15 +00003472%
3473% The format of the ShowKernel method is:
3474%
anthony4fd27e22010-02-07 08:17:18 +00003475% void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00003476%
3477% A description of each parameter follows:
3478%
3479% o kernel: the Morphology/Convolution kernel
3480%
anthony83ba99b2010-01-24 08:48:15 +00003481*/
anthony4fd27e22010-02-07 08:17:18 +00003482MagickExport void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00003483{
anthony7a01dcf2010-05-11 12:25:52 +00003484 KernelInfo
3485 *k;
anthony83ba99b2010-01-24 08:48:15 +00003486
anthony43c49252010-05-18 10:59:50 +00003487 unsigned long
anthony7a01dcf2010-05-11 12:25:52 +00003488 c, i, u, v;
3489
3490 for (c=0, k=kernel; k != (KernelInfo *) NULL; c++, k=k->next ) {
3491
anthony46a369d2010-05-19 02:41:48 +00003492 fprintf(stderr, "Kernel");
anthony7a01dcf2010-05-11 12:25:52 +00003493 if ( kernel->next != (KernelInfo *) NULL )
cristye96405a2010-05-19 02:24:31 +00003494 fprintf(stderr, " #%lu", c );
anthony43c49252010-05-18 10:59:50 +00003495 fprintf(stderr, " \"%s",
3496 MagickOptionToMnemonic(MagickKernelOptions, k->type) );
3497 if ( fabs(k->angle) > MagickEpsilon )
3498 fprintf(stderr, "@%lg", k->angle);
3499 fprintf(stderr, "\" of size %lux%lu%+ld%+ld ",
3500 k->width, k->height,
3501 k->x, k->y );
anthony7a01dcf2010-05-11 12:25:52 +00003502 fprintf(stderr,
3503 " with values from %.*lg to %.*lg\n",
3504 GetMagickPrecision(), k->minimum,
3505 GetMagickPrecision(), k->maximum);
anthony46a369d2010-05-19 02:41:48 +00003506 fprintf(stderr, "Forming a output range from %.*lg to %.*lg",
anthony7a01dcf2010-05-11 12:25:52 +00003507 GetMagickPrecision(), k->negative_range,
anthony46a369d2010-05-19 02:41:48 +00003508 GetMagickPrecision(), k->positive_range);
3509 if ( fabs(k->positive_range+k->negative_range) < MagickEpsilon )
3510 fprintf(stderr, " (Zero-Summing)\n");
3511 else if ( fabs(k->positive_range+k->negative_range-1.0) < MagickEpsilon )
3512 fprintf(stderr, " (Normalized)\n");
3513 else
3514 fprintf(stderr, " (Sum %.*lg)\n",
3515 GetMagickPrecision(), k->positive_range+k->negative_range);
anthony43c49252010-05-18 10:59:50 +00003516 for (i=v=0; v < k->height; v++) {
anthony46a369d2010-05-19 02:41:48 +00003517 fprintf(stderr, "%2lu:", v );
anthony43c49252010-05-18 10:59:50 +00003518 for (u=0; u < k->width; u++, i++)
anthony7a01dcf2010-05-11 12:25:52 +00003519 if ( IsNan(k->values[i]) )
anthonyf4e00312010-05-20 12:06:35 +00003520 fprintf(stderr," %*s", GetMagickPrecision()+3, "nan");
anthony7a01dcf2010-05-11 12:25:52 +00003521 else
anthonyf4e00312010-05-20 12:06:35 +00003522 fprintf(stderr," %*.*lg", GetMagickPrecision()+3,
anthony7a01dcf2010-05-11 12:25:52 +00003523 GetMagickPrecision(), k->values[i]);
3524 fprintf(stderr,"\n");
3525 }
anthony83ba99b2010-01-24 08:48:15 +00003526 }
3527}
anthonycc6c8362010-01-25 04:14:01 +00003528
3529/*
3530%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3531% %
3532% %
3533% %
anthony43c49252010-05-18 10:59:50 +00003534% U n i t y A d d K e r n a l I n f o %
3535% %
3536% %
3537% %
3538%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3539%
3540% UnityAddKernelInfo() Adds a given amount of the 'Unity' Convolution Kernel
3541% to the given pre-scaled and normalized Kernel. This in effect adds that
3542% amount of the original image into the resulting convolution kernel. This
3543% value is usually provided by the user as a percentage value in the
3544% 'convolve:scale' setting.
3545%
3546% The resulting effect is to either convert a 'zero-summing' edge detection
3547% kernel (such as a "Laplacian", "DOG" or a "LOG") into a 'sharpening'
3548% kernel.
3549%
3550% Alternativally by using a purely positive kernel, and using a negative
3551% post-normalizing scaling factor, you can convert a 'blurring' kernel (such
3552% as a "Gaussian") into a 'unsharp' kernel.
3553%
anthony46a369d2010-05-19 02:41:48 +00003554% The format of the UnityAdditionKernelInfo method is:
anthony43c49252010-05-18 10:59:50 +00003555%
3556% void UnityAdditionKernelInfo(KernelInfo *kernel, const double scale )
3557%
3558% A description of each parameter follows:
3559%
3560% o kernel: the Morphology/Convolution kernel
3561%
3562% o scale:
3563% scaling factor for the unity kernel to be added to
3564% the given kernel.
3565%
anthony43c49252010-05-18 10:59:50 +00003566*/
3567MagickExport void UnityAddKernelInfo(KernelInfo *kernel,
3568 const double scale)
3569{
anthony46a369d2010-05-19 02:41:48 +00003570 /* do the other kernels in a multi-kernel list first */
3571 if ( kernel->next != (KernelInfo *) NULL)
3572 UnityAddKernelInfo(kernel->next, scale);
anthony43c49252010-05-18 10:59:50 +00003573
anthony46a369d2010-05-19 02:41:48 +00003574 /* Add the scaled unity kernel to the existing kernel */
anthony43c49252010-05-18 10:59:50 +00003575 kernel->values[kernel->x+kernel->y*kernel->width] += scale;
anthony46a369d2010-05-19 02:41:48 +00003576 CalcKernelMetaData(kernel); /* recalculate the meta-data */
anthony43c49252010-05-18 10:59:50 +00003577
3578 return;
3579}
3580
3581/*
3582%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3583% %
3584% %
3585% %
3586% Z e r o K e r n e l N a n s %
anthonycc6c8362010-01-25 04:14:01 +00003587% %
3588% %
3589% %
3590%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3591%
3592% ZeroKernelNans() replaces any special 'nan' value that may be present in
3593% the kernel with a zero value. This is typically done when the kernel will
3594% be used in special hardware (GPU) convolution processors, to simply
3595% matters.
3596%
3597% The format of the ZeroKernelNans method is:
3598%
anthony46a369d2010-05-19 02:41:48 +00003599% void ZeroKernelNans (KernelInfo *kernel)
anthonycc6c8362010-01-25 04:14:01 +00003600%
3601% A description of each parameter follows:
3602%
3603% o kernel: the Morphology/Convolution kernel
3604%
anthonycc6c8362010-01-25 04:14:01 +00003605*/
anthonyc4c86e02010-01-27 09:30:32 +00003606MagickExport void ZeroKernelNans(KernelInfo *kernel)
anthonycc6c8362010-01-25 04:14:01 +00003607{
anthony43c49252010-05-18 10:59:50 +00003608 register unsigned long
anthonycc6c8362010-01-25 04:14:01 +00003609 i;
3610
anthony46a369d2010-05-19 02:41:48 +00003611 /* do the other kernels in a multi-kernel list first */
anthony1b2bc0a2010-05-12 05:25:22 +00003612 if ( kernel->next != (KernelInfo *) NULL)
3613 ZeroKernelNans(kernel->next);
3614
anthony43c49252010-05-18 10:59:50 +00003615 for (i=0; i < (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00003616 if ( IsNan(kernel->values[i]) )
3617 kernel->values[i] = 0.0;
3618
3619 return;
3620}