blob: af05c7fae685e969f095dbf83fdadc155a365f4d [file] [log] [blame]
cristy701db312009-11-20 03:14:08 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% M M OOO RRRR PPPP H H OOO L OOO GGGG Y Y %
7% MM MM O O R R P P H H O O L O O G Y Y %
8% M M M O O RRRR PPPP HHHHH O O L O O G GGG Y %
9% M M O O R R P H H O O L O O G G Y %
10% M M OOO R R P H H OOO LLLLL OOO GGG Y %
11% %
12% %
13% MagickCore Morphology Methods %
14% %
15% Software Design %
16% Anthony Thyssen %
anthonyc94cdb02010-01-06 08:15:29 +000017% January 2010 %
cristy701db312009-11-20 03:14:08 +000018% %
19% %
cristy16af1cb2009-12-11 21:38:29 +000020% Copyright 1999-2010 ImageMagick Studio LLC, a non-profit organization %
cristy701db312009-11-20 03:14:08 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
anthony1b2bc0a2010-05-12 05:25:22 +000036% Morpology is the the application of various kernels, of any size and even
anthony602ab9b2010-01-05 08:06:50 +000037% shape, to a image in various ways (typically binary, but not always).
cristy701db312009-11-20 03:14:08 +000038%
anthony602ab9b2010-01-05 08:06:50 +000039% Convolution (weighted sum or average) is just one specific type of
40% morphology. Just one that is very common for image bluring and sharpening
41% effects. Not only 2D Gaussian blurring, but also 2-pass 1D Blurring.
42%
43% This module provides not only a general morphology function, and the ability
44% to apply more advanced or iterative morphologies, but also functions for the
45% generation of many different types of kernel arrays from user supplied
46% arguments. Prehaps even the generation of a kernel from a small image.
cristy701db312009-11-20 03:14:08 +000047*/
48
49/*
50 Include declarations.
51*/
52#include "magick/studio.h"
anthony602ab9b2010-01-05 08:06:50 +000053#include "magick/artifact.h"
cristy701db312009-11-20 03:14:08 +000054#include "magick/cache-view.h"
55#include "magick/color-private.h"
56#include "magick/enhance.h"
57#include "magick/exception.h"
58#include "magick/exception-private.h"
anthony602ab9b2010-01-05 08:06:50 +000059#include "magick/gem.h"
cristy701db312009-11-20 03:14:08 +000060#include "magick/hashmap.h"
61#include "magick/image.h"
cristybba804b2010-01-05 15:39:59 +000062#include "magick/image-private.h"
cristy701db312009-11-20 03:14:08 +000063#include "magick/list.h"
anthony29188a82010-01-22 10:12:34 +000064#include "magick/magick.h"
cristy701db312009-11-20 03:14:08 +000065#include "magick/memory_.h"
66#include "magick/monitor-private.h"
67#include "magick/morphology.h"
anthony46a369d2010-05-19 02:41:48 +000068#include "magick/morphology-private.h"
anthony602ab9b2010-01-05 08:06:50 +000069#include "magick/option.h"
cristy701db312009-11-20 03:14:08 +000070#include "magick/pixel-private.h"
71#include "magick/prepress.h"
72#include "magick/quantize.h"
73#include "magick/registry.h"
74#include "magick/semaphore.h"
75#include "magick/splay-tree.h"
76#include "magick/statistic.h"
77#include "magick/string_.h"
anthony602ab9b2010-01-05 08:06:50 +000078#include "magick/string-private.h"
79#include "magick/token.h"
cristya29d45f2010-03-05 21:14:54 +000080
anthony602ab9b2010-01-05 08:06:50 +000081/*
cristya29d45f2010-03-05 21:14:54 +000082 The following test is for special floating point numbers of value NaN (not
83 a number), that may be used within a Kernel Definition. NaN's are defined
84 as part of the IEEE standard for floating point number representation.
85
anthony1b2bc0a2010-05-12 05:25:22 +000086 These are used a Kernel value of NaN means that that kernel position is not
cristya29d45f2010-03-05 21:14:54 +000087 part of the normal convolution or morphology process, and thus allowing the
88 use of 'shaped' kernels.
89
90 Special properities two NaN's are never equal, even if they are from the
91 same variable That is the IsNaN() macro is only true if the value is NaN.
92*/
anthony602ab9b2010-01-05 08:06:50 +000093#define IsNan(a) ((a)!=(a))
94
anthony29188a82010-01-22 10:12:34 +000095/*
cristya29d45f2010-03-05 21:14:54 +000096 Other global definitions used by module.
97*/
anthony29188a82010-01-22 10:12:34 +000098static inline double MagickMin(const double x,const double y)
99{
100 return( x < y ? x : y);
101}
102static inline double MagickMax(const double x,const double y)
103{
104 return( x > y ? x : y);
105}
106#define Minimize(assign,value) assign=MagickMin(assign,value)
107#define Maximize(assign,value) assign=MagickMax(assign,value)
108
anthonyc4c86e02010-01-27 09:30:32 +0000109/* Currently these are only internal to this module */
110static void
anthony46a369d2010-05-19 02:41:48 +0000111 CalcKernelMetaData(KernelInfo *),
anthony3c10fc82010-05-13 02:40:51 +0000112 ExpandKernelInfo(KernelInfo *, double),
cristyef656912010-03-05 19:54:59 +0000113 RotateKernelInfo(KernelInfo *, double);
anthony602ab9b2010-01-05 08:06:50 +0000114
anthony3dd0f622010-05-13 12:57:32 +0000115
116/* Quick function to find last kernel in a kernel list */
117static inline KernelInfo *LastKernelInfo(KernelInfo *kernel)
118{
119 while (kernel->next != (KernelInfo *) NULL)
120 kernel = kernel->next;
121 return(kernel);
122}
123
124
anthony602ab9b2010-01-05 08:06:50 +0000125/*
126%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
127% %
128% %
129% %
anthony83ba99b2010-01-24 08:48:15 +0000130% A c q u i r e K e r n e l I n f o %
anthony602ab9b2010-01-05 08:06:50 +0000131% %
132% %
133% %
134%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
135%
cristy2be15382010-01-21 02:38:03 +0000136% AcquireKernelInfo() takes the given string (generally supplied by the
anthony602ab9b2010-01-05 08:06:50 +0000137% user) and converts it into a Morphology/Convolution Kernel. This allows
138% users to specify a kernel from a number of pre-defined kernels, or to fully
139% specify their own kernel for a specific Convolution or Morphology
140% Operation.
141%
142% The kernel so generated can be any rectangular array of floating point
143% values (doubles) with the 'control point' or 'pixel being affected'
144% anywhere within that array of values.
145%
anthony83ba99b2010-01-24 08:48:15 +0000146% Previously IM was restricted to a square of odd size using the exact
147% center as origin, this is no longer the case, and any rectangular kernel
148% with any value being declared the origin. This in turn allows the use of
149% highly asymmetrical kernels.
anthony602ab9b2010-01-05 08:06:50 +0000150%
151% The floating point values in the kernel can also include a special value
anthony83ba99b2010-01-24 08:48:15 +0000152% known as 'nan' or 'not a number' to indicate that this value is not part
153% of the kernel array. This allows you to shaped the kernel within its
154% rectangular area. That is 'nan' values provide a 'mask' for the kernel
155% shape. However at least one non-nan value must be provided for correct
156% working of a kernel.
anthony602ab9b2010-01-05 08:06:50 +0000157%
anthony7a01dcf2010-05-11 12:25:52 +0000158% The returned kernel should be freed using the DestroyKernelInfo() when you
159% are finished with it. Do not free this memory yourself.
anthony602ab9b2010-01-05 08:06:50 +0000160%
161% Input kernel defintion strings can consist of any of three types.
162%
anthony29188a82010-01-22 10:12:34 +0000163% "name:args"
164% Select from one of the built in kernels, using the name and
165% geometry arguments supplied. See AcquireKernelBuiltIn()
anthony602ab9b2010-01-05 08:06:50 +0000166%
anthony43c49252010-05-18 10:59:50 +0000167% "WxH[+X+Y][^@]:num, num, num ..."
anthony1b2bc0a2010-05-12 05:25:22 +0000168% a kernel of size W by H, with W*H floating point numbers following.
anthony602ab9b2010-01-05 08:06:50 +0000169% the 'center' can be optionally be defined at +X+Y (such that +0+0
anthony29188a82010-01-22 10:12:34 +0000170% is top left corner). If not defined the pixel in the center, for
171% odd sizes, or to the immediate top or left of center for even sizes
172% is automatically selected.
anthony602ab9b2010-01-05 08:06:50 +0000173%
anthony43c49252010-05-18 10:59:50 +0000174% If a '^' is included the kernel expanded with 90-degree rotations,
175% While a '@' will allow you to expand a 3x3 kernel using 45-degree
176% circular rotates.
177%
anthony29188a82010-01-22 10:12:34 +0000178% "num, num, num, num, ..."
179% list of floating point numbers defining an 'old style' odd sized
180% square kernel. At least 9 values should be provided for a 3x3
181% square kernel, 25 for a 5x5 square kernel, 49 for 7x7, etc.
182% Values can be space or comma separated. This is not recommended.
anthony602ab9b2010-01-05 08:06:50 +0000183%
anthony7a01dcf2010-05-11 12:25:52 +0000184% You can define a 'list of kernels' which can be used by some morphology
185% operators A list is defined as a semi-colon seperated list kernels.
186%
anthonydbc89892010-05-12 07:05:27 +0000187% " kernel ; kernel ; kernel ; "
anthony7a01dcf2010-05-11 12:25:52 +0000188%
anthony43c49252010-05-18 10:59:50 +0000189% Any extra ';' characters (at start, end or between kernel defintions are
190% simply ignored.
191%
192% Note that 'name' kernels will start with an alphabetic character while the
193% new kernel specification has a ':' character in its specification string.
194% If neither is the case, it is assumed an old style of a simple list of
195% numbers generating a odd-sized square kernel has been given.
anthony7a01dcf2010-05-11 12:25:52 +0000196%
anthony602ab9b2010-01-05 08:06:50 +0000197% The format of the AcquireKernal method is:
198%
cristy2be15382010-01-21 02:38:03 +0000199% KernelInfo *AcquireKernelInfo(const char *kernel_string)
anthony602ab9b2010-01-05 08:06:50 +0000200%
201% A description of each parameter follows:
202%
203% o kernel_string: the Morphology/Convolution kernel wanted.
204%
205*/
206
anthonyc84dce52010-05-07 05:42:23 +0000207/* This was separated so that it could be used as a separate
anthony5ef8e942010-05-11 06:51:12 +0000208** array input handling function, such as for -color-matrix
anthonyc84dce52010-05-07 05:42:23 +0000209*/
anthony5ef8e942010-05-11 06:51:12 +0000210static KernelInfo *ParseKernelArray(const char *kernel_string)
anthony602ab9b2010-01-05 08:06:50 +0000211{
cristy2be15382010-01-21 02:38:03 +0000212 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000213 *kernel;
214
215 char
216 token[MaxTextExtent];
217
anthony602ab9b2010-01-05 08:06:50 +0000218 const char
anthony5ef8e942010-05-11 06:51:12 +0000219 *p,
220 *end;
anthony602ab9b2010-01-05 08:06:50 +0000221
anthonyc84dce52010-05-07 05:42:23 +0000222 register long
223 i;
anthony602ab9b2010-01-05 08:06:50 +0000224
anthony29188a82010-01-22 10:12:34 +0000225 double
226 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
227
anthony43c49252010-05-18 10:59:50 +0000228 MagickStatusType
229 flags;
230
231 GeometryInfo
232 args;
233
cristy2be15382010-01-21 02:38:03 +0000234 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
235 if (kernel == (KernelInfo *)NULL)
anthony602ab9b2010-01-05 08:06:50 +0000236 return(kernel);
237 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
anthony43c49252010-05-18 10:59:50 +0000238 kernel->minimum = kernel->maximum = kernel->angle = 0.0;
anthony7a01dcf2010-05-11 12:25:52 +0000239 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000240 kernel->type = UserDefinedKernel;
anthony7a01dcf2010-05-11 12:25:52 +0000241 kernel->next = (KernelInfo *) NULL;
cristyd43a46b2010-01-21 02:13:41 +0000242 kernel->signature = MagickSignature;
anthony602ab9b2010-01-05 08:06:50 +0000243
anthony5ef8e942010-05-11 06:51:12 +0000244 /* find end of this specific kernel definition string */
245 end = strchr(kernel_string, ';');
246 if ( end == (char *) NULL )
247 end = strchr(kernel_string, '\0');
248
anthony43c49252010-05-18 10:59:50 +0000249 /* clear flags - for Expanding kernal lists thorugh rotations */
250 flags = NoValue;
251
anthony602ab9b2010-01-05 08:06:50 +0000252 /* Has a ':' in argument - New user kernel specification */
253 p = strchr(kernel_string, ':');
anthony5ef8e942010-05-11 06:51:12 +0000254 if ( p != (char *) NULL && p < end)
anthony602ab9b2010-01-05 08:06:50 +0000255 {
anthony602ab9b2010-01-05 08:06:50 +0000256 /* ParseGeometry() needs the geometry separated! -- Arrgghh */
cristy150989e2010-02-01 14:59:39 +0000257 memcpy(token, kernel_string, (size_t) (p-kernel_string));
anthony602ab9b2010-01-05 08:06:50 +0000258 token[p-kernel_string] = '\0';
anthonyc84dce52010-05-07 05:42:23 +0000259 SetGeometryInfo(&args);
anthony602ab9b2010-01-05 08:06:50 +0000260 flags = ParseGeometry(token, &args);
anthony602ab9b2010-01-05 08:06:50 +0000261
anthony29188a82010-01-22 10:12:34 +0000262 /* Size handling and checks of geometry settings */
anthony602ab9b2010-01-05 08:06:50 +0000263 if ( (flags & WidthValue) == 0 ) /* if no width then */
264 args.rho = args.sigma; /* then width = height */
265 if ( args.rho < 1.0 ) /* if width too small */
266 args.rho = 1.0; /* then width = 1 */
267 if ( args.sigma < 1.0 ) /* if height too small */
268 args.sigma = args.rho; /* then height = width */
269 kernel->width = (unsigned long)args.rho;
270 kernel->height = (unsigned long)args.sigma;
271
272 /* Offset Handling and Checks */
273 if ( args.xi < 0.0 || args.psi < 0.0 )
anthony83ba99b2010-01-24 08:48:15 +0000274 return(DestroyKernelInfo(kernel));
cristyc99304f2010-02-01 15:26:27 +0000275 kernel->x = ((flags & XValue)!=0) ? (long)args.xi
cristy150989e2010-02-01 14:59:39 +0000276 : (long) (kernel->width-1)/2;
cristyc99304f2010-02-01 15:26:27 +0000277 kernel->y = ((flags & YValue)!=0) ? (long)args.psi
cristy150989e2010-02-01 14:59:39 +0000278 : (long) (kernel->height-1)/2;
cristyc99304f2010-02-01 15:26:27 +0000279 if ( kernel->x >= (long) kernel->width ||
280 kernel->y >= (long) kernel->height )
anthony83ba99b2010-01-24 08:48:15 +0000281 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000282
283 p++; /* advance beyond the ':' */
284 }
285 else
anthonyc84dce52010-05-07 05:42:23 +0000286 { /* ELSE - Old old specification, forming odd-square kernel */
anthony602ab9b2010-01-05 08:06:50 +0000287 /* count up number of values given */
288 p=(const char *) kernel_string;
cristya699b172010-01-06 16:48:49 +0000289 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
anthony29188a82010-01-22 10:12:34 +0000290 p++; /* ignore "'" chars for convolve filter usage - Cristy */
anthony5ef8e942010-05-11 06:51:12 +0000291 for (i=0; p < end; i++)
anthony602ab9b2010-01-05 08:06:50 +0000292 {
293 GetMagickToken(p,&p,token);
294 if (*token == ',')
295 GetMagickToken(p,&p,token);
296 }
297 /* set the size of the kernel - old sized square */
298 kernel->width = kernel->height= (unsigned long) sqrt((double) i+1.0);
cristyc99304f2010-02-01 15:26:27 +0000299 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000300 p=(const char *) kernel_string;
anthony29188a82010-01-22 10:12:34 +0000301 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
302 p++; /* ignore "'" chars for convolve filter usage - Cristy */
anthony602ab9b2010-01-05 08:06:50 +0000303 }
304
305 /* Read in the kernel values from rest of input string argument */
306 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
307 kernel->height*sizeof(double));
308 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000309 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000310
cristyc99304f2010-02-01 15:26:27 +0000311 kernel->minimum = +MagickHuge;
312 kernel->maximum = -MagickHuge;
313 kernel->negative_range = kernel->positive_range = 0.0;
anthonyc84dce52010-05-07 05:42:23 +0000314
anthony5ef8e942010-05-11 06:51:12 +0000315 for (i=0; (i < (long) (kernel->width*kernel->height)) && (p < end); i++)
anthony602ab9b2010-01-05 08:06:50 +0000316 {
317 GetMagickToken(p,&p,token);
318 if (*token == ',')
319 GetMagickToken(p,&p,token);
anthony29188a82010-01-22 10:12:34 +0000320 if ( LocaleCompare("nan",token) == 0
anthonyc84dce52010-05-07 05:42:23 +0000321 || LocaleCompare("-",token) == 0 ) {
anthony29188a82010-01-22 10:12:34 +0000322 kernel->values[i] = nan; /* do not include this value in kernel */
323 }
324 else {
325 kernel->values[i] = StringToDouble(token);
326 ( kernel->values[i] < 0)
cristyc99304f2010-02-01 15:26:27 +0000327 ? ( kernel->negative_range += kernel->values[i] )
328 : ( kernel->positive_range += kernel->values[i] );
329 Minimize(kernel->minimum, kernel->values[i]);
330 Maximize(kernel->maximum, kernel->values[i]);
anthony29188a82010-01-22 10:12:34 +0000331 }
anthony602ab9b2010-01-05 08:06:50 +0000332 }
anthony29188a82010-01-22 10:12:34 +0000333
anthony5ef8e942010-05-11 06:51:12 +0000334 /* sanity check -- no more values in kernel definition */
335 GetMagickToken(p,&p,token);
336 if ( *token != '\0' && *token != ';' && *token != '\'' )
337 return(DestroyKernelInfo(kernel));
338
anthonyc84dce52010-05-07 05:42:23 +0000339#if 0
340 /* this was the old method of handling a incomplete kernel */
cristy150989e2010-02-01 14:59:39 +0000341 if ( i < (long) (kernel->width*kernel->height) ) {
cristyc99304f2010-02-01 15:26:27 +0000342 Minimize(kernel->minimum, kernel->values[i]);
343 Maximize(kernel->maximum, kernel->values[i]);
cristy150989e2010-02-01 14:59:39 +0000344 for ( ; i < (long) (kernel->width*kernel->height); i++)
anthony29188a82010-01-22 10:12:34 +0000345 kernel->values[i]=0.0;
346 }
anthonyc84dce52010-05-07 05:42:23 +0000347#else
348 /* Number of values for kernel was not enough - Report Error */
349 if ( i < (long) (kernel->width*kernel->height) )
350 return(DestroyKernelInfo(kernel));
351#endif
352
353 /* check that we recieved at least one real (non-nan) value! */
354 if ( kernel->minimum == MagickHuge )
355 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000356
anthony43c49252010-05-18 10:59:50 +0000357 if ( (flags & AreaValue) != 0 ) /* '@' symbol in kernel size */
358 ExpandKernelInfo(kernel, 45.0);
359 else if ( (flags & MinimumValue) != 0 ) /* '^' symbol in kernel size */
360 ExpandKernelInfo(kernel, 90.0);
361
anthony602ab9b2010-01-05 08:06:50 +0000362 return(kernel);
363}
anthonyc84dce52010-05-07 05:42:23 +0000364
anthony43c49252010-05-18 10:59:50 +0000365static KernelInfo *ParseKernelName(const char *kernel_string)
anthonyc84dce52010-05-07 05:42:23 +0000366{
367 char
368 token[MaxTextExtent];
369
anthony5ef8e942010-05-11 06:51:12 +0000370 long
371 type;
372
anthonyc84dce52010-05-07 05:42:23 +0000373 const char
anthony7a01dcf2010-05-11 12:25:52 +0000374 *p,
375 *end;
anthonyc84dce52010-05-07 05:42:23 +0000376
377 MagickStatusType
378 flags;
379
380 GeometryInfo
381 args;
382
anthonyc84dce52010-05-07 05:42:23 +0000383 /* Parse special 'named' kernel */
anthony5ef8e942010-05-11 06:51:12 +0000384 GetMagickToken(kernel_string,&p,token);
anthonyc84dce52010-05-07 05:42:23 +0000385 type=ParseMagickOption(MagickKernelOptions,MagickFalse,token);
386 if ( type < 0 || type == UserDefinedKernel )
anthony5ef8e942010-05-11 06:51:12 +0000387 return((KernelInfo *)NULL); /* not a valid named kernel */
anthonyc84dce52010-05-07 05:42:23 +0000388
389 while (((isspace((int) ((unsigned char) *p)) != 0) ||
anthony5ef8e942010-05-11 06:51:12 +0000390 (*p == ',') || (*p == ':' )) && (*p != '\0') && (*p != ';'))
anthonyc84dce52010-05-07 05:42:23 +0000391 p++;
anthony7a01dcf2010-05-11 12:25:52 +0000392
393 end = strchr(p, ';'); /* end of this kernel defintion */
394 if ( end == (char *) NULL )
395 end = strchr(p, '\0');
396
397 /* ParseGeometry() needs the geometry separated! -- Arrgghh */
398 memcpy(token, p, (size_t) (end-p));
399 token[end-p] = '\0';
anthonyc84dce52010-05-07 05:42:23 +0000400 SetGeometryInfo(&args);
anthony7a01dcf2010-05-11 12:25:52 +0000401 flags = ParseGeometry(token, &args);
anthonyc84dce52010-05-07 05:42:23 +0000402
anthony3c10fc82010-05-13 02:40:51 +0000403#if 0
404 /* For Debugging Geometry Input */
anthony46a369d2010-05-19 02:41:48 +0000405 fprintf(stderr, "Geometry = 0x%04X : %lg x %lg %+lg %+lg\n",
anthony3c10fc82010-05-13 02:40:51 +0000406 flags, args.rho, args.sigma, args.xi, args.psi );
407#endif
408
anthonyc84dce52010-05-07 05:42:23 +0000409 /* special handling of missing values in input string */
410 switch( type ) {
anthony5ef8e942010-05-11 06:51:12 +0000411 case RectangleKernel:
412 if ( (flags & WidthValue) == 0 ) /* if no width then */
413 args.rho = args.sigma; /* then width = height */
414 if ( args.rho < 1.0 ) /* if width too small */
415 args.rho = 3; /* then width = 3 */
416 if ( args.sigma < 1.0 ) /* if height too small */
417 args.sigma = args.rho; /* then height = width */
418 if ( (flags & XValue) == 0 ) /* center offset if not defined */
419 args.xi = (double)(((long)args.rho-1)/2);
420 if ( (flags & YValue) == 0 )
421 args.psi = (double)(((long)args.sigma-1)/2);
422 break;
423 case SquareKernel:
424 case DiamondKernel:
425 case DiskKernel:
426 case PlusKernel:
anthony3dd0f622010-05-13 12:57:32 +0000427 case CrossKernel:
anthony5ef8e942010-05-11 06:51:12 +0000428 /* If no scale given (a 0 scale is valid! - set it to 1.0 */
429 if ( (flags & HeightValue) == 0 )
430 args.sigma = 1.0;
431 break;
anthonyc1061722010-05-14 06:23:49 +0000432 case RingKernel:
433 if ( (flags & XValue) == 0 )
434 args.xi = 1.0;
435 break;
anthony5ef8e942010-05-11 06:51:12 +0000436 case ChebyshevKernel:
437 case ManhattenKernel:
438 case EuclideanKernel:
anthony43c49252010-05-18 10:59:50 +0000439 if ( (flags & HeightValue) == 0 ) /* no distance scale */
440 args.sigma = 100.0; /* default distance scaling */
441 else if ( (flags & AspectValue ) != 0 ) /* '!' flag */
442 args.sigma = QuantumRange/(args.sigma+1); /* maximum pixel distance */
443 else if ( (flags & PercentValue ) != 0 ) /* '%' flag */
444 args.sigma *= QuantumRange/100.0; /* percentage of color range */
anthony5ef8e942010-05-11 06:51:12 +0000445 break;
446 default:
447 break;
anthonyc84dce52010-05-07 05:42:23 +0000448 }
449
450 return(AcquireKernelBuiltIn((KernelInfoType)type, &args));
451}
452
anthony5ef8e942010-05-11 06:51:12 +0000453MagickExport KernelInfo *AcquireKernelInfo(const char *kernel_string)
454{
anthony7a01dcf2010-05-11 12:25:52 +0000455
456 KernelInfo
anthonydbc89892010-05-12 07:05:27 +0000457 *kernel,
anthony43c49252010-05-18 10:59:50 +0000458 *new_kernel;
anthony7a01dcf2010-05-11 12:25:52 +0000459
anthony5ef8e942010-05-11 06:51:12 +0000460 char
461 token[MaxTextExtent];
462
anthony7a01dcf2010-05-11 12:25:52 +0000463 const char
anthonydbc89892010-05-12 07:05:27 +0000464 *p;
anthony7a01dcf2010-05-11 12:25:52 +0000465
anthonye108a3f2010-05-12 07:24:03 +0000466 unsigned long
467 kernel_number;
468
anthonydbc89892010-05-12 07:05:27 +0000469 p = kernel_string;
anthony43c49252010-05-18 10:59:50 +0000470 kernel = NULL;
anthonye108a3f2010-05-12 07:24:03 +0000471 kernel_number = 0;
anthony5ef8e942010-05-11 06:51:12 +0000472
anthonydbc89892010-05-12 07:05:27 +0000473 while ( GetMagickToken(p,NULL,token), *token != '\0' ) {
anthony7a01dcf2010-05-11 12:25:52 +0000474
anthony43c49252010-05-18 10:59:50 +0000475 /* ignore extra or multiple ';' kernel seperators */
anthonydbc89892010-05-12 07:05:27 +0000476 if ( *token != ';' ) {
anthony7a01dcf2010-05-11 12:25:52 +0000477
anthonydbc89892010-05-12 07:05:27 +0000478 /* tokens starting with alpha is a Named kernel */
anthony43c49252010-05-18 10:59:50 +0000479 if (isalpha((int) *token) != 0)
480 new_kernel = ParseKernelName(p);
anthonydbc89892010-05-12 07:05:27 +0000481 else /* otherwise a user defined kernel array */
anthony43c49252010-05-18 10:59:50 +0000482 new_kernel = ParseKernelArray(p);
anthony7a01dcf2010-05-11 12:25:52 +0000483
anthonye108a3f2010-05-12 07:24:03 +0000484 /* Error handling -- this is not proper error handling! */
485 if ( new_kernel == (KernelInfo *) NULL ) {
486 fprintf(stderr, "Failed to parse kernel number #%lu\n", kernel_number);
487 if ( kernel != (KernelInfo *) NULL )
488 kernel=DestroyKernelInfo(kernel);
489 return((KernelInfo *) NULL);
anthonydbc89892010-05-12 07:05:27 +0000490 }
anthonye108a3f2010-05-12 07:24:03 +0000491
492 /* initialise or append the kernel list */
anthony3dd0f622010-05-13 12:57:32 +0000493 if ( kernel == (KernelInfo *) NULL )
494 kernel = new_kernel;
495 else
anthony43c49252010-05-18 10:59:50 +0000496 LastKernelInfo(kernel)->next = new_kernel;
anthonydbc89892010-05-12 07:05:27 +0000497 }
498
499 /* look for the next kernel in list */
500 p = strchr(p, ';');
501 if ( p == (char *) NULL )
502 break;
503 p++;
504
505 }
anthony7a01dcf2010-05-11 12:25:52 +0000506 return(kernel);
anthony5ef8e942010-05-11 06:51:12 +0000507}
508
anthony602ab9b2010-01-05 08:06:50 +0000509
510/*
511%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
512% %
513% %
514% %
515% A c q u i r e K e r n e l B u i l t I n %
516% %
517% %
518% %
519%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
520%
521% AcquireKernelBuiltIn() returned one of the 'named' built-in types of
522% kernels used for special purposes such as gaussian blurring, skeleton
523% pruning, and edge distance determination.
524%
525% They take a KernelType, and a set of geometry style arguments, which were
526% typically decoded from a user supplied string, or from a more complex
527% Morphology Method that was requested.
528%
529% The format of the AcquireKernalBuiltIn method is:
530%
cristy2be15382010-01-21 02:38:03 +0000531% KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000532% const GeometryInfo args)
533%
534% A description of each parameter follows:
535%
536% o type: the pre-defined type of kernel wanted
537%
538% o args: arguments defining or modifying the kernel
539%
540% Convolution Kernels
541%
anthony46a369d2010-05-19 02:41:48 +0000542% Unity
543% the No-Op kernel, also requivelent to Gaussian of sigma zero.
544% Basically a 3x3 kernel of a 1 surrounded by zeros.
545%
anthony3c10fc82010-05-13 02:40:51 +0000546% Gaussian:{radius},{sigma}
547% Generate a two-dimentional gaussian kernel, as used by -gaussian.
anthonyc1061722010-05-14 06:23:49 +0000548% The sigma for the curve is required. The resulting kernel is
549% normalized,
550%
551% If 'sigma' is zero, you get a single pixel on a field of zeros.
anthony602ab9b2010-01-05 08:06:50 +0000552%
553% NOTE: that the 'radius' is optional, but if provided can limit (clip)
554% the final size of the resulting kernel to a square 2*radius+1 in size.
555% The radius should be at least 2 times that of the sigma value, or
556% sever clipping and aliasing may result. If not given or set to 0 the
557% radius will be determined so as to produce the best minimal error
558% result, which is usally much larger than is normally needed.
559%
anthonyc1061722010-05-14 06:23:49 +0000560% DOG:{radius},{sigma1},{sigma2}
561% "Difference of Gaussians" Kernel.
562% As "Gaussian" but with a gaussian produced by 'sigma2' subtracted
563% from the gaussian produced by 'sigma1'. Typically sigma2 > sigma1.
564% The result is a zero-summing kernel.
anthony602ab9b2010-01-05 08:06:50 +0000565%
anthony9eb4f742010-05-18 02:45:54 +0000566% LOG:{radius},{sigma}
567% "Laplacian of a Gaussian" or "Mexician Hat" Kernel.
568% The supposed ideal edge detection, zero-summing kernel.
569%
570% An alturnative to this kernel is to use a "DOG" with a sigma ratio of
571% approx 1.6, which can also be applied as a 2 pass "DOB" (see below).
572%
anthonyc1061722010-05-14 06:23:49 +0000573% Blur:{radius},{sigma}[,{angle}]
574% Generates a 1 dimensional or linear gaussian blur, at the angle given
575% (current restricted to orthogonal angles). If a 'radius' is given the
576% kernel is clipped to a width of 2*radius+1. Kernel can be rotated
577% by a 90 degree angle.
578%
579% If 'sigma' is zero, you get a single pixel on a field of zeros.
580%
581% Note that two convolutions with two "Blur" kernels perpendicular to
582% each other, is equivelent to a far larger "Gaussian" kernel with the
583% same sigma value, However it is much faster to apply. This is how the
584% "-blur" operator actually works.
585%
586% DOB:{radius},{sigma1},{sigma2}[,{angle}]
587% "Difference of Blurs" Kernel.
588% As "Blur" but with the 1D gaussian produced by 'sigma2' subtracted
589% from thethe 1D gaussian produced by 'sigma1'.
590% The result is a zero-summing kernel.
591%
592% This can be used to generate a faster "DOG" convolution, in the same
593% way "Blur" can.
anthony602ab9b2010-01-05 08:06:50 +0000594%
anthony3c10fc82010-05-13 02:40:51 +0000595% Comet:{width},{sigma},{angle}
596% Blur in one direction only, much like how a bright object leaves
anthony602ab9b2010-01-05 08:06:50 +0000597% a comet like trail. The Kernel is actually half a gaussian curve,
anthony3c10fc82010-05-13 02:40:51 +0000598% Adding two such blurs in opposite directions produces a Blur Kernel.
599% Angle can be rotated in multiples of 90 degrees.
anthony602ab9b2010-01-05 08:06:50 +0000600%
anthony3c10fc82010-05-13 02:40:51 +0000601% Note that the first argument is the width of the kernel and not the
anthony602ab9b2010-01-05 08:06:50 +0000602% radius of the kernel.
603%
604% # Still to be implemented...
605% #
anthony4fd27e22010-02-07 08:17:18 +0000606% # Filter2D
607% # Filter1D
608% # Set kernel values using a resize filter, and given scale (sigma)
609% # Cylindrical or Linear. Is this posible with an image?
610% #
anthony602ab9b2010-01-05 08:06:50 +0000611%
anthony3c10fc82010-05-13 02:40:51 +0000612% Named Constant Convolution Kernels
613%
anthonyc1061722010-05-14 06:23:49 +0000614% All these are unscaled, zero-summing kernels by default. As such for
615% non-HDRI version of ImageMagick some form of normalization, user scaling,
616% and biasing the results is recommended, to prevent the resulting image
617% being 'clipped'.
618%
619% The 3x3 kernels (most of these) can be circularly rotated in multiples of
620% 45 degrees to generate the 8 angled varients of each of the kernels.
anthony3c10fc82010-05-13 02:40:51 +0000621%
622% Laplacian:{type}
anthony43c49252010-05-18 10:59:50 +0000623% Discrete Lapacian Kernels, (without normalization)
anthonyc1061722010-05-14 06:23:49 +0000624% Type 0 : 3x3 with center:8 surounded by -1 (8 neighbourhood)
625% Type 1 : 3x3 with center:4 edge:-1 corner:0 (4 neighbourhood)
anthony9eb4f742010-05-18 02:45:54 +0000626% Type 2 : 3x3 with center:4 edge:1 corner:-2
627% Type 3 : 3x3 with center:4 edge:-2 corner:1
628% Type 5 : 5x5 laplacian
629% Type 7 : 7x7 laplacian
anthony43c49252010-05-18 10:59:50 +0000630% Type 15 : 5x5 LOG (sigma approx 1.4)
631% Type 19 : 9x9 LOG (sigma approx 1.4)
anthonyc1061722010-05-14 06:23:49 +0000632%
633% Sobel:{angle}
anthony46a369d2010-05-19 02:41:48 +0000634% Sobel 'Edge' convolution kernel (3x3)
anthonyc1061722010-05-14 06:23:49 +0000635% -1, 0, 1
636% -2, 0,-2
637% -1, 0, 1
anthony46a369d2010-05-19 02:41:48 +0000638% FreiChen:{angle}
639% Frei-Chen 'Edge' convolution kernel (3x3)
640% -1, 0, 1
641% -sqrt(2), 0, sqrt(2)
642% -1, 0, 1
anthonyc1061722010-05-14 06:23:49 +0000643% Roberts:{angle}
anthony46a369d2010-05-19 02:41:48 +0000644% Roberts convolution kernel (3x3)
anthonyc1061722010-05-14 06:23:49 +0000645% 0, 0, 0
646% -1, 1, 0
647% 0, 0, 0
anthonyc1061722010-05-14 06:23:49 +0000648% Prewitt:{angle}
649% Prewitt Edge convolution kernel (3x3)
650% -1, 0, 1
651% -1, 0, 1
652% -1, 0, 1
anthony9eb4f742010-05-18 02:45:54 +0000653% Compass:{angle}
654% Prewitt's "Compass" convolution kernel (3x3)
655% -1, 1, 1
656% -1,-2, 1
657% -1, 1, 1
658% Kirsch:{angle}
659% Kirsch's "Compass" convolution kernel (3x3)
660% -3,-3, 5
661% -3, 0, 5
662% -3,-3, 5
anthony3c10fc82010-05-13 02:40:51 +0000663%
anthony602ab9b2010-01-05 08:06:50 +0000664% Boolean Kernels
665%
anthony3c10fc82010-05-13 02:40:51 +0000666% Diamond:[{radius}[,{scale}]]
anthony1b2bc0a2010-05-12 05:25:22 +0000667% Generate a diamond shaped kernel with given radius to the points.
anthony602ab9b2010-01-05 08:06:50 +0000668% Kernel size will again be radius*2+1 square and defaults to radius 1,
669% generating a 3x3 kernel that is slightly larger than a square.
670%
anthony3c10fc82010-05-13 02:40:51 +0000671% Square:[{radius}[,{scale}]]
anthony602ab9b2010-01-05 08:06:50 +0000672% Generate a square shaped kernel of size radius*2+1, and defaulting
673% to a 3x3 (radius 1).
674%
anthonyc1061722010-05-14 06:23:49 +0000675% Note that using a larger radius for the "Square" or the "Diamond" is
676% also equivelent to iterating the basic morphological method that many
677% times. However iterating with the smaller radius is actually faster
678% than using a larger kernel radius.
679%
680% Rectangle:{geometry}
681% Simply generate a rectangle of 1's with the size given. You can also
682% specify the location of the 'control point', otherwise the closest
683% pixel to the center of the rectangle is selected.
684%
685% Properly centered and odd sized rectangles work the best.
anthony602ab9b2010-01-05 08:06:50 +0000686%
anthony3c10fc82010-05-13 02:40:51 +0000687% Disk:[{radius}[,{scale}]]
anthony602ab9b2010-01-05 08:06:50 +0000688% Generate a binary disk of the radius given, radius may be a float.
689% Kernel size will be ceil(radius)*2+1 square.
690% NOTE: Here are some disk shapes of specific interest
anthonyc1061722010-05-14 06:23:49 +0000691% "Disk:1" => "diamond" or "cross:1"
692% "Disk:1.5" => "square"
693% "Disk:2" => "diamond:2"
694% "Disk:2.5" => a general disk shape of radius 2
695% "Disk:2.9" => "square:2"
696% "Disk:3.5" => default - octagonal/disk shape of radius 3
697% "Disk:4.2" => roughly octagonal shape of radius 4
698% "Disk:4.3" => a general disk shape of radius 4
anthony602ab9b2010-01-05 08:06:50 +0000699% After this all the kernel shape becomes more and more circular.
700%
701% Because a "disk" is more circular when using a larger radius, using a
702% larger radius is preferred over iterating the morphological operation.
703%
anthonyc1061722010-05-14 06:23:49 +0000704% Symbol Dilation Kernels
705%
706% These kernel is not a good general morphological kernel, but is used
707% more for highlighting and marking any single pixels in an image using,
708% a "Dilate" method as appropriate.
709%
710% For the same reasons iterating these kernels does not produce the
711% same result as using a larger radius for the symbol.
712%
anthony3c10fc82010-05-13 02:40:51 +0000713% Plus:[{radius}[,{scale}]]
anthony3dd0f622010-05-13 12:57:32 +0000714% Cross:[{radius}[,{scale}]]
anthonyc1061722010-05-14 06:23:49 +0000715% Generate a kernel in the shape of a 'plus' or a 'cross' with
716% a each arm the length of the given radius (default 2).
anthony3dd0f622010-05-13 12:57:32 +0000717%
718% NOTE: "plus:1" is equivelent to a "Diamond" kernel.
anthony602ab9b2010-01-05 08:06:50 +0000719%
anthonyc1061722010-05-14 06:23:49 +0000720% Ring:{radius1},{radius2}[,{scale}]
721% A ring of the values given that falls between the two radii.
722% Defaults to a ring of approximataly 3 radius in a 7x7 kernel.
723% This is the 'edge' pixels of the default "Disk" kernel,
724% More specifically, "Ring" -> "Ring:2.5,3.5,1.0"
anthony602ab9b2010-01-05 08:06:50 +0000725%
anthony3dd0f622010-05-13 12:57:32 +0000726% Hit and Miss Kernels
727%
728% Peak:radius1,radius2
anthonyc1061722010-05-14 06:23:49 +0000729% Find any peak larger than the pixels the fall between the two radii.
730% The default ring of pixels is as per "Ring".
anthony43c49252010-05-18 10:59:50 +0000731% Edges
732% Find Edges of a binary shape
anthony3dd0f622010-05-13 12:57:32 +0000733% Corners
734% Find corners of a binary shape
735% LineEnds
736% Find end points of lines (for pruning a skeletion)
737% LineJunctions
anthony43c49252010-05-18 10:59:50 +0000738% Find three line junctions (within a skeletion)
anthony3dd0f622010-05-13 12:57:32 +0000739% ConvexHull
740% Octagonal thicken kernel, to generate convex hulls of 45 degrees
741% Skeleton
742% Thinning kernel, which leaves behind a skeletion of a shape
anthony602ab9b2010-01-05 08:06:50 +0000743%
744% Distance Measuring Kernels
745%
anthonyc1061722010-05-14 06:23:49 +0000746% Different types of distance measuring methods, which are used with the
747% a 'Distance' morphology method for generating a gradient based on
748% distance from an edge of a binary shape, though there is a technique
749% for handling a anti-aliased shape.
750%
751% See the 'Distance' Morphological Method, for information of how it is
752% applied.
753%
anthony3dd0f622010-05-13 12:57:32 +0000754% Chebyshev:[{radius}][x{scale}[%!]]
anthonyc94cdb02010-01-06 08:15:29 +0000755% Chebyshev Distance (also known as Tchebychev Distance) is a value of
756% one to any neighbour, orthogonal or diagonal. One why of thinking of
757% it is the number of squares a 'King' or 'Queen' in chess needs to
758% traverse reach any other position on a chess board. It results in a
759% 'square' like distance function, but one where diagonals are closer
760% than expected.
anthony602ab9b2010-01-05 08:06:50 +0000761%
anthonyc1061722010-05-14 06:23:49 +0000762% Manhatten:[{radius}][x{scale}[%!]]
anthonyc94cdb02010-01-06 08:15:29 +0000763% Manhatten Distance (also known as Rectilinear Distance, or the Taxi
764% Cab metric), is the distance needed when you can only travel in
765% orthogonal (horizontal or vertical) only. It is the distance a 'Rook'
766% in chess would travel. It results in a diamond like distances, where
767% diagonals are further than expected.
anthony602ab9b2010-01-05 08:06:50 +0000768%
anthonyc1061722010-05-14 06:23:49 +0000769% Euclidean:[{radius}][x{scale}[%!]]
anthonyc94cdb02010-01-06 08:15:29 +0000770% Euclidean Distance is the 'direct' or 'as the crow flys distance.
771% However by default the kernel size only has a radius of 1, which
772% limits the distance to 'Knight' like moves, with only orthogonal and
773% diagonal measurements being correct. As such for the default kernel
774% you will get octagonal like distance function, which is reasonally
775% accurate.
776%
777% However if you use a larger radius such as "Euclidean:4" you will
778% get a much smoother distance gradient from the edge of the shape.
779% Of course a larger kernel is slower to use, and generally not needed.
780%
781% To allow the use of fractional distances that you get with diagonals
782% the actual distance is scaled by a fixed value which the user can
783% provide. This is not actually nessary for either ""Chebyshev" or
784% "Manhatten" distance kernels, but is done for all three distance
785% kernels. If no scale is provided it is set to a value of 100,
786% allowing for a maximum distance measurement of 655 pixels using a Q16
787% version of IM, from any edge. However for small images this can
788% result in quite a dark gradient.
789%
anthony602ab9b2010-01-05 08:06:50 +0000790*/
791
cristy2be15382010-01-21 02:38:03 +0000792MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000793 const GeometryInfo *args)
794{
cristy2be15382010-01-21 02:38:03 +0000795 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000796 *kernel;
797
cristy150989e2010-02-01 14:59:39 +0000798 register long
anthony602ab9b2010-01-05 08:06:50 +0000799 i;
800
801 register long
802 u,
803 v;
804
805 double
806 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
807
anthonyc1061722010-05-14 06:23:49 +0000808 /* Generate a new empty kernel if needed */
cristye96405a2010-05-19 02:24:31 +0000809 kernel=(KernelInfo *) NULL;
anthonyc1061722010-05-14 06:23:49 +0000810 switch(type) {
anthony9eb4f742010-05-18 02:45:54 +0000811 case UndefinedKernel: /* These should not be used here */
812 case UserDefinedKernel:
813 break;
814 case LaplacianKernel: /* Named Descrete Convolution Kernels */
815 case SobelKernel:
816 case RobertsKernel:
817 case PrewittKernel:
818 case CompassKernel:
819 case KirschKernel:
820 case CornersKernel: /* Hit and Miss kernels */
821 case LineEndsKernel:
822 case LineJunctionsKernel:
anthony9eb4f742010-05-18 02:45:54 +0000823 case ThinningKernel:
824 case ConvexHullKernel:
825 case SkeletonKernel:
826 /* A pre-generated kernel is not needed */
827 break;
828#if 0
anthonyc1061722010-05-14 06:23:49 +0000829 case GaussianKernel:
830 case DOGKernel:
831 case BlurKernel:
832 case DOBKernel:
833 case CometKernel:
834 case DiamondKernel:
835 case SquareKernel:
836 case RectangleKernel:
837 case DiskKernel:
838 case PlusKernel:
839 case CrossKernel:
840 case RingKernel:
841 case PeaksKernel:
842 case ChebyshevKernel:
843 case ManhattenKernel:
844 case EuclideanKernel:
anthony9eb4f742010-05-18 02:45:54 +0000845#endif
846 default:
847 /* Generate the base Kernel Structure */
anthonyc1061722010-05-14 06:23:49 +0000848 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
849 if (kernel == (KernelInfo *) NULL)
850 return(kernel);
851 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
anthony43c49252010-05-18 10:59:50 +0000852 kernel->minimum = kernel->maximum = kernel->angle = 0.0;
anthonyc1061722010-05-14 06:23:49 +0000853 kernel->negative_range = kernel->positive_range = 0.0;
854 kernel->type = type;
855 kernel->next = (KernelInfo *) NULL;
856 kernel->signature = MagickSignature;
anthonyc1061722010-05-14 06:23:49 +0000857 break;
858 }
anthony602ab9b2010-01-05 08:06:50 +0000859
860 switch(type) {
861 /* Convolution Kernels */
862 case GaussianKernel:
anthonyc1061722010-05-14 06:23:49 +0000863 case DOGKernel:
anthony9eb4f742010-05-18 02:45:54 +0000864 case LOGKernel:
anthony602ab9b2010-01-05 08:06:50 +0000865 { double
anthonyc1061722010-05-14 06:23:49 +0000866 sigma = fabs(args->sigma),
867 sigma2 = fabs(args->xi),
anthony9eb4f742010-05-18 02:45:54 +0000868 A, B, R;
anthony602ab9b2010-01-05 08:06:50 +0000869
anthonyc1061722010-05-14 06:23:49 +0000870 if ( args->rho >= 1.0 )
871 kernel->width = (unsigned long)args->rho*2+1;
anthony9eb4f742010-05-18 02:45:54 +0000872 else if ( (type != DOGKernel) || (sigma >= sigma2) )
anthonyc1061722010-05-14 06:23:49 +0000873 kernel->width = GetOptimalKernelWidth2D(args->rho,sigma);
874 else
875 kernel->width = GetOptimalKernelWidth2D(args->rho,sigma2);
876 kernel->height = kernel->width;
cristyc99304f2010-02-01 15:26:27 +0000877 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000878 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
879 kernel->height*sizeof(double));
880 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000881 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000882
anthony46a369d2010-05-19 02:41:48 +0000883 /* WARNING: The following generates a 'sampled gaussian' kernel.
anthony9eb4f742010-05-18 02:45:54 +0000884 * What we really want is a 'discrete gaussian' kernel.
anthony46a369d2010-05-19 02:41:48 +0000885 *
886 * How to do this is currently not known, but appears to be
887 * basied on the Error Function 'erf()' (intergral of a gaussian)
anthony9eb4f742010-05-18 02:45:54 +0000888 */
889
890 if ( type == GaussianKernel || type == DOGKernel )
891 { /* Calculate a Gaussian, OR positive half of a DOG */
892 if ( sigma > MagickEpsilon )
893 { A = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
894 B = 1.0/(Magick2PI*sigma*sigma);
895 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
896 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
897 kernel->values[i] = exp(-((double)(u*u+v*v))*A)*B;
898 }
899 else /* limiting case - a unity (normalized Dirac) kernel */
900 { (void) ResetMagickMemory(kernel->values,0, (size_t)
901 kernel->width*kernel->height*sizeof(double));
902 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
903 }
anthonyc1061722010-05-14 06:23:49 +0000904 }
anthony9eb4f742010-05-18 02:45:54 +0000905
anthonyc1061722010-05-14 06:23:49 +0000906 if ( type == DOGKernel )
907 { /* Subtract a Negative Gaussian for "Difference of Gaussian" */
908 if ( sigma2 > MagickEpsilon )
909 { sigma = sigma2; /* simplify loop expressions */
anthony9eb4f742010-05-18 02:45:54 +0000910 A = 1.0/(2.0*sigma*sigma);
911 B = 1.0/(Magick2PI*sigma*sigma);
anthonyc1061722010-05-14 06:23:49 +0000912 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
913 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony9eb4f742010-05-18 02:45:54 +0000914 kernel->values[i] -= exp(-((double)(u*u+v*v))*A)*B;
anthonyc1061722010-05-14 06:23:49 +0000915 }
anthony9eb4f742010-05-18 02:45:54 +0000916 else /* limiting case - a unity (normalized Dirac) kernel */
anthonyc1061722010-05-14 06:23:49 +0000917 kernel->values[kernel->x+kernel->y*kernel->width] -= 1.0;
918 }
anthony9eb4f742010-05-18 02:45:54 +0000919
920 if ( type == LOGKernel )
921 { /* Calculate a Laplacian of a Gaussian - Or Mexician Hat */
922 if ( sigma > MagickEpsilon )
923 { A = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
924 B = 1.0/(MagickPI*sigma*sigma*sigma*sigma);
925 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
926 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
927 { R = ((double)(u*u+v*v))*A;
928 kernel->values[i] = (1-R)*exp(-R)*B;
929 }
930 }
931 else /* special case - generate a unity kernel */
932 { (void) ResetMagickMemory(kernel->values,0, (size_t)
933 kernel->width*kernel->height*sizeof(double));
934 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
935 }
936 }
937
938 /* Note the above kernels may have been 'clipped' by a user defined
anthonyc1061722010-05-14 06:23:49 +0000939 ** radius, producing a smaller (darker) kernel. Also for very small
940 ** sigma's (> 0.1) the central value becomes larger than one, and thus
941 ** producing a very bright kernel.
942 **
943 ** Normalization will still be needed.
944 */
anthony602ab9b2010-01-05 08:06:50 +0000945
anthony3dd0f622010-05-13 12:57:32 +0000946 /* Normalize the 2D Gaussian Kernel
947 **
anthonyc1061722010-05-14 06:23:49 +0000948 ** NB: a CorrelateNormalize performs a normal Normalize if
949 ** there are no negative values.
anthony3dd0f622010-05-13 12:57:32 +0000950 */
anthony46a369d2010-05-19 02:41:48 +0000951 CalcKernelMetaData(kernel); /* the other kernel meta-data */
anthonyc1061722010-05-14 06:23:49 +0000952 ScaleKernelInfo(kernel, 1.0, CorrelateNormalizeValue);
anthony602ab9b2010-01-05 08:06:50 +0000953
954 break;
955 }
956 case BlurKernel:
anthonyc1061722010-05-14 06:23:49 +0000957 case DOBKernel:
anthony602ab9b2010-01-05 08:06:50 +0000958 { double
anthonyc1061722010-05-14 06:23:49 +0000959 sigma = fabs(args->sigma),
960 sigma2 = fabs(args->xi),
anthony9eb4f742010-05-18 02:45:54 +0000961 A, B;
anthony602ab9b2010-01-05 08:06:50 +0000962
anthonyc1061722010-05-14 06:23:49 +0000963 if ( args->rho >= 1.0 )
964 kernel->width = (unsigned long)args->rho*2+1;
965 else if ( (type == BlurKernel) || (sigma >= sigma2) )
966 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
967 else
968 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma2);
anthony602ab9b2010-01-05 08:06:50 +0000969 kernel->height = 1;
anthonyc1061722010-05-14 06:23:49 +0000970 kernel->x = (long) (kernel->width-1)/2;
cristyc99304f2010-02-01 15:26:27 +0000971 kernel->y = 0;
972 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000973 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
974 kernel->height*sizeof(double));
975 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000976 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000977
978#if 1
979#define KernelRank 3
980 /* Formula derived from GetBlurKernel() in "effect.c" (plus bug fix).
981 ** It generates a gaussian 3 times the width, and compresses it into
982 ** the expected range. This produces a closer normalization of the
983 ** resulting kernel, especially for very low sigma values.
984 ** As such while wierd it is prefered.
985 **
986 ** I am told this method originally came from Photoshop.
anthony9eb4f742010-05-18 02:45:54 +0000987 **
988 ** A properly normalized curve is generated (apart from edge clipping)
989 ** even though we later normalize the result (for edge clipping)
990 ** to allow the correct generation of a "Difference of Blurs".
anthony602ab9b2010-01-05 08:06:50 +0000991 */
anthonyc1061722010-05-14 06:23:49 +0000992
993 /* initialize */
cristy150989e2010-02-01 14:59:39 +0000994 v = (long) (kernel->width*KernelRank-1)/2; /* start/end points to fit range */
anthony9eb4f742010-05-18 02:45:54 +0000995 (void) ResetMagickMemory(kernel->values,0, (size_t)
996 kernel->width*kernel->height*sizeof(double));
anthonyc1061722010-05-14 06:23:49 +0000997 /* Calculate a Positive 1D Gaussian */
998 if ( sigma > MagickEpsilon )
999 { sigma *= KernelRank; /* simplify loop expressions */
anthony9eb4f742010-05-18 02:45:54 +00001000 A = 1.0/(2.0*sigma*sigma);
1001 B = 1.0/(MagickSQ2PI*sigma );
anthonyc1061722010-05-14 06:23:49 +00001002 for ( u=-v; u <= v; u++) {
anthony9eb4f742010-05-18 02:45:54 +00001003 kernel->values[(u+v)/KernelRank] += exp(-((double)(u*u))*A)*B;
anthonyc1061722010-05-14 06:23:49 +00001004 }
1005 }
1006 else /* special case - generate a unity kernel */
1007 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
anthony9eb4f742010-05-18 02:45:54 +00001008
1009 /* Subtract a Second 1D Gaussian for "Difference of Blur" */
anthonyc1061722010-05-14 06:23:49 +00001010 if ( type == DOBKernel )
anthony9eb4f742010-05-18 02:45:54 +00001011 {
anthonyc1061722010-05-14 06:23:49 +00001012 if ( sigma2 > MagickEpsilon )
1013 { sigma = sigma2*KernelRank; /* simplify loop expressions */
anthony9eb4f742010-05-18 02:45:54 +00001014 A = 1.0/(2.0*sigma*sigma);
1015 B = 1.0/(MagickSQ2PI*sigma);
anthonyc1061722010-05-14 06:23:49 +00001016 for ( u=-v; u <= v; u++)
anthony9eb4f742010-05-18 02:45:54 +00001017 kernel->values[(u+v)/KernelRank] -= exp(-((double)(u*u))*A)*B;
anthonyc1061722010-05-14 06:23:49 +00001018 }
anthony9eb4f742010-05-18 02:45:54 +00001019 else /* limiting case - a unity (normalized Dirac) kernel */
anthonyc1061722010-05-14 06:23:49 +00001020 kernel->values[kernel->x+kernel->y*kernel->width] -= 1.0;
1021 }
anthony602ab9b2010-01-05 08:06:50 +00001022#else
anthonyc1061722010-05-14 06:23:49 +00001023 /* Direct calculation without curve averaging */
1024
1025 /* Calculate a Positive Gaussian */
1026 if ( sigma > MagickEpsilon )
anthony9eb4f742010-05-18 02:45:54 +00001027 { A = 1.0/(2.0*sigma*sigma); /* simplify loop expressions */
1028 B = 1.0/(MagickSQ2PI*sigma);
anthonyc1061722010-05-14 06:23:49 +00001029 for ( i=0, u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony9eb4f742010-05-18 02:45:54 +00001030 kernel->values[i] = exp(-((double)(u*u))*A)*B;
anthonyc1061722010-05-14 06:23:49 +00001031 }
1032 else /* special case - generate a unity kernel */
1033 { (void) ResetMagickMemory(kernel->values,0, (size_t)
1034 kernel->width*kernel->height*sizeof(double));
1035 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1036 }
anthony9eb4f742010-05-18 02:45:54 +00001037
1038 /* Subtract a Second 1D Gaussian for "Difference of Blur" */
anthonyc1061722010-05-14 06:23:49 +00001039 if ( type == DOBKernel )
anthony9eb4f742010-05-18 02:45:54 +00001040 {
anthonyc1061722010-05-14 06:23:49 +00001041 if ( sigma2 > MagickEpsilon )
1042 { sigma = sigma2; /* simplify loop expressions */
anthony9eb4f742010-05-18 02:45:54 +00001043 A = 1.0/(2.0*sigma*sigma);
1044 B = 1.0/(MagickSQ2PI*sigma);
anthonyc1061722010-05-14 06:23:49 +00001045 for ( i=0, u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony9eb4f742010-05-18 02:45:54 +00001046 kernel->values[i] -= exp(-((double)(u*u))*A)*B;
anthonyc1061722010-05-14 06:23:49 +00001047 }
anthony9eb4f742010-05-18 02:45:54 +00001048 else /* limiting case - a unity (normalized Dirac) kernel */
anthonyc1061722010-05-14 06:23:49 +00001049 kernel->values[kernel->x+kernel->y*kernel->width] -= 1.0;
1050 }
anthony602ab9b2010-01-05 08:06:50 +00001051#endif
anthonyc1061722010-05-14 06:23:49 +00001052 /* Note the above kernel may have been 'clipped' by a user defined
anthonycc6c8362010-01-25 04:14:01 +00001053 ** radius, producing a smaller (darker) kernel. Also for very small
1054 ** sigma's (> 0.1) the central value becomes larger than one, and thus
1055 ** producing a very bright kernel.
anthonyc1061722010-05-14 06:23:49 +00001056 **
1057 ** Normalization will still be needed.
anthony602ab9b2010-01-05 08:06:50 +00001058 */
anthonycc6c8362010-01-25 04:14:01 +00001059
anthony602ab9b2010-01-05 08:06:50 +00001060 /* Normalize the 1D Gaussian Kernel
1061 **
anthonyc1061722010-05-14 06:23:49 +00001062 ** NB: a CorrelateNormalize performs a normal Normalize if
1063 ** there are no negative values.
anthony602ab9b2010-01-05 08:06:50 +00001064 */
anthony46a369d2010-05-19 02:41:48 +00001065 CalcKernelMetaData(kernel); /* the other kernel meta-data */
1066 ScaleKernelInfo(kernel, 1.0, CorrelateNormalizeValue);
anthonycc6c8362010-01-25 04:14:01 +00001067
anthonyc1061722010-05-14 06:23:49 +00001068 /* rotate the 1D kernel by given angle */
1069 RotateKernelInfo(kernel, (type == BlurKernel) ? args->xi : args->psi );
anthony602ab9b2010-01-05 08:06:50 +00001070 break;
1071 }
1072 case CometKernel:
1073 { double
anthony9eb4f742010-05-18 02:45:54 +00001074 sigma = fabs(args->sigma),
1075 A;
anthony602ab9b2010-01-05 08:06:50 +00001076
anthony602ab9b2010-01-05 08:06:50 +00001077 if ( args->rho < 1.0 )
1078 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
1079 else
1080 kernel->width = (unsigned long)args->rho;
cristyc99304f2010-02-01 15:26:27 +00001081 kernel->x = kernel->y = 0;
anthony602ab9b2010-01-05 08:06:50 +00001082 kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +00001083 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +00001084 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1085 kernel->height*sizeof(double));
1086 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001087 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001088
anthonyc1061722010-05-14 06:23:49 +00001089 /* A comet blur is half a 1D gaussian curve, so that the object is
anthony602ab9b2010-01-05 08:06:50 +00001090 ** blurred in one direction only. This may not be quite the right
anthony3dd0f622010-05-13 12:57:32 +00001091 ** curve to use so may change in the future. The function must be
1092 ** normalised after generation, which also resolves any clipping.
anthonyc1061722010-05-14 06:23:49 +00001093 **
1094 ** As we are normalizing and not subtracting gaussians,
1095 ** there is no need for a divisor in the gaussian formula
1096 **
anthony43c49252010-05-18 10:59:50 +00001097 ** It is less comples
anthony602ab9b2010-01-05 08:06:50 +00001098 */
anthony9eb4f742010-05-18 02:45:54 +00001099 if ( sigma > MagickEpsilon )
1100 {
anthony602ab9b2010-01-05 08:06:50 +00001101#if 1
1102#define KernelRank 3
anthony9eb4f742010-05-18 02:45:54 +00001103 v = (long) kernel->width*KernelRank; /* start/end points */
1104 (void) ResetMagickMemory(kernel->values,0, (size_t)
1105 kernel->width*sizeof(double));
1106 sigma *= KernelRank; /* simplify the loop expression */
1107 A = 1.0/(2.0*sigma*sigma);
1108 /* B = 1.0/(MagickSQ2PI*sigma); */
1109 for ( u=0; u < v; u++) {
1110 kernel->values[u/KernelRank] +=
1111 exp(-((double)(u*u))*A);
1112 /* exp(-((double)(i*i))/2.0*sigma*sigma)/(MagickSQ2PI*sigma); */
1113 }
1114 for (i=0; i < (long) kernel->width; i++)
1115 kernel->positive_range += kernel->values[i];
anthony602ab9b2010-01-05 08:06:50 +00001116#else
anthony9eb4f742010-05-18 02:45:54 +00001117 A = 1.0/(2.0*sigma*sigma); /* simplify the loop expression */
1118 /* B = 1.0/(MagickSQ2PI*sigma); */
1119 for ( i=0; i < (long) kernel->width; i++)
1120 kernel->positive_range +=
1121 kernel->values[i] =
1122 exp(-((double)(i*i))*A);
1123 /* exp(-((double)(i*i))/2.0*sigma*sigma)/(MagickSQ2PI*sigma); */
anthony602ab9b2010-01-05 08:06:50 +00001124#endif
anthony9eb4f742010-05-18 02:45:54 +00001125 }
1126 else /* special case - generate a unity kernel */
1127 { (void) ResetMagickMemory(kernel->values,0, (size_t)
1128 kernel->width*kernel->height*sizeof(double));
1129 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1130 kernel->positive_range = 1.0;
1131 }
anthony46a369d2010-05-19 02:41:48 +00001132
1133 kernel->minimum = 0.0;
cristyc99304f2010-02-01 15:26:27 +00001134 kernel->maximum = kernel->values[0];
anthony46a369d2010-05-19 02:41:48 +00001135 kernel->negative_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +00001136
anthony999bb2c2010-02-18 12:38:01 +00001137 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
1138 RotateKernelInfo(kernel, args->xi); /* Rotate by angle */
anthony602ab9b2010-01-05 08:06:50 +00001139 break;
1140 }
anthonyc1061722010-05-14 06:23:49 +00001141
anthony3c10fc82010-05-13 02:40:51 +00001142 /* Convolution Kernels - Well Known Constants */
anthony3c10fc82010-05-13 02:40:51 +00001143 case LaplacianKernel:
anthonyc1061722010-05-14 06:23:49 +00001144 {
anthony3c10fc82010-05-13 02:40:51 +00001145 switch ( (int) args->rho ) {
anthony3dd0f622010-05-13 12:57:32 +00001146 case 0:
anthony9eb4f742010-05-18 02:45:54 +00001147 default: /* laplacian square filter -- default */
anthonyc1061722010-05-14 06:23:49 +00001148 kernel=ParseKernelArray("3: -1,-1,-1 -1,8,-1 -1,-1,-1");
anthony3dd0f622010-05-13 12:57:32 +00001149 break;
anthony9eb4f742010-05-18 02:45:54 +00001150 case 1: /* laplacian diamond filter */
anthonyc1061722010-05-14 06:23:49 +00001151 kernel=ParseKernelArray("3: 0,-1,0 -1,4,-1 0,-1,0");
anthony3c10fc82010-05-13 02:40:51 +00001152 break;
1153 case 2:
anthony9eb4f742010-05-18 02:45:54 +00001154 kernel=ParseKernelArray("3: -2,1,-2 1,4,1 -2,1,-2");
1155 break;
1156 case 3:
anthonyc1061722010-05-14 06:23:49 +00001157 kernel=ParseKernelArray("3: 1,-2,1 -2,4,-2 1,-2,1");
anthony3c10fc82010-05-13 02:40:51 +00001158 break;
anthony9eb4f742010-05-18 02:45:54 +00001159 case 5: /* a 5x5 laplacian */
anthony3c10fc82010-05-13 02:40:51 +00001160 kernel=ParseKernelArray(
anthony9eb4f742010-05-18 02:45:54 +00001161 "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 +00001162 break;
anthony9eb4f742010-05-18 02:45:54 +00001163 case 7: /* a 7x7 laplacian */
anthony3c10fc82010-05-13 02:40:51 +00001164 kernel=ParseKernelArray(
anthonyc1061722010-05-14 06:23:49 +00001165 "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 +00001166 break;
anthony43c49252010-05-18 10:59:50 +00001167 case 15: /* a 5x5 LOG (sigma approx 1.4) */
anthony9eb4f742010-05-18 02:45:54 +00001168 kernel=ParseKernelArray(
1169 "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");
1170 break;
anthony43c49252010-05-18 10:59:50 +00001171 case 19: /* a 9x9 LOG (sigma approx 1.4) */
1172 /* http://www.cscjournals.org/csc/manuscript/Journals/IJIP/volume3/Issue1/IJIP-15.pdf */
1173 kernel=ParseKernelArray(
1174 "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");
1175 break;
anthony3c10fc82010-05-13 02:40:51 +00001176 }
1177 if (kernel == (KernelInfo *) NULL)
1178 return(kernel);
1179 kernel->type = type;
1180 break;
1181 }
anthonyc1061722010-05-14 06:23:49 +00001182 case SobelKernel:
anthony602ab9b2010-01-05 08:06:50 +00001183 {
anthonyc1061722010-05-14 06:23:49 +00001184 kernel=ParseKernelArray("3: -1,0,1 -2,0,2 -1,0,1");
1185 if (kernel == (KernelInfo *) NULL)
1186 return(kernel);
1187 kernel->type = type;
1188 RotateKernelInfo(kernel, args->rho); /* Rotate by angle */
1189 break;
1190 }
anthony46a369d2010-05-19 02:41:48 +00001191 case FreiChenKernel:
1192 {
1193 kernel=ParseKernelArray("3: -1,0,1 -2,0,2 -1,0,1");
1194 if (kernel == (KernelInfo *) NULL)
1195 return(kernel);
1196 kernel->values[3] = -MagickSQ2;
1197 kernel->values[5] = +MagickSQ2;
1198 CalcKernelMetaData(kernel); /* recalculate meta-data */
1199 RotateKernelInfo(kernel, args->rho); /* Rotate by angle */
1200 break;
1201 }
anthonyc1061722010-05-14 06:23:49 +00001202 case RobertsKernel:
1203 {
1204 kernel=ParseKernelArray("3: 0,0,0 -1,1,0 0,0,0");
1205 if (kernel == (KernelInfo *) NULL)
1206 return(kernel);
1207 kernel->type = type;
anthony46a369d2010-05-19 02:41:48 +00001208 RotateKernelInfo(kernel, args->rho);
anthonyc1061722010-05-14 06:23:49 +00001209 break;
1210 }
1211 case PrewittKernel:
1212 {
1213 kernel=ParseKernelArray("3: -1,1,1 0,0,0 -1,1,1");
1214 if (kernel == (KernelInfo *) NULL)
1215 return(kernel);
1216 kernel->type = type;
anthony46a369d2010-05-19 02:41:48 +00001217 RotateKernelInfo(kernel, args->rho);
anthonyc1061722010-05-14 06:23:49 +00001218 break;
1219 }
1220 case CompassKernel:
1221 {
1222 kernel=ParseKernelArray("3: -1,1,1 -1,-2,1 -1,1,1");
1223 if (kernel == (KernelInfo *) NULL)
1224 return(kernel);
1225 kernel->type = type;
anthony46a369d2010-05-19 02:41:48 +00001226 RotateKernelInfo(kernel, args->rho);
anthonyc1061722010-05-14 06:23:49 +00001227 break;
1228 }
anthony9eb4f742010-05-18 02:45:54 +00001229 case KirschKernel:
1230 {
1231 kernel=ParseKernelArray("3: -3,-3,5 -3,0,5 -3,-3,5");
1232 if (kernel == (KernelInfo *) NULL)
1233 return(kernel);
1234 kernel->type = type;
anthony46a369d2010-05-19 02:41:48 +00001235 RotateKernelInfo(kernel, args->rho);
anthony9eb4f742010-05-18 02:45:54 +00001236 break;
1237 }
anthonyc1061722010-05-14 06:23:49 +00001238 /* Boolean Kernels */
1239 case DiamondKernel:
1240 {
1241 if (args->rho < 1.0)
1242 kernel->width = kernel->height = 3; /* default radius = 1 */
1243 else
1244 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
1245 kernel->x = kernel->y = (long) (kernel->width-1)/2;
1246
1247 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1248 kernel->height*sizeof(double));
1249 if (kernel->values == (double *) NULL)
1250 return(DestroyKernelInfo(kernel));
1251
1252 /* set all kernel values within diamond area to scale given */
1253 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1254 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1255 if ((labs(u)+labs(v)) <= (long)kernel->x)
1256 kernel->positive_range += kernel->values[i] = args->sigma;
1257 else
1258 kernel->values[i] = nan;
1259 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1260 break;
1261 }
1262 case SquareKernel:
1263 case RectangleKernel:
1264 { double
1265 scale;
anthony602ab9b2010-01-05 08:06:50 +00001266 if ( type == SquareKernel )
1267 {
1268 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001269 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001270 else
cristy150989e2010-02-01 14:59:39 +00001271 kernel->width = kernel->height = (unsigned long) (2*args->rho+1);
cristyc99304f2010-02-01 15:26:27 +00001272 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony4fd27e22010-02-07 08:17:18 +00001273 scale = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +00001274 }
1275 else {
cristy2be15382010-01-21 02:38:03 +00001276 /* NOTE: user defaults set in "AcquireKernelInfo()" */
anthony602ab9b2010-01-05 08:06:50 +00001277 if ( args->rho < 1.0 || args->sigma < 1.0 )
anthony83ba99b2010-01-24 08:48:15 +00001278 return(DestroyKernelInfo(kernel)); /* invalid args given */
anthony602ab9b2010-01-05 08:06:50 +00001279 kernel->width = (unsigned long)args->rho;
1280 kernel->height = (unsigned long)args->sigma;
1281 if ( args->xi < 0.0 || args->xi > (double)kernel->width ||
1282 args->psi < 0.0 || args->psi > (double)kernel->height )
anthony83ba99b2010-01-24 08:48:15 +00001283 return(DestroyKernelInfo(kernel)); /* invalid args given */
cristyc99304f2010-02-01 15:26:27 +00001284 kernel->x = (long) args->xi;
1285 kernel->y = (long) args->psi;
anthony4fd27e22010-02-07 08:17:18 +00001286 scale = 1.0;
anthony602ab9b2010-01-05 08:06:50 +00001287 }
1288 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1289 kernel->height*sizeof(double));
1290 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001291 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001292
anthony3dd0f622010-05-13 12:57:32 +00001293 /* set all kernel values to scale given */
cristy150989e2010-02-01 14:59:39 +00001294 u=(long) kernel->width*kernel->height;
1295 for ( i=0; i < u; i++)
anthony4fd27e22010-02-07 08:17:18 +00001296 kernel->values[i] = scale;
1297 kernel->minimum = kernel->maximum = scale; /* a flat shape */
1298 kernel->positive_range = scale*u;
anthonycc6c8362010-01-25 04:14:01 +00001299 break;
anthony602ab9b2010-01-05 08:06:50 +00001300 }
anthony602ab9b2010-01-05 08:06:50 +00001301 case DiskKernel:
1302 {
anthonyc1061722010-05-14 06:23:49 +00001303 long limit = (long)(args->rho*args->rho);
anthony83ba99b2010-01-24 08:48:15 +00001304 if (args->rho < 0.1) /* default radius approx 3.5 */
1305 kernel->width = kernel->height = 7L, limit = 10L;
anthony602ab9b2010-01-05 08:06:50 +00001306 else
1307 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001308 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001309
1310 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1311 kernel->height*sizeof(double));
1312 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001313 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001314
anthony3dd0f622010-05-13 12:57:32 +00001315 /* set all kernel values within disk area to scale given */
1316 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
cristyc99304f2010-02-01 15:26:27 +00001317 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony602ab9b2010-01-05 08:06:50 +00001318 if ((u*u+v*v) <= limit)
anthony4fd27e22010-02-07 08:17:18 +00001319 kernel->positive_range += kernel->values[i] = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +00001320 else
1321 kernel->values[i] = nan;
anthony4fd27e22010-02-07 08:17:18 +00001322 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
anthony602ab9b2010-01-05 08:06:50 +00001323 break;
1324 }
1325 case PlusKernel:
1326 {
1327 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001328 kernel->width = kernel->height = 5; /* default radius 2 */
anthony602ab9b2010-01-05 08:06:50 +00001329 else
1330 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001331 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001332
1333 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1334 kernel->height*sizeof(double));
1335 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001336 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001337
anthony3dd0f622010-05-13 12:57:32 +00001338 /* set all kernel values along axises to given scale */
cristyc99304f2010-02-01 15:26:27 +00001339 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1340 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony4fd27e22010-02-07 08:17:18 +00001341 kernel->values[i] = (u == 0 || v == 0) ? args->sigma : nan;
1342 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1343 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
anthony602ab9b2010-01-05 08:06:50 +00001344 break;
1345 }
anthony3dd0f622010-05-13 12:57:32 +00001346 case CrossKernel:
1347 {
1348 if (args->rho < 1.0)
1349 kernel->width = kernel->height = 5; /* default radius 2 */
1350 else
1351 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
1352 kernel->x = kernel->y = (long) (kernel->width-1)/2;
1353
1354 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1355 kernel->height*sizeof(double));
1356 if (kernel->values == (double *) NULL)
1357 return(DestroyKernelInfo(kernel));
1358
1359 /* set all kernel values along axises to given scale */
1360 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1361 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1362 kernel->values[i] = (u == v || u == -v) ? args->sigma : nan;
1363 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
1364 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
1365 break;
1366 }
1367 /* HitAndMiss Kernels */
anthonyc1061722010-05-14 06:23:49 +00001368 case RingKernel:
anthony3dd0f622010-05-13 12:57:32 +00001369 case PeaksKernel:
1370 {
1371 long
1372 limit1,
anthonyc1061722010-05-14 06:23:49 +00001373 limit2,
1374 scale;
anthony3dd0f622010-05-13 12:57:32 +00001375
1376 if (args->rho < args->sigma)
1377 {
1378 kernel->width = ((unsigned long)args->sigma)*2+1;
1379 limit1 = (long)args->rho*args->rho;
1380 limit2 = (long)args->sigma*args->sigma;
1381 }
1382 else
1383 {
1384 kernel->width = ((unsigned long)args->rho)*2+1;
1385 limit1 = (long)args->sigma*args->sigma;
1386 limit2 = (long)args->rho*args->rho;
1387 }
anthonyc1061722010-05-14 06:23:49 +00001388 if ( limit2 <= 0 )
1389 kernel->width = 7L, limit1 = 7L, limit2 = 11L;
1390
anthony3dd0f622010-05-13 12:57:32 +00001391 kernel->height = kernel->width;
1392 kernel->x = kernel->y = (long) (kernel->width-1)/2;
1393 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1394 kernel->height*sizeof(double));
1395 if (kernel->values == (double *) NULL)
1396 return(DestroyKernelInfo(kernel));
1397
anthonyc1061722010-05-14 06:23:49 +00001398 /* set a ring of points of 'scale' ( 0.0 for PeaksKernel ) */
cristye96405a2010-05-19 02:24:31 +00001399 scale = (long) (( type == PeaksKernel) ? 0.0 : args->xi);
anthony3dd0f622010-05-13 12:57:32 +00001400 for ( i=0, v= -kernel->y; v <= (long)kernel->y; v++)
1401 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1402 { long radius=u*u+v*v;
anthonyc1061722010-05-14 06:23:49 +00001403 if (limit1 < radius && radius <= limit2)
cristye96405a2010-05-19 02:24:31 +00001404 kernel->positive_range += kernel->values[i] = (double) scale;
anthony3dd0f622010-05-13 12:57:32 +00001405 else
1406 kernel->values[i] = nan;
1407 }
cristye96405a2010-05-19 02:24:31 +00001408 kernel->minimum = kernel->minimum = (double) scale;
anthonyc1061722010-05-14 06:23:49 +00001409 if ( type == PeaksKernel ) {
1410 /* set the central point in the middle */
1411 kernel->values[kernel->x+kernel->y*kernel->width] = 1.0;
1412 kernel->positive_range = 1.0;
1413 kernel->maximum = 1.0;
1414 }
anthony3dd0f622010-05-13 12:57:32 +00001415 break;
1416 }
anthony43c49252010-05-18 10:59:50 +00001417 case EdgesKernel:
1418 {
1419 kernel=ParseKernelArray("3: 0,0,0 -,1,- 1,1,1");
1420 if (kernel == (KernelInfo *) NULL)
1421 return(kernel);
1422 kernel->type = type;
1423 ExpandKernelInfo(kernel, 90.0); /* Create a list of 4 rotated kernels */
1424 break;
1425 }
anthony3dd0f622010-05-13 12:57:32 +00001426 case CornersKernel:
1427 {
anthony4f1dcb72010-05-14 08:43:10 +00001428 kernel=ParseKernelArray("3: 0,0,- 0,1,1 -,1,-");
anthony3dd0f622010-05-13 12:57:32 +00001429 if (kernel == (KernelInfo *) NULL)
1430 return(kernel);
1431 kernel->type = type;
1432 ExpandKernelInfo(kernel, 90.0); /* Create a list of 4 rotated kernels */
1433 break;
1434 }
1435 case LineEndsKernel:
1436 {
anthony43c49252010-05-18 10:59:50 +00001437 KernelInfo
1438 *new_kernel;
1439 kernel=ParseKernelArray("3: 0,0,0 0,1,0 -,1,-");
anthony3dd0f622010-05-13 12:57:32 +00001440 if (kernel == (KernelInfo *) NULL)
1441 return(kernel);
1442 kernel->type = type;
anthony43c49252010-05-18 10:59:50 +00001443 ExpandKernelInfo(kernel, 90.0);
1444 /* append second set of 4 kernels */
1445 new_kernel=ParseKernelArray("3: 0,0,0 0,1,0 0,0,1");
1446 if (new_kernel == (KernelInfo *) NULL)
1447 return(DestroyKernelInfo(kernel));
1448 new_kernel->type = type;
1449 ExpandKernelInfo(new_kernel, 90.0);
1450 LastKernelInfo(kernel)->next = new_kernel;
anthony3dd0f622010-05-13 12:57:32 +00001451 break;
1452 }
1453 case LineJunctionsKernel:
1454 {
1455 KernelInfo
1456 *new_kernel;
anthony3dd0f622010-05-13 12:57:32 +00001457 /* first set of 4 kernels */
anthony4f1dcb72010-05-14 08:43:10 +00001458 kernel=ParseKernelArray("3: -,1,- -,1,- 1,-,1");
anthony3dd0f622010-05-13 12:57:32 +00001459 if (kernel == (KernelInfo *) NULL)
1460 return(kernel);
1461 kernel->type = type;
anthony43c49252010-05-18 10:59:50 +00001462 ExpandKernelInfo(kernel, 45.0);
anthony3dd0f622010-05-13 12:57:32 +00001463 /* append second set of 4 kernels */
anthony4f1dcb72010-05-14 08:43:10 +00001464 new_kernel=ParseKernelArray("3: 1,-,- -,1,- 1,-,1");
anthony3dd0f622010-05-13 12:57:32 +00001465 if (new_kernel == (KernelInfo *) NULL)
1466 return(DestroyKernelInfo(kernel));
anthony43c49252010-05-18 10:59:50 +00001467 new_kernel->type = type;
anthony3dd0f622010-05-13 12:57:32 +00001468 ExpandKernelInfo(new_kernel, 90.0);
1469 LastKernelInfo(kernel)->next = new_kernel;
anthony4f1dcb72010-05-14 08:43:10 +00001470 break;
1471 }
anthony3dd0f622010-05-13 12:57:32 +00001472 case ConvexHullKernel:
1473 {
1474 KernelInfo
1475 *new_kernel;
anthony3dd0f622010-05-13 12:57:32 +00001476 /* first set of 4 kernels */
anthony4f1dcb72010-05-14 08:43:10 +00001477 kernel=ParseKernelArray("3: 1,1,- 1,0,- 1,-,0");
anthony3dd0f622010-05-13 12:57:32 +00001478 if (kernel == (KernelInfo *) NULL)
1479 return(kernel);
1480 kernel->type = type;
1481 ExpandKernelInfo(kernel, 90.0);
1482 /* append second set of 4 kernels */
anthony43c49252010-05-18 10:59:50 +00001483 new_kernel=ParseKernelArray("3: 1,1,1 1,0,0 -,-,0");
anthony3dd0f622010-05-13 12:57:32 +00001484 if (new_kernel == (KernelInfo *) NULL)
1485 return(DestroyKernelInfo(kernel));
anthony43c49252010-05-18 10:59:50 +00001486 new_kernel->type = type;
anthony3dd0f622010-05-13 12:57:32 +00001487 ExpandKernelInfo(new_kernel, 90.0);
1488 LastKernelInfo(kernel)->next = new_kernel;
1489 break;
1490 }
anthony43c49252010-05-18 10:59:50 +00001491 case ThinningKernel:
1492 { /* Thinning Kernel ?? -- filled corner and edge */
1493 kernel=ParseKernelArray("3: 0,0,- 0,1,1 -,1,1");
anthony3dd0f622010-05-13 12:57:32 +00001494 if (kernel == (KernelInfo *) NULL)
1495 return(kernel);
1496 kernel->type = type;
anthony43c49252010-05-18 10:59:50 +00001497 ExpandKernelInfo(kernel, 45);
1498 break;
1499 }
1500 case SkeletonKernel:
1501 {
1502 kernel=AcquireKernelInfo("Edges;Corners");
anthony3dd0f622010-05-13 12:57:32 +00001503 break;
1504 }
anthony602ab9b2010-01-05 08:06:50 +00001505 /* Distance Measuring Kernels */
1506 case ChebyshevKernel:
1507 {
anthony602ab9b2010-01-05 08:06:50 +00001508 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001509 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001510 else
1511 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001512 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001513
1514 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1515 kernel->height*sizeof(double));
1516 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001517 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001518
cristyc99304f2010-02-01 15:26:27 +00001519 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1520 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1521 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001522 args->sigma*((labs(u)>labs(v)) ? labs(u) : labs(v)) );
cristyc99304f2010-02-01 15:26:27 +00001523 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001524 break;
1525 }
1526 case ManhattenKernel:
1527 {
anthony602ab9b2010-01-05 08:06:50 +00001528 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001529 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001530 else
1531 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001532 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001533
1534 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1535 kernel->height*sizeof(double));
1536 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001537 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001538
cristyc99304f2010-02-01 15:26:27 +00001539 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1540 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1541 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001542 args->sigma*(labs(u)+labs(v)) );
cristyc99304f2010-02-01 15:26:27 +00001543 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001544 break;
1545 }
1546 case EuclideanKernel:
1547 {
anthony602ab9b2010-01-05 08:06:50 +00001548 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +00001549 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +00001550 else
1551 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +00001552 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +00001553
1554 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1555 kernel->height*sizeof(double));
1556 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001557 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001558
cristyc99304f2010-02-01 15:26:27 +00001559 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
1560 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1561 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001562 args->sigma*sqrt((double)(u*u+v*v)) );
cristyc99304f2010-02-01 15:26:27 +00001563 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001564 break;
1565 }
anthony46a369d2010-05-19 02:41:48 +00001566 case UnityKernel:
anthony602ab9b2010-01-05 08:06:50 +00001567 default:
anthonyc1061722010-05-14 06:23:49 +00001568 {
anthony46a369d2010-05-19 02:41:48 +00001569 /* Unity or No-Op Kernel - 3x3 with 1 in center */
1570 kernel=ParseKernelArray("3:0,0,0,0,1,0,0,0,0");
anthonyc1061722010-05-14 06:23:49 +00001571 if (kernel == (KernelInfo *) NULL)
1572 return(kernel);
anthony46a369d2010-05-19 02:41:48 +00001573 kernel->type = ( type == UnityKernel ) ? UnityKernel : UndefinedKernel;
anthonyc1061722010-05-14 06:23:49 +00001574 break;
1575 }
anthony602ab9b2010-01-05 08:06:50 +00001576 break;
1577 }
1578
1579 return(kernel);
1580}
anthonyc94cdb02010-01-06 08:15:29 +00001581
anthony602ab9b2010-01-05 08:06:50 +00001582/*
1583%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1584% %
1585% %
1586% %
cristy6771f1e2010-03-05 19:43:39 +00001587% C l o n e K e r n e l I n f o %
anthony4fd27e22010-02-07 08:17:18 +00001588% %
1589% %
1590% %
1591%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1592%
anthony1b2bc0a2010-05-12 05:25:22 +00001593% CloneKernelInfo() creates a new clone of the given Kernel List so that its
1594% can be modified without effecting the original. The cloned kernel should
1595% be destroyed using DestoryKernelInfo() when no longer needed.
anthony7a01dcf2010-05-11 12:25:52 +00001596%
cristye6365592010-04-02 17:31:23 +00001597% The format of the CloneKernelInfo method is:
anthony4fd27e22010-02-07 08:17:18 +00001598%
anthony930be612010-02-08 04:26:15 +00001599% KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +00001600%
1601% A description of each parameter follows:
1602%
1603% o kernel: the Morphology/Convolution kernel to be cloned
1604%
1605*/
cristyef656912010-03-05 19:54:59 +00001606MagickExport KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +00001607{
1608 register long
1609 i;
1610
cristy19eb6412010-04-23 14:42:29 +00001611 KernelInfo
anthony7a01dcf2010-05-11 12:25:52 +00001612 *new_kernel;
anthony4fd27e22010-02-07 08:17:18 +00001613
1614 assert(kernel != (KernelInfo *) NULL);
anthony7a01dcf2010-05-11 12:25:52 +00001615 new_kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
1616 if (new_kernel == (KernelInfo *) NULL)
1617 return(new_kernel);
1618 *new_kernel=(*kernel); /* copy values in structure */
anthony7a01dcf2010-05-11 12:25:52 +00001619
1620 /* replace the values with a copy of the values */
1621 new_kernel->values=(double *) AcquireQuantumMemory(kernel->width,
cristy19eb6412010-04-23 14:42:29 +00001622 kernel->height*sizeof(double));
anthony7a01dcf2010-05-11 12:25:52 +00001623 if (new_kernel->values == (double *) NULL)
1624 return(DestroyKernelInfo(new_kernel));
anthony4fd27e22010-02-07 08:17:18 +00001625 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthony7a01dcf2010-05-11 12:25:52 +00001626 new_kernel->values[i]=kernel->values[i];
anthony1b2bc0a2010-05-12 05:25:22 +00001627
1628 /* Also clone the next kernel in the kernel list */
1629 if ( kernel->next != (KernelInfo *) NULL ) {
1630 new_kernel->next = CloneKernelInfo(kernel->next);
1631 if ( new_kernel->next == (KernelInfo *) NULL )
1632 return(DestroyKernelInfo(new_kernel));
1633 }
1634
anthony7a01dcf2010-05-11 12:25:52 +00001635 return(new_kernel);
anthony4fd27e22010-02-07 08:17:18 +00001636}
1637
1638/*
1639%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1640% %
1641% %
1642% %
anthony83ba99b2010-01-24 08:48:15 +00001643% D e s t r o y K e r n e l I n f o %
anthony602ab9b2010-01-05 08:06:50 +00001644% %
1645% %
1646% %
1647%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1648%
anthony83ba99b2010-01-24 08:48:15 +00001649% DestroyKernelInfo() frees the memory used by a Convolution/Morphology
1650% kernel.
anthony602ab9b2010-01-05 08:06:50 +00001651%
anthony83ba99b2010-01-24 08:48:15 +00001652% The format of the DestroyKernelInfo method is:
anthony602ab9b2010-01-05 08:06:50 +00001653%
anthony83ba99b2010-01-24 08:48:15 +00001654% KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +00001655%
1656% A description of each parameter follows:
1657%
1658% o kernel: the Morphology/Convolution kernel to be destroyed
1659%
1660*/
1661
anthony83ba99b2010-01-24 08:48:15 +00001662MagickExport KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +00001663{
cristy2be15382010-01-21 02:38:03 +00001664 assert(kernel != (KernelInfo *) NULL);
anthony4fd27e22010-02-07 08:17:18 +00001665
anthony7a01dcf2010-05-11 12:25:52 +00001666 if ( kernel->next != (KernelInfo *) NULL )
1667 kernel->next = DestroyKernelInfo(kernel->next);
1668
1669 kernel->values = (double *)RelinquishMagickMemory(kernel->values);
1670 kernel = (KernelInfo *) RelinquishMagickMemory(kernel);
anthony602ab9b2010-01-05 08:06:50 +00001671 return(kernel);
1672}
anthonyc94cdb02010-01-06 08:15:29 +00001673
1674/*
1675%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1676% %
1677% %
1678% %
anthony3c10fc82010-05-13 02:40:51 +00001679% E x p a n d K e r n e l I n f o %
1680% %
1681% %
1682% %
1683%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1684%
1685% ExpandKernelInfo() takes a single kernel, and expands it into a list
1686% of kernels each incrementally rotated the angle given.
1687%
1688% WARNING: 45 degree rotations only works for 3x3 kernels.
1689% While 90 degree roatations only works for linear and square kernels
1690%
1691% The format of the RotateKernelInfo method is:
1692%
1693% void ExpandKernelInfo(KernelInfo *kernel, double angle)
1694%
1695% A description of each parameter follows:
1696%
1697% o kernel: the Morphology/Convolution kernel
1698%
1699% o angle: angle to rotate in degrees
1700%
1701% This function is only internel to this module, as it is not finalized,
1702% especially with regard to non-orthogonal angles, and rotation of larger
1703% 2D kernels.
1704*/
1705static void ExpandKernelInfo(KernelInfo *kernel, double angle)
1706{
1707 KernelInfo
cristya9a61ad2010-05-13 12:47:41 +00001708 *clone,
anthony3c10fc82010-05-13 02:40:51 +00001709 *last;
cristya9a61ad2010-05-13 12:47:41 +00001710
anthony3c10fc82010-05-13 02:40:51 +00001711 double
1712 a;
1713
1714 last = kernel;
1715 for (a=angle; a<355.0; a+=angle) {
cristya9a61ad2010-05-13 12:47:41 +00001716 clone = CloneKernelInfo(last);
1717 RotateKernelInfo(clone, angle);
1718 last->next = clone;
1719 last = clone;
anthony3c10fc82010-05-13 02:40:51 +00001720 }
1721}
1722
1723/*
1724%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1725% %
1726% %
1727% %
anthony46a369d2010-05-19 02:41:48 +00001728+ C a l c M e t a K e r n a l I n f o %
1729% %
1730% %
1731% %
1732%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1733%
1734% CalcKernelMetaData() recalculate the KernelInfo meta-data of this kernel only,
1735% using the kernel values. This should only ne used if it is not posible to
1736% calculate that meta-data in some easier way.
1737%
1738% It is important that the meta-data is correct before ScaleKernelInfo() is
1739% used to perform kernel normalization.
1740%
1741% The format of the CalcKernelMetaData method is:
1742%
1743% void CalcKernelMetaData(KernelInfo *kernel, const double scale )
1744%
1745% A description of each parameter follows:
1746%
1747% o kernel: the Morphology/Convolution kernel to modify
1748%
1749% WARNING: Minimum and Maximum values are assumed to include zero, even if
1750% zero is not part of the kernel (as in Gaussian Derived kernels). This
1751% however is not true for flat-shaped morphological kernels.
1752%
1753% WARNING: Only the specific kernel pointed to is modified, not a list of
1754% multiple kernels.
1755%
1756% This is an internal function and not expected to be useful outside this
1757% module. This could change however.
1758*/
1759static void CalcKernelMetaData(KernelInfo *kernel)
1760{
1761 register unsigned long
1762 i;
1763
1764 kernel->minimum = kernel->maximum = 0.0;
1765 kernel->negative_range = kernel->positive_range = 0.0;
1766 for (i=0; i < (kernel->width*kernel->height); i++)
1767 {
1768 if ( fabs(kernel->values[i]) < MagickEpsilon )
1769 kernel->values[i] = 0.0;
1770 ( kernel->values[i] < 0)
1771 ? ( kernel->negative_range += kernel->values[i] )
1772 : ( kernel->positive_range += kernel->values[i] );
1773 Minimize(kernel->minimum, kernel->values[i]);
1774 Maximize(kernel->maximum, kernel->values[i]);
1775 }
1776
1777 return;
1778}
1779
1780/*
1781%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1782% %
1783% %
1784% %
anthony9eb4f742010-05-18 02:45:54 +00001785% M o r p h o l o g y A p p l y %
anthony602ab9b2010-01-05 08:06:50 +00001786% %
1787% %
1788% %
1789%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1790%
anthony9eb4f742010-05-18 02:45:54 +00001791% MorphologyApply() applies a morphological method, multiple times using
1792% a list of multiple kernels.
anthony602ab9b2010-01-05 08:06:50 +00001793%
anthony9eb4f742010-05-18 02:45:54 +00001794% It is basically equivelent to as MorphologyImageChannel() (see below) but
1795% without user controls, that that function extracts and applies to kernels
1796% and morphology methods.
1797%
1798% More specifically kernels are not normalized/scaled/blended by the
1799% 'convolve:scale' Image Artifact (-set setting), and the convolve bias
1800% (-bias setting or image->bias) is passed directly to this function,
1801% and not extracted from an image.
anthony602ab9b2010-01-05 08:06:50 +00001802%
1803% The format of the MorphologyImage method is:
1804%
anthony9eb4f742010-05-18 02:45:54 +00001805% Image *MorphologyApply(const Image *image,MorphologyMethod method,
1806% const long iterations,const KernelInfo *kernel,const double bias,
1807% ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001808%
1809% A description of each parameter follows:
1810%
1811% o image: the image.
1812%
1813% o method: the morphology method to be applied.
1814%
1815% o iterations: apply the operation this many times (or no change).
1816% A value of -1 means loop until no change found.
1817% How this is applied may depend on the morphology method.
1818% Typically this is a value of 1.
1819%
1820% o channel: the channel type.
1821%
1822% o kernel: An array of double representing the morphology kernel.
anthony29188a82010-01-22 10:12:34 +00001823% Warning: kernel may be normalized for the Convolve method.
anthony602ab9b2010-01-05 08:06:50 +00001824%
anthony9eb4f742010-05-18 02:45:54 +00001825% o bias: Convolution Bias to use.
1826%
anthony602ab9b2010-01-05 08:06:50 +00001827% o exception: return any errors or warnings in this structure.
1828%
anthony602ab9b2010-01-05 08:06:50 +00001829*/
1830
anthony930be612010-02-08 04:26:15 +00001831
anthony9eb4f742010-05-18 02:45:54 +00001832/* Apply a Morphology Primative to an image using the given kernel.
1833** Two pre-created images must be provided, no image is created.
1834** Returning the number of pixels that changed.
1835*/
anthony46a369d2010-05-19 02:41:48 +00001836static unsigned long MorphologyPrimitive(const Image *image, Image
anthony602ab9b2010-01-05 08:06:50 +00001837 *result_image, const MorphologyMethod method, const ChannelType channel,
anthony9eb4f742010-05-18 02:45:54 +00001838 const KernelInfo *kernel,const double bias,ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001839{
cristy2be15382010-01-21 02:38:03 +00001840#define MorphologyTag "Morphology/Image"
anthony602ab9b2010-01-05 08:06:50 +00001841
1842 long
cristy150989e2010-02-01 14:59:39 +00001843 progress,
anthony29188a82010-01-22 10:12:34 +00001844 y, offx, offy,
anthony602ab9b2010-01-05 08:06:50 +00001845 changed;
1846
1847 MagickBooleanType
1848 status;
1849
anthony602ab9b2010-01-05 08:06:50 +00001850 CacheView
1851 *p_view,
1852 *q_view;
1853
anthony602ab9b2010-01-05 08:06:50 +00001854 status=MagickTrue;
1855 changed=0;
1856 progress=0;
1857
anthony602ab9b2010-01-05 08:06:50 +00001858 p_view=AcquireCacheView(image);
1859 q_view=AcquireCacheView(result_image);
anthony29188a82010-01-22 10:12:34 +00001860
anthonycc6c8362010-01-25 04:14:01 +00001861 /* Some methods (including convolve) needs use a reflected kernel.
anthony9eb4f742010-05-18 02:45:54 +00001862 * Adjust 'origin' offsets to loop though kernel as a reflection.
anthony29188a82010-01-22 10:12:34 +00001863 */
cristyc99304f2010-02-01 15:26:27 +00001864 offx = kernel->x;
1865 offy = kernel->y;
anthony29188a82010-01-22 10:12:34 +00001866 switch(method) {
anthony930be612010-02-08 04:26:15 +00001867 case ConvolveMorphology:
1868 case DilateMorphology:
1869 case DilateIntensityMorphology:
1870 case DistanceMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001871 /* kernel needs to used with reflection about origin */
cristy150989e2010-02-01 14:59:39 +00001872 offx = (long) kernel->width-offx-1;
1873 offy = (long) kernel->height-offy-1;
anthony29188a82010-01-22 10:12:34 +00001874 break;
anthony5ef8e942010-05-11 06:51:12 +00001875 case ErodeMorphology:
1876 case ErodeIntensityMorphology:
1877 case HitAndMissMorphology:
1878 case ThinningMorphology:
1879 case ThickenMorphology:
1880 /* kernel is user as is, without reflection */
1881 break;
anthony930be612010-02-08 04:26:15 +00001882 default:
anthony9eb4f742010-05-18 02:45:54 +00001883 assert("Not a Primitive Morphology Method" != (char *) NULL);
anthony930be612010-02-08 04:26:15 +00001884 break;
anthony29188a82010-01-22 10:12:34 +00001885 }
1886
anthony602ab9b2010-01-05 08:06:50 +00001887#if defined(MAGICKCORE_OPENMP_SUPPORT)
1888 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1889#endif
cristy150989e2010-02-01 14:59:39 +00001890 for (y=0; y < (long) image->rows; y++)
anthony602ab9b2010-01-05 08:06:50 +00001891 {
1892 MagickBooleanType
1893 sync;
1894
1895 register const PixelPacket
1896 *restrict p;
1897
1898 register const IndexPacket
1899 *restrict p_indexes;
1900
1901 register PixelPacket
1902 *restrict q;
1903
1904 register IndexPacket
1905 *restrict q_indexes;
1906
cristy150989e2010-02-01 14:59:39 +00001907 register long
anthony602ab9b2010-01-05 08:06:50 +00001908 x;
1909
anthony29188a82010-01-22 10:12:34 +00001910 unsigned long
anthony602ab9b2010-01-05 08:06:50 +00001911 r;
1912
1913 if (status == MagickFalse)
1914 continue;
anthony29188a82010-01-22 10:12:34 +00001915 p=GetCacheViewVirtualPixels(p_view, -offx, y-offy,
1916 image->columns+kernel->width, kernel->height, exception);
anthony602ab9b2010-01-05 08:06:50 +00001917 q=GetCacheViewAuthenticPixels(q_view,0,y,result_image->columns,1,
1918 exception);
1919 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1920 {
1921 status=MagickFalse;
1922 continue;
1923 }
1924 p_indexes=GetCacheViewVirtualIndexQueue(p_view);
1925 q_indexes=GetCacheViewAuthenticIndexQueue(q_view);
anthony29188a82010-01-22 10:12:34 +00001926 r = (image->columns+kernel->width)*offy+offx; /* constant */
1927
cristy150989e2010-02-01 14:59:39 +00001928 for (x=0; x < (long) image->columns; x++)
anthony602ab9b2010-01-05 08:06:50 +00001929 {
cristy150989e2010-02-01 14:59:39 +00001930 long
anthony602ab9b2010-01-05 08:06:50 +00001931 v;
1932
cristy150989e2010-02-01 14:59:39 +00001933 register long
anthony602ab9b2010-01-05 08:06:50 +00001934 u;
1935
1936 register const double
1937 *restrict k;
1938
1939 register const PixelPacket
1940 *restrict k_pixels;
1941
1942 register const IndexPacket
1943 *restrict k_indexes;
1944
1945 MagickPixelPacket
anthony5ef8e942010-05-11 06:51:12 +00001946 result,
1947 min,
1948 max;
anthony602ab9b2010-01-05 08:06:50 +00001949
anthony29188a82010-01-22 10:12:34 +00001950 /* Copy input to ouput image for unused channels
anthony83ba99b2010-01-24 08:48:15 +00001951 * This removes need for 'cloning' a new image every iteration
anthony29188a82010-01-22 10:12:34 +00001952 */
anthony602ab9b2010-01-05 08:06:50 +00001953 *q = p[r];
1954 if (image->colorspace == CMYKColorspace)
1955 q_indexes[x] = p_indexes[r];
1956
anthony5ef8e942010-05-11 06:51:12 +00001957 /* Defaults */
1958 min.red =
1959 min.green =
1960 min.blue =
1961 min.opacity =
1962 min.index = (MagickRealType) QuantumRange;
1963 max.red =
1964 max.green =
1965 max.blue =
1966 max.opacity =
1967 max.index = (MagickRealType) 0;
anthony9eb4f742010-05-18 02:45:54 +00001968 /* default result is the original pixel value */
anthony5ef8e942010-05-11 06:51:12 +00001969 result.red = (MagickRealType) p[r].red;
1970 result.green = (MagickRealType) p[r].green;
1971 result.blue = (MagickRealType) p[r].blue;
1972 result.opacity = QuantumRange - (MagickRealType) p[r].opacity;
cristye96405a2010-05-19 02:24:31 +00001973 result.index = 0.0;
anthony5ef8e942010-05-11 06:51:12 +00001974 if ( image->colorspace == CMYKColorspace)
1975 result.index = (MagickRealType) p_indexes[r];
1976
anthony602ab9b2010-01-05 08:06:50 +00001977 switch (method) {
1978 case ConvolveMorphology:
anthony9eb4f742010-05-18 02:45:54 +00001979 /* Set the user defined bias of the weighted average output */
1980 result.red =
1981 result.green =
1982 result.blue =
1983 result.opacity =
1984 result.index = bias;
anthony930be612010-02-08 04:26:15 +00001985 break;
anthony4fd27e22010-02-07 08:17:18 +00001986 case DilateIntensityMorphology:
1987 case ErodeIntensityMorphology:
anthony9eb4f742010-05-18 02:45:54 +00001988 /* use a boolean flag indicating when first match found */
1989 result.red = 0.0; /* result is not used otherwise */
anthony4fd27e22010-02-07 08:17:18 +00001990 break;
anthony602ab9b2010-01-05 08:06:50 +00001991 default:
anthony602ab9b2010-01-05 08:06:50 +00001992 break;
1993 }
1994
1995 switch ( method ) {
1996 case ConvolveMorphology:
anthony930be612010-02-08 04:26:15 +00001997 /* Weighted Average of pixels using reflected kernel
1998 **
1999 ** NOTE for correct working of this operation for asymetrical
2000 ** kernels, the kernel needs to be applied in its reflected form.
2001 ** That is its values needs to be reversed.
2002 **
2003 ** Correlation is actually the same as this but without reflecting
2004 ** the kernel, and thus 'lower-level' that Convolution. However
2005 ** as Convolution is the more common method used, and it does not
2006 ** really cost us much in terms of processing to use a reflected
anthony5ef8e942010-05-11 06:51:12 +00002007 ** kernel, so it is Convolution that is implemented.
anthony930be612010-02-08 04:26:15 +00002008 **
2009 ** Correlation will have its kernel reflected before calling
2010 ** this function to do a Convolve.
2011 **
2012 ** For more details of Correlation vs Convolution see
2013 ** http://www.cs.umd.edu/~djacobs/CMSC426/Convolution.pdf
2014 */
anthony5ef8e942010-05-11 06:51:12 +00002015 if (((channel & SyncChannels) != 0 ) &&
2016 (image->matte == MagickTrue))
2017 { /* Channel has a 'Sync' Flag, and Alpha Channel enabled.
2018 ** Weight the color channels with Alpha Channel so that
2019 ** transparent pixels are not part of the results.
2020 */
anthony602ab9b2010-01-05 08:06:50 +00002021 MagickRealType
anthony5ef8e942010-05-11 06:51:12 +00002022 alpha, /* color channel weighting : kernel*alpha */
2023 gamma; /* divisor, sum of weighting values */
anthony602ab9b2010-01-05 08:06:50 +00002024
2025 gamma=0.0;
anthony29188a82010-01-22 10:12:34 +00002026 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00002027 k_pixels = p;
2028 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002029 for (v=0; v < (long) kernel->height; v++) {
2030 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00002031 if ( IsNan(*k) ) continue;
2032 alpha=(*k)*(QuantumScale*(QuantumRange-
2033 k_pixels[u].opacity));
2034 gamma += alpha;
2035 result.red += alpha*k_pixels[u].red;
2036 result.green += alpha*k_pixels[u].green;
2037 result.blue += alpha*k_pixels[u].blue;
anthony83ba99b2010-01-24 08:48:15 +00002038 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00002039 if ( image->colorspace == CMYKColorspace)
2040 result.index += alpha*k_indexes[u];
2041 }
2042 k_pixels += image->columns+kernel->width;
2043 k_indexes += image->columns+kernel->width;
2044 }
2045 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
anthony83ba99b2010-01-24 08:48:15 +00002046 result.red *= gamma;
2047 result.green *= gamma;
2048 result.blue *= gamma;
2049 result.opacity *= gamma;
2050 result.index *= gamma;
anthony602ab9b2010-01-05 08:06:50 +00002051 }
anthony5ef8e942010-05-11 06:51:12 +00002052 else
2053 {
2054 /* No 'Sync' flag, or no Alpha involved.
2055 ** Convolution is simple individual channel weigthed sum.
2056 */
2057 k = &kernel->values[ kernel->width*kernel->height-1 ];
2058 k_pixels = p;
2059 k_indexes = p_indexes;
2060 for (v=0; v < (long) kernel->height; v++) {
2061 for (u=0; u < (long) kernel->width; u++, k--) {
2062 if ( IsNan(*k) ) continue;
2063 result.red += (*k)*k_pixels[u].red;
2064 result.green += (*k)*k_pixels[u].green;
2065 result.blue += (*k)*k_pixels[u].blue;
2066 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
2067 if ( image->colorspace == CMYKColorspace)
2068 result.index += (*k)*k_indexes[u];
2069 }
2070 k_pixels += image->columns+kernel->width;
2071 k_indexes += image->columns+kernel->width;
2072 }
2073 }
anthony602ab9b2010-01-05 08:06:50 +00002074 break;
2075
anthony4fd27e22010-02-07 08:17:18 +00002076 case ErodeMorphology:
anthony5ef8e942010-05-11 06:51:12 +00002077 /* Minimum Value within kernel neighbourhood
anthony930be612010-02-08 04:26:15 +00002078 **
2079 ** NOTE that the kernel is not reflected for this operation!
2080 **
2081 ** NOTE: in normal Greyscale Morphology, the kernel value should
2082 ** be added to the real value, this is currently not done, due to
2083 ** the nature of the boolean kernels being used.
2084 */
anthony4fd27e22010-02-07 08:17:18 +00002085 k = kernel->values;
2086 k_pixels = p;
2087 k_indexes = p_indexes;
2088 for (v=0; v < (long) kernel->height; v++) {
2089 for (u=0; u < (long) kernel->width; u++, k++) {
2090 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony5ef8e942010-05-11 06:51:12 +00002091 Minimize(min.red, (double) k_pixels[u].red);
2092 Minimize(min.green, (double) k_pixels[u].green);
2093 Minimize(min.blue, (double) k_pixels[u].blue);
2094 Minimize(min.opacity,
anthonyd37a5cb2010-05-07 06:37:03 +00002095 QuantumRange-(double) k_pixels[u].opacity);
anthony4fd27e22010-02-07 08:17:18 +00002096 if ( image->colorspace == CMYKColorspace)
anthony5ef8e942010-05-11 06:51:12 +00002097 Minimize(min.index, (double) k_indexes[u]);
anthony4fd27e22010-02-07 08:17:18 +00002098 }
2099 k_pixels += image->columns+kernel->width;
2100 k_indexes += image->columns+kernel->width;
2101 }
2102 break;
2103
anthony5ef8e942010-05-11 06:51:12 +00002104
anthony83ba99b2010-01-24 08:48:15 +00002105 case DilateMorphology:
anthony5ef8e942010-05-11 06:51:12 +00002106 /* Maximum Value within kernel neighbourhood
anthony930be612010-02-08 04:26:15 +00002107 **
2108 ** NOTE for correct working of this operation for asymetrical
2109 ** kernels, the kernel needs to be applied in its reflected form.
2110 ** That is its values needs to be reversed.
2111 **
2112 ** NOTE: in normal Greyscale Morphology, the kernel value should
2113 ** be added to the real value, this is currently not done, due to
2114 ** the nature of the boolean kernels being used.
2115 **
2116 */
anthony29188a82010-01-22 10:12:34 +00002117 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00002118 k_pixels = p;
2119 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002120 for (v=0; v < (long) kernel->height; v++) {
2121 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00002122 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony5ef8e942010-05-11 06:51:12 +00002123 Maximize(max.red, (double) k_pixels[u].red);
2124 Maximize(max.green, (double) k_pixels[u].green);
2125 Maximize(max.blue, (double) k_pixels[u].blue);
2126 Maximize(max.opacity,
anthonyd37a5cb2010-05-07 06:37:03 +00002127 QuantumRange-(double) k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00002128 if ( image->colorspace == CMYKColorspace)
anthony5ef8e942010-05-11 06:51:12 +00002129 Maximize(max.index, (double) k_indexes[u]);
anthony602ab9b2010-01-05 08:06:50 +00002130 }
2131 k_pixels += image->columns+kernel->width;
2132 k_indexes += image->columns+kernel->width;
2133 }
anthony602ab9b2010-01-05 08:06:50 +00002134 break;
2135
anthony5ef8e942010-05-11 06:51:12 +00002136 case HitAndMissMorphology:
2137 case ThinningMorphology:
2138 case ThickenMorphology:
2139 /* Minimum of Foreground Pixel minus Maxumum of Background Pixels
2140 **
2141 ** NOTE that the kernel is not reflected for this operation,
2142 ** and consists of both foreground and background pixel
2143 ** neighbourhoods, 0.0 for background, and 1.0 for foreground
2144 ** with either Nan or 0.5 values for don't care.
2145 **
2146 ** Note that this can produce negative results, though really
2147 ** only a positive match has any real value.
2148 */
2149 k = kernel->values;
2150 k_pixels = p;
2151 k_indexes = p_indexes;
2152 for (v=0; v < (long) kernel->height; v++) {
2153 for (u=0; u < (long) kernel->width; u++, k++) {
2154 if ( IsNan(*k) ) continue;
2155 if ( (*k) > 0.7 )
2156 { /* minimim of foreground pixels */
2157 Minimize(min.red, (double) k_pixels[u].red);
2158 Minimize(min.green, (double) k_pixels[u].green);
2159 Minimize(min.blue, (double) k_pixels[u].blue);
2160 Minimize(min.opacity,
2161 QuantumRange-(double) k_pixels[u].opacity);
2162 if ( image->colorspace == CMYKColorspace)
2163 Minimize(min.index, (double) k_indexes[u]);
2164 }
2165 else if ( (*k) < 0.3 )
2166 { /* maximum of background pixels */
2167 Maximize(max.red, (double) k_pixels[u].red);
2168 Maximize(max.green, (double) k_pixels[u].green);
2169 Maximize(max.blue, (double) k_pixels[u].blue);
2170 Maximize(max.opacity,
2171 QuantumRange-(double) k_pixels[u].opacity);
2172 if ( image->colorspace == CMYKColorspace)
2173 Maximize(max.index, (double) k_indexes[u]);
2174 }
2175 }
2176 k_pixels += image->columns+kernel->width;
2177 k_indexes += image->columns+kernel->width;
2178 }
2179 /* Pattern Match only if min fg larger than min bg pixels */
2180 min.red -= max.red; Maximize( min.red, 0.0 );
2181 min.green -= max.green; Maximize( min.green, 0.0 );
2182 min.blue -= max.blue; Maximize( min.blue, 0.0 );
2183 min.opacity -= max.opacity; Maximize( min.opacity, 0.0 );
2184 min.index -= max.index; Maximize( min.index, 0.0 );
2185 break;
2186
anthony4fd27e22010-02-07 08:17:18 +00002187 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00002188 /* Select Pixel with Minimum Intensity within kernel neighbourhood
2189 **
2190 ** WARNING: the intensity test fails for CMYK and does not
2191 ** take into account the moderating effect of teh alpha channel
2192 ** on the intensity.
2193 **
2194 ** NOTE that the kernel is not reflected for this operation!
2195 */
anthony602ab9b2010-01-05 08:06:50 +00002196 k = kernel->values;
2197 k_pixels = p;
2198 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002199 for (v=0; v < (long) kernel->height; v++) {
2200 for (u=0; u < (long) kernel->width; u++, k++) {
anthony602ab9b2010-01-05 08:06:50 +00002201 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony4fd27e22010-02-07 08:17:18 +00002202 if ( result.red == 0.0 ||
2203 PixelIntensity(&(k_pixels[u])) < PixelIntensity(q) ) {
2204 /* copy the whole pixel - no channel selection */
2205 *q = k_pixels[u];
2206 if ( result.red > 0.0 ) changed++;
2207 result.red = 1.0;
2208 }
anthony602ab9b2010-01-05 08:06:50 +00002209 }
2210 k_pixels += image->columns+kernel->width;
2211 k_indexes += image->columns+kernel->width;
2212 }
anthony602ab9b2010-01-05 08:06:50 +00002213 break;
2214
anthony83ba99b2010-01-24 08:48:15 +00002215 case DilateIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00002216 /* Select Pixel with Maximum Intensity within kernel neighbourhood
2217 **
2218 ** WARNING: the intensity test fails for CMYK and does not
anthony9eb4f742010-05-18 02:45:54 +00002219 ** take into account the moderating effect of the alpha channel
2220 ** on the intensity (yet).
anthony930be612010-02-08 04:26:15 +00002221 **
2222 ** NOTE for correct working of this operation for asymetrical
2223 ** kernels, the kernel needs to be applied in its reflected form.
2224 ** That is its values needs to be reversed.
2225 */
anthony29188a82010-01-22 10:12:34 +00002226 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00002227 k_pixels = p;
2228 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002229 for (v=0; v < (long) kernel->height; v++) {
2230 for (u=0; u < (long) kernel->width; u++, k--) {
anthony29188a82010-01-22 10:12:34 +00002231 if ( IsNan(*k) || (*k) < 0.5 ) continue; /* boolean kernel */
2232 if ( result.red == 0.0 ||
2233 PixelIntensity(&(k_pixels[u])) > PixelIntensity(q) ) {
2234 /* copy the whole pixel - no channel selection */
2235 *q = k_pixels[u];
2236 if ( result.red > 0.0 ) changed++;
2237 result.red = 1.0;
2238 }
anthony602ab9b2010-01-05 08:06:50 +00002239 }
2240 k_pixels += image->columns+kernel->width;
2241 k_indexes += image->columns+kernel->width;
2242 }
anthony602ab9b2010-01-05 08:06:50 +00002243 break;
2244
anthony5ef8e942010-05-11 06:51:12 +00002245
anthony602ab9b2010-01-05 08:06:50 +00002246 case DistanceMorphology:
anthony930be612010-02-08 04:26:15 +00002247 /* Add kernel Value and select the minimum value found.
2248 ** The result is a iterative distance from edge of image shape.
2249 **
2250 ** All Distance Kernels are symetrical, but that may not always
2251 ** be the case. For example how about a distance from left edges?
2252 ** To work correctly with asymetrical kernels the reflected kernel
2253 ** needs to be applied.
anthony5ef8e942010-05-11 06:51:12 +00002254 **
2255 ** Actually this is really a GreyErode with a negative kernel!
2256 **
anthony930be612010-02-08 04:26:15 +00002257 */
anthony29188a82010-01-22 10:12:34 +00002258 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00002259 k_pixels = p;
2260 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00002261 for (v=0; v < (long) kernel->height; v++) {
2262 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00002263 if ( IsNan(*k) ) continue;
2264 Minimize(result.red, (*k)+k_pixels[u].red);
2265 Minimize(result.green, (*k)+k_pixels[u].green);
2266 Minimize(result.blue, (*k)+k_pixels[u].blue);
2267 Minimize(result.opacity, (*k)+QuantumRange-k_pixels[u].opacity);
2268 if ( image->colorspace == CMYKColorspace)
2269 Minimize(result.index, (*k)+k_indexes[u]);
2270 }
2271 k_pixels += image->columns+kernel->width;
2272 k_indexes += image->columns+kernel->width;
2273 }
anthony602ab9b2010-01-05 08:06:50 +00002274 break;
2275
2276 case UndefinedMorphology:
2277 default:
2278 break; /* Do nothing */
anthony83ba99b2010-01-24 08:48:15 +00002279 }
anthony5ef8e942010-05-11 06:51:12 +00002280 /* Final mathematics of results (combine with original image?)
2281 **
2282 ** NOTE: Difference Morphology operators Edge* and *Hat could also
2283 ** be done here but works better with iteration as a image difference
2284 ** in the controling function (below). Thicken and Thinning however
2285 ** should be done here so thay can be iterated correctly.
2286 */
2287 switch ( method ) {
2288 case HitAndMissMorphology:
2289 case ErodeMorphology:
2290 result = min; /* minimum of neighbourhood */
2291 break;
2292 case DilateMorphology:
2293 result = max; /* maximum of neighbourhood */
2294 break;
2295 case ThinningMorphology:
2296 /* subtract pattern match from original */
2297 result.red -= min.red;
2298 result.green -= min.green;
2299 result.blue -= min.blue;
2300 result.opacity -= min.opacity;
2301 result.index -= min.index;
2302 break;
2303 case ThickenMorphology:
2304 /* Union with original image (maximize) - or should this be + */
2305 Maximize( result.red, min.red );
2306 Maximize( result.green, min.green );
2307 Maximize( result.blue, min.blue );
2308 Maximize( result.opacity, min.opacity );
2309 Maximize( result.index, min.index );
2310 break;
2311 default:
2312 /* result directly calculated or assigned */
2313 break;
2314 }
2315 /* Assign the resulting pixel values - Clamping Result */
anthony83ba99b2010-01-24 08:48:15 +00002316 switch ( method ) {
2317 case UndefinedMorphology:
2318 case DilateIntensityMorphology:
2319 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00002320 break; /* full pixel was directly assigned - not a channel method */
anthony83ba99b2010-01-24 08:48:15 +00002321 default:
anthony83ba99b2010-01-24 08:48:15 +00002322 if ((channel & RedChannel) != 0)
2323 q->red = ClampToQuantum(result.red);
2324 if ((channel & GreenChannel) != 0)
2325 q->green = ClampToQuantum(result.green);
2326 if ((channel & BlueChannel) != 0)
2327 q->blue = ClampToQuantum(result.blue);
2328 if ((channel & OpacityChannel) != 0
2329 && image->matte == MagickTrue )
2330 q->opacity = ClampToQuantum(QuantumRange-result.opacity);
2331 if ((channel & IndexChannel) != 0
2332 && image->colorspace == CMYKColorspace)
2333 q_indexes[x] = ClampToQuantum(result.index);
2334 break;
2335 }
anthony5ef8e942010-05-11 06:51:12 +00002336 /* Count up changed pixels */
anthony83ba99b2010-01-24 08:48:15 +00002337 if ( ( p[r].red != q->red )
2338 || ( p[r].green != q->green )
2339 || ( p[r].blue != q->blue )
2340 || ( p[r].opacity != q->opacity )
2341 || ( image->colorspace == CMYKColorspace &&
2342 p_indexes[r] != q_indexes[x] ) )
2343 changed++; /* The pixel had some value changed! */
anthony602ab9b2010-01-05 08:06:50 +00002344 p++;
2345 q++;
anthony83ba99b2010-01-24 08:48:15 +00002346 } /* x */
anthony602ab9b2010-01-05 08:06:50 +00002347 sync=SyncCacheViewAuthenticPixels(q_view,exception);
2348 if (sync == MagickFalse)
2349 status=MagickFalse;
2350 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2351 {
2352 MagickBooleanType
2353 proceed;
2354
2355#if defined(MAGICKCORE_OPENMP_SUPPORT)
2356 #pragma omp critical (MagickCore_MorphologyImage)
2357#endif
2358 proceed=SetImageProgress(image,MorphologyTag,progress++,image->rows);
2359 if (proceed == MagickFalse)
2360 status=MagickFalse;
2361 }
anthony83ba99b2010-01-24 08:48:15 +00002362 } /* y */
anthony602ab9b2010-01-05 08:06:50 +00002363 result_image->type=image->type;
2364 q_view=DestroyCacheView(q_view);
2365 p_view=DestroyCacheView(p_view);
cristy150989e2010-02-01 14:59:39 +00002366 return(status ? (unsigned long) changed : 0);
anthony602ab9b2010-01-05 08:06:50 +00002367}
2368
anthony4fd27e22010-02-07 08:17:18 +00002369
anthony9eb4f742010-05-18 02:45:54 +00002370MagickExport Image *MorphologyApply(const Image *image, const ChannelType
2371 channel,const MorphologyMethod method, const long iterations,
2372 const KernelInfo *kernel,const double bias,ExceptionInfo *exception)
cristy2be15382010-01-21 02:38:03 +00002373{
2374 Image
anthony9eb4f742010-05-18 02:45:54 +00002375 *curr_image, /* Image we are working with */
2376 *work_image, /* secondary working image */
2377 *save_image; /* save image for later use */
anthony602ab9b2010-01-05 08:06:50 +00002378
anthony4fd27e22010-02-07 08:17:18 +00002379 KernelInfo
anthony9eb4f742010-05-18 02:45:54 +00002380 *curr_kernel, /* current kernel list to apply */
2381 *this_kernel; /* current individual kernel to apply */
anthony4fd27e22010-02-07 08:17:18 +00002382
2383 MorphologyMethod
cristye96405a2010-05-19 02:24:31 +00002384 primitive; /* the current morphology primitive being applied */
anthony9eb4f742010-05-18 02:45:54 +00002385
2386 MagickBooleanType
2387 verbose; /* verbose output of results */
2388
2389 CompositeOperator
2390 kernel_compose; /* Handling the result of multiple kernels*/
anthony4fd27e22010-02-07 08:17:18 +00002391
anthony1b2bc0a2010-05-12 05:25:22 +00002392 unsigned long
cristye96405a2010-05-19 02:24:31 +00002393 count, /* count of primitive steps applied */
anthony9eb4f742010-05-18 02:45:54 +00002394 loop, /* number of times though kernel list (iterations) */
2395 loop_limit, /* finish looping after this many times */
2396 stage, /* stage number for compound morphology */
cristye96405a2010-05-19 02:24:31 +00002397 changed, /* number pixels changed by one primitive operation */
anthony9eb4f742010-05-18 02:45:54 +00002398 loop_changed, /* changes made over loop though of kernels */
2399 total_changed, /* total count of all changes to image */
2400 kernel_number; /* kernel number being applied */
anthony1b2bc0a2010-05-12 05:25:22 +00002401
anthony602ab9b2010-01-05 08:06:50 +00002402 assert(image != (Image *) NULL);
2403 assert(image->signature == MagickSignature);
anthony4fd27e22010-02-07 08:17:18 +00002404 assert(kernel != (KernelInfo *) NULL);
2405 assert(kernel->signature == MagickSignature);
anthony602ab9b2010-01-05 08:06:50 +00002406 assert(exception != (ExceptionInfo *) NULL);
2407 assert(exception->signature == MagickSignature);
2408
cristye96405a2010-05-19 02:24:31 +00002409 loop_limit = (unsigned long) iterations;
anthony9eb4f742010-05-18 02:45:54 +00002410 if ( iterations < 0 )
2411 loop_limit = image->columns > image->rows ? image->columns : image->rows;
anthony602ab9b2010-01-05 08:06:50 +00002412 if ( iterations == 0 )
2413 return((Image *)NULL); /* null operation - nothing to do! */
2414
2415 /* kernel must be valid at this point
2416 * (except maybe for posible future morphology methods like "Prune"
2417 */
cristy2be15382010-01-21 02:38:03 +00002418 assert(kernel != (KernelInfo *)NULL);
anthony602ab9b2010-01-05 08:06:50 +00002419
cristye96405a2010-05-19 02:24:31 +00002420 verbose = ( GetImageArtifact(image,"verbose") != (const char *) NULL ) ?
2421 MagickTrue : MagickFalse;
anthony4f1dcb72010-05-14 08:43:10 +00002422
anthony9eb4f742010-05-18 02:45:54 +00002423 /* initialise for cleanup */
cristye96405a2010-05-19 02:24:31 +00002424 curr_image = (Image *) image; /* result of morpholgy primitive */
anthony9eb4f742010-05-18 02:45:54 +00002425 work_image = (Image *) NULL; /* secondary working image */
2426 save_image = (Image *) NULL; /* save image for some compound methods */
2427 curr_kernel = (KernelInfo *)kernel; /* allow kernel list to be modified */
anthony4fd27e22010-02-07 08:17:18 +00002428
anthony9eb4f742010-05-18 02:45:54 +00002429 kernel_compose = NoCompositeOp; /* iterated over all kernels */
anthony602ab9b2010-01-05 08:06:50 +00002430
cristye96405a2010-05-19 02:24:31 +00002431 /* Select initial primitive morphology to apply */
2432 primitive = UndefinedMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002433 switch( method ) {
anthony930be612010-02-08 04:26:15 +00002434 case CorrelateMorphology:
2435 /* A Correlation is actually a Convolution with a reflected kernel.
anthony9eb4f742010-05-18 02:45:54 +00002436 ** However a Convolution is a weighted sum using a reflected kernel.
anthony930be612010-02-08 04:26:15 +00002437 ** It may seem stange to convert a Correlation into a Convolution
2438 ** as the Correleation is the simplier method, but Convolution is
2439 ** much more commonly used, and it makes sense to implement it directly
2440 ** so as to avoid the need to duplicate the kernel when it is not
2441 ** required (which is typically the default).
2442 */
anthony9eb4f742010-05-18 02:45:54 +00002443 curr_kernel = CloneKernelInfo(kernel);
anthony1b2bc0a2010-05-12 05:25:22 +00002444 if (curr_kernel == (KernelInfo *) NULL)
2445 goto error_cleanup;
anthony930be612010-02-08 04:26:15 +00002446 RotateKernelInfo(curr_kernel,180);
anthony9eb4f742010-05-18 02:45:54 +00002447 /* FALL THRU to Convolve */
anthonyc94cdb02010-01-06 08:15:29 +00002448 case ConvolveMorphology:
cristye96405a2010-05-19 02:24:31 +00002449 primitive = ConvolveMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002450 kernel_compose = NoCompositeOp;
2451 break;
2452 case ErodeMorphology: /* just erode */
2453 case OpenMorphology: /* erode then dialate */
2454 case EdgeInMorphology: /* erode and image difference */
2455 case TopHatMorphology: /* erode, dilate and image difference */
2456 case SmoothMorphology: /* erode, dilate, dilate, erode */
cristye96405a2010-05-19 02:24:31 +00002457 primitive = ErodeMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002458 break;
2459 case ErodeIntensityMorphology:
2460 case OpenIntensityMorphology:
cristye96405a2010-05-19 02:24:31 +00002461 primitive = ErodeIntensityMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002462 break;
2463 case DilateMorphology: /* just dilate */
2464 case EdgeOutMorphology: /* dilate and image difference */
2465 case EdgeMorphology: /* dilate and erode difference */
cristye96405a2010-05-19 02:24:31 +00002466 primitive = DilateMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002467 break;
2468 case CloseMorphology: /* dilate, then erode */
2469 case BottomHatMorphology: /* dilate and image difference */
2470 curr_kernel = CloneKernelInfo(kernel);
2471 if (curr_kernel == (KernelInfo *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00002472 goto error_cleanup;
anthony9eb4f742010-05-18 02:45:54 +00002473 RotateKernelInfo(curr_kernel,180);
cristye96405a2010-05-19 02:24:31 +00002474 primitive = DilateMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002475 break;
2476 case DilateIntensityMorphology:
2477 case CloseIntensityMorphology:
2478 curr_kernel = CloneKernelInfo(kernel);
2479 if (curr_kernel == (KernelInfo *) NULL)
2480 goto error_cleanup;
2481 RotateKernelInfo(curr_kernel,180);
cristye96405a2010-05-19 02:24:31 +00002482 primitive = DilateIntensityMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002483 break;
2484 case HitAndMissMorphology:
cristye96405a2010-05-19 02:24:31 +00002485 primitive = HitAndMissMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002486 loop_limit = 1; /* iterate only once */
2487 kernel_compose = LightenCompositeOp; /* Union of Hit-And-Miss */
2488 break;
2489 case ThinningMorphology: /* iterative morphology */
2490 case ThickenMorphology:
2491 case DistanceMorphology: /* Distance should never use multple kernels */
2492 case UndefinedMorphology:
cristye96405a2010-05-19 02:24:31 +00002493 primitive = method;
anthony930be612010-02-08 04:26:15 +00002494 break;
anthony602ab9b2010-01-05 08:06:50 +00002495 }
2496
anthony3c1cd552010-05-18 04:33:23 +00002497#if 0
2498 { /* User override of results handling -- Experimental */
anthony9eb4f742010-05-18 02:45:54 +00002499 const char
2500 *artifact = GetImageArtifact(image,"morphology:style");
2501 if ( artifact != (const char *) NULL ) {
2502 if (LocaleCompare("union",artifact) == 0)
2503 kernel_compose = LightenCompositeOp;
2504 if (LocaleCompare("iterate",artifact) == 0)
2505 kernel_compose = NoCompositeOp;
2506 else
2507 kernel_compose = (CompositeOperator) ParseMagickOption(
2508 MagickComposeOptions,MagickFalse,artifact);
2509 if ( kernel_compose == UndefinedCompositeOp )
2510 perror("Invalid \"morphology:compose\" setting\n");
2511 }
2512 }
anthony3c1cd552010-05-18 04:33:23 +00002513#endif
anthony7a01dcf2010-05-11 12:25:52 +00002514
anthony9eb4f742010-05-18 02:45:54 +00002515 /* Initialize compound morphology stages */
cristye96405a2010-05-19 02:24:31 +00002516 count = 0; /* number of low-level morphology primitives performed */
anthony9eb4f742010-05-18 02:45:54 +00002517 total_changed = 0; /* total number of pixels changed thoughout */
2518 stage = 1; /* the compound morphology stage number */
2519
2520 /* compount morphology staging loop */
2521 while ( 1 ) {
2522
2523#if 1
2524 /* Extra information for debugging compound operations */
cristye96405a2010-05-19 02:24:31 +00002525 if ( verbose == MagickTrue && primitive != method )
anthony9eb4f742010-05-18 02:45:54 +00002526 fprintf(stderr, "Morphology %s: Stage %lu %s%s (%s)\n",
2527 MagickOptionToMnemonic(MagickMorphologyOptions, method), stage,
cristye96405a2010-05-19 02:24:31 +00002528 MagickOptionToMnemonic(MagickMorphologyOptions, primitive),
anthony9eb4f742010-05-18 02:45:54 +00002529 ( curr_kernel == kernel) ? "" : "*",
2530 ( kernel_compose == NoCompositeOp ) ? "iterate"
2531 : MagickOptionToMnemonic(MagickComposeOptions, kernel_compose) );
2532#endif
2533
2534 if ( kernel_compose == NoCompositeOp ) {
2535 /******************************
2536 ** Iterate over all Kernels **
2537 ******************************/
2538 loop = 0;
2539 loop_changed = 1;
2540 while ( loop < loop_limit && loop_changed > 0 ) {
2541 loop++; /* the iteration of this kernel */
2542
2543 loop_changed = 0;
2544 this_kernel = curr_kernel;
2545 kernel_number = 0;
2546 while ( this_kernel != NULL ) {
2547
2548 /* Create a destination image, if not yet defined */
2549 if ( work_image == (Image *) NULL )
2550 {
2551 work_image=CloneImage(image,0,0,MagickTrue,exception);
2552 if (work_image == (Image *) NULL)
2553 goto error_cleanup;
2554 if (SetImageStorageClass(work_image,DirectClass) == MagickFalse)
2555 {
2556 InheritException(exception,&work_image->exception);
2557 goto error_cleanup;
2558 }
2559 }
2560
cristye96405a2010-05-19 02:24:31 +00002561 /* morphological primitive curr -> work */
anthony9eb4f742010-05-18 02:45:54 +00002562 count++;
anthony46a369d2010-05-19 02:41:48 +00002563 changed = MorphologyPrimitive(curr_image, work_image, primitive,
anthony9eb4f742010-05-18 02:45:54 +00002564 channel, this_kernel, bias, exception);
2565 loop_changed += changed;
2566 total_changed += changed;
2567
2568 if ( verbose == MagickTrue )
2569 fprintf(stderr, "Morphology %s:%lu.%lu #%lu => Changed %lu\n",
cristye96405a2010-05-19 02:24:31 +00002570 MagickOptionToMnemonic(MagickMorphologyOptions, primitive),
anthony9eb4f742010-05-18 02:45:54 +00002571 loop, kernel_number, count, changed);
2572
2573 /* prepare next loop */
2574 { Image *tmp = work_image; /* swap images for iteration */
2575 work_image = curr_image;
2576 curr_image = tmp;
2577 }
2578 if ( work_image == image )
2579 work_image = (Image *) NULL; /* never assign image to 'work' */
2580 this_kernel = this_kernel->next; /* prepare next kernel (if any) */
anthony1b2bc0a2010-05-12 05:25:22 +00002581 kernel_number++;
2582 }
anthony7a01dcf2010-05-11 12:25:52 +00002583
anthony9eb4f742010-05-18 02:45:54 +00002584 if ( verbose == MagickTrue && kernel->next != NULL )
2585 fprintf(stderr, "Morphology %s:%lu #%lu ===> Changed %lu Total %lu\n",
cristye96405a2010-05-19 02:24:31 +00002586 MagickOptionToMnemonic(MagickMorphologyOptions, primitive),
anthony9eb4f742010-05-18 02:45:54 +00002587 loop, count, loop_changed, total_changed );
anthony602ab9b2010-01-05 08:06:50 +00002588 }
anthony1b2bc0a2010-05-12 05:25:22 +00002589 }
2590
anthony9eb4f742010-05-18 02:45:54 +00002591 else {
2592 /*************************************
2593 ** Composition of Iterated Kernels **
2594 *************************************/
2595 Image
2596 *input_image, /* starting point for kernels */
2597 *union_image;
2598 input_image = curr_image;
2599 union_image = (Image *) NULL;
2600
2601 this_kernel = curr_kernel;
2602 kernel_number = 0;
2603 while ( this_kernel != NULL ) {
2604
2605 if( curr_image != (Image *) NULL && curr_image != input_image )
2606 curr_image=DestroyImage(curr_image);
2607 curr_image = input_image; /* always start with the original image */
2608
2609 loop = 0;
2610 changed = 1;
2611 loop_changed = 0;
2612 while ( loop < loop_limit && changed > 0 ) {
2613 loop++; /* the iteration of this kernel */
2614
2615 /* Create a destination image, if not defined */
2616 if ( work_image == (Image *) NULL )
2617 {
2618 work_image=CloneImage(image,0,0,MagickTrue,exception);
2619 if (work_image == (Image *) NULL)
2620 goto error_cleanup;
2621 if (SetImageStorageClass(work_image,DirectClass) == MagickFalse)
2622 {
2623 InheritException(exception,&curr_image->exception);
2624 if( union_image != (Image *) NULL )
2625 union_image=DestroyImage(union_image);
2626 if( curr_image != input_image )
2627 curr_image = DestroyImage(curr_image);
2628 curr_image = (Image *) input_image;
2629 goto error_cleanup;
2630 }
2631 }
2632
cristye96405a2010-05-19 02:24:31 +00002633 /* morphological primitive curr -> work */
anthony9eb4f742010-05-18 02:45:54 +00002634 count++;
anthony46a369d2010-05-19 02:41:48 +00002635 changed = MorphologyPrimitive(curr_image,work_image,primitive,
anthony9eb4f742010-05-18 02:45:54 +00002636 channel, this_kernel, bias, exception);
2637 loop_changed += changed;
2638 total_changed += changed;
2639
2640 if ( verbose == MagickTrue )
anthony4f1dcb72010-05-14 08:43:10 +00002641 fprintf(stderr, "Morphology %s:%lu.%lu #%lu => Changed %lu\n",
cristye96405a2010-05-19 02:24:31 +00002642 MagickOptionToMnemonic(MagickMorphologyOptions, primitive),
anthony9eb4f742010-05-18 02:45:54 +00002643 loop, kernel_number, count, changed);
2644
2645 /* prepare next loop */
2646 { Image *tmp = work_image; /* swap images for iteration */
2647 work_image = curr_image; /* curr_image is now the results */
2648 curr_image = tmp;
2649 }
2650 if ( work_image == input_image )
2651 work_image = (Image *) NULL; /* clear work of the input_image */
2652
2653 } /* end kernel iteration */
2654
2655 /* make a union of the iterated kernel */
2656 if ( union_image == (Image *) NULL) /* start the union? */
2657 union_image = curr_image, curr_image = (Image *)NULL;
2658 else
2659 (void) CompositeImageChannel(union_image,
2660 (ChannelType) (channel & ~SyncChannels), kernel_compose,
2661 curr_image, 0, 0);
2662
2663 this_kernel = this_kernel->next; /* next kernel (if any) */
anthony1b2bc0a2010-05-12 05:25:22 +00002664 kernel_number++;
anthony602ab9b2010-01-05 08:06:50 +00002665 }
anthony9eb4f742010-05-18 02:45:54 +00002666
2667 if ( verbose == MagickTrue && kernel->next != NULL && loop_limit > 1 )
2668 fprintf(stderr, "Morphology %s:%lu #%lu ===> Changed %lu Total %lu\n",
cristye96405a2010-05-19 02:24:31 +00002669 MagickOptionToMnemonic(MagickMorphologyOptions, primitive),
anthony9eb4f742010-05-18 02:45:54 +00002670 loop, count, loop_changed, total_changed );
2671
2672#if 0
2673fprintf(stderr, "--E-- image=0x%lx\n", (unsigned long)image);
2674fprintf(stderr, " input=0x%lx\n", (unsigned long)input_image);
2675fprintf(stderr, " union=0x%lx\n", (unsigned long)union_image);
2676fprintf(stderr, " curr =0x%lx\n", (unsigned long)curr_image);
2677fprintf(stderr, " work =0x%lx\n", (unsigned long)work_image);
2678fprintf(stderr, " save =0x%lx\n", (unsigned long)save_image);
2679#endif
2680
2681 /* Finish up - return the union of results */
2682 if( curr_image != (Image *) NULL && curr_image != input_image )
2683 curr_image=DestroyImage(curr_image);
2684 if( input_image != input_image )
2685 input_image = DestroyImage(input_image);
2686 curr_image = union_image;
anthony1b2bc0a2010-05-12 05:25:22 +00002687 }
anthony9eb4f742010-05-18 02:45:54 +00002688
2689 /* Compound Morphology Operations
cristye96405a2010-05-19 02:24:31 +00002690 * set next 'primitive' iteration, and continue
anthony9eb4f742010-05-18 02:45:54 +00002691 * or break when all operations are complete.
2692 */
2693 stage++; /* what is the next stage number to do */
2694 switch( method ) {
2695 case SmoothMorphology: /* open, close */
2696 switch ( stage ) {
2697 /* case 1: initialized above */
2698 case 2: /* open part 2 */
cristye96405a2010-05-19 02:24:31 +00002699 primitive = DilateMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002700 continue;
2701 case 3: /* close part 1 */
2702 curr_kernel = CloneKernelInfo(kernel);
2703 if (curr_kernel == (KernelInfo *) NULL)
2704 goto error_cleanup;
2705 RotateKernelInfo(curr_kernel,180);
2706 continue;
2707 case 4: /* close part 2 */
cristye96405a2010-05-19 02:24:31 +00002708 primitive = ErodeMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002709 continue;
2710 }
2711 break;
2712 case OpenMorphology: /* erode, dilate */
2713 case TopHatMorphology:
cristye96405a2010-05-19 02:24:31 +00002714 primitive = DilateMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002715 if ( stage <= 2 ) continue;
2716 break;
2717 case OpenIntensityMorphology:
cristye96405a2010-05-19 02:24:31 +00002718 primitive = DilateIntensityMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002719 if ( stage <= 2 ) continue;
2720 break;
2721 case CloseMorphology: /* dilate, erode */
2722 case BottomHatMorphology:
cristye96405a2010-05-19 02:24:31 +00002723 primitive = ErodeMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002724 if ( stage <= 2 ) continue;
2725 break;
2726 case CloseIntensityMorphology:
cristye96405a2010-05-19 02:24:31 +00002727 primitive = ErodeIntensityMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002728 if ( stage <= 2 ) continue;
2729 break;
2730 case EdgeMorphology: /* dilate and erode difference */
2731 if (stage <= 2) {
2732 save_image = curr_image;
2733 curr_image = (Image *) image;
cristye96405a2010-05-19 02:24:31 +00002734 primitive = ErodeMorphology;
anthony9eb4f742010-05-18 02:45:54 +00002735 continue;
2736 }
2737 break;
2738 default: /* Primitive Morphology is just finished! */
2739 break;
2740 }
2741
2742 if ( verbose == MagickTrue && count > 1 )
2743 fprintf(stderr, "Morphology %s: ======> Total %lu\n",
2744 MagickOptionToMnemonic(MagickMorphologyOptions, method),
2745 total_changed );
2746
2747 /* If we reach this point we are finished! - Break the Loop */
2748 break;
anthony602ab9b2010-01-05 08:06:50 +00002749 }
anthony930be612010-02-08 04:26:15 +00002750
anthony9eb4f742010-05-18 02:45:54 +00002751 /* Final Post-processing for some Compound Methods
anthony7d10d742010-05-06 07:05:29 +00002752 **
2753 ** The removal of any 'Sync' channel flag in the Image Compositon below
2754 ** ensures the compose method is applied in a purely mathematical way, only
2755 ** the selected channels, without any normal 'alpha blending' normally
2756 ** associated with the compose method.
2757 **
2758 ** Note "method" here is the 'original' morphological method, and not the
2759 ** 'current' morphological method used above to generate "new_image".
2760 */
anthony4fd27e22010-02-07 08:17:18 +00002761 switch( method ) {
2762 case EdgeOutMorphology:
2763 case EdgeInMorphology:
2764 case TopHatMorphology:
2765 case BottomHatMorphology:
anthony9eb4f742010-05-18 02:45:54 +00002766 (void) CompositeImageChannel(curr_image,
2767 (ChannelType) (channel & ~SyncChannels), DifferenceCompositeOp,
2768 image, 0, 0);
anthony4fd27e22010-02-07 08:17:18 +00002769 break;
anthony7d10d742010-05-06 07:05:29 +00002770 case EdgeMorphology:
anthony9eb4f742010-05-18 02:45:54 +00002771 /* Difference the Eroded Image with a Dilate image */
2772 (void) CompositeImageChannel(curr_image,
2773 (ChannelType) (channel & ~SyncChannels), DifferenceCompositeOp,
2774 save_image, 0, 0);
anthony4fd27e22010-02-07 08:17:18 +00002775 break;
2776 default:
2777 break;
2778 }
anthony602ab9b2010-01-05 08:06:50 +00002779
anthony9eb4f742010-05-18 02:45:54 +00002780 goto exit_cleanup;
anthony1b2bc0a2010-05-12 05:25:22 +00002781
2782 /* Yes goto's are bad, but in this case it makes cleanup lot more efficient */
2783error_cleanup:
anthony9eb4f742010-05-18 02:45:54 +00002784 if ( curr_image != (Image *) NULL && curr_image != image )
cristye96405a2010-05-19 02:24:31 +00002785 (void) DestroyImage(curr_image);
anthony1b2bc0a2010-05-12 05:25:22 +00002786exit_cleanup:
anthony9eb4f742010-05-18 02:45:54 +00002787 if ( work_image != (Image *) NULL )
cristye96405a2010-05-19 02:24:31 +00002788 (void) DestroyImage(work_image);
anthony9eb4f742010-05-18 02:45:54 +00002789 if ( save_image != (Image *) NULL )
cristye96405a2010-05-19 02:24:31 +00002790 (void) DestroyImage(save_image);
anthony9eb4f742010-05-18 02:45:54 +00002791 return(curr_image);
2792}
2793
2794/*
2795%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2796% %
2797% %
2798% %
2799% M o r p h o l o g y I m a g e C h a n n e l %
2800% %
2801% %
2802% %
2803%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2804%
2805% MorphologyImageChannel() applies a user supplied kernel to the image
2806% according to the given mophology method.
2807%
2808% This function applies any and all user defined settings before calling
2809% the above internal function MorphologyApply().
2810%
2811% User defined settings include...
anthony46a369d2010-05-19 02:41:48 +00002812% * Output Bias for Convolution and correlation ("-bias")
2813% * Kernel Scale/normalize settings ("-set 'option:convolve:scale'")
2814% This can also includes the addition of a scaled unity kernel.
2815% * Show Kernel being applied ("-set option:showkernel 1")
anthony9eb4f742010-05-18 02:45:54 +00002816%
2817% The format of the MorphologyImage method is:
2818%
2819% Image *MorphologyImage(const Image *image,MorphologyMethod method,
2820% const long iterations,KernelInfo *kernel,ExceptionInfo *exception)
2821%
2822% Image *MorphologyImageChannel(const Image *image, const ChannelType
2823% channel,MorphologyMethod method,const long iterations,
2824% KernelInfo *kernel,ExceptionInfo *exception)
2825%
2826% A description of each parameter follows:
2827%
2828% o image: the image.
2829%
2830% o method: the morphology method to be applied.
2831%
2832% o iterations: apply the operation this many times (or no change).
2833% A value of -1 means loop until no change found.
2834% How this is applied may depend on the morphology method.
2835% Typically this is a value of 1.
2836%
2837% o channel: the channel type.
2838%
2839% o kernel: An array of double representing the morphology kernel.
2840% Warning: kernel may be normalized for the Convolve method.
2841%
2842% o exception: return any errors or warnings in this structure.
2843%
2844*/
2845
2846MagickExport Image *MorphologyImageChannel(const Image *image,
2847 const ChannelType channel,const MorphologyMethod method,
2848 const long iterations,const KernelInfo *kernel,ExceptionInfo *exception)
2849{
2850 const char
2851 *artifact;
2852
2853 KernelInfo
2854 *curr_kernel;
2855
2856 Image
2857 *morphology_image;
2858
2859
anthony46a369d2010-05-19 02:41:48 +00002860 /* Apply Convolve/Correlate Normalization and Scaling Factors.
2861 * This is done BEFORE the ShowKernelInfo() function is called so that
2862 * users can see the results of the 'option:convolve:scale' option.
anthony9eb4f742010-05-18 02:45:54 +00002863 */
2864 curr_kernel = (KernelInfo *) kernel;
2865 if ( method == ConvolveMorphology )
2866 {
2867 artifact = GetImageArtifact(image,"convolve:scale");
2868 if ( artifact != (char *)NULL ) {
anthony9eb4f742010-05-18 02:45:54 +00002869 if ( curr_kernel == kernel )
2870 curr_kernel = CloneKernelInfo(kernel);
2871 if (curr_kernel == (KernelInfo *) NULL) {
2872 curr_kernel=DestroyKernelInfo(curr_kernel);
2873 return((Image *) NULL);
2874 }
anthony46a369d2010-05-19 02:41:48 +00002875 ScaleGeometryKernelInfo(curr_kernel, artifact);
anthony9eb4f742010-05-18 02:45:54 +00002876 }
2877 }
2878
2879 /* display the (normalized) kernel via stderr */
2880 artifact = GetImageArtifact(image,"showkernel");
2881 if ( artifact != (const char *) NULL)
2882 ShowKernelInfo(curr_kernel);
2883
2884 /* Apply the Morphology */
2885 morphology_image = MorphologyApply(image, channel, method, iterations,
2886 curr_kernel, image->bias, exception);
2887
2888 /* Cleanup and Exit */
2889 if ( curr_kernel != kernel )
anthony1b2bc0a2010-05-12 05:25:22 +00002890 curr_kernel=DestroyKernelInfo(curr_kernel);
anthony9eb4f742010-05-18 02:45:54 +00002891 return(morphology_image);
2892}
2893
anthony46a369d2010-05-19 02:41:48 +00002894
anthony9eb4f742010-05-18 02:45:54 +00002895MagickExport Image *MorphologyImage(const Image *image, const MorphologyMethod
2896 method, const long iterations,const KernelInfo *kernel, ExceptionInfo
2897 *exception)
2898{
2899 Image
2900 *morphology_image;
2901
2902 morphology_image=MorphologyImageChannel(image,DefaultChannels,method,
2903 iterations,kernel,exception);
2904 return(morphology_image);
anthony602ab9b2010-01-05 08:06:50 +00002905}
anthony83ba99b2010-01-24 08:48:15 +00002906
2907/*
2908%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2909% %
2910% %
2911% %
anthony4fd27e22010-02-07 08:17:18 +00002912+ R o t a t e K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00002913% %
2914% %
2915% %
2916%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2917%
anthony46a369d2010-05-19 02:41:48 +00002918% RotateKernelInfo() rotates the kernel by the angle given.
2919%
2920% Currently it is restricted to 90 degree angles, of either 1D kernels
2921% or square kernels. And 'circular' rotations of 45 degrees for 3x3 kernels.
2922% It will ignore usless rotations for specific 'named' built-in kernels.
anthony83ba99b2010-01-24 08:48:15 +00002923%
anthony4fd27e22010-02-07 08:17:18 +00002924% The format of the RotateKernelInfo method is:
anthony83ba99b2010-01-24 08:48:15 +00002925%
anthony4fd27e22010-02-07 08:17:18 +00002926% void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00002927%
2928% A description of each parameter follows:
2929%
2930% o kernel: the Morphology/Convolution kernel
2931%
2932% o angle: angle to rotate in degrees
2933%
anthony46a369d2010-05-19 02:41:48 +00002934% This function is currently internal to this module only, but can be exported
2935% to other modules if needed.
anthony83ba99b2010-01-24 08:48:15 +00002936*/
anthony4fd27e22010-02-07 08:17:18 +00002937static void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00002938{
anthony1b2bc0a2010-05-12 05:25:22 +00002939 /* angle the lower kernels first */
2940 if ( kernel->next != (KernelInfo *) NULL)
2941 RotateKernelInfo(kernel->next, angle);
2942
anthony83ba99b2010-01-24 08:48:15 +00002943 /* WARNING: Currently assumes the kernel (rightly) is horizontally symetrical
2944 **
2945 ** TODO: expand beyond simple 90 degree rotates, flips and flops
2946 */
2947
2948 /* Modulus the angle */
2949 angle = fmod(angle, 360.0);
2950 if ( angle < 0 )
2951 angle += 360.0;
2952
anthony3c10fc82010-05-13 02:40:51 +00002953 if ( 337.5 < angle || angle <= 22.5 )
anthony43c49252010-05-18 10:59:50 +00002954 return; /* Near zero angle - no change! - At least not at this time */
anthony83ba99b2010-01-24 08:48:15 +00002955
anthony3dd0f622010-05-13 12:57:32 +00002956 /* Handle special cases */
anthony83ba99b2010-01-24 08:48:15 +00002957 switch (kernel->type) {
2958 /* These built-in kernels are cylindrical kernels, rotating is useless */
2959 case GaussianKernel:
anthony83ba99b2010-01-24 08:48:15 +00002960 case DOGKernel:
2961 case DiskKernel:
anthony3dd0f622010-05-13 12:57:32 +00002962 case PeaksKernel:
2963 case LaplacianKernel:
anthony83ba99b2010-01-24 08:48:15 +00002964 case ChebyshevKernel:
2965 case ManhattenKernel:
2966 case EuclideanKernel:
2967 return;
2968
2969 /* These may be rotatable at non-90 angles in the future */
2970 /* but simply rotating them in multiples of 90 degrees is useless */
2971 case SquareKernel:
2972 case DiamondKernel:
2973 case PlusKernel:
anthony3dd0f622010-05-13 12:57:32 +00002974 case CrossKernel:
anthony83ba99b2010-01-24 08:48:15 +00002975 return;
2976
2977 /* These only allows a +/-90 degree rotation (by transpose) */
2978 /* A 180 degree rotation is useless */
2979 case BlurKernel:
2980 case RectangleKernel:
2981 if ( 135.0 < angle && angle <= 225.0 )
2982 return;
2983 if ( 225.0 < angle && angle <= 315.0 )
2984 angle -= 180;
2985 break;
2986
anthony3dd0f622010-05-13 12:57:32 +00002987 default:
anthony83ba99b2010-01-24 08:48:15 +00002988 break;
2989 }
anthony3c10fc82010-05-13 02:40:51 +00002990 /* Attempt rotations by 45 degrees */
2991 if ( 22.5 < fmod(angle,90.0) && fmod(angle,90.0) <= 67.5 )
2992 {
2993 if ( kernel->width == 3 && kernel->height == 3 )
2994 { /* Rotate a 3x3 square by 45 degree angle */
2995 MagickRealType t = kernel->values[0];
anthony43c49252010-05-18 10:59:50 +00002996 kernel->values[0] = kernel->values[3];
2997 kernel->values[3] = kernel->values[6];
2998 kernel->values[6] = kernel->values[7];
2999 kernel->values[7] = kernel->values[8];
3000 kernel->values[8] = kernel->values[5];
3001 kernel->values[5] = kernel->values[2];
3002 kernel->values[2] = kernel->values[1];
3003 kernel->values[1] = t;
3004 /* NOT DONE - rotate a off-centered origin as well! */
3005 angle = fmod(angle+315.0, 360.0); /* angle reduced 45 degrees */
3006 kernel->angle = fmod(kernel->angle+45.0, 360.0);
anthony3c10fc82010-05-13 02:40:51 +00003007 }
3008 else
3009 perror("Unable to rotate non-3x3 kernel by 45 degrees");
3010 }
3011 if ( 45.0 < fmod(angle, 180.0) && fmod(angle,180.0) <= 135.0 )
3012 {
3013 if ( kernel->width == 1 || kernel->height == 1 )
3014 { /* Do a transpose of the image, which results in a 90
3015 ** degree rotation of a 1 dimentional kernel
3016 */
3017 long
3018 t;
3019 t = (long) kernel->width;
3020 kernel->width = kernel->height;
3021 kernel->height = (unsigned long) t;
3022 t = kernel->x;
3023 kernel->x = kernel->y;
3024 kernel->y = t;
anthony43c49252010-05-18 10:59:50 +00003025 if ( kernel->width == 1 ) {
3026 angle = fmod(angle+270.0, 360.0); /* angle reduced 90 degrees */
3027 kernel->angle = fmod(kernel->angle+90.0, 360.0);
3028 } else {
3029 angle = fmod(angle+90.0, 360.0); /* angle increased 90 degrees */
3030 kernel->angle = fmod(kernel->angle+270.0, 360.0);
3031 }
anthony3c10fc82010-05-13 02:40:51 +00003032 }
3033 else if ( kernel->width == kernel->height )
3034 { /* Rotate a square array of values by 90 degrees */
3035 register unsigned long
3036 i,j,x,y;
3037 register MagickRealType
3038 *k,t;
3039 k=kernel->values;
3040 for( i=0, x=kernel->width-1; i<=x; i++, x--)
3041 for( j=0, y=kernel->height-1; j<y; j++, y--)
3042 { t = k[i+j*kernel->width];
anthony43c49252010-05-18 10:59:50 +00003043 k[i+j*kernel->width] = k[j+x*kernel->width];
3044 k[j+x*kernel->width] = k[x+y*kernel->width];
3045 k[x+y*kernel->width] = k[y+i*kernel->width];
3046 k[y+i*kernel->width] = t;
anthony3c10fc82010-05-13 02:40:51 +00003047 }
anthony43c49252010-05-18 10:59:50 +00003048 /* NOT DONE - rotate a off-centered origin as well! */
3049 angle = fmod(angle+270.0, 360.0); /* angle reduced 90 degrees */
3050 kernel->angle = fmod(kernel->angle+90.0, 360.0);
anthony3c10fc82010-05-13 02:40:51 +00003051 }
3052 else
3053 perror("Unable to rotate a non-square, non-linear kernel 90 degrees");
3054 }
anthony83ba99b2010-01-24 08:48:15 +00003055 if ( 135.0 < angle && angle <= 225.0 )
3056 {
anthony43c49252010-05-18 10:59:50 +00003057 /* For a 180 degree rotation - also know as a reflection
3058 * This is actually a very very common operation!
3059 * Basically all that is needed is a reversal of the kernel data!
3060 * And a reflection of the origon
3061 */
anthony83ba99b2010-01-24 08:48:15 +00003062 unsigned long
3063 i,j;
3064 register double
3065 *k,t;
3066
3067 k=kernel->values;
3068 for ( i=0, j=kernel->width*kernel->height-1; i<j; i++, j--)
3069 t=k[i], k[i]=k[j], k[j]=t;
3070
anthony930be612010-02-08 04:26:15 +00003071 kernel->x = (long) kernel->width - kernel->x - 1;
3072 kernel->y = (long) kernel->height - kernel->y - 1;
anthony43c49252010-05-18 10:59:50 +00003073 angle = fmod(angle-180.0, 360.0); /* angle+180 degrees */
3074 kernel->angle = fmod(kernel->angle+180.0, 360.0);
anthony83ba99b2010-01-24 08:48:15 +00003075 }
anthony3c10fc82010-05-13 02:40:51 +00003076 /* At this point angle should at least between -45 (315) and +45 degrees
anthony83ba99b2010-01-24 08:48:15 +00003077 * In the future some form of non-orthogonal angled rotates could be
3078 * performed here, posibily with a linear kernel restriction.
3079 */
3080
3081#if 0
anthony3c10fc82010-05-13 02:40:51 +00003082 { /* Do a Flop by reversing each row.
anthony83ba99b2010-01-24 08:48:15 +00003083 */
3084 unsigned long
3085 y;
cristy150989e2010-02-01 14:59:39 +00003086 register long
anthony83ba99b2010-01-24 08:48:15 +00003087 x,r;
3088 register double
3089 *k,t;
3090
3091 for ( y=0, k=kernel->values; y < kernel->height; y++, k+=kernel->width)
3092 for ( x=0, r=kernel->width-1; x<kernel->width/2; x++, r--)
3093 t=k[x], k[x]=k[r], k[r]=t;
3094
cristyc99304f2010-02-01 15:26:27 +00003095 kernel->x = kernel->width - kernel->x - 1;
anthony83ba99b2010-01-24 08:48:15 +00003096 angle = fmod(angle+180.0, 360.0);
3097 }
3098#endif
3099 return;
3100}
3101
3102/*
3103%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3104% %
3105% %
3106% %
anthony46a369d2010-05-19 02:41:48 +00003107% S c a l e G e o m e t r y K e r n e l I n f o %
3108% %
3109% %
3110% %
3111%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3112%
3113% ScaleGeometryKernelInfo() takes a geometry argument string, typically
3114% provided as a "-set option:convolve:scale {geometry}" user setting,
3115% and modifies the kernel according to the parsed arguments of that setting.
3116%
3117% The first argument (and any normalization flags) are passed to
3118% ScaleKernelInfo() to scale/normalize the kernel. The second argument
3119% is then passed to UnityAddKernelInfo() to add a scled unity kernel
3120% into the scaled/normalized kernel.
3121%
3122% The format of the ScaleKernelInfo method is:
3123%
3124% void ScaleKernelInfo(KernelInfo *kernel, const double scaling_factor,
3125% const MagickStatusType normalize_flags )
3126%
3127% A description of each parameter follows:
3128%
3129% o kernel: the Morphology/Convolution kernel to modify
3130%
3131% o geometry:
3132% The geometry string to parse, typically from the user provided
3133% "-set option:convolve:scale {geometry}" setting.
3134%
3135*/
3136MagickExport void ScaleGeometryKernelInfo (KernelInfo *kernel,
3137 const char *geometry)
3138{
3139 GeometryFlags
3140 flags;
3141 GeometryInfo
3142 args;
3143
3144 SetGeometryInfo(&args);
3145 flags = (GeometryFlags) ParseGeometry(geometry, &args);
3146
3147#if 0
3148 /* For Debugging Geometry Input */
3149 fprintf(stderr, "Geometry = 0x%04X : %lg x %lg %+lg %+lg\n",
3150 flags, args.rho, args.sigma, args.xi, args.psi );
3151#endif
3152
3153 if ( (flags & PercentValue) != 0 ) /* Handle Percentage flag*/
3154 args.rho *= 0.01, args.sigma *= 0.01;
3155
3156 if ( (flags & RhoValue) == 0 ) /* Set Defaults for missing args */
3157 args.rho = 1.0;
3158 if ( (flags & SigmaValue) == 0 )
3159 args.sigma = 0.0;
3160
3161 /* Scale/Normalize the input kernel */
3162 ScaleKernelInfo(kernel, args.rho, flags);
3163
3164 /* Add Unity Kernel, for blending with original */
3165 if ( (flags & SigmaValue) != 0 )
3166 UnityAddKernelInfo(kernel, args.sigma);
3167
3168 return;
3169}
3170/*
3171%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3172% %
3173% %
3174% %
cristy6771f1e2010-03-05 19:43:39 +00003175% S c a l e K e r n e l I n f o %
anthonycc6c8362010-01-25 04:14:01 +00003176% %
3177% %
3178% %
3179%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3180%
anthony1b2bc0a2010-05-12 05:25:22 +00003181% ScaleKernelInfo() scales the given kernel list by the given amount, with or
3182% without normalization of the sum of the kernel values (as per given flags).
anthonycc6c8362010-01-25 04:14:01 +00003183%
anthony999bb2c2010-02-18 12:38:01 +00003184% By default (no flags given) the values within the kernel is scaled
anthony1b2bc0a2010-05-12 05:25:22 +00003185% directly using given scaling factor without change.
anthonycc6c8362010-01-25 04:14:01 +00003186%
anthony46a369d2010-05-19 02:41:48 +00003187% If either of the two 'normalize_flags' are given the kernel will first be
3188% normalized and then further scaled by the scaling factor value given.
anthony999bb2c2010-02-18 12:38:01 +00003189%
3190% Kernel normalization ('normalize_flags' given) is designed to ensure that
3191% any use of the kernel scaling factor with 'Convolve' or 'Correlate'
anthony1b2bc0a2010-05-12 05:25:22 +00003192% morphology methods will fall into -1.0 to +1.0 range. Note that for
3193% non-HDRI versions of IM this may cause images to have any negative results
3194% clipped, unless some 'bias' is used.
anthony999bb2c2010-02-18 12:38:01 +00003195%
3196% More specifically. Kernels which only contain positive values (such as a
3197% 'Gaussian' kernel) will be scaled so that those values sum to +1.0,
anthony1b2bc0a2010-05-12 05:25:22 +00003198% ensuring a 0.0 to +1.0 output range for non-HDRI images.
anthony999bb2c2010-02-18 12:38:01 +00003199%
3200% For Kernels that contain some negative values, (such as 'Sharpen' kernels)
3201% the kernel will be scaled by the absolute of the sum of kernel values, so
3202% that it will generally fall within the +/- 1.0 range.
3203%
3204% For kernels whose values sum to zero, (such as 'Laplician' kernels) kernel
3205% will be scaled by just the sum of the postive values, so that its output
3206% range will again fall into the +/- 1.0 range.
3207%
3208% For special kernels designed for locating shapes using 'Correlate', (often
3209% only containing +1 and -1 values, representing foreground/brackground
3210% matching) a special normalization method is provided to scale the positive
3211% values seperatally to those of the negative values, so the kernel will be
3212% forced to become a zero-sum kernel better suited to such searches.
3213%
anthony1b2bc0a2010-05-12 05:25:22 +00003214% WARNING: Correct normalization of the kernel assumes that the '*_range'
anthony999bb2c2010-02-18 12:38:01 +00003215% attributes within the kernel structure have been correctly set during the
3216% kernels creation.
3217%
3218% NOTE: The values used for 'normalize_flags' have been selected specifically
anthony46a369d2010-05-19 02:41:48 +00003219% to match the use of geometry options, so that '!' means NormalizeValue, '^'
3220% means CorrelateNormalizeValue. All other GeometryFlags values are ignored.
anthonycc6c8362010-01-25 04:14:01 +00003221%
anthony4fd27e22010-02-07 08:17:18 +00003222% The format of the ScaleKernelInfo method is:
anthonycc6c8362010-01-25 04:14:01 +00003223%
anthony999bb2c2010-02-18 12:38:01 +00003224% void ScaleKernelInfo(KernelInfo *kernel, const double scaling_factor,
3225% const MagickStatusType normalize_flags )
anthonycc6c8362010-01-25 04:14:01 +00003226%
3227% A description of each parameter follows:
3228%
3229% o kernel: the Morphology/Convolution kernel
3230%
anthony999bb2c2010-02-18 12:38:01 +00003231% o scaling_factor:
3232% multiply all values (after normalization) by this factor if not
3233% zero. If the kernel is normalized regardless of any flags.
3234%
3235% o normalize_flags:
3236% GeometryFlags defining normalization method to use.
3237% specifically: NormalizeValue, CorrelateNormalizeValue,
3238% and/or PercentValue
anthonycc6c8362010-01-25 04:14:01 +00003239%
3240*/
cristy6771f1e2010-03-05 19:43:39 +00003241MagickExport void ScaleKernelInfo(KernelInfo *kernel,
3242 const double scaling_factor,const GeometryFlags normalize_flags)
anthonycc6c8362010-01-25 04:14:01 +00003243{
cristy150989e2010-02-01 14:59:39 +00003244 register long
anthonycc6c8362010-01-25 04:14:01 +00003245 i;
3246
anthony999bb2c2010-02-18 12:38:01 +00003247 register double
3248 pos_scale,
3249 neg_scale;
3250
anthony46a369d2010-05-19 02:41:48 +00003251 /* do the other kernels in a multi-kernel list first */
anthony1b2bc0a2010-05-12 05:25:22 +00003252 if ( kernel->next != (KernelInfo *) NULL)
3253 ScaleKernelInfo(kernel->next, scaling_factor, normalize_flags);
3254
anthony46a369d2010-05-19 02:41:48 +00003255 /* Normalization of Kernel */
anthony999bb2c2010-02-18 12:38:01 +00003256 pos_scale = 1.0;
3257 if ( (normalize_flags&NormalizeValue) != 0 ) {
anthony46a369d2010-05-19 02:41:48 +00003258 /* non-zero and zero-summing kernels */
anthony999bb2c2010-02-18 12:38:01 +00003259 if ( fabs(kernel->positive_range + kernel->negative_range) > MagickEpsilon )
3260 pos_scale = fabs(kernel->positive_range + kernel->negative_range);
anthonycc6c8362010-01-25 04:14:01 +00003261 else
anthony999bb2c2010-02-18 12:38:01 +00003262 pos_scale = kernel->positive_range; /* special zero-summing kernel */
3263 }
anthony46a369d2010-05-19 02:41:48 +00003264 /* Force kernel into a normalized zero-summing kernel */
anthony999bb2c2010-02-18 12:38:01 +00003265 if ( (normalize_flags&CorrelateNormalizeValue) != 0 ) {
3266 pos_scale = ( fabs(kernel->positive_range) > MagickEpsilon )
3267 ? kernel->positive_range : 1.0;
3268 neg_scale = ( fabs(kernel->negative_range) > MagickEpsilon )
3269 ? -kernel->negative_range : 1.0;
3270 }
3271 else
3272 neg_scale = pos_scale;
3273
3274 /* finialize scaling_factor for positive and negative components */
3275 pos_scale = scaling_factor/pos_scale;
3276 neg_scale = scaling_factor/neg_scale;
anthonycc6c8362010-01-25 04:14:01 +00003277
cristy150989e2010-02-01 14:59:39 +00003278 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00003279 if ( ! IsNan(kernel->values[i]) )
anthony999bb2c2010-02-18 12:38:01 +00003280 kernel->values[i] *= (kernel->values[i] >= 0) ? pos_scale : neg_scale;
anthonycc6c8362010-01-25 04:14:01 +00003281
anthony999bb2c2010-02-18 12:38:01 +00003282 /* convolution output range */
3283 kernel->positive_range *= pos_scale;
3284 kernel->negative_range *= neg_scale;
3285 /* maximum and minimum values in kernel */
3286 kernel->maximum *= (kernel->maximum >= 0.0) ? pos_scale : neg_scale;
3287 kernel->minimum *= (kernel->minimum >= 0.0) ? pos_scale : neg_scale;
3288
anthony46a369d2010-05-19 02:41:48 +00003289 /* swap kernel settings if user's scaling factor is negative */
anthony999bb2c2010-02-18 12:38:01 +00003290 if ( scaling_factor < MagickEpsilon ) {
3291 double t;
3292 t = kernel->positive_range;
3293 kernel->positive_range = kernel->negative_range;
3294 kernel->negative_range = t;
3295 t = kernel->maximum;
3296 kernel->maximum = kernel->minimum;
3297 kernel->minimum = 1;
3298 }
anthonycc6c8362010-01-25 04:14:01 +00003299
3300 return;
3301}
3302
3303/*
3304%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3305% %
3306% %
3307% %
anthony46a369d2010-05-19 02:41:48 +00003308% S h o w K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00003309% %
3310% %
3311% %
3312%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3313%
anthony4fd27e22010-02-07 08:17:18 +00003314% ShowKernelInfo() outputs the details of the given kernel defination to
3315% standard error, generally due to a users 'showkernel' option request.
anthony83ba99b2010-01-24 08:48:15 +00003316%
3317% The format of the ShowKernel method is:
3318%
anthony4fd27e22010-02-07 08:17:18 +00003319% void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00003320%
3321% A description of each parameter follows:
3322%
3323% o kernel: the Morphology/Convolution kernel
3324%
anthony83ba99b2010-01-24 08:48:15 +00003325*/
anthony4fd27e22010-02-07 08:17:18 +00003326MagickExport void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00003327{
anthony7a01dcf2010-05-11 12:25:52 +00003328 KernelInfo
3329 *k;
anthony83ba99b2010-01-24 08:48:15 +00003330
anthony43c49252010-05-18 10:59:50 +00003331 unsigned long
anthony7a01dcf2010-05-11 12:25:52 +00003332 c, i, u, v;
3333
3334 for (c=0, k=kernel; k != (KernelInfo *) NULL; c++, k=k->next ) {
3335
anthony46a369d2010-05-19 02:41:48 +00003336 fprintf(stderr, "Kernel");
anthony7a01dcf2010-05-11 12:25:52 +00003337 if ( kernel->next != (KernelInfo *) NULL )
cristye96405a2010-05-19 02:24:31 +00003338 fprintf(stderr, " #%lu", c );
anthony43c49252010-05-18 10:59:50 +00003339 fprintf(stderr, " \"%s",
3340 MagickOptionToMnemonic(MagickKernelOptions, k->type) );
3341 if ( fabs(k->angle) > MagickEpsilon )
3342 fprintf(stderr, "@%lg", k->angle);
3343 fprintf(stderr, "\" of size %lux%lu%+ld%+ld ",
3344 k->width, k->height,
3345 k->x, k->y );
anthony7a01dcf2010-05-11 12:25:52 +00003346 fprintf(stderr,
3347 " with values from %.*lg to %.*lg\n",
3348 GetMagickPrecision(), k->minimum,
3349 GetMagickPrecision(), k->maximum);
anthony46a369d2010-05-19 02:41:48 +00003350 fprintf(stderr, "Forming a output range from %.*lg to %.*lg",
anthony7a01dcf2010-05-11 12:25:52 +00003351 GetMagickPrecision(), k->negative_range,
anthony46a369d2010-05-19 02:41:48 +00003352 GetMagickPrecision(), k->positive_range);
3353 if ( fabs(k->positive_range+k->negative_range) < MagickEpsilon )
3354 fprintf(stderr, " (Zero-Summing)\n");
3355 else if ( fabs(k->positive_range+k->negative_range-1.0) < MagickEpsilon )
3356 fprintf(stderr, " (Normalized)\n");
3357 else
3358 fprintf(stderr, " (Sum %.*lg)\n",
3359 GetMagickPrecision(), k->positive_range+k->negative_range);
anthony43c49252010-05-18 10:59:50 +00003360 for (i=v=0; v < k->height; v++) {
anthony46a369d2010-05-19 02:41:48 +00003361 fprintf(stderr, "%2lu:", v );
anthony43c49252010-05-18 10:59:50 +00003362 for (u=0; u < k->width; u++, i++)
anthony7a01dcf2010-05-11 12:25:52 +00003363 if ( IsNan(k->values[i]) )
3364 fprintf(stderr," %*s", GetMagickPrecision()+2, "nan");
3365 else
3366 fprintf(stderr," %*.*lg", GetMagickPrecision()+2,
3367 GetMagickPrecision(), k->values[i]);
3368 fprintf(stderr,"\n");
3369 }
anthony83ba99b2010-01-24 08:48:15 +00003370 }
3371}
anthonycc6c8362010-01-25 04:14:01 +00003372
3373/*
3374%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3375% %
3376% %
3377% %
anthony43c49252010-05-18 10:59:50 +00003378% U n i t y A d d K e r n a l I n f o %
3379% %
3380% %
3381% %
3382%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3383%
3384% UnityAddKernelInfo() Adds a given amount of the 'Unity' Convolution Kernel
3385% to the given pre-scaled and normalized Kernel. This in effect adds that
3386% amount of the original image into the resulting convolution kernel. This
3387% value is usually provided by the user as a percentage value in the
3388% 'convolve:scale' setting.
3389%
3390% The resulting effect is to either convert a 'zero-summing' edge detection
3391% kernel (such as a "Laplacian", "DOG" or a "LOG") into a 'sharpening'
3392% kernel.
3393%
3394% Alternativally by using a purely positive kernel, and using a negative
3395% post-normalizing scaling factor, you can convert a 'blurring' kernel (such
3396% as a "Gaussian") into a 'unsharp' kernel.
3397%
anthony46a369d2010-05-19 02:41:48 +00003398% The format of the UnityAdditionKernelInfo method is:
anthony43c49252010-05-18 10:59:50 +00003399%
3400% void UnityAdditionKernelInfo(KernelInfo *kernel, const double scale )
3401%
3402% A description of each parameter follows:
3403%
3404% o kernel: the Morphology/Convolution kernel
3405%
3406% o scale:
3407% scaling factor for the unity kernel to be added to
3408% the given kernel.
3409%
anthony43c49252010-05-18 10:59:50 +00003410*/
3411MagickExport void UnityAddKernelInfo(KernelInfo *kernel,
3412 const double scale)
3413{
anthony46a369d2010-05-19 02:41:48 +00003414 /* do the other kernels in a multi-kernel list first */
3415 if ( kernel->next != (KernelInfo *) NULL)
3416 UnityAddKernelInfo(kernel->next, scale);
anthony43c49252010-05-18 10:59:50 +00003417
anthony46a369d2010-05-19 02:41:48 +00003418 /* Add the scaled unity kernel to the existing kernel */
anthony43c49252010-05-18 10:59:50 +00003419 kernel->values[kernel->x+kernel->y*kernel->width] += scale;
anthony46a369d2010-05-19 02:41:48 +00003420 CalcKernelMetaData(kernel); /* recalculate the meta-data */
anthony43c49252010-05-18 10:59:50 +00003421
3422 return;
3423}
3424
3425/*
3426%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3427% %
3428% %
3429% %
3430% Z e r o K e r n e l N a n s %
anthonycc6c8362010-01-25 04:14:01 +00003431% %
3432% %
3433% %
3434%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3435%
3436% ZeroKernelNans() replaces any special 'nan' value that may be present in
3437% the kernel with a zero value. This is typically done when the kernel will
3438% be used in special hardware (GPU) convolution processors, to simply
3439% matters.
3440%
3441% The format of the ZeroKernelNans method is:
3442%
anthony46a369d2010-05-19 02:41:48 +00003443% void ZeroKernelNans (KernelInfo *kernel)
anthonycc6c8362010-01-25 04:14:01 +00003444%
3445% A description of each parameter follows:
3446%
3447% o kernel: the Morphology/Convolution kernel
3448%
anthonycc6c8362010-01-25 04:14:01 +00003449*/
anthonyc4c86e02010-01-27 09:30:32 +00003450MagickExport void ZeroKernelNans(KernelInfo *kernel)
anthonycc6c8362010-01-25 04:14:01 +00003451{
anthony43c49252010-05-18 10:59:50 +00003452 register unsigned long
anthonycc6c8362010-01-25 04:14:01 +00003453 i;
3454
anthony46a369d2010-05-19 02:41:48 +00003455 /* do the other kernels in a multi-kernel list first */
anthony1b2bc0a2010-05-12 05:25:22 +00003456 if ( kernel->next != (KernelInfo *) NULL)
3457 ZeroKernelNans(kernel->next);
3458
anthony43c49252010-05-18 10:59:50 +00003459 for (i=0; i < (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00003460 if ( IsNan(kernel->values[i]) )
3461 kernel->values[i] = 0.0;
3462
3463 return;
3464}