blob: d23d84ab0dd67ebe27778e6eb73d9bffddb6c68f [file] [log] [blame]
cristy701db312009-11-20 03:14:08 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% M M OOO RRRR PPPP H H OOO L OOO GGGG Y Y %
7% MM MM O O R R P P H H O O L O O G Y Y %
8% M M M O O RRRR PPPP HHHHH O O L O O G GGG Y %
9% M M O O R R P H H O O L O O G G Y %
10% M M OOO R R P H H OOO LLLLL OOO GGG Y %
11% %
12% %
13% MagickCore Morphology Methods %
14% %
15% Software Design %
16% Anthony Thyssen %
anthonyc94cdb02010-01-06 08:15:29 +000017% January 2010 %
cristy701db312009-11-20 03:14:08 +000018% %
19% %
cristy16af1cb2009-12-11 21:38:29 +000020% Copyright 1999-2010 ImageMagick Studio LLC, a non-profit organization %
cristy701db312009-11-20 03:14:08 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
anthony1b2bc0a2010-05-12 05:25:22 +000036% Morpology is the the application of various kernels, of any size and even
anthony602ab9b2010-01-05 08:06:50 +000037% shape, to a image in various ways (typically binary, but not always).
cristy701db312009-11-20 03:14:08 +000038%
anthony602ab9b2010-01-05 08:06:50 +000039% Convolution (weighted sum or average) is just one specific type of
40% morphology. Just one that is very common for image bluring and sharpening
41% effects. Not only 2D Gaussian blurring, but also 2-pass 1D Blurring.
42%
43% This module provides not only a general morphology function, and the ability
44% to apply more advanced or iterative morphologies, but also functions for the
45% generation of many different types of kernel arrays from user supplied
46% arguments. Prehaps even the generation of a kernel from a small image.
cristy701db312009-11-20 03:14:08 +000047*/
48
49/*
50 Include declarations.
51*/
52#include "magick/studio.h"
anthony602ab9b2010-01-05 08:06:50 +000053#include "magick/artifact.h"
cristy701db312009-11-20 03:14:08 +000054#include "magick/cache-view.h"
55#include "magick/color-private.h"
56#include "magick/enhance.h"
57#include "magick/exception.h"
58#include "magick/exception-private.h"
anthony602ab9b2010-01-05 08:06:50 +000059#include "magick/gem.h"
cristy701db312009-11-20 03:14:08 +000060#include "magick/hashmap.h"
61#include "magick/image.h"
cristybba804b2010-01-05 15:39:59 +000062#include "magick/image-private.h"
cristy701db312009-11-20 03:14:08 +000063#include "magick/list.h"
anthony29188a82010-01-22 10:12:34 +000064#include "magick/magick.h"
cristy701db312009-11-20 03:14:08 +000065#include "magick/memory_.h"
66#include "magick/monitor-private.h"
67#include "magick/morphology.h"
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
anthony1b2bc0a2010-05-12 05:25:22 +000085 These are used a Kernel value of NaN means that that kernel position is not
cristya29d45f2010-03-05 21:14:54 +000086 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 ..."
anthony1b2bc0a2010-05-12 05:25:22 +0000155% a kernel of size W by H, with W*H floating point numbers following.
anthony602ab9b2010-01-05 08:06:50 +0000156% 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}]]"
anthony1b2bc0a2010-05-12 05:25:22 +0000540% Generate a diamond shaped kernel with given radius to the points.
anthony602ab9b2010-01-05 08:06:50 +0000541% 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%
anthony1b2bc0a2010-05-12 05:25:22 +00001013% CloneKernelInfo() creates a new clone of the given Kernel List so that its
1014% can be modified without effecting the original. The cloned kernel should
1015% be destroyed using DestoryKernelInfo() when no longer needed.
anthony7a01dcf2010-05-11 12:25:52 +00001016%
cristye6365592010-04-02 17:31:23 +00001017% The format of the CloneKernelInfo method is:
anthony4fd27e22010-02-07 08:17:18 +00001018%
anthony930be612010-02-08 04:26:15 +00001019% KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +00001020%
1021% A description of each parameter follows:
1022%
1023% o kernel: the Morphology/Convolution kernel to be cloned
1024%
1025*/
cristyef656912010-03-05 19:54:59 +00001026MagickExport KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +00001027{
1028 register long
1029 i;
1030
cristy19eb6412010-04-23 14:42:29 +00001031 KernelInfo
anthony7a01dcf2010-05-11 12:25:52 +00001032 *new_kernel;
anthony4fd27e22010-02-07 08:17:18 +00001033
1034 assert(kernel != (KernelInfo *) NULL);
anthony7a01dcf2010-05-11 12:25:52 +00001035 new_kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
1036 if (new_kernel == (KernelInfo *) NULL)
1037 return(new_kernel);
1038 *new_kernel=(*kernel); /* copy values in structure */
anthony7a01dcf2010-05-11 12:25:52 +00001039
1040 /* replace the values with a copy of the values */
1041 new_kernel->values=(double *) AcquireQuantumMemory(kernel->width,
cristy19eb6412010-04-23 14:42:29 +00001042 kernel->height*sizeof(double));
anthony7a01dcf2010-05-11 12:25:52 +00001043 if (new_kernel->values == (double *) NULL)
1044 return(DestroyKernelInfo(new_kernel));
anthony4fd27e22010-02-07 08:17:18 +00001045 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthony7a01dcf2010-05-11 12:25:52 +00001046 new_kernel->values[i]=kernel->values[i];
anthony1b2bc0a2010-05-12 05:25:22 +00001047
1048 /* Also clone the next kernel in the kernel list */
1049 if ( kernel->next != (KernelInfo *) NULL ) {
1050 new_kernel->next = CloneKernelInfo(kernel->next);
1051 if ( new_kernel->next == (KernelInfo *) NULL )
1052 return(DestroyKernelInfo(new_kernel));
1053 }
1054
anthony7a01dcf2010-05-11 12:25:52 +00001055 return(new_kernel);
anthony4fd27e22010-02-07 08:17:18 +00001056}
1057
1058/*
1059%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1060% %
1061% %
1062% %
anthony83ba99b2010-01-24 08:48:15 +00001063% D e s t r o y K e r n e l I n f o %
anthony602ab9b2010-01-05 08:06:50 +00001064% %
1065% %
1066% %
1067%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1068%
anthony83ba99b2010-01-24 08:48:15 +00001069% DestroyKernelInfo() frees the memory used by a Convolution/Morphology
1070% kernel.
anthony602ab9b2010-01-05 08:06:50 +00001071%
anthony83ba99b2010-01-24 08:48:15 +00001072% The format of the DestroyKernelInfo method is:
anthony602ab9b2010-01-05 08:06:50 +00001073%
anthony83ba99b2010-01-24 08:48:15 +00001074% KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +00001075%
1076% A description of each parameter follows:
1077%
1078% o kernel: the Morphology/Convolution kernel to be destroyed
1079%
1080*/
1081
anthony83ba99b2010-01-24 08:48:15 +00001082MagickExport KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +00001083{
cristy2be15382010-01-21 02:38:03 +00001084 assert(kernel != (KernelInfo *) NULL);
anthony4fd27e22010-02-07 08:17:18 +00001085
anthony7a01dcf2010-05-11 12:25:52 +00001086 if ( kernel->next != (KernelInfo *) NULL )
1087 kernel->next = DestroyKernelInfo(kernel->next);
1088
1089 kernel->values = (double *)RelinquishMagickMemory(kernel->values);
1090 kernel = (KernelInfo *) RelinquishMagickMemory(kernel);
anthony602ab9b2010-01-05 08:06:50 +00001091 return(kernel);
1092}
anthonyc94cdb02010-01-06 08:15:29 +00001093
1094/*
1095%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1096% %
1097% %
1098% %
anthony29188a82010-01-22 10:12:34 +00001099% 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 +00001100% %
1101% %
1102% %
1103%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1104%
anthony29188a82010-01-22 10:12:34 +00001105% MorphologyImageChannel() applies a user supplied kernel to the image
1106% according to the given mophology method.
anthony602ab9b2010-01-05 08:06:50 +00001107%
1108% The given kernel is assumed to have been pre-scaled appropriatally, usally
1109% by the kernel generator.
1110%
1111% The format of the MorphologyImage method is:
1112%
cristyef656912010-03-05 19:54:59 +00001113% Image *MorphologyImage(const Image *image,MorphologyMethod method,
1114% const long iterations,KernelInfo *kernel,ExceptionInfo *exception)
anthony29188a82010-01-22 10:12:34 +00001115% Image *MorphologyImageChannel(const Image *image, const ChannelType
cristyef656912010-03-05 19:54:59 +00001116% channel,MorphologyMethod method,const long iterations,
1117% KernelInfo *kernel,ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001118%
1119% A description of each parameter follows:
1120%
1121% o image: the image.
1122%
1123% o method: the morphology method to be applied.
1124%
1125% o iterations: apply the operation this many times (or no change).
1126% A value of -1 means loop until no change found.
1127% How this is applied may depend on the morphology method.
1128% Typically this is a value of 1.
1129%
1130% o channel: the channel type.
1131%
1132% o kernel: An array of double representing the morphology kernel.
anthony29188a82010-01-22 10:12:34 +00001133% Warning: kernel may be normalized for the Convolve method.
anthony602ab9b2010-01-05 08:06:50 +00001134%
1135% o exception: return any errors or warnings in this structure.
1136%
1137%
1138% TODO: bias and auto-scale handling of the kernel for convolution
1139% The given kernel is assumed to have been pre-scaled appropriatally, usally
1140% by the kernel generator.
1141%
1142*/
1143
anthony930be612010-02-08 04:26:15 +00001144
anthony602ab9b2010-01-05 08:06:50 +00001145/* Internal function
anthony7a01dcf2010-05-11 12:25:52 +00001146 * Apply the low-level Morphology Method Primatives using the given Kernel
anthony930be612010-02-08 04:26:15 +00001147 * Returning the number of pixels that changed.
1148 * Two pre-created images must be provided, no image is created.
anthony602ab9b2010-01-05 08:06:50 +00001149 */
anthony7a01dcf2010-05-11 12:25:52 +00001150static unsigned long MorphologyPrimative(const Image *image, Image
anthony602ab9b2010-01-05 08:06:50 +00001151 *result_image, const MorphologyMethod method, const ChannelType channel,
cristy2be15382010-01-21 02:38:03 +00001152 const KernelInfo *kernel, ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001153{
cristy2be15382010-01-21 02:38:03 +00001154#define MorphologyTag "Morphology/Image"
anthony602ab9b2010-01-05 08:06:50 +00001155
1156 long
cristy150989e2010-02-01 14:59:39 +00001157 progress,
anthony29188a82010-01-22 10:12:34 +00001158 y, offx, offy,
anthony602ab9b2010-01-05 08:06:50 +00001159 changed;
1160
1161 MagickBooleanType
1162 status;
1163
1164 MagickPixelPacket
1165 bias;
1166
1167 CacheView
1168 *p_view,
1169 *q_view;
1170
anthony4fd27e22010-02-07 08:17:18 +00001171 /* Only the most basic morphology is actually performed by this routine */
anthony4fd27e22010-02-07 08:17:18 +00001172
anthony602ab9b2010-01-05 08:06:50 +00001173 /*
anthony4fd27e22010-02-07 08:17:18 +00001174 Apply Basic Morphology to Image.
anthony602ab9b2010-01-05 08:06:50 +00001175 */
1176 status=MagickTrue;
1177 changed=0;
1178 progress=0;
1179
1180 GetMagickPixelPacket(image,&bias);
1181 SetMagickPixelPacketBias(image,&bias);
anthonycc6c8362010-01-25 04:14:01 +00001182 /* Future: handle auto-bias from user, based on kernel input */
anthony602ab9b2010-01-05 08:06:50 +00001183
1184 p_view=AcquireCacheView(image);
1185 q_view=AcquireCacheView(result_image);
anthony29188a82010-01-22 10:12:34 +00001186
anthonycc6c8362010-01-25 04:14:01 +00001187 /* Some methods (including convolve) needs use a reflected kernel.
1188 * Adjust 'origin' offsets for this reflected kernel.
anthony29188a82010-01-22 10:12:34 +00001189 */
cristyc99304f2010-02-01 15:26:27 +00001190 offx = kernel->x;
1191 offy = kernel->y;
anthony29188a82010-01-22 10:12:34 +00001192 switch(method) {
anthony930be612010-02-08 04:26:15 +00001193 case ConvolveMorphology:
1194 case DilateMorphology:
1195 case DilateIntensityMorphology:
1196 case DistanceMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001197 /* kernel needs to used with reflection about origin */
cristy150989e2010-02-01 14:59:39 +00001198 offx = (long) kernel->width-offx-1;
1199 offy = (long) kernel->height-offy-1;
anthony29188a82010-01-22 10:12:34 +00001200 break;
anthony5ef8e942010-05-11 06:51:12 +00001201 case ErodeMorphology:
1202 case ErodeIntensityMorphology:
1203 case HitAndMissMorphology:
1204 case ThinningMorphology:
1205 case ThickenMorphology:
1206 /* kernel is user as is, without reflection */
1207 break;
anthony930be612010-02-08 04:26:15 +00001208 default:
anthony5ef8e942010-05-11 06:51:12 +00001209 perror("Not a low level Morphology Method");
anthony930be612010-02-08 04:26:15 +00001210 break;
anthony29188a82010-01-22 10:12:34 +00001211 }
1212
anthony602ab9b2010-01-05 08:06:50 +00001213#if defined(MAGICKCORE_OPENMP_SUPPORT)
1214 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1215#endif
cristy150989e2010-02-01 14:59:39 +00001216 for (y=0; y < (long) image->rows; y++)
anthony602ab9b2010-01-05 08:06:50 +00001217 {
1218 MagickBooleanType
1219 sync;
1220
1221 register const PixelPacket
1222 *restrict p;
1223
1224 register const IndexPacket
1225 *restrict p_indexes;
1226
1227 register PixelPacket
1228 *restrict q;
1229
1230 register IndexPacket
1231 *restrict q_indexes;
1232
cristy150989e2010-02-01 14:59:39 +00001233 register long
anthony602ab9b2010-01-05 08:06:50 +00001234 x;
1235
anthony29188a82010-01-22 10:12:34 +00001236 unsigned long
anthony602ab9b2010-01-05 08:06:50 +00001237 r;
1238
1239 if (status == MagickFalse)
1240 continue;
anthony29188a82010-01-22 10:12:34 +00001241 p=GetCacheViewVirtualPixels(p_view, -offx, y-offy,
1242 image->columns+kernel->width, kernel->height, exception);
anthony602ab9b2010-01-05 08:06:50 +00001243 q=GetCacheViewAuthenticPixels(q_view,0,y,result_image->columns,1,
1244 exception);
1245 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1246 {
1247 status=MagickFalse;
1248 continue;
1249 }
1250 p_indexes=GetCacheViewVirtualIndexQueue(p_view);
1251 q_indexes=GetCacheViewAuthenticIndexQueue(q_view);
anthony29188a82010-01-22 10:12:34 +00001252 r = (image->columns+kernel->width)*offy+offx; /* constant */
1253
cristy150989e2010-02-01 14:59:39 +00001254 for (x=0; x < (long) image->columns; x++)
anthony602ab9b2010-01-05 08:06:50 +00001255 {
cristy150989e2010-02-01 14:59:39 +00001256 long
anthony602ab9b2010-01-05 08:06:50 +00001257 v;
1258
cristy150989e2010-02-01 14:59:39 +00001259 register long
anthony602ab9b2010-01-05 08:06:50 +00001260 u;
1261
1262 register const double
1263 *restrict k;
1264
1265 register const PixelPacket
1266 *restrict k_pixels;
1267
1268 register const IndexPacket
1269 *restrict k_indexes;
1270
1271 MagickPixelPacket
anthony5ef8e942010-05-11 06:51:12 +00001272 result,
1273 min,
1274 max;
anthony602ab9b2010-01-05 08:06:50 +00001275
anthony29188a82010-01-22 10:12:34 +00001276 /* Copy input to ouput image for unused channels
anthony83ba99b2010-01-24 08:48:15 +00001277 * This removes need for 'cloning' a new image every iteration
anthony29188a82010-01-22 10:12:34 +00001278 */
anthony602ab9b2010-01-05 08:06:50 +00001279 *q = p[r];
1280 if (image->colorspace == CMYKColorspace)
1281 q_indexes[x] = p_indexes[r];
1282
anthony5ef8e942010-05-11 06:51:12 +00001283 /* Defaults */
1284 min.red =
1285 min.green =
1286 min.blue =
1287 min.opacity =
1288 min.index = (MagickRealType) QuantumRange;
1289 max.red =
1290 max.green =
1291 max.blue =
1292 max.opacity =
1293 max.index = (MagickRealType) 0;
1294 /* original pixel value */
1295 result.red = (MagickRealType) p[r].red;
1296 result.green = (MagickRealType) p[r].green;
1297 result.blue = (MagickRealType) p[r].blue;
1298 result.opacity = QuantumRange - (MagickRealType) p[r].opacity;
1299 if ( image->colorspace == CMYKColorspace)
1300 result.index = (MagickRealType) p_indexes[r];
1301
anthony602ab9b2010-01-05 08:06:50 +00001302 switch (method) {
1303 case ConvolveMorphology:
anthony930be612010-02-08 04:26:15 +00001304 /* Set the user defined bias of the weighted average output
1305 **
1306 ** FUTURE: provide some way for internal functions to disable
anthony5ef8e942010-05-11 06:51:12 +00001307 ** user provided bias and scaling effects.
anthony930be612010-02-08 04:26:15 +00001308 */
anthony602ab9b2010-01-05 08:06:50 +00001309 result=bias;
anthony930be612010-02-08 04:26:15 +00001310 break;
anthony4fd27e22010-02-07 08:17:18 +00001311 case DilateIntensityMorphology:
1312 case ErodeIntensityMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001313 result.red = 0.0; /* flag indicating when first match found */
anthony4fd27e22010-02-07 08:17:18 +00001314 break;
anthony602ab9b2010-01-05 08:06:50 +00001315 default:
anthony602ab9b2010-01-05 08:06:50 +00001316 break;
1317 }
1318
1319 switch ( method ) {
1320 case ConvolveMorphology:
anthony930be612010-02-08 04:26:15 +00001321 /* Weighted Average of pixels using reflected kernel
1322 **
1323 ** NOTE for correct working of this operation for asymetrical
1324 ** kernels, the kernel needs to be applied in its reflected form.
1325 ** That is its values needs to be reversed.
1326 **
1327 ** Correlation is actually the same as this but without reflecting
1328 ** the kernel, and thus 'lower-level' that Convolution. However
1329 ** as Convolution is the more common method used, and it does not
1330 ** really cost us much in terms of processing to use a reflected
anthony5ef8e942010-05-11 06:51:12 +00001331 ** kernel, so it is Convolution that is implemented.
anthony930be612010-02-08 04:26:15 +00001332 **
1333 ** Correlation will have its kernel reflected before calling
1334 ** this function to do a Convolve.
1335 **
1336 ** For more details of Correlation vs Convolution see
1337 ** http://www.cs.umd.edu/~djacobs/CMSC426/Convolution.pdf
1338 */
anthony5ef8e942010-05-11 06:51:12 +00001339 if (((channel & SyncChannels) != 0 ) &&
1340 (image->matte == MagickTrue))
1341 { /* Channel has a 'Sync' Flag, and Alpha Channel enabled.
1342 ** Weight the color channels with Alpha Channel so that
1343 ** transparent pixels are not part of the results.
1344 */
anthony602ab9b2010-01-05 08:06:50 +00001345 MagickRealType
anthony5ef8e942010-05-11 06:51:12 +00001346 alpha, /* color channel weighting : kernel*alpha */
1347 gamma; /* divisor, sum of weighting values */
anthony602ab9b2010-01-05 08:06:50 +00001348
1349 gamma=0.0;
anthony29188a82010-01-22 10:12:34 +00001350 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001351 k_pixels = p;
1352 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001353 for (v=0; v < (long) kernel->height; v++) {
1354 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001355 if ( IsNan(*k) ) continue;
1356 alpha=(*k)*(QuantumScale*(QuantumRange-
1357 k_pixels[u].opacity));
1358 gamma += alpha;
1359 result.red += alpha*k_pixels[u].red;
1360 result.green += alpha*k_pixels[u].green;
1361 result.blue += alpha*k_pixels[u].blue;
anthony83ba99b2010-01-24 08:48:15 +00001362 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00001363 if ( image->colorspace == CMYKColorspace)
1364 result.index += alpha*k_indexes[u];
1365 }
1366 k_pixels += image->columns+kernel->width;
1367 k_indexes += image->columns+kernel->width;
1368 }
1369 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
anthony83ba99b2010-01-24 08:48:15 +00001370 result.red *= gamma;
1371 result.green *= gamma;
1372 result.blue *= gamma;
1373 result.opacity *= gamma;
1374 result.index *= gamma;
anthony602ab9b2010-01-05 08:06:50 +00001375 }
anthony5ef8e942010-05-11 06:51:12 +00001376 else
1377 {
1378 /* No 'Sync' flag, or no Alpha involved.
1379 ** Convolution is simple individual channel weigthed sum.
1380 */
1381 k = &kernel->values[ kernel->width*kernel->height-1 ];
1382 k_pixels = p;
1383 k_indexes = p_indexes;
1384 for (v=0; v < (long) kernel->height; v++) {
1385 for (u=0; u < (long) kernel->width; u++, k--) {
1386 if ( IsNan(*k) ) continue;
1387 result.red += (*k)*k_pixels[u].red;
1388 result.green += (*k)*k_pixels[u].green;
1389 result.blue += (*k)*k_pixels[u].blue;
1390 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
1391 if ( image->colorspace == CMYKColorspace)
1392 result.index += (*k)*k_indexes[u];
1393 }
1394 k_pixels += image->columns+kernel->width;
1395 k_indexes += image->columns+kernel->width;
1396 }
1397 }
anthony602ab9b2010-01-05 08:06:50 +00001398 break;
1399
anthony4fd27e22010-02-07 08:17:18 +00001400 case ErodeMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001401 /* Minimum Value within kernel neighbourhood
anthony930be612010-02-08 04:26:15 +00001402 **
1403 ** NOTE that the kernel is not reflected for this operation!
1404 **
1405 ** NOTE: in normal Greyscale Morphology, the kernel value should
1406 ** be added to the real value, this is currently not done, due to
1407 ** the nature of the boolean kernels being used.
1408 */
anthony4fd27e22010-02-07 08:17:18 +00001409 k = kernel->values;
1410 k_pixels = p;
1411 k_indexes = p_indexes;
1412 for (v=0; v < (long) kernel->height; v++) {
1413 for (u=0; u < (long) kernel->width; u++, k++) {
1414 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony5ef8e942010-05-11 06:51:12 +00001415 Minimize(min.red, (double) k_pixels[u].red);
1416 Minimize(min.green, (double) k_pixels[u].green);
1417 Minimize(min.blue, (double) k_pixels[u].blue);
1418 Minimize(min.opacity,
anthonyd37a5cb2010-05-07 06:37:03 +00001419 QuantumRange-(double) k_pixels[u].opacity);
anthony4fd27e22010-02-07 08:17:18 +00001420 if ( image->colorspace == CMYKColorspace)
anthony5ef8e942010-05-11 06:51:12 +00001421 Minimize(min.index, (double) k_indexes[u]);
anthony4fd27e22010-02-07 08:17:18 +00001422 }
1423 k_pixels += image->columns+kernel->width;
1424 k_indexes += image->columns+kernel->width;
1425 }
1426 break;
1427
anthony5ef8e942010-05-11 06:51:12 +00001428
anthony83ba99b2010-01-24 08:48:15 +00001429 case DilateMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001430 /* Maximum Value within kernel neighbourhood
anthony930be612010-02-08 04:26:15 +00001431 **
1432 ** NOTE for correct working of this operation for asymetrical
1433 ** kernels, the kernel needs to be applied in its reflected form.
1434 ** That is its values needs to be reversed.
1435 **
1436 ** NOTE: in normal Greyscale Morphology, the kernel value should
1437 ** be added to the real value, this is currently not done, due to
1438 ** the nature of the boolean kernels being used.
1439 **
1440 */
anthony29188a82010-01-22 10:12:34 +00001441 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001442 k_pixels = p;
1443 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001444 for (v=0; v < (long) kernel->height; v++) {
1445 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001446 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony5ef8e942010-05-11 06:51:12 +00001447 Maximize(max.red, (double) k_pixels[u].red);
1448 Maximize(max.green, (double) k_pixels[u].green);
1449 Maximize(max.blue, (double) k_pixels[u].blue);
1450 Maximize(max.opacity,
anthonyd37a5cb2010-05-07 06:37:03 +00001451 QuantumRange-(double) k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00001452 if ( image->colorspace == CMYKColorspace)
anthony5ef8e942010-05-11 06:51:12 +00001453 Maximize(max.index, (double) k_indexes[u]);
anthony602ab9b2010-01-05 08:06:50 +00001454 }
1455 k_pixels += image->columns+kernel->width;
1456 k_indexes += image->columns+kernel->width;
1457 }
anthony602ab9b2010-01-05 08:06:50 +00001458 break;
1459
anthony5ef8e942010-05-11 06:51:12 +00001460 case HitAndMissMorphology:
1461 case ThinningMorphology:
1462 case ThickenMorphology:
1463 /* Minimum of Foreground Pixel minus Maxumum of Background Pixels
1464 **
1465 ** NOTE that the kernel is not reflected for this operation,
1466 ** and consists of both foreground and background pixel
1467 ** neighbourhoods, 0.0 for background, and 1.0 for foreground
1468 ** with either Nan or 0.5 values for don't care.
1469 **
1470 ** Note that this can produce negative results, though really
1471 ** only a positive match has any real value.
1472 */
1473 k = kernel->values;
1474 k_pixels = p;
1475 k_indexes = p_indexes;
1476 for (v=0; v < (long) kernel->height; v++) {
1477 for (u=0; u < (long) kernel->width; u++, k++) {
1478 if ( IsNan(*k) ) continue;
1479 if ( (*k) > 0.7 )
1480 { /* minimim of foreground pixels */
1481 Minimize(min.red, (double) k_pixels[u].red);
1482 Minimize(min.green, (double) k_pixels[u].green);
1483 Minimize(min.blue, (double) k_pixels[u].blue);
1484 Minimize(min.opacity,
1485 QuantumRange-(double) k_pixels[u].opacity);
1486 if ( image->colorspace == CMYKColorspace)
1487 Minimize(min.index, (double) k_indexes[u]);
1488 }
1489 else if ( (*k) < 0.3 )
1490 { /* maximum of background pixels */
1491 Maximize(max.red, (double) k_pixels[u].red);
1492 Maximize(max.green, (double) k_pixels[u].green);
1493 Maximize(max.blue, (double) k_pixels[u].blue);
1494 Maximize(max.opacity,
1495 QuantumRange-(double) k_pixels[u].opacity);
1496 if ( image->colorspace == CMYKColorspace)
1497 Maximize(max.index, (double) k_indexes[u]);
1498 }
1499 }
1500 k_pixels += image->columns+kernel->width;
1501 k_indexes += image->columns+kernel->width;
1502 }
1503 /* Pattern Match only if min fg larger than min bg pixels */
1504 min.red -= max.red; Maximize( min.red, 0.0 );
1505 min.green -= max.green; Maximize( min.green, 0.0 );
1506 min.blue -= max.blue; Maximize( min.blue, 0.0 );
1507 min.opacity -= max.opacity; Maximize( min.opacity, 0.0 );
1508 min.index -= max.index; Maximize( min.index, 0.0 );
1509 break;
1510
anthony4fd27e22010-02-07 08:17:18 +00001511 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001512 /* Select Pixel with Minimum Intensity within kernel neighbourhood
1513 **
1514 ** WARNING: the intensity test fails for CMYK and does not
1515 ** take into account the moderating effect of teh alpha channel
1516 ** on the intensity.
1517 **
1518 ** NOTE that the kernel is not reflected for this operation!
1519 */
anthony602ab9b2010-01-05 08:06:50 +00001520 k = kernel->values;
1521 k_pixels = p;
1522 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001523 for (v=0; v < (long) kernel->height; v++) {
1524 for (u=0; u < (long) kernel->width; u++, k++) {
anthony602ab9b2010-01-05 08:06:50 +00001525 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony4fd27e22010-02-07 08:17:18 +00001526 if ( result.red == 0.0 ||
1527 PixelIntensity(&(k_pixels[u])) < PixelIntensity(q) ) {
1528 /* copy the whole pixel - no channel selection */
1529 *q = k_pixels[u];
1530 if ( result.red > 0.0 ) changed++;
1531 result.red = 1.0;
1532 }
anthony602ab9b2010-01-05 08:06:50 +00001533 }
1534 k_pixels += image->columns+kernel->width;
1535 k_indexes += image->columns+kernel->width;
1536 }
anthony602ab9b2010-01-05 08:06:50 +00001537 break;
1538
anthony83ba99b2010-01-24 08:48:15 +00001539 case DilateIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001540 /* Select Pixel with Maximum Intensity within kernel neighbourhood
1541 **
1542 ** WARNING: the intensity test fails for CMYK and does not
1543 ** take into account the moderating effect of teh alpha channel
1544 ** on the intensity.
1545 **
1546 ** NOTE for correct working of this operation for asymetrical
1547 ** kernels, the kernel needs to be applied in its reflected form.
1548 ** That is its values needs to be reversed.
1549 */
anthony29188a82010-01-22 10:12:34 +00001550 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001551 k_pixels = p;
1552 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001553 for (v=0; v < (long) kernel->height; v++) {
1554 for (u=0; u < (long) kernel->width; u++, k--) {
anthony29188a82010-01-22 10:12:34 +00001555 if ( IsNan(*k) || (*k) < 0.5 ) continue; /* boolean kernel */
1556 if ( result.red == 0.0 ||
1557 PixelIntensity(&(k_pixels[u])) > PixelIntensity(q) ) {
1558 /* copy the whole pixel - no channel selection */
1559 *q = k_pixels[u];
1560 if ( result.red > 0.0 ) changed++;
1561 result.red = 1.0;
1562 }
anthony602ab9b2010-01-05 08:06:50 +00001563 }
1564 k_pixels += image->columns+kernel->width;
1565 k_indexes += image->columns+kernel->width;
1566 }
anthony602ab9b2010-01-05 08:06:50 +00001567 break;
1568
anthony5ef8e942010-05-11 06:51:12 +00001569
anthony602ab9b2010-01-05 08:06:50 +00001570 case DistanceMorphology:
anthony930be612010-02-08 04:26:15 +00001571 /* Add kernel Value and select the minimum value found.
1572 ** The result is a iterative distance from edge of image shape.
1573 **
1574 ** All Distance Kernels are symetrical, but that may not always
1575 ** be the case. For example how about a distance from left edges?
1576 ** To work correctly with asymetrical kernels the reflected kernel
1577 ** needs to be applied.
anthony5ef8e942010-05-11 06:51:12 +00001578 **
1579 ** Actually this is really a GreyErode with a negative kernel!
1580 **
anthony930be612010-02-08 04:26:15 +00001581 */
anthony29188a82010-01-22 10:12:34 +00001582 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001583 k_pixels = p;
1584 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001585 for (v=0; v < (long) kernel->height; v++) {
1586 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001587 if ( IsNan(*k) ) continue;
1588 Minimize(result.red, (*k)+k_pixels[u].red);
1589 Minimize(result.green, (*k)+k_pixels[u].green);
1590 Minimize(result.blue, (*k)+k_pixels[u].blue);
1591 Minimize(result.opacity, (*k)+QuantumRange-k_pixels[u].opacity);
1592 if ( image->colorspace == CMYKColorspace)
1593 Minimize(result.index, (*k)+k_indexes[u]);
1594 }
1595 k_pixels += image->columns+kernel->width;
1596 k_indexes += image->columns+kernel->width;
1597 }
anthony602ab9b2010-01-05 08:06:50 +00001598 break;
1599
1600 case UndefinedMorphology:
1601 default:
1602 break; /* Do nothing */
anthony83ba99b2010-01-24 08:48:15 +00001603 }
anthony5ef8e942010-05-11 06:51:12 +00001604 /* Final mathematics of results (combine with original image?)
1605 **
1606 ** NOTE: Difference Morphology operators Edge* and *Hat could also
1607 ** be done here but works better with iteration as a image difference
1608 ** in the controling function (below). Thicken and Thinning however
1609 ** should be done here so thay can be iterated correctly.
1610 */
1611 switch ( method ) {
1612 case HitAndMissMorphology:
1613 case ErodeMorphology:
1614 result = min; /* minimum of neighbourhood */
1615 break;
1616 case DilateMorphology:
1617 result = max; /* maximum of neighbourhood */
1618 break;
1619 case ThinningMorphology:
1620 /* subtract pattern match from original */
1621 result.red -= min.red;
1622 result.green -= min.green;
1623 result.blue -= min.blue;
1624 result.opacity -= min.opacity;
1625 result.index -= min.index;
1626 break;
1627 case ThickenMorphology:
1628 /* Union with original image (maximize) - or should this be + */
1629 Maximize( result.red, min.red );
1630 Maximize( result.green, min.green );
1631 Maximize( result.blue, min.blue );
1632 Maximize( result.opacity, min.opacity );
1633 Maximize( result.index, min.index );
1634 break;
1635 default:
1636 /* result directly calculated or assigned */
1637 break;
1638 }
1639 /* Assign the resulting pixel values - Clamping Result */
anthony83ba99b2010-01-24 08:48:15 +00001640 switch ( method ) {
1641 case UndefinedMorphology:
1642 case DilateIntensityMorphology:
1643 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001644 break; /* full pixel was directly assigned - not a channel method */
anthony83ba99b2010-01-24 08:48:15 +00001645 default:
anthony83ba99b2010-01-24 08:48:15 +00001646 if ((channel & RedChannel) != 0)
1647 q->red = ClampToQuantum(result.red);
1648 if ((channel & GreenChannel) != 0)
1649 q->green = ClampToQuantum(result.green);
1650 if ((channel & BlueChannel) != 0)
1651 q->blue = ClampToQuantum(result.blue);
1652 if ((channel & OpacityChannel) != 0
1653 && image->matte == MagickTrue )
1654 q->opacity = ClampToQuantum(QuantumRange-result.opacity);
1655 if ((channel & IndexChannel) != 0
1656 && image->colorspace == CMYKColorspace)
1657 q_indexes[x] = ClampToQuantum(result.index);
1658 break;
1659 }
anthony5ef8e942010-05-11 06:51:12 +00001660 /* Count up changed pixels */
anthony83ba99b2010-01-24 08:48:15 +00001661 if ( ( p[r].red != q->red )
1662 || ( p[r].green != q->green )
1663 || ( p[r].blue != q->blue )
1664 || ( p[r].opacity != q->opacity )
1665 || ( image->colorspace == CMYKColorspace &&
1666 p_indexes[r] != q_indexes[x] ) )
1667 changed++; /* The pixel had some value changed! */
anthony602ab9b2010-01-05 08:06:50 +00001668 p++;
1669 q++;
anthony83ba99b2010-01-24 08:48:15 +00001670 } /* x */
anthony602ab9b2010-01-05 08:06:50 +00001671 sync=SyncCacheViewAuthenticPixels(q_view,exception);
1672 if (sync == MagickFalse)
1673 status=MagickFalse;
1674 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1675 {
1676 MagickBooleanType
1677 proceed;
1678
1679#if defined(MAGICKCORE_OPENMP_SUPPORT)
1680 #pragma omp critical (MagickCore_MorphologyImage)
1681#endif
1682 proceed=SetImageProgress(image,MorphologyTag,progress++,image->rows);
1683 if (proceed == MagickFalse)
1684 status=MagickFalse;
1685 }
anthony83ba99b2010-01-24 08:48:15 +00001686 } /* y */
anthony602ab9b2010-01-05 08:06:50 +00001687 result_image->type=image->type;
1688 q_view=DestroyCacheView(q_view);
1689 p_view=DestroyCacheView(p_view);
cristy150989e2010-02-01 14:59:39 +00001690 return(status ? (unsigned long) changed : 0);
anthony602ab9b2010-01-05 08:06:50 +00001691}
1692
anthony4fd27e22010-02-07 08:17:18 +00001693
1694MagickExport Image *MorphologyImage(const Image *image, const MorphologyMethod
anthony930be612010-02-08 04:26:15 +00001695 method, const long iterations,const KernelInfo *kernel, ExceptionInfo
1696 *exception)
cristy2be15382010-01-21 02:38:03 +00001697{
1698 Image
1699 *morphology_image;
1700
1701 morphology_image=MorphologyImageChannel(image,DefaultChannels,method,
1702 iterations,kernel,exception);
1703 return(morphology_image);
1704}
1705
anthony4fd27e22010-02-07 08:17:18 +00001706
cristyef656912010-03-05 19:54:59 +00001707MagickExport Image *MorphologyImageChannel(const Image *image,
1708 const ChannelType channel,const MorphologyMethod method,
1709 const long iterations,const KernelInfo *kernel,ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001710{
anthony602ab9b2010-01-05 08:06:50 +00001711 Image
1712 *new_image,
anthony1b2bc0a2010-05-12 05:25:22 +00001713 *old_image,
anthony4fd27e22010-02-07 08:17:18 +00001714 *grad_image;
anthony602ab9b2010-01-05 08:06:50 +00001715
anthony4fd27e22010-02-07 08:17:18 +00001716 KernelInfo
anthony1b2bc0a2010-05-12 05:25:22 +00001717 *curr_kernel,
1718 *this_kernel;
anthony4fd27e22010-02-07 08:17:18 +00001719
1720 MorphologyMethod
1721 curr_method;
1722
anthony1b2bc0a2010-05-12 05:25:22 +00001723 unsigned long
1724 count,
1725 limit,
1726 changed,
1727 total_changed,
1728 kernel_number;
1729
anthony602ab9b2010-01-05 08:06:50 +00001730 assert(image != (Image *) NULL);
1731 assert(image->signature == MagickSignature);
anthony4fd27e22010-02-07 08:17:18 +00001732 assert(kernel != (KernelInfo *) NULL);
1733 assert(kernel->signature == MagickSignature);
anthony602ab9b2010-01-05 08:06:50 +00001734 assert(exception != (ExceptionInfo *) NULL);
1735 assert(exception->signature == MagickSignature);
1736
anthony602ab9b2010-01-05 08:06:50 +00001737 if ( iterations == 0 )
1738 return((Image *)NULL); /* null operation - nothing to do! */
1739
1740 /* kernel must be valid at this point
1741 * (except maybe for posible future morphology methods like "Prune"
1742 */
cristy2be15382010-01-21 02:38:03 +00001743 assert(kernel != (KernelInfo *)NULL);
anthony602ab9b2010-01-05 08:06:50 +00001744
anthony1b2bc0a2010-05-12 05:25:22 +00001745 new_image = (Image *) NULL; /* for cleanup */
1746 old_image = (Image *) NULL;
1747 grad_image = (Image *) NULL;
1748 curr_kernel = (KernelInfo *) NULL;
1749 count = 0; /* interation count */
1750 changed = 1; /* was last run succesfull */
anthony930be612010-02-08 04:26:15 +00001751 curr_kernel = (KernelInfo *)kernel; /* allow kernel and method */
1752 curr_method = method; /* to be changed as nessary */
anthony4fd27e22010-02-07 08:17:18 +00001753
cristy150989e2010-02-01 14:59:39 +00001754 limit = (unsigned long) iterations;
anthony602ab9b2010-01-05 08:06:50 +00001755 if ( iterations < 0 )
1756 limit = image->columns > image->rows ? image->columns : image->rows;
1757
anthony4fd27e22010-02-07 08:17:18 +00001758 /* Third-level morphology methods */
1759 switch( curr_method ) {
1760 case EdgeMorphology:
1761 grad_image = MorphologyImageChannel(image, channel,
1762 DilateMorphology, iterations, curr_kernel, exception);
anthony1b2bc0a2010-05-12 05:25:22 +00001763 if ( grad_image == (Image *) NULL )
1764 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00001765 /* FALL-THRU */
1766 case EdgeInMorphology:
1767 curr_method = ErodeMorphology;
anthony602ab9b2010-01-05 08:06:50 +00001768 break;
anthony4fd27e22010-02-07 08:17:18 +00001769 case EdgeOutMorphology:
1770 curr_method = DilateMorphology;
anthony602ab9b2010-01-05 08:06:50 +00001771 break;
anthony4fd27e22010-02-07 08:17:18 +00001772 case TopHatMorphology:
1773 curr_method = OpenMorphology;
1774 break;
1775 case BottomHatMorphology:
1776 curr_method = CloseMorphology;
1777 break;
1778 default:
anthony930be612010-02-08 04:26:15 +00001779 break; /* not a third-level method */
anthony4fd27e22010-02-07 08:17:18 +00001780 }
1781
1782 /* Second-level morphology methods */
1783 switch( curr_method ) {
anthony930be612010-02-08 04:26:15 +00001784 case OpenMorphology:
1785 /* Open is a Erode then a Dilate without reflection */
anthony4fd27e22010-02-07 08:17:18 +00001786 new_image = MorphologyImageChannel(image, channel,
1787 ErodeMorphology, iterations, curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001788 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00001789 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00001790 curr_method = DilateMorphology;
1791 break;
anthony602ab9b2010-01-05 08:06:50 +00001792 case OpenIntensityMorphology:
anthony4fd27e22010-02-07 08:17:18 +00001793 new_image = MorphologyImageChannel(image, channel,
1794 ErodeIntensityMorphology, iterations, curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001795 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00001796 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00001797 curr_method = DilateIntensityMorphology;
1798 break;
anthony930be612010-02-08 04:26:15 +00001799
1800 case CloseMorphology:
1801 /* Close is a Dilate then Erode using reflected kernel */
1802 /* A reflected kernel is needed for a Close */
1803 if ( curr_kernel == kernel )
1804 curr_kernel = CloneKernelInfo(kernel);
anthony1b2bc0a2010-05-12 05:25:22 +00001805 if (curr_kernel == (KernelInfo *) NULL)
1806 goto error_cleanup;
anthony930be612010-02-08 04:26:15 +00001807 RotateKernelInfo(curr_kernel,180);
1808 new_image = MorphologyImageChannel(image, channel,
1809 DilateMorphology, iterations, curr_kernel, exception);
1810 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00001811 goto error_cleanup;
anthony930be612010-02-08 04:26:15 +00001812 curr_method = ErodeMorphology;
1813 break;
anthony4fd27e22010-02-07 08:17:18 +00001814 case CloseIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001815 /* A reflected kernel is needed for a Close */
1816 if ( curr_kernel == kernel )
1817 curr_kernel = CloneKernelInfo(kernel);
anthony1b2bc0a2010-05-12 05:25:22 +00001818 if (curr_kernel == (KernelInfo *) NULL)
1819 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00001820 RotateKernelInfo(curr_kernel,180);
1821 new_image = MorphologyImageChannel(image, channel,
1822 DilateIntensityMorphology, iterations, curr_kernel, exception);
1823 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00001824 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00001825 curr_method = ErodeIntensityMorphology;
anthony602ab9b2010-01-05 08:06:50 +00001826 break;
1827
anthony930be612010-02-08 04:26:15 +00001828 case CorrelateMorphology:
1829 /* A Correlation is actually a Convolution with a reflected kernel.
1830 ** However a Convolution is a weighted sum with a reflected kernel.
1831 ** It may seem stange to convert a Correlation into a Convolution
1832 ** as the Correleation is the simplier method, but Convolution is
1833 ** much more commonly used, and it makes sense to implement it directly
1834 ** so as to avoid the need to duplicate the kernel when it is not
1835 ** required (which is typically the default).
1836 */
1837 if ( curr_kernel == kernel )
1838 curr_kernel = CloneKernelInfo(kernel);
anthony1b2bc0a2010-05-12 05:25:22 +00001839 if (curr_kernel == (KernelInfo *) NULL)
1840 goto error_cleanup;
anthony930be612010-02-08 04:26:15 +00001841 RotateKernelInfo(curr_kernel,180);
1842 curr_method = ConvolveMorphology;
1843 /* FALL-THRU into Correlate (weigthed sum without reflection) */
1844
anthonyc94cdb02010-01-06 08:15:29 +00001845 case ConvolveMorphology:
anthony1b2bc0a2010-05-12 05:25:22 +00001846 { /* Scale or Normalize kernel, according to user wishes
1847 ** before using it for the Convolve/Correlate method.
1848 **
1849 ** FUTURE: provide some way for internal functions to disable
1850 ** user bias and scaling effects.
1851 */
1852 const char
1853 *artifact = GetImageArtifact(image,"convolve:scale");
1854 if ( artifact != (char *)NULL ) {
1855 GeometryFlags
1856 flags;
1857 GeometryInfo
1858 args;
anthony999bb2c2010-02-18 12:38:01 +00001859
anthony1b2bc0a2010-05-12 05:25:22 +00001860 if ( curr_kernel == kernel )
1861 curr_kernel = CloneKernelInfo(kernel);
1862 if (curr_kernel == (KernelInfo *) NULL)
1863 goto error_cleanup;
1864 args.rho = 1.0;
1865 flags = (GeometryFlags) ParseGeometry(artifact, &args);
1866 ScaleKernelInfo(curr_kernel, args.rho, flags);
1867 }
anthony4fd27e22010-02-07 08:17:18 +00001868 }
anthony930be612010-02-08 04:26:15 +00001869 /* FALL-THRU to do the first, and typically the only iteration */
anthony4fd27e22010-02-07 08:17:18 +00001870
anthony602ab9b2010-01-05 08:06:50 +00001871 default:
anthony930be612010-02-08 04:26:15 +00001872 /* Do a single iteration using the Low-Level Morphology method!
1873 ** This ensures a "new_image" has been generated, but allows us to skip
1874 ** the creation of 'old_image' if no more iterations are needed.
1875 **
1876 ** The "curr_method" should also be set to a low-level method that is
anthony7a01dcf2010-05-11 12:25:52 +00001877 ** understood by the MorphologyPrimative() internal function.
anthony602ab9b2010-01-05 08:06:50 +00001878 */
1879 new_image=CloneImage(image,0,0,MagickTrue,exception);
1880 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00001881 goto error_cleanup;
anthony602ab9b2010-01-05 08:06:50 +00001882 if (SetImageStorageClass(new_image,DirectClass) == MagickFalse)
1883 {
1884 InheritException(exception,&new_image->exception);
anthony1b2bc0a2010-05-12 05:25:22 +00001885 goto exit_cleanup;
anthony602ab9b2010-01-05 08:06:50 +00001886 }
anthony1b2bc0a2010-05-12 05:25:22 +00001887 count++; /* iteration count */
anthony7a01dcf2010-05-11 12:25:52 +00001888 changed = MorphologyPrimative(image,new_image,curr_method,channel,
1889 curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001890 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
anthony1b2bc0a2010-05-12 05:25:22 +00001891 fprintf(stderr, "Morphology %s:%lu.%lu => Changed %lu\n",
anthony4fd27e22010-02-07 08:17:18 +00001892 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
anthony1b2bc0a2010-05-12 05:25:22 +00001893 count, 0L, changed);
anthony930be612010-02-08 04:26:15 +00001894 break;
anthony602ab9b2010-01-05 08:06:50 +00001895 }
anthony1b2bc0a2010-05-12 05:25:22 +00001896 /* At this point
1897 * + "curr_method" should be set to a low-level morphology method
1898 * + "count=1" if the first iteration of the first kernel has been done.
1899 * + "new_image" is defined and contains the current resulting image
1900 */
anthony602ab9b2010-01-05 08:06:50 +00001901
anthony1b2bc0a2010-05-12 05:25:22 +00001902 /* The Hit-And-Miss Morphology is not 'iterated' against the resulting
1903 ** image from the previous morphology step. It must always be applied
1904 ** to the original image, with the results collected into a union (maximimum
1905 ** or lighten) of all the results. Multiple kernels may be applied but
1906 ** an iteration of the morphology does nothing, so is ignored.
anthony7a01dcf2010-05-11 12:25:52 +00001907 **
anthony1b2bc0a2010-05-12 05:25:22 +00001908 ** The first kernel is guranteed to have been done, so lets continue the
1909 ** process, with the other kernels in the kernel list.
anthony7a01dcf2010-05-11 12:25:52 +00001910 */
anthony1b2bc0a2010-05-12 05:25:22 +00001911 if ( method == HitAndMissMorphology )
anthony7a01dcf2010-05-11 12:25:52 +00001912 {
anthony1b2bc0a2010-05-12 05:25:22 +00001913 if ( curr_kernel->next != (KernelInfo *) NULL ) {
1914 /* create a second working image */
1915 old_image = CloneImage(image,0,0,MagickTrue,exception);
1916 if (old_image == (Image *) NULL)
1917 goto error_cleanup;
1918 if (SetImageStorageClass(old_image,DirectClass) == MagickFalse)
1919 {
1920 InheritException(exception,&old_image->exception);
1921 goto exit_cleanup;
1922 }
anthony7a01dcf2010-05-11 12:25:52 +00001923
anthony1b2bc0a2010-05-12 05:25:22 +00001924 /* loop through rest of the kernels */
1925 this_kernel=curr_kernel->next;
1926 kernel_number=1;
1927 while( this_kernel != (KernelInfo *) NULL )
1928 {
1929 changed = MorphologyPrimative(image,old_image,curr_method,channel,
1930 this_kernel,exception);
1931 (void) CompositeImageChannel(new_image,
1932 (ChannelType) (channel & ~SyncChannels), LightenCompositeOp,
1933 old_image, 0, 0);
1934 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
1935 fprintf(stderr, "Morphology %s:%lu.%lu => Changed %lu\n",
1936 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
1937 count, kernel_number, changed);
1938 this_kernel = this_kernel->next;
1939 kernel_number++;
1940 }
1941 old_image=DestroyImage(old_image);
1942 }
1943 goto exit_cleanup;
anthony7a01dcf2010-05-11 12:25:52 +00001944 }
1945
anthony1b2bc0a2010-05-12 05:25:22 +00001946 /* Repeat the low-level morphology over all kernels
1947 until iteration count limit or no change from any kernel is found */
1948 if ( ( count < limit && changed > 0 ) ||
1949 curr_kernel->next != (KernelInfo *) NULL ) {
anthony930be612010-02-08 04:26:15 +00001950
anthony1b2bc0a2010-05-12 05:25:22 +00001951 /* create a second working image */
1952 old_image = CloneImage(image,0,0,MagickTrue,exception);
anthony602ab9b2010-01-05 08:06:50 +00001953 if (old_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00001954 goto error_cleanup;
anthony602ab9b2010-01-05 08:06:50 +00001955 if (SetImageStorageClass(old_image,DirectClass) == MagickFalse)
1956 {
1957 InheritException(exception,&old_image->exception);
anthony1b2bc0a2010-05-12 05:25:22 +00001958 goto exit_cleanup;
anthony602ab9b2010-01-05 08:06:50 +00001959 }
anthony1b2bc0a2010-05-12 05:25:22 +00001960
1961 /* reset variables for the first/next iteration, or next kernel) */
1962 kernel_number = 0;
1963 this_kernel = curr_kernel;
1964 total_changed = count != 0 ? changed : 0;
1965 if ( count != 0 && this_kernel != (KernelInfo *) NULL ) {
1966 count = 0; /* first iteration is not yet finished! */
1967 this_kernel = curr_kernel->next;
1968 kernel_number = 1;
1969 total_changed = changed;
1970 }
1971
1972 while ( count < limit ) {
1973 count++;
1974 while ( this_kernel != (KernelInfo *) NULL ) {
anthony602ab9b2010-01-05 08:06:50 +00001975 Image *tmp = old_image;
1976 old_image = new_image;
1977 new_image = tmp;
anthony7a01dcf2010-05-11 12:25:52 +00001978 changed = MorphologyPrimative(old_image,new_image,curr_method,channel,
anthony1b2bc0a2010-05-12 05:25:22 +00001979 this_kernel,exception);
anthony602ab9b2010-01-05 08:06:50 +00001980 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
anthony1b2bc0a2010-05-12 05:25:22 +00001981 fprintf(stderr, "Morphology %s:%lu.%lu => Changed %lu\n",
anthony4fd27e22010-02-07 08:17:18 +00001982 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
anthony1b2bc0a2010-05-12 05:25:22 +00001983 count, kernel_number, changed);
1984 total_changed += changed;
1985 this_kernel = this_kernel->next;
1986 kernel_number++;
anthony602ab9b2010-01-05 08:06:50 +00001987 }
anthony1b2bc0a2010-05-12 05:25:22 +00001988 if ( total_changed == 0 )
1989 break; /* no changes after processing all kernels - ABORT */
1990 /* prepare for next loop */
1991 total_changed = 0;
1992 kernel_number = 0;
1993 this_kernel = curr_kernel;
1994 }
cristy150989e2010-02-01 14:59:39 +00001995 old_image=DestroyImage(old_image);
anthony602ab9b2010-01-05 08:06:50 +00001996 }
anthony930be612010-02-08 04:26:15 +00001997
anthony1b2bc0a2010-05-12 05:25:22 +00001998 /* finished with kernel - destary any copy that was made */
anthony4fd27e22010-02-07 08:17:18 +00001999 if ( curr_kernel != kernel )
2000 curr_kernel=DestroyKernelInfo(curr_kernel);
2001
anthony7d10d742010-05-06 07:05:29 +00002002 /* Third-level Subtractive methods post-processing
2003 **
2004 ** The removal of any 'Sync' channel flag in the Image Compositon below
2005 ** ensures the compose method is applied in a purely mathematical way, only
2006 ** the selected channels, without any normal 'alpha blending' normally
2007 ** associated with the compose method.
2008 **
2009 ** Note "method" here is the 'original' morphological method, and not the
2010 ** 'current' morphological method used above to generate "new_image".
2011 */
anthony4fd27e22010-02-07 08:17:18 +00002012 switch( method ) {
2013 case EdgeOutMorphology:
2014 case EdgeInMorphology:
2015 case TopHatMorphology:
2016 case BottomHatMorphology:
anthony930be612010-02-08 04:26:15 +00002017 /* Get Difference relative to the original image */
cristyaeb2cbc2010-05-07 13:28:58 +00002018 (void) CompositeImageChannel(new_image, (ChannelType) (channel & ~SyncChannels),
anthony7d10d742010-05-06 07:05:29 +00002019 DifferenceCompositeOp, image, 0, 0);
anthony4fd27e22010-02-07 08:17:18 +00002020 break;
anthony7d10d742010-05-06 07:05:29 +00002021 case EdgeMorphology:
2022 /* Difference the Eroded image from the saved Dilated image */
cristyaeb2cbc2010-05-07 13:28:58 +00002023 (void) CompositeImageChannel(new_image, (ChannelType) (channel & ~SyncChannels),
anthony7d10d742010-05-06 07:05:29 +00002024 DifferenceCompositeOp, grad_image, 0, 0);
anthony4fd27e22010-02-07 08:17:18 +00002025 grad_image=DestroyImage(grad_image);
2026 break;
2027 default:
2028 break;
2029 }
anthony602ab9b2010-01-05 08:06:50 +00002030
2031 return(new_image);
anthony1b2bc0a2010-05-12 05:25:22 +00002032
2033 /* Yes goto's are bad, but in this case it makes cleanup lot more efficient */
2034error_cleanup:
2035 if ( new_image != (Image *) NULL )
2036 DestroyImage(new_image);
2037exit_cleanup:
2038 if ( old_image != (Image *) NULL )
2039 DestroyImage(old_image);
2040 if ( curr_kernel != (KernelInfo *) NULL && curr_kernel != kernel )
2041 curr_kernel=DestroyKernelInfo(curr_kernel);
2042 return(new_image);
anthony602ab9b2010-01-05 08:06:50 +00002043}
anthony83ba99b2010-01-24 08:48:15 +00002044
2045/*
2046%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2047% %
2048% %
2049% %
anthony4fd27e22010-02-07 08:17:18 +00002050+ R o t a t e K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00002051% %
2052% %
2053% %
2054%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2055%
anthony4fd27e22010-02-07 08:17:18 +00002056% RotateKernelInfo() rotates the kernel by the angle given. Currently it is
anthony83ba99b2010-01-24 08:48:15 +00002057% restricted to 90 degree angles, but this may be improved in the future.
2058%
anthony4fd27e22010-02-07 08:17:18 +00002059% The format of the RotateKernelInfo method is:
anthony83ba99b2010-01-24 08:48:15 +00002060%
anthony4fd27e22010-02-07 08:17:18 +00002061% void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00002062%
2063% A description of each parameter follows:
2064%
2065% o kernel: the Morphology/Convolution kernel
2066%
2067% o angle: angle to rotate in degrees
2068%
anthonyc4c86e02010-01-27 09:30:32 +00002069% This function is only internel to this module, as it is not finalized,
2070% especially with regard to non-orthogonal angles, and rotation of larger
2071% 2D kernels.
anthony83ba99b2010-01-24 08:48:15 +00002072*/
anthony4fd27e22010-02-07 08:17:18 +00002073static void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00002074{
anthony1b2bc0a2010-05-12 05:25:22 +00002075 /* angle the lower kernels first */
2076 if ( kernel->next != (KernelInfo *) NULL)
2077 RotateKernelInfo(kernel->next, angle);
2078
anthony83ba99b2010-01-24 08:48:15 +00002079 /* WARNING: Currently assumes the kernel (rightly) is horizontally symetrical
2080 **
2081 ** TODO: expand beyond simple 90 degree rotates, flips and flops
2082 */
2083
2084 /* Modulus the angle */
2085 angle = fmod(angle, 360.0);
2086 if ( angle < 0 )
2087 angle += 360.0;
2088
2089 if ( 315.0 < angle || angle <= 45.0 )
2090 return; /* no change! - At least at this time */
2091
2092 switch (kernel->type) {
2093 /* These built-in kernels are cylindrical kernels, rotating is useless */
2094 case GaussianKernel:
2095 case LaplacianKernel:
2096 case LOGKernel:
2097 case DOGKernel:
2098 case DiskKernel:
2099 case ChebyshevKernel:
2100 case ManhattenKernel:
2101 case EuclideanKernel:
2102 return;
2103
2104 /* These may be rotatable at non-90 angles in the future */
2105 /* but simply rotating them in multiples of 90 degrees is useless */
2106 case SquareKernel:
2107 case DiamondKernel:
2108 case PlusKernel:
2109 return;
2110
2111 /* These only allows a +/-90 degree rotation (by transpose) */
2112 /* A 180 degree rotation is useless */
2113 case BlurKernel:
2114 case RectangleKernel:
2115 if ( 135.0 < angle && angle <= 225.0 )
2116 return;
2117 if ( 225.0 < angle && angle <= 315.0 )
2118 angle -= 180;
2119 break;
2120
2121 /* these are freely rotatable in 90 degree units */
2122 case CometKernel:
2123 case UndefinedKernel:
2124 case UserDefinedKernel:
2125 break;
2126 }
2127 if ( 135.0 < angle && angle <= 225.0 )
2128 {
2129 /* For a 180 degree rotation - also know as a reflection */
2130 /* This is actually a very very common operation! */
2131 /* Basically all that is needed is a reversal of the kernel data! */
2132 unsigned long
2133 i,j;
2134 register double
2135 *k,t;
2136
2137 k=kernel->values;
2138 for ( i=0, j=kernel->width*kernel->height-1; i<j; i++, j--)
2139 t=k[i], k[i]=k[j], k[j]=t;
2140
anthony930be612010-02-08 04:26:15 +00002141 kernel->x = (long) kernel->width - kernel->x - 1;
2142 kernel->y = (long) kernel->height - kernel->y - 1;
anthony83ba99b2010-01-24 08:48:15 +00002143 angle = fmod(angle+180.0, 360.0);
2144 }
2145 if ( 45.0 < angle && angle <= 135.0 )
2146 { /* Do a transpose and a flop, of the image, which results in a 90
2147 * degree rotation using two mirror operations.
2148 *
2149 * WARNING: this assumes the original image was a 1 dimentional image
2150 * but currently that is the only built-ins it is applied to.
2151 */
cristy150989e2010-02-01 14:59:39 +00002152 long
anthony83ba99b2010-01-24 08:48:15 +00002153 t;
cristy150989e2010-02-01 14:59:39 +00002154 t = (long) kernel->width;
anthony83ba99b2010-01-24 08:48:15 +00002155 kernel->width = kernel->height;
cristy150989e2010-02-01 14:59:39 +00002156 kernel->height = (unsigned long) t;
cristyc99304f2010-02-01 15:26:27 +00002157 t = kernel->x;
2158 kernel->x = kernel->y;
2159 kernel->y = t;
anthony83ba99b2010-01-24 08:48:15 +00002160 angle = fmod(450.0 - angle, 360.0);
2161 }
2162 /* At this point angle should be between -45 (315) and +45 degrees
2163 * In the future some form of non-orthogonal angled rotates could be
2164 * performed here, posibily with a linear kernel restriction.
2165 */
2166
2167#if 0
2168 Not currently in use!
2169 { /* Do a flop, this assumes kernel is horizontally symetrical.
2170 * Each row of the kernel needs to be reversed!
2171 */
2172 unsigned long
2173 y;
cristy150989e2010-02-01 14:59:39 +00002174 register long
anthony83ba99b2010-01-24 08:48:15 +00002175 x,r;
2176 register double
2177 *k,t;
2178
2179 for ( y=0, k=kernel->values; y < kernel->height; y++, k+=kernel->width)
2180 for ( x=0, r=kernel->width-1; x<kernel->width/2; x++, r--)
2181 t=k[x], k[x]=k[r], k[r]=t;
2182
cristyc99304f2010-02-01 15:26:27 +00002183 kernel->x = kernel->width - kernel->x - 1;
anthony83ba99b2010-01-24 08:48:15 +00002184 angle = fmod(angle+180.0, 360.0);
2185 }
2186#endif
2187 return;
2188}
2189
2190/*
2191%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2192% %
2193% %
2194% %
cristy6771f1e2010-03-05 19:43:39 +00002195% S c a l e K e r n e l I n f o %
anthonycc6c8362010-01-25 04:14:01 +00002196% %
2197% %
2198% %
2199%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2200%
anthony1b2bc0a2010-05-12 05:25:22 +00002201% ScaleKernelInfo() scales the given kernel list by the given amount, with or
2202% without normalization of the sum of the kernel values (as per given flags).
anthonycc6c8362010-01-25 04:14:01 +00002203%
anthony999bb2c2010-02-18 12:38:01 +00002204% By default (no flags given) the values within the kernel is scaled
anthony1b2bc0a2010-05-12 05:25:22 +00002205% directly using given scaling factor without change.
anthonycc6c8362010-01-25 04:14:01 +00002206%
anthony1b2bc0a2010-05-12 05:25:22 +00002207% If any 'normalize_flags' are given the kernel will first be normalized and
2208% then further scaled by the scaling factor value given. A 'PercentValue'
2209% flag will cause the given scaling factor to be divided by one hundred
2210% percent.
anthony999bb2c2010-02-18 12:38:01 +00002211%
2212% Kernel normalization ('normalize_flags' given) is designed to ensure that
2213% any use of the kernel scaling factor with 'Convolve' or 'Correlate'
anthony1b2bc0a2010-05-12 05:25:22 +00002214% morphology methods will fall into -1.0 to +1.0 range. Note that for
2215% non-HDRI versions of IM this may cause images to have any negative results
2216% clipped, unless some 'bias' is used.
anthony999bb2c2010-02-18 12:38:01 +00002217%
2218% More specifically. Kernels which only contain positive values (such as a
2219% 'Gaussian' kernel) will be scaled so that those values sum to +1.0,
anthony1b2bc0a2010-05-12 05:25:22 +00002220% ensuring a 0.0 to +1.0 output range for non-HDRI images.
anthony999bb2c2010-02-18 12:38:01 +00002221%
2222% For Kernels that contain some negative values, (such as 'Sharpen' kernels)
2223% the kernel will be scaled by the absolute of the sum of kernel values, so
2224% that it will generally fall within the +/- 1.0 range.
2225%
2226% For kernels whose values sum to zero, (such as 'Laplician' kernels) kernel
2227% will be scaled by just the sum of the postive values, so that its output
2228% range will again fall into the +/- 1.0 range.
2229%
2230% For special kernels designed for locating shapes using 'Correlate', (often
2231% only containing +1 and -1 values, representing foreground/brackground
2232% matching) a special normalization method is provided to scale the positive
2233% values seperatally to those of the negative values, so the kernel will be
2234% forced to become a zero-sum kernel better suited to such searches.
2235%
anthony1b2bc0a2010-05-12 05:25:22 +00002236% WARNING: Correct normalization of the kernel assumes that the '*_range'
anthony999bb2c2010-02-18 12:38:01 +00002237% attributes within the kernel structure have been correctly set during the
2238% kernels creation.
2239%
2240% NOTE: The values used for 'normalize_flags' have been selected specifically
anthony1b2bc0a2010-05-12 05:25:22 +00002241% to match the use of geometry options, so that '!' means NormalizeValue,
2242% '^' means CorrelateNormalizeValue, and '%' means PercentValue. All other
anthony999bb2c2010-02-18 12:38:01 +00002243% GeometryFlags values are ignored.
anthonycc6c8362010-01-25 04:14:01 +00002244%
anthony4fd27e22010-02-07 08:17:18 +00002245% The format of the ScaleKernelInfo method is:
anthonycc6c8362010-01-25 04:14:01 +00002246%
anthony999bb2c2010-02-18 12:38:01 +00002247% void ScaleKernelInfo(KernelInfo *kernel, const double scaling_factor,
2248% const MagickStatusType normalize_flags )
anthonycc6c8362010-01-25 04:14:01 +00002249%
2250% A description of each parameter follows:
2251%
2252% o kernel: the Morphology/Convolution kernel
2253%
anthony999bb2c2010-02-18 12:38:01 +00002254% o scaling_factor:
2255% multiply all values (after normalization) by this factor if not
2256% zero. If the kernel is normalized regardless of any flags.
2257%
2258% o normalize_flags:
2259% GeometryFlags defining normalization method to use.
2260% specifically: NormalizeValue, CorrelateNormalizeValue,
2261% and/or PercentValue
anthonycc6c8362010-01-25 04:14:01 +00002262%
anthonyc4c86e02010-01-27 09:30:32 +00002263% This function is internal to this module only at this time, but can be
2264% exported to other modules if needed.
anthonycc6c8362010-01-25 04:14:01 +00002265*/
cristy6771f1e2010-03-05 19:43:39 +00002266MagickExport void ScaleKernelInfo(KernelInfo *kernel,
2267 const double scaling_factor,const GeometryFlags normalize_flags)
anthonycc6c8362010-01-25 04:14:01 +00002268{
cristy150989e2010-02-01 14:59:39 +00002269 register long
anthonycc6c8362010-01-25 04:14:01 +00002270 i;
2271
anthony999bb2c2010-02-18 12:38:01 +00002272 register double
2273 pos_scale,
2274 neg_scale;
2275
anthony1b2bc0a2010-05-12 05:25:22 +00002276 /* scale the lower kernels first */
2277 if ( kernel->next != (KernelInfo *) NULL)
2278 ScaleKernelInfo(kernel->next, scaling_factor, normalize_flags);
2279
anthony999bb2c2010-02-18 12:38:01 +00002280 pos_scale = 1.0;
2281 if ( (normalize_flags&NormalizeValue) != 0 ) {
2282 /* normalize kernel appropriately */
2283 if ( fabs(kernel->positive_range + kernel->negative_range) > MagickEpsilon )
2284 pos_scale = fabs(kernel->positive_range + kernel->negative_range);
anthonycc6c8362010-01-25 04:14:01 +00002285 else
anthony999bb2c2010-02-18 12:38:01 +00002286 pos_scale = kernel->positive_range; /* special zero-summing kernel */
2287 }
2288 /* force kernel into being a normalized zero-summing kernel */
2289 if ( (normalize_flags&CorrelateNormalizeValue) != 0 ) {
2290 pos_scale = ( fabs(kernel->positive_range) > MagickEpsilon )
2291 ? kernel->positive_range : 1.0;
2292 neg_scale = ( fabs(kernel->negative_range) > MagickEpsilon )
2293 ? -kernel->negative_range : 1.0;
2294 }
2295 else
2296 neg_scale = pos_scale;
2297
2298 /* finialize scaling_factor for positive and negative components */
2299 pos_scale = scaling_factor/pos_scale;
2300 neg_scale = scaling_factor/neg_scale;
2301 if ( (normalize_flags&PercentValue) != 0 ) {
2302 pos_scale /= 100.0;
2303 neg_scale /= 100.0;
anthonycc6c8362010-01-25 04:14:01 +00002304 }
2305
cristy150989e2010-02-01 14:59:39 +00002306 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00002307 if ( ! IsNan(kernel->values[i]) )
anthony999bb2c2010-02-18 12:38:01 +00002308 kernel->values[i] *= (kernel->values[i] >= 0) ? pos_scale : neg_scale;
anthonycc6c8362010-01-25 04:14:01 +00002309
anthony999bb2c2010-02-18 12:38:01 +00002310 /* convolution output range */
2311 kernel->positive_range *= pos_scale;
2312 kernel->negative_range *= neg_scale;
2313 /* maximum and minimum values in kernel */
2314 kernel->maximum *= (kernel->maximum >= 0.0) ? pos_scale : neg_scale;
2315 kernel->minimum *= (kernel->minimum >= 0.0) ? pos_scale : neg_scale;
2316
2317 /* swap kernel settings if user scaling factor is negative */
2318 if ( scaling_factor < MagickEpsilon ) {
2319 double t;
2320 t = kernel->positive_range;
2321 kernel->positive_range = kernel->negative_range;
2322 kernel->negative_range = t;
2323 t = kernel->maximum;
2324 kernel->maximum = kernel->minimum;
2325 kernel->minimum = 1;
2326 }
anthonycc6c8362010-01-25 04:14:01 +00002327
2328 return;
2329}
2330
2331/*
2332%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2333% %
2334% %
2335% %
anthony4fd27e22010-02-07 08:17:18 +00002336+ S h o w K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00002337% %
2338% %
2339% %
2340%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2341%
anthony4fd27e22010-02-07 08:17:18 +00002342% ShowKernelInfo() outputs the details of the given kernel defination to
2343% standard error, generally due to a users 'showkernel' option request.
anthony83ba99b2010-01-24 08:48:15 +00002344%
2345% The format of the ShowKernel method is:
2346%
anthony4fd27e22010-02-07 08:17:18 +00002347% void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00002348%
2349% A description of each parameter follows:
2350%
2351% o kernel: the Morphology/Convolution kernel
2352%
anthonyc4c86e02010-01-27 09:30:32 +00002353% This function is internal to this module only at this time. That may change
2354% in the future.
anthony83ba99b2010-01-24 08:48:15 +00002355*/
anthony4fd27e22010-02-07 08:17:18 +00002356MagickExport void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00002357{
anthony7a01dcf2010-05-11 12:25:52 +00002358 KernelInfo
2359 *k;
anthony83ba99b2010-01-24 08:48:15 +00002360
anthony7a01dcf2010-05-11 12:25:52 +00002361 long
2362 c, i, u, v;
2363
2364 for (c=0, k=kernel; k != (KernelInfo *) NULL; c++, k=k->next ) {
2365
2366 fprintf(stderr, "Kernel ");
2367 if ( kernel->next != (KernelInfo *) NULL )
2368 fprintf(stderr, " #%ld", c );
2369 fprintf(stderr, " \"%s\" of size %lux%lu%+ld%+ld ",
2370 MagickOptionToMnemonic(MagickKernelOptions, k->type),
2371 kernel->width, k->height,
2372 kernel->x, kernel->y );
2373 fprintf(stderr,
2374 " with values from %.*lg to %.*lg\n",
2375 GetMagickPrecision(), k->minimum,
2376 GetMagickPrecision(), k->maximum);
2377 fprintf(stderr, "Forming convolution output range from %.*lg to %.*lg%s\n",
2378 GetMagickPrecision(), k->negative_range,
2379 GetMagickPrecision(), k->positive_range,
2380 /*kernel->normalized == MagickTrue ? " (normalized)" : */ "" );
2381 for (i=v=0; v < (long) k->height; v++) {
2382 fprintf(stderr,"%2ld:",v);
2383 for (u=0; u < (long) k->width; u++, i++)
2384 if ( IsNan(k->values[i]) )
2385 fprintf(stderr," %*s", GetMagickPrecision()+2, "nan");
2386 else
2387 fprintf(stderr," %*.*lg", GetMagickPrecision()+2,
2388 GetMagickPrecision(), k->values[i]);
2389 fprintf(stderr,"\n");
2390 }
anthony83ba99b2010-01-24 08:48:15 +00002391 }
2392}
anthonycc6c8362010-01-25 04:14:01 +00002393
2394/*
2395%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2396% %
2397% %
2398% %
anthony4fd27e22010-02-07 08:17:18 +00002399+ Z e r o K e r n e l N a n s %
anthonycc6c8362010-01-25 04:14:01 +00002400% %
2401% %
2402% %
2403%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2404%
2405% ZeroKernelNans() replaces any special 'nan' value that may be present in
2406% the kernel with a zero value. This is typically done when the kernel will
2407% be used in special hardware (GPU) convolution processors, to simply
2408% matters.
2409%
2410% The format of the ZeroKernelNans method is:
2411%
2412% voidZeroKernelNans (KernelInfo *kernel)
2413%
2414% A description of each parameter follows:
2415%
2416% o kernel: the Morphology/Convolution kernel
2417%
2418% FUTURE: return the information in a string for API usage.
2419*/
anthonyc4c86e02010-01-27 09:30:32 +00002420MagickExport void ZeroKernelNans(KernelInfo *kernel)
anthonycc6c8362010-01-25 04:14:01 +00002421{
cristy150989e2010-02-01 14:59:39 +00002422 register long
anthonycc6c8362010-01-25 04:14:01 +00002423 i;
2424
anthony1b2bc0a2010-05-12 05:25:22 +00002425 /* scale the lower kernels first */
2426 if ( kernel->next != (KernelInfo *) NULL)
2427 ZeroKernelNans(kernel->next);
2428
cristy150989e2010-02-01 14:59:39 +00002429 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00002430 if ( IsNan(kernel->values[i]) )
2431 kernel->values[i] = 0.0;
2432
2433 return;
2434}