blob: 9f2f3f8cf79c63cbed9c3ec3b6ca8bbc15e37c68 [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%
anthony602ab9b2010-01-05 08:06:50 +000036% Morpology is the the application of various kernals, of any size and even
37% 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"
anthony602ab9b2010-01-05 08:06:50 +000068#include "magick/option.h"
cristy701db312009-11-20 03:14:08 +000069#include "magick/pixel-private.h"
70#include "magick/prepress.h"
71#include "magick/quantize.h"
72#include "magick/registry.h"
73#include "magick/semaphore.h"
74#include "magick/splay-tree.h"
75#include "magick/statistic.h"
76#include "magick/string_.h"
anthony602ab9b2010-01-05 08:06:50 +000077#include "magick/string-private.h"
78#include "magick/token.h"
cristya29d45f2010-03-05 21:14:54 +000079
anthony602ab9b2010-01-05 08:06:50 +000080/*
cristya29d45f2010-03-05 21:14:54 +000081 The following test is for special floating point numbers of value NaN (not
82 a number), that may be used within a Kernel Definition. NaN's are defined
83 as part of the IEEE standard for floating point number representation.
84
85 These are used a Kernel value of NaN means that that kernal position is not
86 part of the normal convolution or morphology process, and thus allowing the
87 use of 'shaped' kernels.
88
89 Special properities two NaN's are never equal, even if they are from the
90 same variable That is the IsNaN() macro is only true if the value is NaN.
91*/
anthony602ab9b2010-01-05 08:06:50 +000092#define IsNan(a) ((a)!=(a))
93
anthony29188a82010-01-22 10:12:34 +000094/*
cristya29d45f2010-03-05 21:14:54 +000095 Other global definitions used by module.
96*/
anthony29188a82010-01-22 10:12:34 +000097static inline double MagickMin(const double x,const double y)
98{
99 return( x < y ? x : y);
100}
101static inline double MagickMax(const double x,const double y)
102{
103 return( x > y ? x : y);
104}
105#define Minimize(assign,value) assign=MagickMin(assign,value)
106#define Maximize(assign,value) assign=MagickMax(assign,value)
107
anthonyc4c86e02010-01-27 09:30:32 +0000108/* Currently these are only internal to this module */
109static void
cristyef656912010-03-05 19:54:59 +0000110 RotateKernelInfo(KernelInfo *, double);
anthony602ab9b2010-01-05 08:06:50 +0000111
112/*
113%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
114% %
115% %
116% %
anthony83ba99b2010-01-24 08:48:15 +0000117% A c q u i r e K e r n e l I n f o %
anthony602ab9b2010-01-05 08:06:50 +0000118% %
119% %
120% %
121%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
122%
cristy2be15382010-01-21 02:38:03 +0000123% AcquireKernelInfo() takes the given string (generally supplied by the
anthony602ab9b2010-01-05 08:06:50 +0000124% user) and converts it into a Morphology/Convolution Kernel. This allows
125% users to specify a kernel from a number of pre-defined kernels, or to fully
126% specify their own kernel for a specific Convolution or Morphology
127% Operation.
128%
129% The kernel so generated can be any rectangular array of floating point
130% values (doubles) with the 'control point' or 'pixel being affected'
131% anywhere within that array of values.
132%
anthony83ba99b2010-01-24 08:48:15 +0000133% Previously IM was restricted to a square of odd size using the exact
134% center as origin, this is no longer the case, and any rectangular kernel
135% with any value being declared the origin. This in turn allows the use of
136% highly asymmetrical kernels.
anthony602ab9b2010-01-05 08:06:50 +0000137%
138% The floating point values in the kernel can also include a special value
anthony83ba99b2010-01-24 08:48:15 +0000139% known as 'nan' or 'not a number' to indicate that this value is not part
140% of the kernel array. This allows you to shaped the kernel within its
141% rectangular area. That is 'nan' values provide a 'mask' for the kernel
142% shape. However at least one non-nan value must be provided for correct
143% working of a kernel.
anthony602ab9b2010-01-05 08:06:50 +0000144%
anthony7a01dcf2010-05-11 12:25:52 +0000145% The returned kernel should be freed using the DestroyKernelInfo() when you
146% are finished with it. Do not free this memory yourself.
anthony602ab9b2010-01-05 08:06:50 +0000147%
148% Input kernel defintion strings can consist of any of three types.
149%
anthony29188a82010-01-22 10:12:34 +0000150% "name:args"
151% Select from one of the built in kernels, using the name and
152% geometry arguments supplied. See AcquireKernelBuiltIn()
anthony602ab9b2010-01-05 08:06:50 +0000153%
154% "WxH[+X+Y]:num, num, num ..."
155% a kernal of size W by H, with W*H floating point numbers following.
156% the 'center' can be optionally be defined at +X+Y (such that +0+0
anthony29188a82010-01-22 10:12:34 +0000157% is top left corner). If not defined the pixel in the center, for
158% odd sizes, or to the immediate top or left of center for even sizes
159% is automatically selected.
anthony602ab9b2010-01-05 08:06:50 +0000160%
anthony29188a82010-01-22 10:12:34 +0000161% "num, num, num, num, ..."
162% list of floating point numbers defining an 'old style' odd sized
163% square kernel. At least 9 values should be provided for a 3x3
164% square kernel, 25 for a 5x5 square kernel, 49 for 7x7, etc.
165% Values can be space or comma separated. This is not recommended.
anthony602ab9b2010-01-05 08:06:50 +0000166%
anthony83ba99b2010-01-24 08:48:15 +0000167% Note that 'name' kernels will start with an alphabetic character while the
168% new kernel specification has a ':' character in its specification string.
169% If neither is the case, it is assumed an old style of a simple list of
170% numbers generating a odd-sized square kernel has been given.
anthony602ab9b2010-01-05 08:06:50 +0000171%
anthony7a01dcf2010-05-11 12:25:52 +0000172% You can define a 'list of kernels' which can be used by some morphology
173% operators A list is defined as a semi-colon seperated list kernels.
174%
175% " kernel ; kernel ; kernel "
176%
177%
anthony602ab9b2010-01-05 08:06:50 +0000178% The format of the AcquireKernal method is:
179%
cristy2be15382010-01-21 02:38:03 +0000180% KernelInfo *AcquireKernelInfo(const char *kernel_string)
anthony602ab9b2010-01-05 08:06:50 +0000181%
182% A description of each parameter follows:
183%
184% o kernel_string: the Morphology/Convolution kernel wanted.
185%
186*/
187
anthonyc84dce52010-05-07 05:42:23 +0000188/* This was separated so that it could be used as a separate
anthony5ef8e942010-05-11 06:51:12 +0000189** array input handling function, such as for -color-matrix
anthonyc84dce52010-05-07 05:42:23 +0000190*/
anthony5ef8e942010-05-11 06:51:12 +0000191static KernelInfo *ParseKernelArray(const char *kernel_string)
anthony602ab9b2010-01-05 08:06:50 +0000192{
cristy2be15382010-01-21 02:38:03 +0000193 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000194 *kernel;
195
196 char
197 token[MaxTextExtent];
198
anthony602ab9b2010-01-05 08:06:50 +0000199 const char
anthony5ef8e942010-05-11 06:51:12 +0000200 *p,
201 *end;
anthony602ab9b2010-01-05 08:06:50 +0000202
anthonyc84dce52010-05-07 05:42:23 +0000203 register long
204 i;
anthony602ab9b2010-01-05 08:06:50 +0000205
anthony29188a82010-01-22 10:12:34 +0000206 double
207 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
208
cristy2be15382010-01-21 02:38:03 +0000209 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
210 if (kernel == (KernelInfo *)NULL)
anthony602ab9b2010-01-05 08:06:50 +0000211 return(kernel);
212 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
anthony7a01dcf2010-05-11 12:25:52 +0000213 kernel->minimum = kernel->maximum = 0.0;
214 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000215 kernel->type = UserDefinedKernel;
anthony7a01dcf2010-05-11 12:25:52 +0000216 kernel->next = (KernelInfo *) NULL;
cristyd43a46b2010-01-21 02:13:41 +0000217 kernel->signature = MagickSignature;
anthony602ab9b2010-01-05 08:06:50 +0000218
anthony5ef8e942010-05-11 06:51:12 +0000219 /* find end of this specific kernel definition string */
220 end = strchr(kernel_string, ';');
221 if ( end == (char *) NULL )
222 end = strchr(kernel_string, '\0');
223
anthony602ab9b2010-01-05 08:06:50 +0000224 /* Has a ':' in argument - New user kernel specification */
225 p = strchr(kernel_string, ':');
anthony5ef8e942010-05-11 06:51:12 +0000226 if ( p != (char *) NULL && p < end)
anthony602ab9b2010-01-05 08:06:50 +0000227 {
anthonyc84dce52010-05-07 05:42:23 +0000228 MagickStatusType
229 flags;
230
231 GeometryInfo
232 args;
233
anthony602ab9b2010-01-05 08:06:50 +0000234 /* ParseGeometry() needs the geometry separated! -- Arrgghh */
cristy150989e2010-02-01 14:59:39 +0000235 memcpy(token, kernel_string, (size_t) (p-kernel_string));
anthony602ab9b2010-01-05 08:06:50 +0000236 token[p-kernel_string] = '\0';
anthonyc84dce52010-05-07 05:42:23 +0000237 SetGeometryInfo(&args);
anthony602ab9b2010-01-05 08:06:50 +0000238 flags = ParseGeometry(token, &args);
anthony602ab9b2010-01-05 08:06:50 +0000239
anthony29188a82010-01-22 10:12:34 +0000240 /* Size handling and checks of geometry settings */
anthony602ab9b2010-01-05 08:06:50 +0000241 if ( (flags & WidthValue) == 0 ) /* if no width then */
242 args.rho = args.sigma; /* then width = height */
243 if ( args.rho < 1.0 ) /* if width too small */
244 args.rho = 1.0; /* then width = 1 */
245 if ( args.sigma < 1.0 ) /* if height too small */
246 args.sigma = args.rho; /* then height = width */
247 kernel->width = (unsigned long)args.rho;
248 kernel->height = (unsigned long)args.sigma;
249
250 /* Offset Handling and Checks */
251 if ( args.xi < 0.0 || args.psi < 0.0 )
anthony83ba99b2010-01-24 08:48:15 +0000252 return(DestroyKernelInfo(kernel));
cristyc99304f2010-02-01 15:26:27 +0000253 kernel->x = ((flags & XValue)!=0) ? (long)args.xi
cristy150989e2010-02-01 14:59:39 +0000254 : (long) (kernel->width-1)/2;
cristyc99304f2010-02-01 15:26:27 +0000255 kernel->y = ((flags & YValue)!=0) ? (long)args.psi
cristy150989e2010-02-01 14:59:39 +0000256 : (long) (kernel->height-1)/2;
cristyc99304f2010-02-01 15:26:27 +0000257 if ( kernel->x >= (long) kernel->width ||
258 kernel->y >= (long) kernel->height )
anthony83ba99b2010-01-24 08:48:15 +0000259 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000260
261 p++; /* advance beyond the ':' */
262 }
263 else
anthonyc84dce52010-05-07 05:42:23 +0000264 { /* ELSE - Old old specification, forming odd-square kernel */
anthony602ab9b2010-01-05 08:06:50 +0000265 /* count up number of values given */
266 p=(const char *) kernel_string;
cristya699b172010-01-06 16:48:49 +0000267 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
anthony29188a82010-01-22 10:12:34 +0000268 p++; /* ignore "'" chars for convolve filter usage - Cristy */
anthony5ef8e942010-05-11 06:51:12 +0000269 for (i=0; p < end; i++)
anthony602ab9b2010-01-05 08:06:50 +0000270 {
271 GetMagickToken(p,&p,token);
272 if (*token == ',')
273 GetMagickToken(p,&p,token);
274 }
275 /* set the size of the kernel - old sized square */
276 kernel->width = kernel->height= (unsigned long) sqrt((double) i+1.0);
cristyc99304f2010-02-01 15:26:27 +0000277 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000278 p=(const char *) kernel_string;
anthony29188a82010-01-22 10:12:34 +0000279 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
280 p++; /* ignore "'" chars for convolve filter usage - Cristy */
anthony602ab9b2010-01-05 08:06:50 +0000281 }
282
283 /* Read in the kernel values from rest of input string argument */
284 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
285 kernel->height*sizeof(double));
286 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000287 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000288
cristyc99304f2010-02-01 15:26:27 +0000289 kernel->minimum = +MagickHuge;
290 kernel->maximum = -MagickHuge;
291 kernel->negative_range = kernel->positive_range = 0.0;
anthonyc84dce52010-05-07 05:42:23 +0000292
anthony5ef8e942010-05-11 06:51:12 +0000293 for (i=0; (i < (long) (kernel->width*kernel->height)) && (p < end); i++)
anthony602ab9b2010-01-05 08:06:50 +0000294 {
295 GetMagickToken(p,&p,token);
296 if (*token == ',')
297 GetMagickToken(p,&p,token);
anthony29188a82010-01-22 10:12:34 +0000298 if ( LocaleCompare("nan",token) == 0
anthonyc84dce52010-05-07 05:42:23 +0000299 || LocaleCompare("-",token) == 0 ) {
anthony29188a82010-01-22 10:12:34 +0000300 kernel->values[i] = nan; /* do not include this value in kernel */
301 }
302 else {
303 kernel->values[i] = StringToDouble(token);
304 ( kernel->values[i] < 0)
cristyc99304f2010-02-01 15:26:27 +0000305 ? ( kernel->negative_range += kernel->values[i] )
306 : ( kernel->positive_range += kernel->values[i] );
307 Minimize(kernel->minimum, kernel->values[i]);
308 Maximize(kernel->maximum, kernel->values[i]);
anthony29188a82010-01-22 10:12:34 +0000309 }
anthony602ab9b2010-01-05 08:06:50 +0000310 }
anthony29188a82010-01-22 10:12:34 +0000311
anthony5ef8e942010-05-11 06:51:12 +0000312 /* sanity check -- no more values in kernel definition */
313 GetMagickToken(p,&p,token);
314 if ( *token != '\0' && *token != ';' && *token != '\'' )
315 return(DestroyKernelInfo(kernel));
316
anthonyc84dce52010-05-07 05:42:23 +0000317#if 0
318 /* this was the old method of handling a incomplete kernel */
cristy150989e2010-02-01 14:59:39 +0000319 if ( i < (long) (kernel->width*kernel->height) ) {
cristyc99304f2010-02-01 15:26:27 +0000320 Minimize(kernel->minimum, kernel->values[i]);
321 Maximize(kernel->maximum, kernel->values[i]);
cristy150989e2010-02-01 14:59:39 +0000322 for ( ; i < (long) (kernel->width*kernel->height); i++)
anthony29188a82010-01-22 10:12:34 +0000323 kernel->values[i]=0.0;
324 }
anthonyc84dce52010-05-07 05:42:23 +0000325#else
326 /* Number of values for kernel was not enough - Report Error */
327 if ( i < (long) (kernel->width*kernel->height) )
328 return(DestroyKernelInfo(kernel));
329#endif
330
331 /* check that we recieved at least one real (non-nan) value! */
332 if ( kernel->minimum == MagickHuge )
333 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000334
335 return(kernel);
336}
anthonyc84dce52010-05-07 05:42:23 +0000337
anthony5ef8e942010-05-11 06:51:12 +0000338static KernelInfo *ParseNamedKernel(const char *kernel_string)
anthonyc84dce52010-05-07 05:42:23 +0000339{
340 char
341 token[MaxTextExtent];
342
anthony5ef8e942010-05-11 06:51:12 +0000343 long
344 type;
345
anthonyc84dce52010-05-07 05:42:23 +0000346 const char
anthony7a01dcf2010-05-11 12:25:52 +0000347 *p,
348 *end;
anthonyc84dce52010-05-07 05:42:23 +0000349
350 MagickStatusType
351 flags;
352
353 GeometryInfo
354 args;
355
anthonyc84dce52010-05-07 05:42:23 +0000356 /* Parse special 'named' kernel */
anthony5ef8e942010-05-11 06:51:12 +0000357 GetMagickToken(kernel_string,&p,token);
anthonyc84dce52010-05-07 05:42:23 +0000358 type=ParseMagickOption(MagickKernelOptions,MagickFalse,token);
359 if ( type < 0 || type == UserDefinedKernel )
anthony5ef8e942010-05-11 06:51:12 +0000360 return((KernelInfo *)NULL); /* not a valid named kernel */
anthonyc84dce52010-05-07 05:42:23 +0000361
362 while (((isspace((int) ((unsigned char) *p)) != 0) ||
anthony5ef8e942010-05-11 06:51:12 +0000363 (*p == ',') || (*p == ':' )) && (*p != '\0') && (*p != ';'))
anthonyc84dce52010-05-07 05:42:23 +0000364 p++;
anthony7a01dcf2010-05-11 12:25:52 +0000365
366 end = strchr(p, ';'); /* end of this kernel defintion */
367 if ( end == (char *) NULL )
368 end = strchr(p, '\0');
369
370 /* ParseGeometry() needs the geometry separated! -- Arrgghh */
371 memcpy(token, p, (size_t) (end-p));
372 token[end-p] = '\0';
anthonyc84dce52010-05-07 05:42:23 +0000373 SetGeometryInfo(&args);
anthony7a01dcf2010-05-11 12:25:52 +0000374 flags = ParseGeometry(token, &args);
anthonyc84dce52010-05-07 05:42:23 +0000375
376 /* special handling of missing values in input string */
377 switch( type ) {
anthony5ef8e942010-05-11 06:51:12 +0000378 case RectangleKernel:
379 if ( (flags & WidthValue) == 0 ) /* if no width then */
380 args.rho = args.sigma; /* then width = height */
381 if ( args.rho < 1.0 ) /* if width too small */
382 args.rho = 3; /* then width = 3 */
383 if ( args.sigma < 1.0 ) /* if height too small */
384 args.sigma = args.rho; /* then height = width */
385 if ( (flags & XValue) == 0 ) /* center offset if not defined */
386 args.xi = (double)(((long)args.rho-1)/2);
387 if ( (flags & YValue) == 0 )
388 args.psi = (double)(((long)args.sigma-1)/2);
389 break;
390 case SquareKernel:
391 case DiamondKernel:
392 case DiskKernel:
393 case PlusKernel:
394 /* If no scale given (a 0 scale is valid! - set it to 1.0 */
395 if ( (flags & HeightValue) == 0 )
396 args.sigma = 1.0;
397 break;
398 case ChebyshevKernel:
399 case ManhattenKernel:
400 case EuclideanKernel:
401 if ( (flags & HeightValue) == 0 )
402 args.sigma = 100.0; /* default distance scaling */
403 else if ( (flags & AspectValue ) != 0 ) /* '!' flag */
404 args.sigma = QuantumRange/args.sigma; /* maximum pixel distance */
405 else if ( (flags & PercentValue ) != 0 ) /* '%' flag */
406 args.sigma *= QuantumRange/100.0; /* percentage of color range */
407 break;
408 default:
409 break;
anthonyc84dce52010-05-07 05:42:23 +0000410 }
411
412 return(AcquireKernelBuiltIn((KernelInfoType)type, &args));
413}
414
anthony5ef8e942010-05-11 06:51:12 +0000415MagickExport KernelInfo *AcquireKernelInfo(const char *kernel_string)
416{
anthony7a01dcf2010-05-11 12:25:52 +0000417
418 KernelInfo
419 *kernel;
420
anthony5ef8e942010-05-11 06:51:12 +0000421 char
422 token[MaxTextExtent];
423
anthony7a01dcf2010-05-11 12:25:52 +0000424 const char
425 *next;
426
anthony5ef8e942010-05-11 06:51:12 +0000427 /* If it does not start with an alpha - Its is a user defined kernel array */
428 GetMagickToken(kernel_string,NULL,token);
429 if (isalpha((int) ((unsigned char) *token)) == 0)
anthony7a01dcf2010-05-11 12:25:52 +0000430 kernel = ParseKernelArray(kernel_string);
431 else
432 kernel = ParseNamedKernel(kernel_string);
anthony5ef8e942010-05-11 06:51:12 +0000433
anthony7a01dcf2010-05-11 12:25:52 +0000434 /* was this the last (or only) kernel in the string */
435 next = strchr(kernel_string, ';');
436 if ( next == (char *) NULL )
437 return(kernel);
438
439 /* Add the next kernel to the list */
440 kernel->next = AcquireKernelInfo( next+1 );
441
442 /* failed for some reason? */
443 if ( kernel->next == (KernelInfo *) NULL )
444 return(DestroyKernelInfo(kernel));
445
446 return(kernel);
anthony5ef8e942010-05-11 06:51:12 +0000447}
448
anthony602ab9b2010-01-05 08:06:50 +0000449
450/*
451%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
452% %
453% %
454% %
455% A c q u i r e K e r n e l B u i l t I n %
456% %
457% %
458% %
459%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
460%
461% AcquireKernelBuiltIn() returned one of the 'named' built-in types of
462% kernels used for special purposes such as gaussian blurring, skeleton
463% pruning, and edge distance determination.
464%
465% They take a KernelType, and a set of geometry style arguments, which were
466% typically decoded from a user supplied string, or from a more complex
467% Morphology Method that was requested.
468%
469% The format of the AcquireKernalBuiltIn method is:
470%
cristy2be15382010-01-21 02:38:03 +0000471% KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000472% const GeometryInfo args)
473%
474% A description of each parameter follows:
475%
476% o type: the pre-defined type of kernel wanted
477%
478% o args: arguments defining or modifying the kernel
479%
480% Convolution Kernels
481%
anthony4fd27e22010-02-07 08:17:18 +0000482% Gaussian "{radius},{sigma}"
anthony602ab9b2010-01-05 08:06:50 +0000483% Generate a two-dimentional gaussian kernel, as used by -gaussian
484% A sigma is required, (with the 'x'), due to historical reasons.
485%
486% NOTE: that the 'radius' is optional, but if provided can limit (clip)
487% the final size of the resulting kernel to a square 2*radius+1 in size.
488% The radius should be at least 2 times that of the sigma value, or
489% sever clipping and aliasing may result. If not given or set to 0 the
490% radius will be determined so as to produce the best minimal error
491% result, which is usally much larger than is normally needed.
492%
anthony4fd27e22010-02-07 08:17:18 +0000493% Blur "{radius},{sigma},{angle}"
anthony602ab9b2010-01-05 08:06:50 +0000494% As per Gaussian, but generates a 1 dimensional or linear gaussian
495% blur, at the angle given (current restricted to orthogonal angles).
496% If a 'radius' is given the kernel is clipped to a width of 2*radius+1.
497%
498% NOTE that two such blurs perpendicular to each other is equivelent to
499% -blur and the previous gaussian, but is often 10 or more times faster.
500%
anthony4fd27e22010-02-07 08:17:18 +0000501% Comet "{width},{sigma},{angle}"
anthony602ab9b2010-01-05 08:06:50 +0000502% Blur in one direction only, mush like how a bright object leaves
503% a comet like trail. The Kernel is actually half a gaussian curve,
504% Adding two such blurs in oppiste directions produces a Linear Blur.
505%
506% NOTE: that the first argument is the width of the kernel and not the
507% radius of the kernel.
508%
509% # Still to be implemented...
510% #
anthony4fd27e22010-02-07 08:17:18 +0000511% # Sharpen "{radius},{sigma}
512% # Negated Gaussian (center zeroed and re-normalized),
513% # with a 2 unit positive peak. -- Check On line documentation
514% #
515% # Laplacian "{radius},{sigma}"
anthony602ab9b2010-01-05 08:06:50 +0000516% # Laplacian (a mexican hat like) Function
517% #
518% # LOG "{radius},{sigma1},{sigma2}
519% # Laplacian of Gaussian
520% #
521% # DOG "{radius},{sigma1},{sigma2}
anthony4fd27e22010-02-07 08:17:18 +0000522% # Difference of two Gaussians
523% #
524% # Filter2D
525% # Filter1D
526% # Set kernel values using a resize filter, and given scale (sigma)
527% # Cylindrical or Linear. Is this posible with an image?
528% #
anthony602ab9b2010-01-05 08:06:50 +0000529%
530% Boolean Kernels
531%
532% Rectangle "{geometry}"
533% Simply generate a rectangle of 1's with the size given. You can also
534% specify the location of the 'control point', otherwise the closest
535% pixel to the center of the rectangle is selected.
536%
537% Properly centered and odd sized rectangles work the best.
538%
anthony4fd27e22010-02-07 08:17:18 +0000539% Diamond "[{radius}[,{scale}]]"
anthony602ab9b2010-01-05 08:06:50 +0000540% Generate a diamond shaped kernal with given radius to the points.
541% Kernel size will again be radius*2+1 square and defaults to radius 1,
542% generating a 3x3 kernel that is slightly larger than a square.
543%
anthony4fd27e22010-02-07 08:17:18 +0000544% Square "[{radius}[,{scale}]]"
anthony602ab9b2010-01-05 08:06:50 +0000545% Generate a square shaped kernel of size radius*2+1, and defaulting
546% to a 3x3 (radius 1).
547%
548% Note that using a larger radius for the "Square" or the "Diamond"
549% is also equivelent to iterating the basic morphological method
550% that many times. However However iterating with the smaller radius 1
551% default is actually faster than using a larger kernel radius.
552%
anthony4fd27e22010-02-07 08:17:18 +0000553% Disk "[{radius}[,{scale}]]
anthony602ab9b2010-01-05 08:06:50 +0000554% Generate a binary disk of the radius given, radius may be a float.
555% Kernel size will be ceil(radius)*2+1 square.
556% NOTE: Here are some disk shapes of specific interest
557% "disk:1" => "diamond" or "cross:1"
558% "disk:1.5" => "square"
559% "disk:2" => "diamond:2"
anthony83ba99b2010-01-24 08:48:15 +0000560% "disk:2.5" => a general disk shape of radius 2
anthony602ab9b2010-01-05 08:06:50 +0000561% "disk:2.9" => "square:2"
anthony83ba99b2010-01-24 08:48:15 +0000562% "disk:3.5" => default - octagonal/disk shape of radius 3
anthony602ab9b2010-01-05 08:06:50 +0000563% "disk:4.2" => roughly octagonal shape of radius 4
anthony83ba99b2010-01-24 08:48:15 +0000564% "disk:4.3" => a general disk shape of radius 4
anthony602ab9b2010-01-05 08:06:50 +0000565% After this all the kernel shape becomes more and more circular.
566%
567% Because a "disk" is more circular when using a larger radius, using a
568% larger radius is preferred over iterating the morphological operation.
569%
anthony4fd27e22010-02-07 08:17:18 +0000570% Plus "[{radius}[,{scale}]]"
anthony602ab9b2010-01-05 08:06:50 +0000571% Generate a kernel in the shape of a 'plus' sign. The length of each
572% arm is also the radius, which defaults to 2.
573%
574% This kernel is not a good general morphological kernel, but is used
575% more for highlighting and marking any single pixels in an image using,
576% a "Dilate" or "Erode" method as appropriate.
anthonyc94cdb02010-01-06 08:15:29 +0000577%
anthony602ab9b2010-01-05 08:06:50 +0000578% NOTE: "plus:1" is equivelent to a "Diamond" kernel.
579%
580% Note that unlike other kernels iterating a plus does not produce the
581% same result as using a larger radius for the cross.
582%
583% Distance Measuring Kernels
584%
anthonyc84dce52010-05-07 05:42:23 +0000585% Chebyshev "[{radius}][x{scale}[%!]]"
586% Manhatten "[{radius}][x{scale}[%!]]"
587% Euclidean "[{radius}][x{scale}[%!]]"
anthony602ab9b2010-01-05 08:06:50 +0000588%
589% Different types of distance measuring methods, which are used with the
590% a 'Distance' morphology method for generating a gradient based on
591% distance from an edge of a binary shape, though there is a technique
592% for handling a anti-aliased shape.
593%
anthonyc94cdb02010-01-06 08:15:29 +0000594% Chebyshev Distance (also known as Tchebychev Distance) is a value of
595% one to any neighbour, orthogonal or diagonal. One why of thinking of
596% it is the number of squares a 'King' or 'Queen' in chess needs to
597% traverse reach any other position on a chess board. It results in a
598% 'square' like distance function, but one where diagonals are closer
599% than expected.
anthony602ab9b2010-01-05 08:06:50 +0000600%
anthonyc94cdb02010-01-06 08:15:29 +0000601% Manhatten Distance (also known as Rectilinear Distance, or the Taxi
602% Cab metric), is the distance needed when you can only travel in
603% orthogonal (horizontal or vertical) only. It is the distance a 'Rook'
604% in chess would travel. It results in a diamond like distances, where
605% diagonals are further than expected.
anthony602ab9b2010-01-05 08:06:50 +0000606%
anthonyc94cdb02010-01-06 08:15:29 +0000607% Euclidean Distance is the 'direct' or 'as the crow flys distance.
608% However by default the kernel size only has a radius of 1, which
609% limits the distance to 'Knight' like moves, with only orthogonal and
610% diagonal measurements being correct. As such for the default kernel
611% you will get octagonal like distance function, which is reasonally
612% accurate.
613%
614% However if you use a larger radius such as "Euclidean:4" you will
615% get a much smoother distance gradient from the edge of the shape.
616% Of course a larger kernel is slower to use, and generally not needed.
617%
618% To allow the use of fractional distances that you get with diagonals
619% the actual distance is scaled by a fixed value which the user can
620% provide. This is not actually nessary for either ""Chebyshev" or
621% "Manhatten" distance kernels, but is done for all three distance
622% kernels. If no scale is provided it is set to a value of 100,
623% allowing for a maximum distance measurement of 655 pixels using a Q16
624% version of IM, from any edge. However for small images this can
625% result in quite a dark gradient.
626%
627% See the 'Distance' Morphological Method, for information of how it is
628% applied.
anthony602ab9b2010-01-05 08:06:50 +0000629%
anthony4fd27e22010-02-07 08:17:18 +0000630% # Hit-n-Miss Kernel-Lists -- Still to be implemented
631% #
632% # specifically for Pruning, Thinning, Thickening
633% #
anthony602ab9b2010-01-05 08:06:50 +0000634*/
635
cristy2be15382010-01-21 02:38:03 +0000636MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000637 const GeometryInfo *args)
638{
cristy2be15382010-01-21 02:38:03 +0000639 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000640 *kernel;
641
cristy150989e2010-02-01 14:59:39 +0000642 register long
anthony602ab9b2010-01-05 08:06:50 +0000643 i;
644
645 register long
646 u,
647 v;
648
649 double
650 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
651
cristy2be15382010-01-21 02:38:03 +0000652 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
653 if (kernel == (KernelInfo *) NULL)
anthony602ab9b2010-01-05 08:06:50 +0000654 return(kernel);
655 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
cristyc99304f2010-02-01 15:26:27 +0000656 kernel->minimum = kernel->maximum = 0.0;
657 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000658 kernel->type = type;
anthony7a01dcf2010-05-11 12:25:52 +0000659 kernel->next = (KernelInfo *) NULL;
cristyd43a46b2010-01-21 02:13:41 +0000660 kernel->signature = MagickSignature;
anthony602ab9b2010-01-05 08:06:50 +0000661
662 switch(type) {
663 /* Convolution Kernels */
664 case GaussianKernel:
665 { double
666 sigma = fabs(args->sigma);
667
668 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
669
670 kernel->width = kernel->height =
671 GetOptimalKernelWidth2D(args->rho,sigma);
cristyc99304f2010-02-01 15:26:27 +0000672 kernel->x = kernel->y = (long) (kernel->width-1)/2;
673 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000674 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
675 kernel->height*sizeof(double));
676 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000677 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000678
679 sigma = 2.0*sigma*sigma; /* simplify the expression */
cristyc99304f2010-02-01 15:26:27 +0000680 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
681 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
682 kernel->positive_range += (
anthony602ab9b2010-01-05 08:06:50 +0000683 kernel->values[i] =
684 exp(-((double)(u*u+v*v))/sigma)
685 /* / (MagickPI*sigma) */ );
cristyc99304f2010-02-01 15:26:27 +0000686 kernel->minimum = 0;
687 kernel->maximum = kernel->values[
688 kernel->y*kernel->width+kernel->x ];
anthony602ab9b2010-01-05 08:06:50 +0000689
anthony999bb2c2010-02-18 12:38:01 +0000690 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
anthony602ab9b2010-01-05 08:06:50 +0000691
692 break;
693 }
694 case BlurKernel:
695 { double
696 sigma = fabs(args->sigma);
697
698 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
699
700 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
cristyc99304f2010-02-01 15:26:27 +0000701 kernel->x = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000702 kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +0000703 kernel->y = 0;
704 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000705 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
706 kernel->height*sizeof(double));
707 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000708 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000709
710#if 1
711#define KernelRank 3
712 /* Formula derived from GetBlurKernel() in "effect.c" (plus bug fix).
713 ** It generates a gaussian 3 times the width, and compresses it into
714 ** the expected range. This produces a closer normalization of the
715 ** resulting kernel, especially for very low sigma values.
716 ** As such while wierd it is prefered.
717 **
718 ** I am told this method originally came from Photoshop.
719 */
720 sigma *= KernelRank; /* simplify expanded curve */
cristy150989e2010-02-01 14:59:39 +0000721 v = (long) (kernel->width*KernelRank-1)/2; /* start/end points to fit range */
anthony602ab9b2010-01-05 08:06:50 +0000722 (void) ResetMagickMemory(kernel->values,0, (size_t)
723 kernel->width*sizeof(double));
724 for ( u=-v; u <= v; u++) {
725 kernel->values[(u+v)/KernelRank] +=
726 exp(-((double)(u*u))/(2.0*sigma*sigma))
727 /* / (MagickSQ2PI*sigma/KernelRank) */ ;
728 }
cristy150989e2010-02-01 14:59:39 +0000729 for (i=0; i < (long) kernel->width; i++)
cristyc99304f2010-02-01 15:26:27 +0000730 kernel->positive_range += kernel->values[i];
anthony602ab9b2010-01-05 08:06:50 +0000731#else
cristyc99304f2010-02-01 15:26:27 +0000732 for ( i=0, u=-kernel->x; i < kernel->width; i++, u++)
733 kernel->positive_range += (
anthony602ab9b2010-01-05 08:06:50 +0000734 kernel->values[i] =
735 exp(-((double)(u*u))/(2.0*sigma*sigma))
736 /* / (MagickSQ2PI*sigma) */ );
737#endif
cristyc99304f2010-02-01 15:26:27 +0000738 kernel->minimum = 0;
739 kernel->maximum = kernel->values[ kernel->x ];
anthonycc6c8362010-01-25 04:14:01 +0000740 /* Note that neither methods above generate a normalized kernel,
741 ** though it gets close. The kernel may be 'clipped' by a user defined
742 ** radius, producing a smaller (darker) kernel. Also for very small
743 ** sigma's (> 0.1) the central value becomes larger than one, and thus
744 ** producing a very bright kernel.
anthony602ab9b2010-01-05 08:06:50 +0000745 */
anthonycc6c8362010-01-25 04:14:01 +0000746
anthony602ab9b2010-01-05 08:06:50 +0000747 /* Normalize the 1D Gaussian Kernel
748 **
749 ** Because of this the divisor in the above kernel generator is
anthonyc94cdb02010-01-06 08:15:29 +0000750 ** not needed, so is not done above.
anthony602ab9b2010-01-05 08:06:50 +0000751 */
anthony999bb2c2010-02-18 12:38:01 +0000752 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
anthonycc6c8362010-01-25 04:14:01 +0000753
anthony602ab9b2010-01-05 08:06:50 +0000754 /* rotate the kernel by given angle */
anthony4fd27e22010-02-07 08:17:18 +0000755 RotateKernelInfo(kernel, args->xi);
anthony602ab9b2010-01-05 08:06:50 +0000756 break;
757 }
758 case CometKernel:
759 { double
760 sigma = fabs(args->sigma);
761
762 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
763
764 if ( args->rho < 1.0 )
765 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
766 else
767 kernel->width = (unsigned long)args->rho;
cristyc99304f2010-02-01 15:26:27 +0000768 kernel->x = kernel->y = 0;
anthony602ab9b2010-01-05 08:06:50 +0000769 kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +0000770 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000771 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
772 kernel->height*sizeof(double));
773 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000774 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000775
776 /* A comet blur is half a gaussian curve, so that the object is
777 ** blurred in one direction only. This may not be quite the right
778 ** curve so may change in the future. The function must be normalised.
779 */
780#if 1
781#define KernelRank 3
782 sigma *= KernelRank; /* simplify expanded curve */
cristy150989e2010-02-01 14:59:39 +0000783 v = (long) kernel->width*KernelRank; /* start/end points to fit range */
anthony602ab9b2010-01-05 08:06:50 +0000784 (void) ResetMagickMemory(kernel->values,0, (size_t)
785 kernel->width*sizeof(double));
786 for ( u=0; u < v; u++) {
787 kernel->values[u/KernelRank] +=
788 exp(-((double)(u*u))/(2.0*sigma*sigma))
789 /* / (MagickSQ2PI*sigma/KernelRank) */ ;
790 }
cristy150989e2010-02-01 14:59:39 +0000791 for (i=0; i < (long) kernel->width; i++)
cristyc99304f2010-02-01 15:26:27 +0000792 kernel->positive_range += kernel->values[i];
anthony602ab9b2010-01-05 08:06:50 +0000793#else
cristy150989e2010-02-01 14:59:39 +0000794 for ( i=0; i < (long) kernel->width; i++)
cristyc99304f2010-02-01 15:26:27 +0000795 kernel->positive_range += (
anthony602ab9b2010-01-05 08:06:50 +0000796 kernel->values[i] =
797 exp(-((double)(i*i))/(2.0*sigma*sigma))
798 /* / (MagickSQ2PI*sigma) */ );
799#endif
cristyc99304f2010-02-01 15:26:27 +0000800 kernel->minimum = 0;
801 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000802
anthony999bb2c2010-02-18 12:38:01 +0000803 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
804 RotateKernelInfo(kernel, args->xi); /* Rotate by angle */
anthony602ab9b2010-01-05 08:06:50 +0000805 break;
806 }
807 /* Boolean Kernels */
808 case RectangleKernel:
809 case SquareKernel:
810 {
anthony4fd27e22010-02-07 08:17:18 +0000811 double scale;
anthony602ab9b2010-01-05 08:06:50 +0000812 if ( type == SquareKernel )
813 {
814 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000815 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000816 else
cristy150989e2010-02-01 14:59:39 +0000817 kernel->width = kernel->height = (unsigned long) (2*args->rho+1);
cristyc99304f2010-02-01 15:26:27 +0000818 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony4fd27e22010-02-07 08:17:18 +0000819 scale = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +0000820 }
821 else {
cristy2be15382010-01-21 02:38:03 +0000822 /* NOTE: user defaults set in "AcquireKernelInfo()" */
anthony602ab9b2010-01-05 08:06:50 +0000823 if ( args->rho < 1.0 || args->sigma < 1.0 )
anthony83ba99b2010-01-24 08:48:15 +0000824 return(DestroyKernelInfo(kernel)); /* invalid args given */
anthony602ab9b2010-01-05 08:06:50 +0000825 kernel->width = (unsigned long)args->rho;
826 kernel->height = (unsigned long)args->sigma;
827 if ( args->xi < 0.0 || args->xi > (double)kernel->width ||
828 args->psi < 0.0 || args->psi > (double)kernel->height )
anthony83ba99b2010-01-24 08:48:15 +0000829 return(DestroyKernelInfo(kernel)); /* invalid args given */
cristyc99304f2010-02-01 15:26:27 +0000830 kernel->x = (long) args->xi;
831 kernel->y = (long) args->psi;
anthony4fd27e22010-02-07 08:17:18 +0000832 scale = 1.0;
anthony602ab9b2010-01-05 08:06:50 +0000833 }
834 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
835 kernel->height*sizeof(double));
836 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000837 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000838
anthonycc6c8362010-01-25 04:14:01 +0000839 /* set all kernel values to 1.0 */
cristy150989e2010-02-01 14:59:39 +0000840 u=(long) kernel->width*kernel->height;
841 for ( i=0; i < u; i++)
anthony4fd27e22010-02-07 08:17:18 +0000842 kernel->values[i] = scale;
843 kernel->minimum = kernel->maximum = scale; /* a flat shape */
844 kernel->positive_range = scale*u;
anthonycc6c8362010-01-25 04:14:01 +0000845 break;
anthony602ab9b2010-01-05 08:06:50 +0000846 }
847 case DiamondKernel:
848 {
849 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000850 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000851 else
852 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000853 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000854
855 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
856 kernel->height*sizeof(double));
857 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000858 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000859
anthony4fd27e22010-02-07 08:17:18 +0000860 /* set all kernel values within diamond area to scale given */
cristyc99304f2010-02-01 15:26:27 +0000861 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
862 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
863 if ((labs(u)+labs(v)) <= (long)kernel->x)
anthony4fd27e22010-02-07 08:17:18 +0000864 kernel->positive_range += kernel->values[i] = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +0000865 else
866 kernel->values[i] = nan;
anthony4fd27e22010-02-07 08:17:18 +0000867 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
anthony602ab9b2010-01-05 08:06:50 +0000868 break;
869 }
870 case DiskKernel:
871 {
872 long
873 limit;
874
875 limit = (long)(args->rho*args->rho);
anthony83ba99b2010-01-24 08:48:15 +0000876 if (args->rho < 0.1) /* default radius approx 3.5 */
877 kernel->width = kernel->height = 7L, limit = 10L;
anthony602ab9b2010-01-05 08:06:50 +0000878 else
879 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000880 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000881
882 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
883 kernel->height*sizeof(double));
884 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000885 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000886
anthonycc6c8362010-01-25 04:14:01 +0000887 /* set all kernel values within disk area to 1.0 */
cristyc99304f2010-02-01 15:26:27 +0000888 for ( i=0, v= -kernel->y; v <= (long)kernel->y; v++)
889 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony602ab9b2010-01-05 08:06:50 +0000890 if ((u*u+v*v) <= limit)
anthony4fd27e22010-02-07 08:17:18 +0000891 kernel->positive_range += kernel->values[i] = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +0000892 else
893 kernel->values[i] = nan;
anthony4fd27e22010-02-07 08:17:18 +0000894 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
anthony602ab9b2010-01-05 08:06:50 +0000895 break;
896 }
897 case PlusKernel:
898 {
899 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000900 kernel->width = kernel->height = 5; /* default radius 2 */
anthony602ab9b2010-01-05 08:06:50 +0000901 else
902 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000903 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000904
905 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
906 kernel->height*sizeof(double));
907 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000908 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000909
anthonycc6c8362010-01-25 04:14:01 +0000910 /* set all kernel values along axises to 1.0 */
cristyc99304f2010-02-01 15:26:27 +0000911 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
912 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony4fd27e22010-02-07 08:17:18 +0000913 kernel->values[i] = (u == 0 || v == 0) ? args->sigma : nan;
914 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
915 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
anthony602ab9b2010-01-05 08:06:50 +0000916 break;
917 }
918 /* Distance Measuring Kernels */
919 case ChebyshevKernel:
920 {
anthony602ab9b2010-01-05 08:06:50 +0000921 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000922 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000923 else
924 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000925 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000926
927 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
928 kernel->height*sizeof(double));
929 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000930 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000931
cristyc99304f2010-02-01 15:26:27 +0000932 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
933 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
934 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +0000935 args->sigma*((labs(u)>labs(v)) ? labs(u) : labs(v)) );
cristyc99304f2010-02-01 15:26:27 +0000936 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000937 break;
938 }
939 case ManhattenKernel:
940 {
anthony602ab9b2010-01-05 08:06:50 +0000941 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000942 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000943 else
944 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000945 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000946
947 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
948 kernel->height*sizeof(double));
949 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000950 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000951
cristyc99304f2010-02-01 15:26:27 +0000952 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
953 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
954 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +0000955 args->sigma*(labs(u)+labs(v)) );
cristyc99304f2010-02-01 15:26:27 +0000956 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000957 break;
958 }
959 case EuclideanKernel:
960 {
anthony602ab9b2010-01-05 08:06:50 +0000961 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000962 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000963 else
964 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000965 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000966
967 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
968 kernel->height*sizeof(double));
969 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000970 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000971
cristyc99304f2010-02-01 15:26:27 +0000972 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
973 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
974 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +0000975 args->sigma*sqrt((double)(u*u+v*v)) );
cristyc99304f2010-02-01 15:26:27 +0000976 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000977 break;
978 }
979 /* Undefined Kernels */
980 case LaplacianKernel:
981 case LOGKernel:
982 case DOGKernel:
cristy150989e2010-02-01 14:59:39 +0000983 perror("Kernel Type has not been defined yet");
anthony602ab9b2010-01-05 08:06:50 +0000984 /* FALL THRU */
985 default:
986 /* Generate a No-Op minimal kernel - 1x1 pixel */
987 kernel->values=(double *)AcquireQuantumMemory((size_t)1,sizeof(double));
988 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000989 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000990 kernel->width = kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +0000991 kernel->x = kernel->x = 0;
anthony602ab9b2010-01-05 08:06:50 +0000992 kernel->type = UndefinedKernel;
cristyc99304f2010-02-01 15:26:27 +0000993 kernel->maximum =
994 kernel->positive_range =
anthonyc94cdb02010-01-06 08:15:29 +0000995 kernel->values[0] = 1.0; /* a flat single-point no-op kernel! */
anthony602ab9b2010-01-05 08:06:50 +0000996 break;
997 }
998
999 return(kernel);
1000}
anthonyc94cdb02010-01-06 08:15:29 +00001001
anthony602ab9b2010-01-05 08:06:50 +00001002/*
1003%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1004% %
1005% %
1006% %
cristy6771f1e2010-03-05 19:43:39 +00001007% C l o n e K e r n e l I n f o %
anthony4fd27e22010-02-07 08:17:18 +00001008% %
1009% %
1010% %
1011%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1012%
1013% CloneKernelInfo() creates a new clone of the given Kernel so that its can
1014% be modified without effecting the original. The cloned kernel should be
1015% destroyed using DestoryKernelInfo() when no longer needed.
1016%
anthony7a01dcf2010-05-11 12:25:52 +00001017% Note that only the kernel pointed to is cloned, any attached list of
1018% kernels is not touched.
1019%
cristye6365592010-04-02 17:31:23 +00001020% The format of the CloneKernelInfo method is:
anthony4fd27e22010-02-07 08:17:18 +00001021%
anthony930be612010-02-08 04:26:15 +00001022% KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +00001023%
1024% A description of each parameter follows:
1025%
1026% o kernel: the Morphology/Convolution kernel to be cloned
1027%
1028*/
cristyef656912010-03-05 19:54:59 +00001029MagickExport KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +00001030{
1031 register long
1032 i;
1033
cristy19eb6412010-04-23 14:42:29 +00001034 KernelInfo
anthony7a01dcf2010-05-11 12:25:52 +00001035 *new_kernel;
anthony4fd27e22010-02-07 08:17:18 +00001036
1037 assert(kernel != (KernelInfo *) NULL);
anthony7a01dcf2010-05-11 12:25:52 +00001038 new_kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
1039 if (new_kernel == (KernelInfo *) NULL)
1040 return(new_kernel);
1041 *new_kernel=(*kernel); /* copy values in structure */
1042 new_kernel->next = (KernelInfo *) NULL; /* but not the 'next' */
1043
1044 /* replace the values with a copy of the values */
1045 new_kernel->values=(double *) AcquireQuantumMemory(kernel->width,
cristy19eb6412010-04-23 14:42:29 +00001046 kernel->height*sizeof(double));
anthony7a01dcf2010-05-11 12:25:52 +00001047 if (new_kernel->values == (double *) NULL)
1048 return(DestroyKernelInfo(new_kernel));
anthony4fd27e22010-02-07 08:17:18 +00001049 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthony7a01dcf2010-05-11 12:25:52 +00001050 new_kernel->values[i]=kernel->values[i];
1051 return(new_kernel);
anthony4fd27e22010-02-07 08:17:18 +00001052}
1053
1054/*
1055%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1056% %
1057% %
1058% %
anthony83ba99b2010-01-24 08:48:15 +00001059% D e s t r o y K e r n e l I n f o %
anthony602ab9b2010-01-05 08:06:50 +00001060% %
1061% %
1062% %
1063%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1064%
anthony83ba99b2010-01-24 08:48:15 +00001065% DestroyKernelInfo() frees the memory used by a Convolution/Morphology
1066% kernel.
anthony602ab9b2010-01-05 08:06:50 +00001067%
anthony83ba99b2010-01-24 08:48:15 +00001068% The format of the DestroyKernelInfo method is:
anthony602ab9b2010-01-05 08:06:50 +00001069%
anthony83ba99b2010-01-24 08:48:15 +00001070% KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +00001071%
1072% A description of each parameter follows:
1073%
1074% o kernel: the Morphology/Convolution kernel to be destroyed
1075%
1076*/
1077
anthony83ba99b2010-01-24 08:48:15 +00001078MagickExport KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +00001079{
cristy2be15382010-01-21 02:38:03 +00001080 assert(kernel != (KernelInfo *) NULL);
anthony4fd27e22010-02-07 08:17:18 +00001081
anthony7a01dcf2010-05-11 12:25:52 +00001082 if ( kernel->next != (KernelInfo *) NULL )
1083 kernel->next = DestroyKernelInfo(kernel->next);
1084
1085 kernel->values = (double *)RelinquishMagickMemory(kernel->values);
1086 kernel = (KernelInfo *) RelinquishMagickMemory(kernel);
anthony602ab9b2010-01-05 08:06:50 +00001087 return(kernel);
1088}
anthonyc94cdb02010-01-06 08:15:29 +00001089
1090/*
1091%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1092% %
1093% %
1094% %
anthony29188a82010-01-22 10:12:34 +00001095% M o r p h o l o g y I m a g e C h a n n e l %
anthony602ab9b2010-01-05 08:06:50 +00001096% %
1097% %
1098% %
1099%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1100%
anthony29188a82010-01-22 10:12:34 +00001101% MorphologyImageChannel() applies a user supplied kernel to the image
1102% according to the given mophology method.
anthony602ab9b2010-01-05 08:06:50 +00001103%
1104% The given kernel is assumed to have been pre-scaled appropriatally, usally
1105% by the kernel generator.
1106%
1107% The format of the MorphologyImage method is:
1108%
cristyef656912010-03-05 19:54:59 +00001109% Image *MorphologyImage(const Image *image,MorphologyMethod method,
1110% const long iterations,KernelInfo *kernel,ExceptionInfo *exception)
anthony29188a82010-01-22 10:12:34 +00001111% Image *MorphologyImageChannel(const Image *image, const ChannelType
cristyef656912010-03-05 19:54:59 +00001112% channel,MorphologyMethod method,const long iterations,
1113% KernelInfo *kernel,ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001114%
1115% A description of each parameter follows:
1116%
1117% o image: the image.
1118%
1119% o method: the morphology method to be applied.
1120%
1121% o iterations: apply the operation this many times (or no change).
1122% A value of -1 means loop until no change found.
1123% How this is applied may depend on the morphology method.
1124% Typically this is a value of 1.
1125%
1126% o channel: the channel type.
1127%
1128% o kernel: An array of double representing the morphology kernel.
anthony29188a82010-01-22 10:12:34 +00001129% Warning: kernel may be normalized for the Convolve method.
anthony602ab9b2010-01-05 08:06:50 +00001130%
1131% o exception: return any errors or warnings in this structure.
1132%
1133%
1134% TODO: bias and auto-scale handling of the kernel for convolution
1135% The given kernel is assumed to have been pre-scaled appropriatally, usally
1136% by the kernel generator.
1137%
1138*/
1139
anthony930be612010-02-08 04:26:15 +00001140
anthony602ab9b2010-01-05 08:06:50 +00001141/* Internal function
anthony7a01dcf2010-05-11 12:25:52 +00001142 * Apply the low-level Morphology Method Primatives using the given Kernel
anthony930be612010-02-08 04:26:15 +00001143 * Returning the number of pixels that changed.
1144 * Two pre-created images must be provided, no image is created.
anthony602ab9b2010-01-05 08:06:50 +00001145 */
anthony7a01dcf2010-05-11 12:25:52 +00001146static unsigned long MorphologyPrimative(const Image *image, Image
anthony602ab9b2010-01-05 08:06:50 +00001147 *result_image, const MorphologyMethod method, const ChannelType channel,
cristy2be15382010-01-21 02:38:03 +00001148 const KernelInfo *kernel, ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001149{
cristy2be15382010-01-21 02:38:03 +00001150#define MorphologyTag "Morphology/Image"
anthony602ab9b2010-01-05 08:06:50 +00001151
1152 long
cristy150989e2010-02-01 14:59:39 +00001153 progress,
anthony29188a82010-01-22 10:12:34 +00001154 y, offx, offy,
anthony602ab9b2010-01-05 08:06:50 +00001155 changed;
1156
1157 MagickBooleanType
1158 status;
1159
1160 MagickPixelPacket
1161 bias;
1162
1163 CacheView
1164 *p_view,
1165 *q_view;
1166
anthony4fd27e22010-02-07 08:17:18 +00001167 /* Only the most basic morphology is actually performed by this routine */
anthony4fd27e22010-02-07 08:17:18 +00001168
anthony602ab9b2010-01-05 08:06:50 +00001169 /*
anthony4fd27e22010-02-07 08:17:18 +00001170 Apply Basic Morphology to Image.
anthony602ab9b2010-01-05 08:06:50 +00001171 */
1172 status=MagickTrue;
1173 changed=0;
1174 progress=0;
1175
1176 GetMagickPixelPacket(image,&bias);
1177 SetMagickPixelPacketBias(image,&bias);
anthonycc6c8362010-01-25 04:14:01 +00001178 /* Future: handle auto-bias from user, based on kernel input */
anthony602ab9b2010-01-05 08:06:50 +00001179
1180 p_view=AcquireCacheView(image);
1181 q_view=AcquireCacheView(result_image);
anthony29188a82010-01-22 10:12:34 +00001182
anthonycc6c8362010-01-25 04:14:01 +00001183 /* Some methods (including convolve) needs use a reflected kernel.
1184 * Adjust 'origin' offsets for this reflected kernel.
anthony29188a82010-01-22 10:12:34 +00001185 */
cristyc99304f2010-02-01 15:26:27 +00001186 offx = kernel->x;
1187 offy = kernel->y;
anthony29188a82010-01-22 10:12:34 +00001188 switch(method) {
anthony930be612010-02-08 04:26:15 +00001189 case ConvolveMorphology:
1190 case DilateMorphology:
1191 case DilateIntensityMorphology:
1192 case DistanceMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001193 /* kernel needs to used with reflection about origin */
cristy150989e2010-02-01 14:59:39 +00001194 offx = (long) kernel->width-offx-1;
1195 offy = (long) kernel->height-offy-1;
anthony29188a82010-01-22 10:12:34 +00001196 break;
anthony5ef8e942010-05-11 06:51:12 +00001197 case ErodeMorphology:
1198 case ErodeIntensityMorphology:
1199 case HitAndMissMorphology:
1200 case ThinningMorphology:
1201 case ThickenMorphology:
1202 /* kernel is user as is, without reflection */
1203 break;
anthony930be612010-02-08 04:26:15 +00001204 default:
anthony5ef8e942010-05-11 06:51:12 +00001205 perror("Not a low level Morphology Method");
anthony930be612010-02-08 04:26:15 +00001206 break;
anthony29188a82010-01-22 10:12:34 +00001207 }
1208
anthony602ab9b2010-01-05 08:06:50 +00001209#if defined(MAGICKCORE_OPENMP_SUPPORT)
1210 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1211#endif
cristy150989e2010-02-01 14:59:39 +00001212 for (y=0; y < (long) image->rows; y++)
anthony602ab9b2010-01-05 08:06:50 +00001213 {
1214 MagickBooleanType
1215 sync;
1216
1217 register const PixelPacket
1218 *restrict p;
1219
1220 register const IndexPacket
1221 *restrict p_indexes;
1222
1223 register PixelPacket
1224 *restrict q;
1225
1226 register IndexPacket
1227 *restrict q_indexes;
1228
cristy150989e2010-02-01 14:59:39 +00001229 register long
anthony602ab9b2010-01-05 08:06:50 +00001230 x;
1231
anthony29188a82010-01-22 10:12:34 +00001232 unsigned long
anthony602ab9b2010-01-05 08:06:50 +00001233 r;
1234
1235 if (status == MagickFalse)
1236 continue;
anthony29188a82010-01-22 10:12:34 +00001237 p=GetCacheViewVirtualPixels(p_view, -offx, y-offy,
1238 image->columns+kernel->width, kernel->height, exception);
anthony602ab9b2010-01-05 08:06:50 +00001239 q=GetCacheViewAuthenticPixels(q_view,0,y,result_image->columns,1,
1240 exception);
1241 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1242 {
1243 status=MagickFalse;
1244 continue;
1245 }
1246 p_indexes=GetCacheViewVirtualIndexQueue(p_view);
1247 q_indexes=GetCacheViewAuthenticIndexQueue(q_view);
anthony29188a82010-01-22 10:12:34 +00001248 r = (image->columns+kernel->width)*offy+offx; /* constant */
1249
cristy150989e2010-02-01 14:59:39 +00001250 for (x=0; x < (long) image->columns; x++)
anthony602ab9b2010-01-05 08:06:50 +00001251 {
cristy150989e2010-02-01 14:59:39 +00001252 long
anthony602ab9b2010-01-05 08:06:50 +00001253 v;
1254
cristy150989e2010-02-01 14:59:39 +00001255 register long
anthony602ab9b2010-01-05 08:06:50 +00001256 u;
1257
1258 register const double
1259 *restrict k;
1260
1261 register const PixelPacket
1262 *restrict k_pixels;
1263
1264 register const IndexPacket
1265 *restrict k_indexes;
1266
1267 MagickPixelPacket
anthony5ef8e942010-05-11 06:51:12 +00001268 result,
1269 min,
1270 max;
anthony602ab9b2010-01-05 08:06:50 +00001271
anthony29188a82010-01-22 10:12:34 +00001272 /* Copy input to ouput image for unused channels
anthony83ba99b2010-01-24 08:48:15 +00001273 * This removes need for 'cloning' a new image every iteration
anthony29188a82010-01-22 10:12:34 +00001274 */
anthony602ab9b2010-01-05 08:06:50 +00001275 *q = p[r];
1276 if (image->colorspace == CMYKColorspace)
1277 q_indexes[x] = p_indexes[r];
1278
anthony5ef8e942010-05-11 06:51:12 +00001279 /* Defaults */
1280 min.red =
1281 min.green =
1282 min.blue =
1283 min.opacity =
1284 min.index = (MagickRealType) QuantumRange;
1285 max.red =
1286 max.green =
1287 max.blue =
1288 max.opacity =
1289 max.index = (MagickRealType) 0;
1290 /* original pixel value */
1291 result.red = (MagickRealType) p[r].red;
1292 result.green = (MagickRealType) p[r].green;
1293 result.blue = (MagickRealType) p[r].blue;
1294 result.opacity = QuantumRange - (MagickRealType) p[r].opacity;
1295 if ( image->colorspace == CMYKColorspace)
1296 result.index = (MagickRealType) p_indexes[r];
1297
anthony602ab9b2010-01-05 08:06:50 +00001298 switch (method) {
1299 case ConvolveMorphology:
anthony930be612010-02-08 04:26:15 +00001300 /* Set the user defined bias of the weighted average output
1301 **
1302 ** FUTURE: provide some way for internal functions to disable
anthony5ef8e942010-05-11 06:51:12 +00001303 ** user provided bias and scaling effects.
anthony930be612010-02-08 04:26:15 +00001304 */
anthony602ab9b2010-01-05 08:06:50 +00001305 result=bias;
anthony930be612010-02-08 04:26:15 +00001306 break;
anthony4fd27e22010-02-07 08:17:18 +00001307 case DilateIntensityMorphology:
1308 case ErodeIntensityMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001309 result.red = 0.0; /* flag indicating when first match found */
anthony4fd27e22010-02-07 08:17:18 +00001310 break;
anthony602ab9b2010-01-05 08:06:50 +00001311 default:
anthony602ab9b2010-01-05 08:06:50 +00001312 break;
1313 }
1314
1315 switch ( method ) {
1316 case ConvolveMorphology:
anthony930be612010-02-08 04:26:15 +00001317 /* Weighted Average of pixels using reflected kernel
1318 **
1319 ** NOTE for correct working of this operation for asymetrical
1320 ** kernels, the kernel needs to be applied in its reflected form.
1321 ** That is its values needs to be reversed.
1322 **
1323 ** Correlation is actually the same as this but without reflecting
1324 ** the kernel, and thus 'lower-level' that Convolution. However
1325 ** as Convolution is the more common method used, and it does not
1326 ** really cost us much in terms of processing to use a reflected
anthony5ef8e942010-05-11 06:51:12 +00001327 ** kernel, so it is Convolution that is implemented.
anthony930be612010-02-08 04:26:15 +00001328 **
1329 ** Correlation will have its kernel reflected before calling
1330 ** this function to do a Convolve.
1331 **
1332 ** For more details of Correlation vs Convolution see
1333 ** http://www.cs.umd.edu/~djacobs/CMSC426/Convolution.pdf
1334 */
anthony5ef8e942010-05-11 06:51:12 +00001335 if (((channel & SyncChannels) != 0 ) &&
1336 (image->matte == MagickTrue))
1337 { /* Channel has a 'Sync' Flag, and Alpha Channel enabled.
1338 ** Weight the color channels with Alpha Channel so that
1339 ** transparent pixels are not part of the results.
1340 */
anthony602ab9b2010-01-05 08:06:50 +00001341 MagickRealType
anthony5ef8e942010-05-11 06:51:12 +00001342 alpha, /* color channel weighting : kernel*alpha */
1343 gamma; /* divisor, sum of weighting values */
anthony602ab9b2010-01-05 08:06:50 +00001344
1345 gamma=0.0;
anthony29188a82010-01-22 10:12:34 +00001346 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001347 k_pixels = p;
1348 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001349 for (v=0; v < (long) kernel->height; v++) {
1350 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001351 if ( IsNan(*k) ) continue;
1352 alpha=(*k)*(QuantumScale*(QuantumRange-
1353 k_pixels[u].opacity));
1354 gamma += alpha;
1355 result.red += alpha*k_pixels[u].red;
1356 result.green += alpha*k_pixels[u].green;
1357 result.blue += alpha*k_pixels[u].blue;
anthony83ba99b2010-01-24 08:48:15 +00001358 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00001359 if ( image->colorspace == CMYKColorspace)
1360 result.index += alpha*k_indexes[u];
1361 }
1362 k_pixels += image->columns+kernel->width;
1363 k_indexes += image->columns+kernel->width;
1364 }
1365 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
anthony83ba99b2010-01-24 08:48:15 +00001366 result.red *= gamma;
1367 result.green *= gamma;
1368 result.blue *= gamma;
1369 result.opacity *= gamma;
1370 result.index *= gamma;
anthony602ab9b2010-01-05 08:06:50 +00001371 }
anthony5ef8e942010-05-11 06:51:12 +00001372 else
1373 {
1374 /* No 'Sync' flag, or no Alpha involved.
1375 ** Convolution is simple individual channel weigthed sum.
1376 */
1377 k = &kernel->values[ kernel->width*kernel->height-1 ];
1378 k_pixels = p;
1379 k_indexes = p_indexes;
1380 for (v=0; v < (long) kernel->height; v++) {
1381 for (u=0; u < (long) kernel->width; u++, k--) {
1382 if ( IsNan(*k) ) continue;
1383 result.red += (*k)*k_pixels[u].red;
1384 result.green += (*k)*k_pixels[u].green;
1385 result.blue += (*k)*k_pixels[u].blue;
1386 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
1387 if ( image->colorspace == CMYKColorspace)
1388 result.index += (*k)*k_indexes[u];
1389 }
1390 k_pixels += image->columns+kernel->width;
1391 k_indexes += image->columns+kernel->width;
1392 }
1393 }
anthony602ab9b2010-01-05 08:06:50 +00001394 break;
1395
anthony4fd27e22010-02-07 08:17:18 +00001396 case ErodeMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001397 /* Minimum Value within kernel neighbourhood
anthony930be612010-02-08 04:26:15 +00001398 **
1399 ** NOTE that the kernel is not reflected for this operation!
1400 **
1401 ** NOTE: in normal Greyscale Morphology, the kernel value should
1402 ** be added to the real value, this is currently not done, due to
1403 ** the nature of the boolean kernels being used.
1404 */
anthony4fd27e22010-02-07 08:17:18 +00001405 k = kernel->values;
1406 k_pixels = p;
1407 k_indexes = p_indexes;
1408 for (v=0; v < (long) kernel->height; v++) {
1409 for (u=0; u < (long) kernel->width; u++, k++) {
1410 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony5ef8e942010-05-11 06:51:12 +00001411 Minimize(min.red, (double) k_pixels[u].red);
1412 Minimize(min.green, (double) k_pixels[u].green);
1413 Minimize(min.blue, (double) k_pixels[u].blue);
1414 Minimize(min.opacity,
anthonyd37a5cb2010-05-07 06:37:03 +00001415 QuantumRange-(double) k_pixels[u].opacity);
anthony4fd27e22010-02-07 08:17:18 +00001416 if ( image->colorspace == CMYKColorspace)
anthony5ef8e942010-05-11 06:51:12 +00001417 Minimize(min.index, (double) k_indexes[u]);
anthony4fd27e22010-02-07 08:17:18 +00001418 }
1419 k_pixels += image->columns+kernel->width;
1420 k_indexes += image->columns+kernel->width;
1421 }
1422 break;
1423
anthony5ef8e942010-05-11 06:51:12 +00001424
anthony83ba99b2010-01-24 08:48:15 +00001425 case DilateMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001426 /* Maximum Value within kernel neighbourhood
anthony930be612010-02-08 04:26:15 +00001427 **
1428 ** NOTE for correct working of this operation for asymetrical
1429 ** kernels, the kernel needs to be applied in its reflected form.
1430 ** That is its values needs to be reversed.
1431 **
1432 ** NOTE: in normal Greyscale Morphology, the kernel value should
1433 ** be added to the real value, this is currently not done, due to
1434 ** the nature of the boolean kernels being used.
1435 **
1436 */
anthony29188a82010-01-22 10:12:34 +00001437 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001438 k_pixels = p;
1439 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001440 for (v=0; v < (long) kernel->height; v++) {
1441 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001442 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony5ef8e942010-05-11 06:51:12 +00001443 Maximize(max.red, (double) k_pixels[u].red);
1444 Maximize(max.green, (double) k_pixels[u].green);
1445 Maximize(max.blue, (double) k_pixels[u].blue);
1446 Maximize(max.opacity,
anthonyd37a5cb2010-05-07 06:37:03 +00001447 QuantumRange-(double) k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00001448 if ( image->colorspace == CMYKColorspace)
anthony5ef8e942010-05-11 06:51:12 +00001449 Maximize(max.index, (double) k_indexes[u]);
anthony602ab9b2010-01-05 08:06:50 +00001450 }
1451 k_pixels += image->columns+kernel->width;
1452 k_indexes += image->columns+kernel->width;
1453 }
anthony602ab9b2010-01-05 08:06:50 +00001454 break;
1455
anthony5ef8e942010-05-11 06:51:12 +00001456 case HitAndMissMorphology:
1457 case ThinningMorphology:
1458 case ThickenMorphology:
1459 /* Minimum of Foreground Pixel minus Maxumum of Background Pixels
1460 **
1461 ** NOTE that the kernel is not reflected for this operation,
1462 ** and consists of both foreground and background pixel
1463 ** neighbourhoods, 0.0 for background, and 1.0 for foreground
1464 ** with either Nan or 0.5 values for don't care.
1465 **
1466 ** Note that this can produce negative results, though really
1467 ** only a positive match has any real value.
1468 */
1469 k = kernel->values;
1470 k_pixels = p;
1471 k_indexes = p_indexes;
1472 for (v=0; v < (long) kernel->height; v++) {
1473 for (u=0; u < (long) kernel->width; u++, k++) {
1474 if ( IsNan(*k) ) continue;
1475 if ( (*k) > 0.7 )
1476 { /* minimim of foreground pixels */
1477 Minimize(min.red, (double) k_pixels[u].red);
1478 Minimize(min.green, (double) k_pixels[u].green);
1479 Minimize(min.blue, (double) k_pixels[u].blue);
1480 Minimize(min.opacity,
1481 QuantumRange-(double) k_pixels[u].opacity);
1482 if ( image->colorspace == CMYKColorspace)
1483 Minimize(min.index, (double) k_indexes[u]);
1484 }
1485 else if ( (*k) < 0.3 )
1486 { /* maximum of background pixels */
1487 Maximize(max.red, (double) k_pixels[u].red);
1488 Maximize(max.green, (double) k_pixels[u].green);
1489 Maximize(max.blue, (double) k_pixels[u].blue);
1490 Maximize(max.opacity,
1491 QuantumRange-(double) k_pixels[u].opacity);
1492 if ( image->colorspace == CMYKColorspace)
1493 Maximize(max.index, (double) k_indexes[u]);
1494 }
1495 }
1496 k_pixels += image->columns+kernel->width;
1497 k_indexes += image->columns+kernel->width;
1498 }
1499 /* Pattern Match only if min fg larger than min bg pixels */
1500 min.red -= max.red; Maximize( min.red, 0.0 );
1501 min.green -= max.green; Maximize( min.green, 0.0 );
1502 min.blue -= max.blue; Maximize( min.blue, 0.0 );
1503 min.opacity -= max.opacity; Maximize( min.opacity, 0.0 );
1504 min.index -= max.index; Maximize( min.index, 0.0 );
1505 break;
1506
anthony4fd27e22010-02-07 08:17:18 +00001507 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001508 /* Select Pixel with Minimum Intensity within kernel neighbourhood
1509 **
1510 ** WARNING: the intensity test fails for CMYK and does not
1511 ** take into account the moderating effect of teh alpha channel
1512 ** on the intensity.
1513 **
1514 ** NOTE that the kernel is not reflected for this operation!
1515 */
anthony602ab9b2010-01-05 08:06:50 +00001516 k = kernel->values;
1517 k_pixels = p;
1518 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001519 for (v=0; v < (long) kernel->height; v++) {
1520 for (u=0; u < (long) kernel->width; u++, k++) {
anthony602ab9b2010-01-05 08:06:50 +00001521 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony4fd27e22010-02-07 08:17:18 +00001522 if ( result.red == 0.0 ||
1523 PixelIntensity(&(k_pixels[u])) < PixelIntensity(q) ) {
1524 /* copy the whole pixel - no channel selection */
1525 *q = k_pixels[u];
1526 if ( result.red > 0.0 ) changed++;
1527 result.red = 1.0;
1528 }
anthony602ab9b2010-01-05 08:06:50 +00001529 }
1530 k_pixels += image->columns+kernel->width;
1531 k_indexes += image->columns+kernel->width;
1532 }
anthony602ab9b2010-01-05 08:06:50 +00001533 break;
1534
anthony83ba99b2010-01-24 08:48:15 +00001535 case DilateIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001536 /* Select Pixel with Maximum Intensity within kernel neighbourhood
1537 **
1538 ** WARNING: the intensity test fails for CMYK and does not
1539 ** take into account the moderating effect of teh alpha channel
1540 ** on the intensity.
1541 **
1542 ** NOTE for correct working of this operation for asymetrical
1543 ** kernels, the kernel needs to be applied in its reflected form.
1544 ** That is its values needs to be reversed.
1545 */
anthony29188a82010-01-22 10:12:34 +00001546 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001547 k_pixels = p;
1548 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001549 for (v=0; v < (long) kernel->height; v++) {
1550 for (u=0; u < (long) kernel->width; u++, k--) {
anthony29188a82010-01-22 10:12:34 +00001551 if ( IsNan(*k) || (*k) < 0.5 ) continue; /* boolean kernel */
1552 if ( result.red == 0.0 ||
1553 PixelIntensity(&(k_pixels[u])) > PixelIntensity(q) ) {
1554 /* copy the whole pixel - no channel selection */
1555 *q = k_pixels[u];
1556 if ( result.red > 0.0 ) changed++;
1557 result.red = 1.0;
1558 }
anthony602ab9b2010-01-05 08:06:50 +00001559 }
1560 k_pixels += image->columns+kernel->width;
1561 k_indexes += image->columns+kernel->width;
1562 }
anthony602ab9b2010-01-05 08:06:50 +00001563 break;
1564
anthony5ef8e942010-05-11 06:51:12 +00001565
anthony602ab9b2010-01-05 08:06:50 +00001566 case DistanceMorphology:
anthony930be612010-02-08 04:26:15 +00001567 /* Add kernel Value and select the minimum value found.
1568 ** The result is a iterative distance from edge of image shape.
1569 **
1570 ** All Distance Kernels are symetrical, but that may not always
1571 ** be the case. For example how about a distance from left edges?
1572 ** To work correctly with asymetrical kernels the reflected kernel
1573 ** needs to be applied.
anthony5ef8e942010-05-11 06:51:12 +00001574 **
1575 ** Actually this is really a GreyErode with a negative kernel!
1576 **
anthony930be612010-02-08 04:26:15 +00001577 */
anthony602ab9b2010-01-05 08:06:50 +00001578#if 0
anthony930be612010-02-08 04:26:15 +00001579 /* No need to do distance morphology if original value is zero
1580 ** Unfortunatally I have not been able to get this right
1581 ** when channel selection also becomes involved. -- Arrgghhh
1582 */
1583 if ( ((channel & RedChannel) == 0 && p[r].red == 0)
1584 || ((channel & GreenChannel) == 0 && p[r].green == 0)
1585 || ((channel & BlueChannel) == 0 && p[r].blue == 0)
1586 || ((channel & OpacityChannel) == 0 && p[r].opacity == 0)
1587 || (( (channel & IndexChannel) == 0
1588 || image->colorspace != CMYKColorspace
1589 ) && p_indexes[x] ==0 )
1590 )
1591 break;
anthony602ab9b2010-01-05 08:06:50 +00001592#endif
anthony29188a82010-01-22 10:12:34 +00001593 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001594 k_pixels = p;
1595 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001596 for (v=0; v < (long) kernel->height; v++) {
1597 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001598 if ( IsNan(*k) ) continue;
1599 Minimize(result.red, (*k)+k_pixels[u].red);
1600 Minimize(result.green, (*k)+k_pixels[u].green);
1601 Minimize(result.blue, (*k)+k_pixels[u].blue);
1602 Minimize(result.opacity, (*k)+QuantumRange-k_pixels[u].opacity);
1603 if ( image->colorspace == CMYKColorspace)
1604 Minimize(result.index, (*k)+k_indexes[u]);
1605 }
1606 k_pixels += image->columns+kernel->width;
1607 k_indexes += image->columns+kernel->width;
1608 }
anthony602ab9b2010-01-05 08:06:50 +00001609 break;
1610
1611 case UndefinedMorphology:
1612 default:
1613 break; /* Do nothing */
anthony83ba99b2010-01-24 08:48:15 +00001614 }
anthony5ef8e942010-05-11 06:51:12 +00001615 /* Final mathematics of results (combine with original image?)
1616 **
1617 ** NOTE: Difference Morphology operators Edge* and *Hat could also
1618 ** be done here but works better with iteration as a image difference
1619 ** in the controling function (below). Thicken and Thinning however
1620 ** should be done here so thay can be iterated correctly.
1621 */
1622 switch ( method ) {
1623 case HitAndMissMorphology:
1624 case ErodeMorphology:
1625 result = min; /* minimum of neighbourhood */
1626 break;
1627 case DilateMorphology:
1628 result = max; /* maximum of neighbourhood */
1629 break;
1630 case ThinningMorphology:
1631 /* subtract pattern match from original */
1632 result.red -= min.red;
1633 result.green -= min.green;
1634 result.blue -= min.blue;
1635 result.opacity -= min.opacity;
1636 result.index -= min.index;
1637 break;
1638 case ThickenMorphology:
1639 /* Union with original image (maximize) - or should this be + */
1640 Maximize( result.red, min.red );
1641 Maximize( result.green, min.green );
1642 Maximize( result.blue, min.blue );
1643 Maximize( result.opacity, min.opacity );
1644 Maximize( result.index, min.index );
1645 break;
1646 default:
1647 /* result directly calculated or assigned */
1648 break;
1649 }
1650 /* Assign the resulting pixel values - Clamping Result */
anthony83ba99b2010-01-24 08:48:15 +00001651 switch ( method ) {
1652 case UndefinedMorphology:
1653 case DilateIntensityMorphology:
1654 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001655 break; /* full pixel was directly assigned - not a channel method */
anthony83ba99b2010-01-24 08:48:15 +00001656 default:
anthony83ba99b2010-01-24 08:48:15 +00001657 if ((channel & RedChannel) != 0)
1658 q->red = ClampToQuantum(result.red);
1659 if ((channel & GreenChannel) != 0)
1660 q->green = ClampToQuantum(result.green);
1661 if ((channel & BlueChannel) != 0)
1662 q->blue = ClampToQuantum(result.blue);
1663 if ((channel & OpacityChannel) != 0
1664 && image->matte == MagickTrue )
1665 q->opacity = ClampToQuantum(QuantumRange-result.opacity);
1666 if ((channel & IndexChannel) != 0
1667 && image->colorspace == CMYKColorspace)
1668 q_indexes[x] = ClampToQuantum(result.index);
1669 break;
1670 }
anthony5ef8e942010-05-11 06:51:12 +00001671 /* Count up changed pixels */
anthony83ba99b2010-01-24 08:48:15 +00001672 if ( ( p[r].red != q->red )
1673 || ( p[r].green != q->green )
1674 || ( p[r].blue != q->blue )
1675 || ( p[r].opacity != q->opacity )
1676 || ( image->colorspace == CMYKColorspace &&
1677 p_indexes[r] != q_indexes[x] ) )
1678 changed++; /* The pixel had some value changed! */
anthony602ab9b2010-01-05 08:06:50 +00001679 p++;
1680 q++;
anthony83ba99b2010-01-24 08:48:15 +00001681 } /* x */
anthony602ab9b2010-01-05 08:06:50 +00001682 sync=SyncCacheViewAuthenticPixels(q_view,exception);
1683 if (sync == MagickFalse)
1684 status=MagickFalse;
1685 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1686 {
1687 MagickBooleanType
1688 proceed;
1689
1690#if defined(MAGICKCORE_OPENMP_SUPPORT)
1691 #pragma omp critical (MagickCore_MorphologyImage)
1692#endif
1693 proceed=SetImageProgress(image,MorphologyTag,progress++,image->rows);
1694 if (proceed == MagickFalse)
1695 status=MagickFalse;
1696 }
anthony83ba99b2010-01-24 08:48:15 +00001697 } /* y */
anthony602ab9b2010-01-05 08:06:50 +00001698 result_image->type=image->type;
1699 q_view=DestroyCacheView(q_view);
1700 p_view=DestroyCacheView(p_view);
cristy150989e2010-02-01 14:59:39 +00001701 return(status ? (unsigned long) changed : 0);
anthony602ab9b2010-01-05 08:06:50 +00001702}
1703
anthony4fd27e22010-02-07 08:17:18 +00001704
1705MagickExport Image *MorphologyImage(const Image *image, const MorphologyMethod
anthony930be612010-02-08 04:26:15 +00001706 method, const long iterations,const KernelInfo *kernel, ExceptionInfo
1707 *exception)
cristy2be15382010-01-21 02:38:03 +00001708{
1709 Image
1710 *morphology_image;
1711
1712 morphology_image=MorphologyImageChannel(image,DefaultChannels,method,
1713 iterations,kernel,exception);
1714 return(morphology_image);
1715}
1716
anthony4fd27e22010-02-07 08:17:18 +00001717
cristyef656912010-03-05 19:54:59 +00001718MagickExport Image *MorphologyImageChannel(const Image *image,
1719 const ChannelType channel,const MorphologyMethod method,
1720 const long iterations,const KernelInfo *kernel,ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001721{
cristy150989e2010-02-01 14:59:39 +00001722 long
1723 count;
anthony602ab9b2010-01-05 08:06:50 +00001724
1725 Image
1726 *new_image,
anthony4fd27e22010-02-07 08:17:18 +00001727 *grad_image;
anthony602ab9b2010-01-05 08:06:50 +00001728
anthonycc6c8362010-01-25 04:14:01 +00001729 const char
1730 *artifact;
1731
cristy150989e2010-02-01 14:59:39 +00001732 unsigned long
1733 changed,
1734 limit;
1735
anthony4fd27e22010-02-07 08:17:18 +00001736 KernelInfo
1737 *curr_kernel;
1738
1739 MorphologyMethod
1740 curr_method;
1741
anthony602ab9b2010-01-05 08:06:50 +00001742 assert(image != (Image *) NULL);
1743 assert(image->signature == MagickSignature);
anthony4fd27e22010-02-07 08:17:18 +00001744 assert(kernel != (KernelInfo *) NULL);
1745 assert(kernel->signature == MagickSignature);
anthony602ab9b2010-01-05 08:06:50 +00001746 assert(exception != (ExceptionInfo *) NULL);
1747 assert(exception->signature == MagickSignature);
1748
anthony602ab9b2010-01-05 08:06:50 +00001749 if ( iterations == 0 )
1750 return((Image *)NULL); /* null operation - nothing to do! */
1751
1752 /* kernel must be valid at this point
1753 * (except maybe for posible future morphology methods like "Prune"
1754 */
cristy2be15382010-01-21 02:38:03 +00001755 assert(kernel != (KernelInfo *)NULL);
anthony602ab9b2010-01-05 08:06:50 +00001756
anthony4fd27e22010-02-07 08:17:18 +00001757 count = 0; /* interation count */
1758 changed = 1; /* if compound method assume image was changed */
anthony930be612010-02-08 04:26:15 +00001759 curr_kernel = (KernelInfo *)kernel; /* allow kernel and method */
1760 curr_method = method; /* to be changed as nessary */
anthony4fd27e22010-02-07 08:17:18 +00001761
cristy150989e2010-02-01 14:59:39 +00001762 limit = (unsigned long) iterations;
anthony602ab9b2010-01-05 08:06:50 +00001763 if ( iterations < 0 )
1764 limit = image->columns > image->rows ? image->columns : image->rows;
1765
anthony4fd27e22010-02-07 08:17:18 +00001766 /* Third-level morphology methods */
cristy5ee247a2010-02-12 15:42:34 +00001767 grad_image=(Image *) NULL;
anthony4fd27e22010-02-07 08:17:18 +00001768 switch( curr_method ) {
1769 case EdgeMorphology:
1770 grad_image = MorphologyImageChannel(image, channel,
1771 DilateMorphology, iterations, curr_kernel, exception);
1772 /* FALL-THRU */
1773 case EdgeInMorphology:
1774 curr_method = ErodeMorphology;
anthony602ab9b2010-01-05 08:06:50 +00001775 break;
anthony4fd27e22010-02-07 08:17:18 +00001776 case EdgeOutMorphology:
1777 curr_method = DilateMorphology;
anthony602ab9b2010-01-05 08:06:50 +00001778 break;
anthony4fd27e22010-02-07 08:17:18 +00001779 case TopHatMorphology:
1780 curr_method = OpenMorphology;
1781 break;
1782 case BottomHatMorphology:
1783 curr_method = CloseMorphology;
1784 break;
1785 default:
anthony930be612010-02-08 04:26:15 +00001786 break; /* not a third-level method */
anthony4fd27e22010-02-07 08:17:18 +00001787 }
1788
1789 /* Second-level morphology methods */
1790 switch( curr_method ) {
anthony930be612010-02-08 04:26:15 +00001791 case OpenMorphology:
1792 /* Open is a Erode then a Dilate without reflection */
anthony4fd27e22010-02-07 08:17:18 +00001793 new_image = MorphologyImageChannel(image, channel,
1794 ErodeMorphology, iterations, curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001795 if (new_image == (Image *) NULL)
1796 return((Image *) NULL);
anthony4fd27e22010-02-07 08:17:18 +00001797 curr_method = DilateMorphology;
1798 break;
anthony602ab9b2010-01-05 08:06:50 +00001799 case OpenIntensityMorphology:
anthony4fd27e22010-02-07 08:17:18 +00001800 new_image = MorphologyImageChannel(image, channel,
1801 ErodeIntensityMorphology, iterations, curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001802 if (new_image == (Image *) NULL)
1803 return((Image *) NULL);
anthony4fd27e22010-02-07 08:17:18 +00001804 curr_method = DilateIntensityMorphology;
1805 break;
anthony930be612010-02-08 04:26:15 +00001806
1807 case CloseMorphology:
1808 /* Close is a Dilate then Erode using reflected kernel */
1809 /* A reflected kernel is needed for a Close */
1810 if ( curr_kernel == kernel )
1811 curr_kernel = CloneKernelInfo(kernel);
1812 RotateKernelInfo(curr_kernel,180);
1813 new_image = MorphologyImageChannel(image, channel,
1814 DilateMorphology, iterations, curr_kernel, exception);
1815 if (new_image == (Image *) NULL)
1816 return((Image *) NULL);
1817 curr_method = ErodeMorphology;
1818 break;
anthony4fd27e22010-02-07 08:17:18 +00001819 case CloseIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001820 /* A reflected kernel is needed for a Close */
1821 if ( curr_kernel == kernel )
1822 curr_kernel = CloneKernelInfo(kernel);
anthony4fd27e22010-02-07 08:17:18 +00001823 RotateKernelInfo(curr_kernel,180);
1824 new_image = MorphologyImageChannel(image, channel,
1825 DilateIntensityMorphology, iterations, curr_kernel, exception);
1826 if (new_image == (Image *) NULL)
1827 return((Image *) NULL);
1828 curr_method = ErodeIntensityMorphology;
anthony602ab9b2010-01-05 08:06:50 +00001829 break;
1830
anthony930be612010-02-08 04:26:15 +00001831 case CorrelateMorphology:
1832 /* A Correlation is actually a Convolution with a reflected kernel.
1833 ** However a Convolution is a weighted sum with a reflected kernel.
1834 ** It may seem stange to convert a Correlation into a Convolution
1835 ** as the Correleation is the simplier method, but Convolution is
1836 ** much more commonly used, and it makes sense to implement it directly
1837 ** so as to avoid the need to duplicate the kernel when it is not
1838 ** required (which is typically the default).
1839 */
1840 if ( curr_kernel == kernel )
1841 curr_kernel = CloneKernelInfo(kernel);
1842 RotateKernelInfo(curr_kernel,180);
1843 curr_method = ConvolveMorphology;
1844 /* FALL-THRU into Correlate (weigthed sum without reflection) */
1845
anthonyc94cdb02010-01-06 08:15:29 +00001846 case ConvolveMorphology:
anthony4fd27e22010-02-07 08:17:18 +00001847 /* Scale or Normalize kernel, according to user wishes
anthony930be612010-02-08 04:26:15 +00001848 ** before using it for the Convolve/Correlate method.
1849 **
1850 ** FUTURE: provide some way for internal functions to disable
1851 ** user bias and scaling effects.
anthonycc6c8362010-01-25 04:14:01 +00001852 */
1853 artifact = GetImageArtifact(image,"convolve:scale");
anthony4fd27e22010-02-07 08:17:18 +00001854 if ( artifact != (char *)NULL ) {
cristy19eb6412010-04-23 14:42:29 +00001855 GeometryFlags
anthony999bb2c2010-02-18 12:38:01 +00001856 flags;
1857 GeometryInfo
1858 args;
1859
anthony930be612010-02-08 04:26:15 +00001860 if ( curr_kernel == kernel )
1861 curr_kernel = CloneKernelInfo(kernel);
anthony999bb2c2010-02-18 12:38:01 +00001862
1863 args.rho = 1.0;
cristy19eb6412010-04-23 14:42:29 +00001864 flags = (GeometryFlags) ParseGeometry(artifact, &args);
anthony999bb2c2010-02-18 12:38:01 +00001865 ScaleKernelInfo(curr_kernel, args.rho, flags);
anthony4fd27e22010-02-07 08:17:18 +00001866 }
anthony930be612010-02-08 04:26:15 +00001867 /* FALL-THRU to do the first, and typically the only iteration */
anthony4fd27e22010-02-07 08:17:18 +00001868
anthony602ab9b2010-01-05 08:06:50 +00001869 default:
anthony930be612010-02-08 04:26:15 +00001870 /* Do a single iteration using the Low-Level Morphology method!
1871 ** This ensures a "new_image" has been generated, but allows us to skip
1872 ** the creation of 'old_image' if no more iterations are needed.
1873 **
1874 ** The "curr_method" should also be set to a low-level method that is
anthony7a01dcf2010-05-11 12:25:52 +00001875 ** understood by the MorphologyPrimative() internal function.
anthony602ab9b2010-01-05 08:06:50 +00001876 */
1877 new_image=CloneImage(image,0,0,MagickTrue,exception);
1878 if (new_image == (Image *) NULL)
1879 return((Image *) NULL);
1880 if (SetImageStorageClass(new_image,DirectClass) == MagickFalse)
1881 {
1882 InheritException(exception,&new_image->exception);
1883 new_image=DestroyImage(new_image);
1884 return((Image *) NULL);
1885 }
anthony7a01dcf2010-05-11 12:25:52 +00001886 changed = MorphologyPrimative(image,new_image,curr_method,channel,
1887 curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001888 count++;
1889 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
cristy150989e2010-02-01 14:59:39 +00001890 fprintf(stderr, "Morphology %s:%ld => Changed %lu\n",
anthony4fd27e22010-02-07 08:17:18 +00001891 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
anthony602ab9b2010-01-05 08:06:50 +00001892 count, changed);
anthony930be612010-02-08 04:26:15 +00001893 break;
anthony602ab9b2010-01-05 08:06:50 +00001894 }
1895
anthony7a01dcf2010-05-11 12:25:52 +00001896 /* The Hit-And-Miss Morphology must apply a list of kernels to the image
1897 ** provided, and then create a union (maximimum or lighten) of the results.
1898 ** Iterations are not permitted for this process.
1899 **
1900 ** The first kernal has now been done, so lets continue the process.
1901 */
1902 if ( method == HitAndMissMorphology &&
1903 curr_kernel->next != (KernelInfo *) NULL )
1904 {
1905 Image
1906 *next_image;
1907
1908 next_image = CloneImage(new_image,0,0,MagickTrue,exception);
1909 if (next_image == (Image *) NULL)
1910 return(DestroyImage(new_image));
1911 while( curr_kernel->next != (KernelInfo *) NULL )
1912 {
1913 curr_kernel = curr_kernel->next;
1914 changed = MorphologyPrimative(image,next_image,curr_method,channel,
1915 curr_kernel,exception);
1916 count++;
1917 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
1918 fprintf(stderr, " kernel %s:%ld => Changed %lu\n",
1919 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
1920 count, changed);
1921 (void) CompositeImageChannel(new_image,
1922 (ChannelType) (channel & ~SyncChannels), LightenCompositeOp,
1923 next_image, 0, 0);
1924 }
1925 next_image=DestroyImage(next_image);
1926
1927 /* we should not have made a clone of the kernel info - no need to free */
1928
1929 return(new_image);
1930 }
1931
anthony930be612010-02-08 04:26:15 +00001932 /* At this point the "curr_method" should not only be set to a low-level
1933 ** method that is understood by the MorphologyApply() internal function,
1934 ** but "new_image" should now be defined, as the image to apply the
1935 ** "curr_method" to.
1936 */
1937
1938 /* Repeat the low-level morphology until count or no change reached */
cristy150989e2010-02-01 14:59:39 +00001939 if ( count < (long) limit && changed > 0 ) {
anthony7a01dcf2010-05-11 12:25:52 +00001940 Image
1941 *old_image;
1942
anthony602ab9b2010-01-05 08:06:50 +00001943 old_image = CloneImage(new_image,0,0,MagickTrue,exception);
1944 if (old_image == (Image *) NULL)
1945 return(DestroyImage(new_image));
1946 if (SetImageStorageClass(old_image,DirectClass) == MagickFalse)
1947 {
1948 InheritException(exception,&old_image->exception);
1949 old_image=DestroyImage(old_image);
1950 return(DestroyImage(new_image));
1951 }
cristy150989e2010-02-01 14:59:39 +00001952 while( count < (long) limit && changed != 0 )
anthony602ab9b2010-01-05 08:06:50 +00001953 {
1954 Image *tmp = old_image;
1955 old_image = new_image;
1956 new_image = tmp;
anthony7a01dcf2010-05-11 12:25:52 +00001957 changed = MorphologyPrimative(old_image,new_image,curr_method,channel,
1958 curr_kernel,exception);
anthony602ab9b2010-01-05 08:06:50 +00001959 count++;
1960 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
cristy150989e2010-02-01 14:59:39 +00001961 fprintf(stderr, "Morphology %s:%ld => Changed %lu\n",
anthony4fd27e22010-02-07 08:17:18 +00001962 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
anthony602ab9b2010-01-05 08:06:50 +00001963 count, changed);
1964 }
cristy150989e2010-02-01 14:59:39 +00001965 old_image=DestroyImage(old_image);
anthony602ab9b2010-01-05 08:06:50 +00001966 }
anthony930be612010-02-08 04:26:15 +00001967
1968 /* We are finished with kernel - destroy it if we made a clone */
anthony4fd27e22010-02-07 08:17:18 +00001969 if ( curr_kernel != kernel )
1970 curr_kernel=DestroyKernelInfo(curr_kernel);
1971
anthony7d10d742010-05-06 07:05:29 +00001972 /* Third-level Subtractive methods post-processing
1973 **
1974 ** The removal of any 'Sync' channel flag in the Image Compositon below
1975 ** ensures the compose method is applied in a purely mathematical way, only
1976 ** the selected channels, without any normal 'alpha blending' normally
1977 ** associated with the compose method.
1978 **
1979 ** Note "method" here is the 'original' morphological method, and not the
1980 ** 'current' morphological method used above to generate "new_image".
1981 */
anthony4fd27e22010-02-07 08:17:18 +00001982 switch( method ) {
1983 case EdgeOutMorphology:
1984 case EdgeInMorphology:
1985 case TopHatMorphology:
1986 case BottomHatMorphology:
anthony930be612010-02-08 04:26:15 +00001987 /* Get Difference relative to the original image */
cristyaeb2cbc2010-05-07 13:28:58 +00001988 (void) CompositeImageChannel(new_image, (ChannelType) (channel & ~SyncChannels),
anthony7d10d742010-05-06 07:05:29 +00001989 DifferenceCompositeOp, image, 0, 0);
anthony4fd27e22010-02-07 08:17:18 +00001990 break;
anthony7d10d742010-05-06 07:05:29 +00001991 case EdgeMorphology:
1992 /* Difference the Eroded image from the saved Dilated image */
cristyaeb2cbc2010-05-07 13:28:58 +00001993 (void) CompositeImageChannel(new_image, (ChannelType) (channel & ~SyncChannels),
anthony7d10d742010-05-06 07:05:29 +00001994 DifferenceCompositeOp, grad_image, 0, 0);
anthony4fd27e22010-02-07 08:17:18 +00001995 grad_image=DestroyImage(grad_image);
1996 break;
1997 default:
1998 break;
1999 }
anthony602ab9b2010-01-05 08:06:50 +00002000
2001 return(new_image);
2002}
anthony83ba99b2010-01-24 08:48:15 +00002003
2004/*
2005%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2006% %
2007% %
2008% %
anthony4fd27e22010-02-07 08:17:18 +00002009+ R o t a t e K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00002010% %
2011% %
2012% %
2013%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2014%
anthony4fd27e22010-02-07 08:17:18 +00002015% RotateKernelInfo() rotates the kernel by the angle given. Currently it is
anthony83ba99b2010-01-24 08:48:15 +00002016% restricted to 90 degree angles, but this may be improved in the future.
2017%
anthony4fd27e22010-02-07 08:17:18 +00002018% The format of the RotateKernelInfo method is:
anthony83ba99b2010-01-24 08:48:15 +00002019%
anthony4fd27e22010-02-07 08:17:18 +00002020% void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00002021%
2022% A description of each parameter follows:
2023%
2024% o kernel: the Morphology/Convolution kernel
2025%
2026% o angle: angle to rotate in degrees
2027%
anthonyc4c86e02010-01-27 09:30:32 +00002028% This function is only internel to this module, as it is not finalized,
2029% especially with regard to non-orthogonal angles, and rotation of larger
2030% 2D kernels.
anthony83ba99b2010-01-24 08:48:15 +00002031*/
anthony4fd27e22010-02-07 08:17:18 +00002032static void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00002033{
2034 /* WARNING: Currently assumes the kernel (rightly) is horizontally symetrical
2035 **
2036 ** TODO: expand beyond simple 90 degree rotates, flips and flops
2037 */
2038
2039 /* Modulus the angle */
2040 angle = fmod(angle, 360.0);
2041 if ( angle < 0 )
2042 angle += 360.0;
2043
2044 if ( 315.0 < angle || angle <= 45.0 )
2045 return; /* no change! - At least at this time */
2046
2047 switch (kernel->type) {
2048 /* These built-in kernels are cylindrical kernels, rotating is useless */
2049 case GaussianKernel:
2050 case LaplacianKernel:
2051 case LOGKernel:
2052 case DOGKernel:
2053 case DiskKernel:
2054 case ChebyshevKernel:
2055 case ManhattenKernel:
2056 case EuclideanKernel:
2057 return;
2058
2059 /* These may be rotatable at non-90 angles in the future */
2060 /* but simply rotating them in multiples of 90 degrees is useless */
2061 case SquareKernel:
2062 case DiamondKernel:
2063 case PlusKernel:
2064 return;
2065
2066 /* These only allows a +/-90 degree rotation (by transpose) */
2067 /* A 180 degree rotation is useless */
2068 case BlurKernel:
2069 case RectangleKernel:
2070 if ( 135.0 < angle && angle <= 225.0 )
2071 return;
2072 if ( 225.0 < angle && angle <= 315.0 )
2073 angle -= 180;
2074 break;
2075
2076 /* these are freely rotatable in 90 degree units */
2077 case CometKernel:
2078 case UndefinedKernel:
2079 case UserDefinedKernel:
2080 break;
2081 }
2082 if ( 135.0 < angle && angle <= 225.0 )
2083 {
2084 /* For a 180 degree rotation - also know as a reflection */
2085 /* This is actually a very very common operation! */
2086 /* Basically all that is needed is a reversal of the kernel data! */
2087 unsigned long
2088 i,j;
2089 register double
2090 *k,t;
2091
2092 k=kernel->values;
2093 for ( i=0, j=kernel->width*kernel->height-1; i<j; i++, j--)
2094 t=k[i], k[i]=k[j], k[j]=t;
2095
anthony930be612010-02-08 04:26:15 +00002096 kernel->x = (long) kernel->width - kernel->x - 1;
2097 kernel->y = (long) kernel->height - kernel->y - 1;
anthony83ba99b2010-01-24 08:48:15 +00002098 angle = fmod(angle+180.0, 360.0);
2099 }
2100 if ( 45.0 < angle && angle <= 135.0 )
2101 { /* Do a transpose and a flop, of the image, which results in a 90
2102 * degree rotation using two mirror operations.
2103 *
2104 * WARNING: this assumes the original image was a 1 dimentional image
2105 * but currently that is the only built-ins it is applied to.
2106 */
cristy150989e2010-02-01 14:59:39 +00002107 long
anthony83ba99b2010-01-24 08:48:15 +00002108 t;
cristy150989e2010-02-01 14:59:39 +00002109 t = (long) kernel->width;
anthony83ba99b2010-01-24 08:48:15 +00002110 kernel->width = kernel->height;
cristy150989e2010-02-01 14:59:39 +00002111 kernel->height = (unsigned long) t;
cristyc99304f2010-02-01 15:26:27 +00002112 t = kernel->x;
2113 kernel->x = kernel->y;
2114 kernel->y = t;
anthony83ba99b2010-01-24 08:48:15 +00002115 angle = fmod(450.0 - angle, 360.0);
2116 }
2117 /* At this point angle should be between -45 (315) and +45 degrees
2118 * In the future some form of non-orthogonal angled rotates could be
2119 * performed here, posibily with a linear kernel restriction.
2120 */
2121
2122#if 0
2123 Not currently in use!
2124 { /* Do a flop, this assumes kernel is horizontally symetrical.
2125 * Each row of the kernel needs to be reversed!
2126 */
2127 unsigned long
2128 y;
cristy150989e2010-02-01 14:59:39 +00002129 register long
anthony83ba99b2010-01-24 08:48:15 +00002130 x,r;
2131 register double
2132 *k,t;
2133
2134 for ( y=0, k=kernel->values; y < kernel->height; y++, k+=kernel->width)
2135 for ( x=0, r=kernel->width-1; x<kernel->width/2; x++, r--)
2136 t=k[x], k[x]=k[r], k[r]=t;
2137
cristyc99304f2010-02-01 15:26:27 +00002138 kernel->x = kernel->width - kernel->x - 1;
anthony83ba99b2010-01-24 08:48:15 +00002139 angle = fmod(angle+180.0, 360.0);
2140 }
2141#endif
2142 return;
2143}
2144
2145/*
2146%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2147% %
2148% %
2149% %
cristy6771f1e2010-03-05 19:43:39 +00002150% S c a l e K e r n e l I n f o %
anthonycc6c8362010-01-25 04:14:01 +00002151% %
2152% %
2153% %
2154%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2155%
anthony999bb2c2010-02-18 12:38:01 +00002156% ScaleKernelInfo() scales the kernel by the given amount, with or without
2157% normalization of the sum of the kernel values.
anthonycc6c8362010-01-25 04:14:01 +00002158%
anthony999bb2c2010-02-18 12:38:01 +00002159% By default (no flags given) the values within the kernel is scaled
2160% according the given scaling factor.
anthonycc6c8362010-01-25 04:14:01 +00002161%
anthony999bb2c2010-02-18 12:38:01 +00002162% If any 'normalize_flags' are given the kernel will be normalized and then
2163% further scaled by the scaleing factor value given. A 'PercentValue' flag
2164% will cause the given scaling factor to be divided by one hundred percent.
2165%
2166% Kernel normalization ('normalize_flags' given) is designed to ensure that
2167% any use of the kernel scaling factor with 'Convolve' or 'Correlate'
2168% morphology methods will fall into -1.0 to +1.0 range. Note however that
2169% for non-HDRI versions of IM this may cause images to have any negative
2170% results clipped, unless some 'clip' any negative output from 'Convolve'
2171% with the use of some kernels.
2172%
2173% More specifically. Kernels which only contain positive values (such as a
2174% 'Gaussian' kernel) will be scaled so that those values sum to +1.0,
2175% ensuring a 0.0 to +1.0 convolution output range for non-HDRI images.
2176%
2177% For Kernels that contain some negative values, (such as 'Sharpen' kernels)
2178% the kernel will be scaled by the absolute of the sum of kernel values, so
2179% that it will generally fall within the +/- 1.0 range.
2180%
2181% For kernels whose values sum to zero, (such as 'Laplician' kernels) kernel
2182% will be scaled by just the sum of the postive values, so that its output
2183% range will again fall into the +/- 1.0 range.
2184%
2185% For special kernels designed for locating shapes using 'Correlate', (often
2186% only containing +1 and -1 values, representing foreground/brackground
2187% matching) a special normalization method is provided to scale the positive
2188% values seperatally to those of the negative values, so the kernel will be
2189% forced to become a zero-sum kernel better suited to such searches.
2190%
2191% WARNING: Correct normalization of the kernal assumes that the '*_range'
2192% attributes within the kernel structure have been correctly set during the
2193% kernels creation.
2194%
2195% NOTE: The values used for 'normalize_flags' have been selected specifically
2196% to match the use of geometry options, so that '!' means NormalizeValue, '^'
2197% means CorrelateNormalizeValue, and '%' means PercentValue. All other
2198% GeometryFlags values are ignored.
anthonycc6c8362010-01-25 04:14:01 +00002199%
anthony4fd27e22010-02-07 08:17:18 +00002200% The format of the ScaleKernelInfo method is:
anthonycc6c8362010-01-25 04:14:01 +00002201%
anthony999bb2c2010-02-18 12:38:01 +00002202% void ScaleKernelInfo(KernelInfo *kernel, const double scaling_factor,
2203% const MagickStatusType normalize_flags )
anthonycc6c8362010-01-25 04:14:01 +00002204%
2205% A description of each parameter follows:
2206%
2207% o kernel: the Morphology/Convolution kernel
2208%
anthony999bb2c2010-02-18 12:38:01 +00002209% o scaling_factor:
2210% multiply all values (after normalization) by this factor if not
2211% zero. If the kernel is normalized regardless of any flags.
2212%
2213% o normalize_flags:
2214% GeometryFlags defining normalization method to use.
2215% specifically: NormalizeValue, CorrelateNormalizeValue,
2216% and/or PercentValue
anthonycc6c8362010-01-25 04:14:01 +00002217%
anthonyc4c86e02010-01-27 09:30:32 +00002218% This function is internal to this module only at this time, but can be
2219% exported to other modules if needed.
anthonycc6c8362010-01-25 04:14:01 +00002220*/
cristy6771f1e2010-03-05 19:43:39 +00002221MagickExport void ScaleKernelInfo(KernelInfo *kernel,
2222 const double scaling_factor,const GeometryFlags normalize_flags)
anthonycc6c8362010-01-25 04:14:01 +00002223{
cristy150989e2010-02-01 14:59:39 +00002224 register long
anthonycc6c8362010-01-25 04:14:01 +00002225 i;
2226
anthony999bb2c2010-02-18 12:38:01 +00002227 register double
2228 pos_scale,
2229 neg_scale;
2230
2231 pos_scale = 1.0;
2232 if ( (normalize_flags&NormalizeValue) != 0 ) {
2233 /* normalize kernel appropriately */
2234 if ( fabs(kernel->positive_range + kernel->negative_range) > MagickEpsilon )
2235 pos_scale = fabs(kernel->positive_range + kernel->negative_range);
anthonycc6c8362010-01-25 04:14:01 +00002236 else
anthony999bb2c2010-02-18 12:38:01 +00002237 pos_scale = kernel->positive_range; /* special zero-summing kernel */
2238 }
2239 /* force kernel into being a normalized zero-summing kernel */
2240 if ( (normalize_flags&CorrelateNormalizeValue) != 0 ) {
2241 pos_scale = ( fabs(kernel->positive_range) > MagickEpsilon )
2242 ? kernel->positive_range : 1.0;
2243 neg_scale = ( fabs(kernel->negative_range) > MagickEpsilon )
2244 ? -kernel->negative_range : 1.0;
2245 }
2246 else
2247 neg_scale = pos_scale;
2248
2249 /* finialize scaling_factor for positive and negative components */
2250 pos_scale = scaling_factor/pos_scale;
2251 neg_scale = scaling_factor/neg_scale;
2252 if ( (normalize_flags&PercentValue) != 0 ) {
2253 pos_scale /= 100.0;
2254 neg_scale /= 100.0;
anthonycc6c8362010-01-25 04:14:01 +00002255 }
2256
cristy150989e2010-02-01 14:59:39 +00002257 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00002258 if ( ! IsNan(kernel->values[i]) )
anthony999bb2c2010-02-18 12:38:01 +00002259 kernel->values[i] *= (kernel->values[i] >= 0) ? pos_scale : neg_scale;
anthonycc6c8362010-01-25 04:14:01 +00002260
anthony999bb2c2010-02-18 12:38:01 +00002261 /* convolution output range */
2262 kernel->positive_range *= pos_scale;
2263 kernel->negative_range *= neg_scale;
2264 /* maximum and minimum values in kernel */
2265 kernel->maximum *= (kernel->maximum >= 0.0) ? pos_scale : neg_scale;
2266 kernel->minimum *= (kernel->minimum >= 0.0) ? pos_scale : neg_scale;
2267
2268 /* swap kernel settings if user scaling factor is negative */
2269 if ( scaling_factor < MagickEpsilon ) {
2270 double t;
2271 t = kernel->positive_range;
2272 kernel->positive_range = kernel->negative_range;
2273 kernel->negative_range = t;
2274 t = kernel->maximum;
2275 kernel->maximum = kernel->minimum;
2276 kernel->minimum = 1;
2277 }
anthonycc6c8362010-01-25 04:14:01 +00002278
2279 return;
2280}
2281
2282/*
2283%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2284% %
2285% %
2286% %
anthony4fd27e22010-02-07 08:17:18 +00002287+ S h o w K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00002288% %
2289% %
2290% %
2291%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2292%
anthony4fd27e22010-02-07 08:17:18 +00002293% ShowKernelInfo() outputs the details of the given kernel defination to
2294% standard error, generally due to a users 'showkernel' option request.
anthony83ba99b2010-01-24 08:48:15 +00002295%
2296% The format of the ShowKernel method is:
2297%
anthony4fd27e22010-02-07 08:17:18 +00002298% void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00002299%
2300% A description of each parameter follows:
2301%
2302% o kernel: the Morphology/Convolution kernel
2303%
anthonyc4c86e02010-01-27 09:30:32 +00002304% This function is internal to this module only at this time. That may change
2305% in the future.
anthony83ba99b2010-01-24 08:48:15 +00002306*/
anthony4fd27e22010-02-07 08:17:18 +00002307MagickExport void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00002308{
anthony7a01dcf2010-05-11 12:25:52 +00002309 KernelInfo
2310 *k;
anthony83ba99b2010-01-24 08:48:15 +00002311
anthony7a01dcf2010-05-11 12:25:52 +00002312 long
2313 c, i, u, v;
2314
2315 for (c=0, k=kernel; k != (KernelInfo *) NULL; c++, k=k->next ) {
2316
2317 fprintf(stderr, "Kernel ");
2318 if ( kernel->next != (KernelInfo *) NULL )
2319 fprintf(stderr, " #%ld", c );
2320 fprintf(stderr, " \"%s\" of size %lux%lu%+ld%+ld ",
2321 MagickOptionToMnemonic(MagickKernelOptions, k->type),
2322 kernel->width, k->height,
2323 kernel->x, kernel->y );
2324 fprintf(stderr,
2325 " with values from %.*lg to %.*lg\n",
2326 GetMagickPrecision(), k->minimum,
2327 GetMagickPrecision(), k->maximum);
2328 fprintf(stderr, "Forming convolution output range from %.*lg to %.*lg%s\n",
2329 GetMagickPrecision(), k->negative_range,
2330 GetMagickPrecision(), k->positive_range,
2331 /*kernel->normalized == MagickTrue ? " (normalized)" : */ "" );
2332 for (i=v=0; v < (long) k->height; v++) {
2333 fprintf(stderr,"%2ld:",v);
2334 for (u=0; u < (long) k->width; u++, i++)
2335 if ( IsNan(k->values[i]) )
2336 fprintf(stderr," %*s", GetMagickPrecision()+2, "nan");
2337 else
2338 fprintf(stderr," %*.*lg", GetMagickPrecision()+2,
2339 GetMagickPrecision(), k->values[i]);
2340 fprintf(stderr,"\n");
2341 }
anthony83ba99b2010-01-24 08:48:15 +00002342 }
2343}
anthonycc6c8362010-01-25 04:14:01 +00002344
2345/*
2346%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2347% %
2348% %
2349% %
anthony4fd27e22010-02-07 08:17:18 +00002350+ Z e r o K e r n e l N a n s %
anthonycc6c8362010-01-25 04:14:01 +00002351% %
2352% %
2353% %
2354%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2355%
2356% ZeroKernelNans() replaces any special 'nan' value that may be present in
2357% the kernel with a zero value. This is typically done when the kernel will
2358% be used in special hardware (GPU) convolution processors, to simply
2359% matters.
2360%
2361% The format of the ZeroKernelNans method is:
2362%
2363% voidZeroKernelNans (KernelInfo *kernel)
2364%
2365% A description of each parameter follows:
2366%
2367% o kernel: the Morphology/Convolution kernel
2368%
2369% FUTURE: return the information in a string for API usage.
2370*/
anthonyc4c86e02010-01-27 09:30:32 +00002371MagickExport void ZeroKernelNans(KernelInfo *kernel)
anthonycc6c8362010-01-25 04:14:01 +00002372{
cristy150989e2010-02-01 14:59:39 +00002373 register long
anthonycc6c8362010-01-25 04:14:01 +00002374 i;
2375
cristy150989e2010-02-01 14:59:39 +00002376 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00002377 if ( IsNan(kernel->values[i]) )
2378 kernel->values[i] = 0.0;
2379
2380 return;
2381}