blob: 6bbbcd728408d6838ba6e7576d3d088405f95d7c [file] [log] [blame]
cristy701db312009-11-20 03:14:08 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% M M OOO RRRR PPPP H H OOO L OOO GGGG Y Y %
7% MM MM O O R R P P H H O O L O O G Y Y %
8% M M M O O RRRR PPPP HHHHH O O L O O G GGG Y %
9% M M O O R R P H H O O L O O G G Y %
10% M M OOO R R P H H OOO LLLLL OOO GGG Y %
11% %
12% %
13% MagickCore Morphology Methods %
14% %
15% Software Design %
16% Anthony Thyssen %
anthonyc94cdb02010-01-06 08:15:29 +000017% January 2010 %
cristy701db312009-11-20 03:14:08 +000018% %
19% %
cristy16af1cb2009-12-11 21:38:29 +000020% Copyright 1999-2010 ImageMagick Studio LLC, a non-profit organization %
cristy701db312009-11-20 03:14:08 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
anthony602ab9b2010-01-05 08:06:50 +000036% Morpology is the the application of various kernals, of any size and even
37% shape, to a image in various ways (typically binary, but not always).
cristy701db312009-11-20 03:14:08 +000038%
anthony602ab9b2010-01-05 08:06:50 +000039% Convolution (weighted sum or average) is just one specific type of
40% morphology. Just one that is very common for image bluring and sharpening
41% effects. Not only 2D Gaussian blurring, but also 2-pass 1D Blurring.
42%
43% This module provides not only a general morphology function, and the ability
44% to apply more advanced or iterative morphologies, but also functions for the
45% generation of many different types of kernel arrays from user supplied
46% arguments. Prehaps even the generation of a kernel from a small image.
cristy701db312009-11-20 03:14:08 +000047*/
48
49/*
50 Include declarations.
51*/
52#include "magick/studio.h"
anthony602ab9b2010-01-05 08:06:50 +000053#include "magick/artifact.h"
cristy701db312009-11-20 03:14:08 +000054#include "magick/cache-view.h"
55#include "magick/color-private.h"
56#include "magick/enhance.h"
57#include "magick/exception.h"
58#include "magick/exception-private.h"
anthony602ab9b2010-01-05 08:06:50 +000059#include "magick/gem.h"
cristy701db312009-11-20 03:14:08 +000060#include "magick/hashmap.h"
61#include "magick/image.h"
cristybba804b2010-01-05 15:39:59 +000062#include "magick/image-private.h"
cristy701db312009-11-20 03:14:08 +000063#include "magick/list.h"
anthony29188a82010-01-22 10:12:34 +000064#include "magick/magick.h"
cristy701db312009-11-20 03:14:08 +000065#include "magick/memory_.h"
66#include "magick/monitor-private.h"
67#include "magick/morphology.h"
anthony602ab9b2010-01-05 08:06:50 +000068#include "magick/option.h"
cristy701db312009-11-20 03:14:08 +000069#include "magick/pixel-private.h"
70#include "magick/prepress.h"
71#include "magick/quantize.h"
72#include "magick/registry.h"
73#include "magick/semaphore.h"
74#include "magick/splay-tree.h"
75#include "magick/statistic.h"
76#include "magick/string_.h"
anthony602ab9b2010-01-05 08:06:50 +000077#include "magick/string-private.h"
78#include "magick/token.h"
cristya29d45f2010-03-05 21:14:54 +000079
anthony602ab9b2010-01-05 08:06:50 +000080/*
cristya29d45f2010-03-05 21:14:54 +000081 The following test is for special floating point numbers of value NaN (not
82 a number), that may be used within a Kernel Definition. NaN's are defined
83 as part of the IEEE standard for floating point number representation.
84
85 These are used a Kernel value of NaN means that that kernal position is not
86 part of the normal convolution or morphology process, and thus allowing the
87 use of 'shaped' kernels.
88
89 Special properities two NaN's are never equal, even if they are from the
90 same variable That is the IsNaN() macro is only true if the value is NaN.
91*/
anthony602ab9b2010-01-05 08:06:50 +000092#define IsNan(a) ((a)!=(a))
93
anthony29188a82010-01-22 10:12:34 +000094/*
cristya29d45f2010-03-05 21:14:54 +000095 Other global definitions used by module.
96*/
anthony29188a82010-01-22 10:12:34 +000097static inline double MagickMin(const double x,const double y)
98{
99 return( x < y ? x : y);
100}
101static inline double MagickMax(const double x,const double y)
102{
103 return( x > y ? x : y);
104}
105#define Minimize(assign,value) assign=MagickMin(assign,value)
106#define Maximize(assign,value) assign=MagickMax(assign,value)
107
anthonyc4c86e02010-01-27 09:30:32 +0000108/* Currently these are only internal to this module */
109static void
cristyef656912010-03-05 19:54:59 +0000110 RotateKernelInfo(KernelInfo *, double);
anthony602ab9b2010-01-05 08:06:50 +0000111
112/*
113%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
114% %
115% %
116% %
anthony83ba99b2010-01-24 08:48:15 +0000117% A c q u i r e K e r n e l I n f o %
anthony602ab9b2010-01-05 08:06:50 +0000118% %
119% %
120% %
121%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
122%
cristy2be15382010-01-21 02:38:03 +0000123% AcquireKernelInfo() takes the given string (generally supplied by the
anthony602ab9b2010-01-05 08:06:50 +0000124% user) and converts it into a Morphology/Convolution Kernel. This allows
125% users to specify a kernel from a number of pre-defined kernels, or to fully
126% specify their own kernel for a specific Convolution or Morphology
127% Operation.
128%
129% The kernel so generated can be any rectangular array of floating point
130% values (doubles) with the 'control point' or 'pixel being affected'
131% anywhere within that array of values.
132%
anthony83ba99b2010-01-24 08:48:15 +0000133% Previously IM was restricted to a square of odd size using the exact
134% center as origin, this is no longer the case, and any rectangular kernel
135% with any value being declared the origin. This in turn allows the use of
136% highly asymmetrical kernels.
anthony602ab9b2010-01-05 08:06:50 +0000137%
138% The floating point values in the kernel can also include a special value
anthony83ba99b2010-01-24 08:48:15 +0000139% known as 'nan' or 'not a number' to indicate that this value is not part
140% of the kernel array. This allows you to shaped the kernel within its
141% rectangular area. That is 'nan' values provide a 'mask' for the kernel
142% shape. However at least one non-nan value must be provided for correct
143% working of a kernel.
anthony602ab9b2010-01-05 08:06:50 +0000144%
anthony83ba99b2010-01-24 08:48:15 +0000145% The returned kernel should be free using the DestroyKernelInfo() when you
146% are finished with it.
anthony602ab9b2010-01-05 08:06:50 +0000147%
148% Input kernel defintion strings can consist of any of three types.
149%
anthony29188a82010-01-22 10:12:34 +0000150% "name:args"
151% Select from one of the built in kernels, using the name and
152% geometry arguments supplied. See AcquireKernelBuiltIn()
anthony602ab9b2010-01-05 08:06:50 +0000153%
154% "WxH[+X+Y]:num, num, num ..."
155% a kernal of size W by H, with W*H floating point numbers following.
156% the 'center' can be optionally be defined at +X+Y (such that +0+0
anthony29188a82010-01-22 10:12:34 +0000157% is top left corner). If not defined the pixel in the center, for
158% odd sizes, or to the immediate top or left of center for even sizes
159% is automatically selected.
anthony602ab9b2010-01-05 08:06:50 +0000160%
anthony29188a82010-01-22 10:12:34 +0000161% "num, num, num, num, ..."
162% list of floating point numbers defining an 'old style' odd sized
163% square kernel. At least 9 values should be provided for a 3x3
164% square kernel, 25 for a 5x5 square kernel, 49 for 7x7, etc.
165% Values can be space or comma separated. This is not recommended.
anthony602ab9b2010-01-05 08:06:50 +0000166%
anthony83ba99b2010-01-24 08:48:15 +0000167% Note that 'name' kernels will start with an alphabetic character while the
168% new kernel specification has a ':' character in its specification string.
169% If neither is the case, it is assumed an old style of a simple list of
170% numbers generating a odd-sized square kernel has been given.
anthony602ab9b2010-01-05 08:06:50 +0000171%
172% The format of the AcquireKernal method is:
173%
cristy2be15382010-01-21 02:38:03 +0000174% KernelInfo *AcquireKernelInfo(const char *kernel_string)
anthony602ab9b2010-01-05 08:06:50 +0000175%
176% A description of each parameter follows:
177%
178% o kernel_string: the Morphology/Convolution kernel wanted.
179%
180*/
181
anthonyc84dce52010-05-07 05:42:23 +0000182/* This was separated so that it could be used as a separate
183** array input handling function.
184*/
185static KernelInfo *ParseArray(const char *kernel_string)
anthony602ab9b2010-01-05 08:06:50 +0000186{
cristy2be15382010-01-21 02:38:03 +0000187 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000188 *kernel;
189
190 char
191 token[MaxTextExtent];
192
anthony602ab9b2010-01-05 08:06:50 +0000193 const char
194 *p;
195
anthonyc84dce52010-05-07 05:42:23 +0000196 register long
197 i;
anthony602ab9b2010-01-05 08:06:50 +0000198
anthony29188a82010-01-22 10:12:34 +0000199 double
200 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
201
cristy2be15382010-01-21 02:38:03 +0000202 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
203 if (kernel == (KernelInfo *)NULL)
anthony602ab9b2010-01-05 08:06:50 +0000204 return(kernel);
205 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
206 kernel->type = UserDefinedKernel;
cristyd43a46b2010-01-21 02:13:41 +0000207 kernel->signature = MagickSignature;
anthony602ab9b2010-01-05 08:06:50 +0000208
209 /* Has a ':' in argument - New user kernel specification */
210 p = strchr(kernel_string, ':');
211 if ( p != (char *) NULL)
212 {
anthonyc84dce52010-05-07 05:42:23 +0000213 MagickStatusType
214 flags;
215
216 GeometryInfo
217 args;
218
anthony602ab9b2010-01-05 08:06:50 +0000219 /* ParseGeometry() needs the geometry separated! -- Arrgghh */
cristy150989e2010-02-01 14:59:39 +0000220 memcpy(token, kernel_string, (size_t) (p-kernel_string));
anthony602ab9b2010-01-05 08:06:50 +0000221 token[p-kernel_string] = '\0';
anthonyc84dce52010-05-07 05:42:23 +0000222 SetGeometryInfo(&args);
anthony602ab9b2010-01-05 08:06:50 +0000223 flags = ParseGeometry(token, &args);
anthony602ab9b2010-01-05 08:06:50 +0000224
anthony29188a82010-01-22 10:12:34 +0000225 /* Size handling and checks of geometry settings */
anthony602ab9b2010-01-05 08:06:50 +0000226 if ( (flags & WidthValue) == 0 ) /* if no width then */
227 args.rho = args.sigma; /* then width = height */
228 if ( args.rho < 1.0 ) /* if width too small */
229 args.rho = 1.0; /* then width = 1 */
230 if ( args.sigma < 1.0 ) /* if height too small */
231 args.sigma = args.rho; /* then height = width */
232 kernel->width = (unsigned long)args.rho;
233 kernel->height = (unsigned long)args.sigma;
234
235 /* Offset Handling and Checks */
236 if ( args.xi < 0.0 || args.psi < 0.0 )
anthony83ba99b2010-01-24 08:48:15 +0000237 return(DestroyKernelInfo(kernel));
cristyc99304f2010-02-01 15:26:27 +0000238 kernel->x = ((flags & XValue)!=0) ? (long)args.xi
cristy150989e2010-02-01 14:59:39 +0000239 : (long) (kernel->width-1)/2;
cristyc99304f2010-02-01 15:26:27 +0000240 kernel->y = ((flags & YValue)!=0) ? (long)args.psi
cristy150989e2010-02-01 14:59:39 +0000241 : (long) (kernel->height-1)/2;
cristyc99304f2010-02-01 15:26:27 +0000242 if ( kernel->x >= (long) kernel->width ||
243 kernel->y >= (long) kernel->height )
anthony83ba99b2010-01-24 08:48:15 +0000244 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000245
246 p++; /* advance beyond the ':' */
247 }
248 else
anthonyc84dce52010-05-07 05:42:23 +0000249 { /* ELSE - Old old specification, forming odd-square kernel */
anthony602ab9b2010-01-05 08:06:50 +0000250 /* count up number of values given */
251 p=(const char *) kernel_string;
cristya699b172010-01-06 16:48:49 +0000252 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
anthony29188a82010-01-22 10:12:34 +0000253 p++; /* ignore "'" chars for convolve filter usage - Cristy */
anthony602ab9b2010-01-05 08:06:50 +0000254 for (i=0; *p != '\0'; i++)
255 {
256 GetMagickToken(p,&p,token);
257 if (*token == ',')
258 GetMagickToken(p,&p,token);
259 }
260 /* set the size of the kernel - old sized square */
261 kernel->width = kernel->height= (unsigned long) sqrt((double) i+1.0);
cristyc99304f2010-02-01 15:26:27 +0000262 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000263 p=(const char *) kernel_string;
anthony29188a82010-01-22 10:12:34 +0000264 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
265 p++; /* ignore "'" chars for convolve filter usage - Cristy */
anthony602ab9b2010-01-05 08:06:50 +0000266 }
267
268 /* Read in the kernel values from rest of input string argument */
269 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
270 kernel->height*sizeof(double));
271 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000272 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000273
cristyc99304f2010-02-01 15:26:27 +0000274 kernel->minimum = +MagickHuge;
275 kernel->maximum = -MagickHuge;
276 kernel->negative_range = kernel->positive_range = 0.0;
anthonyc84dce52010-05-07 05:42:23 +0000277
cristy150989e2010-02-01 14:59:39 +0000278 for (i=0; (i < (long) (kernel->width*kernel->height)) && (*p != '\0'); i++)
anthony602ab9b2010-01-05 08:06:50 +0000279 {
280 GetMagickToken(p,&p,token);
281 if (*token == ',')
282 GetMagickToken(p,&p,token);
anthony29188a82010-01-22 10:12:34 +0000283 if ( LocaleCompare("nan",token) == 0
anthonyc84dce52010-05-07 05:42:23 +0000284 || LocaleCompare("-",token) == 0 ) {
anthony29188a82010-01-22 10:12:34 +0000285 kernel->values[i] = nan; /* do not include this value in kernel */
286 }
287 else {
288 kernel->values[i] = StringToDouble(token);
289 ( kernel->values[i] < 0)
cristyc99304f2010-02-01 15:26:27 +0000290 ? ( kernel->negative_range += kernel->values[i] )
291 : ( kernel->positive_range += kernel->values[i] );
292 Minimize(kernel->minimum, kernel->values[i]);
293 Maximize(kernel->maximum, kernel->values[i]);
anthony29188a82010-01-22 10:12:34 +0000294 }
anthony602ab9b2010-01-05 08:06:50 +0000295 }
anthony29188a82010-01-22 10:12:34 +0000296
anthonyc84dce52010-05-07 05:42:23 +0000297#if 0
298 /* this was the old method of handling a incomplete kernel */
cristy150989e2010-02-01 14:59:39 +0000299 if ( i < (long) (kernel->width*kernel->height) ) {
cristyc99304f2010-02-01 15:26:27 +0000300 Minimize(kernel->minimum, kernel->values[i]);
301 Maximize(kernel->maximum, kernel->values[i]);
cristy150989e2010-02-01 14:59:39 +0000302 for ( ; i < (long) (kernel->width*kernel->height); i++)
anthony29188a82010-01-22 10:12:34 +0000303 kernel->values[i]=0.0;
304 }
anthonyc84dce52010-05-07 05:42:23 +0000305#else
306 /* Number of values for kernel was not enough - Report Error */
307 if ( i < (long) (kernel->width*kernel->height) )
308 return(DestroyKernelInfo(kernel));
309#endif
310
311 /* check that we recieved at least one real (non-nan) value! */
312 if ( kernel->minimum == MagickHuge )
313 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000314
315 return(kernel);
316}
anthonyc84dce52010-05-07 05:42:23 +0000317
318
319MagickExport KernelInfo *AcquireKernelInfo(const char *kernel_string)
320{
321 char
322 token[MaxTextExtent];
323
324 const char
325 *p;
326
327 MagickStatusType
328 flags;
329
330 GeometryInfo
331 args;
332
333 long
334 type;
335
336 /* If it does not start with an alpha - user defined kernel */
337 GetMagickToken(kernel_string,&p,token);
338 if (isalpha((int) ((unsigned char) *token)) == 0)
339 return(ParseArray(kernel_string));
340
341 /* Parse special 'named' kernel */
342 type=ParseMagickOption(MagickKernelOptions,MagickFalse,token);
343 if ( type < 0 || type == UserDefinedKernel )
344 return((KernelInfo *)NULL);
345
346 while (((isspace((int) ((unsigned char) *p)) != 0) ||
347 (*p == ',') || (*p == ':' )) && (*p != '\0'))
348 p++;
349 SetGeometryInfo(&args);
350 flags = ParseGeometry(p, &args);
351
352 /* special handling of missing values in input string */
353 switch( type ) {
354 case RectangleKernel:
355 if ( (flags & WidthValue) == 0 ) /* if no width then */
356 args.rho = args.sigma; /* then width = height */
357 if ( args.rho < 1.0 ) /* if width too small */
358 args.rho = 3; /* then width = 3 */
359 if ( args.sigma < 1.0 ) /* if height too small */
360 args.sigma = args.rho; /* then height = width */
361 if ( (flags & XValue) == 0 ) /* center offset if not defined */
362 args.xi = (double)(((long)args.rho-1)/2);
363 if ( (flags & YValue) == 0 )
364 args.psi = (double)(((long)args.sigma-1)/2);
365 break;
366 case SquareKernel:
367 case DiamondKernel:
368 case DiskKernel:
369 case PlusKernel:
370 /* If no scale given (a 0 scale is valid! - set it to 1.0 */
371 if ( (flags & HeightValue) == 0 )
372 args.sigma = 1.0;
373 break;
374 case ChebyshevKernel:
375 case ManhattenKernel:
376 case EuclideanKernel:
377 if ( (flags & HeightValue) == 0 )
378 args.sigma = 100.0; /* default distance scaling */
379 else if ( (flags & AspectValue ) != 0 ) /* '!' flag */
380 args.sigma = QuantumRange/args.sigma; /* maximum pixel distance */
381 else if ( (flags & PercentValue ) != 0 ) /* '%' flag */
382 args.sigma *= QuantumRange/100.0; /* percentage of color range */
383 break;
384 default:
385 break;
386 }
387
388 return(AcquireKernelBuiltIn((KernelInfoType)type, &args));
389}
390
anthony602ab9b2010-01-05 08:06:50 +0000391
392/*
393%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
394% %
395% %
396% %
397% A c q u i r e K e r n e l B u i l t I n %
398% %
399% %
400% %
401%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
402%
403% AcquireKernelBuiltIn() returned one of the 'named' built-in types of
404% kernels used for special purposes such as gaussian blurring, skeleton
405% pruning, and edge distance determination.
406%
407% They take a KernelType, and a set of geometry style arguments, which were
408% typically decoded from a user supplied string, or from a more complex
409% Morphology Method that was requested.
410%
411% The format of the AcquireKernalBuiltIn method is:
412%
cristy2be15382010-01-21 02:38:03 +0000413% KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000414% const GeometryInfo args)
415%
416% A description of each parameter follows:
417%
418% o type: the pre-defined type of kernel wanted
419%
420% o args: arguments defining or modifying the kernel
421%
422% Convolution Kernels
423%
anthony4fd27e22010-02-07 08:17:18 +0000424% Gaussian "{radius},{sigma}"
anthony602ab9b2010-01-05 08:06:50 +0000425% Generate a two-dimentional gaussian kernel, as used by -gaussian
426% A sigma is required, (with the 'x'), due to historical reasons.
427%
428% NOTE: that the 'radius' is optional, but if provided can limit (clip)
429% the final size of the resulting kernel to a square 2*radius+1 in size.
430% The radius should be at least 2 times that of the sigma value, or
431% sever clipping and aliasing may result. If not given or set to 0 the
432% radius will be determined so as to produce the best minimal error
433% result, which is usally much larger than is normally needed.
434%
anthony4fd27e22010-02-07 08:17:18 +0000435% Blur "{radius},{sigma},{angle}"
anthony602ab9b2010-01-05 08:06:50 +0000436% As per Gaussian, but generates a 1 dimensional or linear gaussian
437% blur, at the angle given (current restricted to orthogonal angles).
438% If a 'radius' is given the kernel is clipped to a width of 2*radius+1.
439%
440% NOTE that two such blurs perpendicular to each other is equivelent to
441% -blur and the previous gaussian, but is often 10 or more times faster.
442%
anthony4fd27e22010-02-07 08:17:18 +0000443% Comet "{width},{sigma},{angle}"
anthony602ab9b2010-01-05 08:06:50 +0000444% Blur in one direction only, mush like how a bright object leaves
445% a comet like trail. The Kernel is actually half a gaussian curve,
446% Adding two such blurs in oppiste directions produces a Linear Blur.
447%
448% NOTE: that the first argument is the width of the kernel and not the
449% radius of the kernel.
450%
451% # Still to be implemented...
452% #
anthony4fd27e22010-02-07 08:17:18 +0000453% # Sharpen "{radius},{sigma}
454% # Negated Gaussian (center zeroed and re-normalized),
455% # with a 2 unit positive peak. -- Check On line documentation
456% #
457% # Laplacian "{radius},{sigma}"
anthony602ab9b2010-01-05 08:06:50 +0000458% # Laplacian (a mexican hat like) Function
459% #
460% # LOG "{radius},{sigma1},{sigma2}
461% # Laplacian of Gaussian
462% #
463% # DOG "{radius},{sigma1},{sigma2}
anthony4fd27e22010-02-07 08:17:18 +0000464% # Difference of two Gaussians
465% #
466% # Filter2D
467% # Filter1D
468% # Set kernel values using a resize filter, and given scale (sigma)
469% # Cylindrical or Linear. Is this posible with an image?
470% #
anthony602ab9b2010-01-05 08:06:50 +0000471%
472% Boolean Kernels
473%
474% Rectangle "{geometry}"
475% Simply generate a rectangle of 1's with the size given. You can also
476% specify the location of the 'control point', otherwise the closest
477% pixel to the center of the rectangle is selected.
478%
479% Properly centered and odd sized rectangles work the best.
480%
anthony4fd27e22010-02-07 08:17:18 +0000481% Diamond "[{radius}[,{scale}]]"
anthony602ab9b2010-01-05 08:06:50 +0000482% Generate a diamond shaped kernal with given radius to the points.
483% Kernel size will again be radius*2+1 square and defaults to radius 1,
484% generating a 3x3 kernel that is slightly larger than a square.
485%
anthony4fd27e22010-02-07 08:17:18 +0000486% Square "[{radius}[,{scale}]]"
anthony602ab9b2010-01-05 08:06:50 +0000487% Generate a square shaped kernel of size radius*2+1, and defaulting
488% to a 3x3 (radius 1).
489%
490% Note that using a larger radius for the "Square" or the "Diamond"
491% is also equivelent to iterating the basic morphological method
492% that many times. However However iterating with the smaller radius 1
493% default is actually faster than using a larger kernel radius.
494%
anthony4fd27e22010-02-07 08:17:18 +0000495% Disk "[{radius}[,{scale}]]
anthony602ab9b2010-01-05 08:06:50 +0000496% Generate a binary disk of the radius given, radius may be a float.
497% Kernel size will be ceil(radius)*2+1 square.
498% NOTE: Here are some disk shapes of specific interest
499% "disk:1" => "diamond" or "cross:1"
500% "disk:1.5" => "square"
501% "disk:2" => "diamond:2"
anthony83ba99b2010-01-24 08:48:15 +0000502% "disk:2.5" => a general disk shape of radius 2
anthony602ab9b2010-01-05 08:06:50 +0000503% "disk:2.9" => "square:2"
anthony83ba99b2010-01-24 08:48:15 +0000504% "disk:3.5" => default - octagonal/disk shape of radius 3
anthony602ab9b2010-01-05 08:06:50 +0000505% "disk:4.2" => roughly octagonal shape of radius 4
anthony83ba99b2010-01-24 08:48:15 +0000506% "disk:4.3" => a general disk shape of radius 4
anthony602ab9b2010-01-05 08:06:50 +0000507% After this all the kernel shape becomes more and more circular.
508%
509% Because a "disk" is more circular when using a larger radius, using a
510% larger radius is preferred over iterating the morphological operation.
511%
anthony4fd27e22010-02-07 08:17:18 +0000512% Plus "[{radius}[,{scale}]]"
anthony602ab9b2010-01-05 08:06:50 +0000513% Generate a kernel in the shape of a 'plus' sign. The length of each
514% arm is also the radius, which defaults to 2.
515%
516% This kernel is not a good general morphological kernel, but is used
517% more for highlighting and marking any single pixels in an image using,
518% a "Dilate" or "Erode" method as appropriate.
anthonyc94cdb02010-01-06 08:15:29 +0000519%
anthony602ab9b2010-01-05 08:06:50 +0000520% NOTE: "plus:1" is equivelent to a "Diamond" kernel.
521%
522% Note that unlike other kernels iterating a plus does not produce the
523% same result as using a larger radius for the cross.
524%
525% Distance Measuring Kernels
526%
anthonyc84dce52010-05-07 05:42:23 +0000527% Chebyshev "[{radius}][x{scale}[%!]]"
528% Manhatten "[{radius}][x{scale}[%!]]"
529% Euclidean "[{radius}][x{scale}[%!]]"
anthony602ab9b2010-01-05 08:06:50 +0000530%
531% Different types of distance measuring methods, which are used with the
532% a 'Distance' morphology method for generating a gradient based on
533% distance from an edge of a binary shape, though there is a technique
534% for handling a anti-aliased shape.
535%
anthonyc94cdb02010-01-06 08:15:29 +0000536% Chebyshev Distance (also known as Tchebychev Distance) is a value of
537% one to any neighbour, orthogonal or diagonal. One why of thinking of
538% it is the number of squares a 'King' or 'Queen' in chess needs to
539% traverse reach any other position on a chess board. It results in a
540% 'square' like distance function, but one where diagonals are closer
541% than expected.
anthony602ab9b2010-01-05 08:06:50 +0000542%
anthonyc94cdb02010-01-06 08:15:29 +0000543% Manhatten Distance (also known as Rectilinear Distance, or the Taxi
544% Cab metric), is the distance needed when you can only travel in
545% orthogonal (horizontal or vertical) only. It is the distance a 'Rook'
546% in chess would travel. It results in a diamond like distances, where
547% diagonals are further than expected.
anthony602ab9b2010-01-05 08:06:50 +0000548%
anthonyc94cdb02010-01-06 08:15:29 +0000549% Euclidean Distance is the 'direct' or 'as the crow flys distance.
550% However by default the kernel size only has a radius of 1, which
551% limits the distance to 'Knight' like moves, with only orthogonal and
552% diagonal measurements being correct. As such for the default kernel
553% you will get octagonal like distance function, which is reasonally
554% accurate.
555%
556% However if you use a larger radius such as "Euclidean:4" you will
557% get a much smoother distance gradient from the edge of the shape.
558% Of course a larger kernel is slower to use, and generally not needed.
559%
560% To allow the use of fractional distances that you get with diagonals
561% the actual distance is scaled by a fixed value which the user can
562% provide. This is not actually nessary for either ""Chebyshev" or
563% "Manhatten" distance kernels, but is done for all three distance
564% kernels. If no scale is provided it is set to a value of 100,
565% allowing for a maximum distance measurement of 655 pixels using a Q16
566% version of IM, from any edge. However for small images this can
567% result in quite a dark gradient.
568%
569% See the 'Distance' Morphological Method, for information of how it is
570% applied.
anthony602ab9b2010-01-05 08:06:50 +0000571%
anthony4fd27e22010-02-07 08:17:18 +0000572% # Hit-n-Miss Kernel-Lists -- Still to be implemented
573% #
574% # specifically for Pruning, Thinning, Thickening
575% #
anthony602ab9b2010-01-05 08:06:50 +0000576*/
577
cristy2be15382010-01-21 02:38:03 +0000578MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000579 const GeometryInfo *args)
580{
cristy2be15382010-01-21 02:38:03 +0000581 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000582 *kernel;
583
cristy150989e2010-02-01 14:59:39 +0000584 register long
anthony602ab9b2010-01-05 08:06:50 +0000585 i;
586
587 register long
588 u,
589 v;
590
591 double
592 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
593
cristy2be15382010-01-21 02:38:03 +0000594 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
595 if (kernel == (KernelInfo *) NULL)
anthony602ab9b2010-01-05 08:06:50 +0000596 return(kernel);
597 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
cristyc99304f2010-02-01 15:26:27 +0000598 kernel->minimum = kernel->maximum = 0.0;
599 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000600 kernel->type = type;
cristyd43a46b2010-01-21 02:13:41 +0000601 kernel->signature = MagickSignature;
anthony602ab9b2010-01-05 08:06:50 +0000602
603 switch(type) {
604 /* Convolution Kernels */
605 case GaussianKernel:
606 { double
607 sigma = fabs(args->sigma);
608
609 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
610
611 kernel->width = kernel->height =
612 GetOptimalKernelWidth2D(args->rho,sigma);
cristyc99304f2010-02-01 15:26:27 +0000613 kernel->x = kernel->y = (long) (kernel->width-1)/2;
614 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000615 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
616 kernel->height*sizeof(double));
617 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000618 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000619
620 sigma = 2.0*sigma*sigma; /* simplify the expression */
cristyc99304f2010-02-01 15:26:27 +0000621 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
622 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
623 kernel->positive_range += (
anthony602ab9b2010-01-05 08:06:50 +0000624 kernel->values[i] =
625 exp(-((double)(u*u+v*v))/sigma)
626 /* / (MagickPI*sigma) */ );
cristyc99304f2010-02-01 15:26:27 +0000627 kernel->minimum = 0;
628 kernel->maximum = kernel->values[
629 kernel->y*kernel->width+kernel->x ];
anthony602ab9b2010-01-05 08:06:50 +0000630
anthony999bb2c2010-02-18 12:38:01 +0000631 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
anthony602ab9b2010-01-05 08:06:50 +0000632
633 break;
634 }
635 case BlurKernel:
636 { double
637 sigma = fabs(args->sigma);
638
639 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
640
641 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
cristyc99304f2010-02-01 15:26:27 +0000642 kernel->x = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000643 kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +0000644 kernel->y = 0;
645 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000646 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
647 kernel->height*sizeof(double));
648 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000649 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000650
651#if 1
652#define KernelRank 3
653 /* Formula derived from GetBlurKernel() in "effect.c" (plus bug fix).
654 ** It generates a gaussian 3 times the width, and compresses it into
655 ** the expected range. This produces a closer normalization of the
656 ** resulting kernel, especially for very low sigma values.
657 ** As such while wierd it is prefered.
658 **
659 ** I am told this method originally came from Photoshop.
660 */
661 sigma *= KernelRank; /* simplify expanded curve */
cristy150989e2010-02-01 14:59:39 +0000662 v = (long) (kernel->width*KernelRank-1)/2; /* start/end points to fit range */
anthony602ab9b2010-01-05 08:06:50 +0000663 (void) ResetMagickMemory(kernel->values,0, (size_t)
664 kernel->width*sizeof(double));
665 for ( u=-v; u <= v; u++) {
666 kernel->values[(u+v)/KernelRank] +=
667 exp(-((double)(u*u))/(2.0*sigma*sigma))
668 /* / (MagickSQ2PI*sigma/KernelRank) */ ;
669 }
cristy150989e2010-02-01 14:59:39 +0000670 for (i=0; i < (long) kernel->width; i++)
cristyc99304f2010-02-01 15:26:27 +0000671 kernel->positive_range += kernel->values[i];
anthony602ab9b2010-01-05 08:06:50 +0000672#else
cristyc99304f2010-02-01 15:26:27 +0000673 for ( i=0, u=-kernel->x; i < kernel->width; i++, u++)
674 kernel->positive_range += (
anthony602ab9b2010-01-05 08:06:50 +0000675 kernel->values[i] =
676 exp(-((double)(u*u))/(2.0*sigma*sigma))
677 /* / (MagickSQ2PI*sigma) */ );
678#endif
cristyc99304f2010-02-01 15:26:27 +0000679 kernel->minimum = 0;
680 kernel->maximum = kernel->values[ kernel->x ];
anthonycc6c8362010-01-25 04:14:01 +0000681 /* Note that neither methods above generate a normalized kernel,
682 ** though it gets close. The kernel may be 'clipped' by a user defined
683 ** radius, producing a smaller (darker) kernel. Also for very small
684 ** sigma's (> 0.1) the central value becomes larger than one, and thus
685 ** producing a very bright kernel.
anthony602ab9b2010-01-05 08:06:50 +0000686 */
anthonycc6c8362010-01-25 04:14:01 +0000687
anthony602ab9b2010-01-05 08:06:50 +0000688 /* Normalize the 1D Gaussian Kernel
689 **
690 ** Because of this the divisor in the above kernel generator is
anthonyc94cdb02010-01-06 08:15:29 +0000691 ** not needed, so is not done above.
anthony602ab9b2010-01-05 08:06:50 +0000692 */
anthony999bb2c2010-02-18 12:38:01 +0000693 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
anthonycc6c8362010-01-25 04:14:01 +0000694
anthony602ab9b2010-01-05 08:06:50 +0000695 /* rotate the kernel by given angle */
anthony4fd27e22010-02-07 08:17:18 +0000696 RotateKernelInfo(kernel, args->xi);
anthony602ab9b2010-01-05 08:06:50 +0000697 break;
698 }
699 case CometKernel:
700 { double
701 sigma = fabs(args->sigma);
702
703 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
704
705 if ( args->rho < 1.0 )
706 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
707 else
708 kernel->width = (unsigned long)args->rho;
cristyc99304f2010-02-01 15:26:27 +0000709 kernel->x = kernel->y = 0;
anthony602ab9b2010-01-05 08:06:50 +0000710 kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +0000711 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000712 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
713 kernel->height*sizeof(double));
714 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000715 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000716
717 /* A comet blur is half a gaussian curve, so that the object is
718 ** blurred in one direction only. This may not be quite the right
719 ** curve so may change in the future. The function must be normalised.
720 */
721#if 1
722#define KernelRank 3
723 sigma *= KernelRank; /* simplify expanded curve */
cristy150989e2010-02-01 14:59:39 +0000724 v = (long) kernel->width*KernelRank; /* start/end points to fit range */
anthony602ab9b2010-01-05 08:06:50 +0000725 (void) ResetMagickMemory(kernel->values,0, (size_t)
726 kernel->width*sizeof(double));
727 for ( u=0; u < v; u++) {
728 kernel->values[u/KernelRank] +=
729 exp(-((double)(u*u))/(2.0*sigma*sigma))
730 /* / (MagickSQ2PI*sigma/KernelRank) */ ;
731 }
cristy150989e2010-02-01 14:59:39 +0000732 for (i=0; i < (long) kernel->width; i++)
cristyc99304f2010-02-01 15:26:27 +0000733 kernel->positive_range += kernel->values[i];
anthony602ab9b2010-01-05 08:06:50 +0000734#else
cristy150989e2010-02-01 14:59:39 +0000735 for ( i=0; i < (long) kernel->width; i++)
cristyc99304f2010-02-01 15:26:27 +0000736 kernel->positive_range += (
anthony602ab9b2010-01-05 08:06:50 +0000737 kernel->values[i] =
738 exp(-((double)(i*i))/(2.0*sigma*sigma))
739 /* / (MagickSQ2PI*sigma) */ );
740#endif
cristyc99304f2010-02-01 15:26:27 +0000741 kernel->minimum = 0;
742 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000743
anthony999bb2c2010-02-18 12:38:01 +0000744 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
745 RotateKernelInfo(kernel, args->xi); /* Rotate by angle */
anthony602ab9b2010-01-05 08:06:50 +0000746 break;
747 }
748 /* Boolean Kernels */
749 case RectangleKernel:
750 case SquareKernel:
751 {
anthony4fd27e22010-02-07 08:17:18 +0000752 double scale;
anthony602ab9b2010-01-05 08:06:50 +0000753 if ( type == SquareKernel )
754 {
755 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000756 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000757 else
cristy150989e2010-02-01 14:59:39 +0000758 kernel->width = kernel->height = (unsigned long) (2*args->rho+1);
cristyc99304f2010-02-01 15:26:27 +0000759 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony4fd27e22010-02-07 08:17:18 +0000760 scale = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +0000761 }
762 else {
cristy2be15382010-01-21 02:38:03 +0000763 /* NOTE: user defaults set in "AcquireKernelInfo()" */
anthony602ab9b2010-01-05 08:06:50 +0000764 if ( args->rho < 1.0 || args->sigma < 1.0 )
anthony83ba99b2010-01-24 08:48:15 +0000765 return(DestroyKernelInfo(kernel)); /* invalid args given */
anthony602ab9b2010-01-05 08:06:50 +0000766 kernel->width = (unsigned long)args->rho;
767 kernel->height = (unsigned long)args->sigma;
768 if ( args->xi < 0.0 || args->xi > (double)kernel->width ||
769 args->psi < 0.0 || args->psi > (double)kernel->height )
anthony83ba99b2010-01-24 08:48:15 +0000770 return(DestroyKernelInfo(kernel)); /* invalid args given */
cristyc99304f2010-02-01 15:26:27 +0000771 kernel->x = (long) args->xi;
772 kernel->y = (long) args->psi;
anthony4fd27e22010-02-07 08:17:18 +0000773 scale = 1.0;
anthony602ab9b2010-01-05 08:06:50 +0000774 }
775 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
776 kernel->height*sizeof(double));
777 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000778 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000779
anthonycc6c8362010-01-25 04:14:01 +0000780 /* set all kernel values to 1.0 */
cristy150989e2010-02-01 14:59:39 +0000781 u=(long) kernel->width*kernel->height;
782 for ( i=0; i < u; i++)
anthony4fd27e22010-02-07 08:17:18 +0000783 kernel->values[i] = scale;
784 kernel->minimum = kernel->maximum = scale; /* a flat shape */
785 kernel->positive_range = scale*u;
anthonycc6c8362010-01-25 04:14:01 +0000786 break;
anthony602ab9b2010-01-05 08:06:50 +0000787 }
788 case DiamondKernel:
789 {
790 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000791 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000792 else
793 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000794 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000795
796 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
797 kernel->height*sizeof(double));
798 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000799 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000800
anthony4fd27e22010-02-07 08:17:18 +0000801 /* set all kernel values within diamond area to scale given */
cristyc99304f2010-02-01 15:26:27 +0000802 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
803 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
804 if ((labs(u)+labs(v)) <= (long)kernel->x)
anthony4fd27e22010-02-07 08:17:18 +0000805 kernel->positive_range += kernel->values[i] = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +0000806 else
807 kernel->values[i] = nan;
anthony4fd27e22010-02-07 08:17:18 +0000808 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
anthony602ab9b2010-01-05 08:06:50 +0000809 break;
810 }
811 case DiskKernel:
812 {
813 long
814 limit;
815
816 limit = (long)(args->rho*args->rho);
anthony83ba99b2010-01-24 08:48:15 +0000817 if (args->rho < 0.1) /* default radius approx 3.5 */
818 kernel->width = kernel->height = 7L, limit = 10L;
anthony602ab9b2010-01-05 08:06:50 +0000819 else
820 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000821 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000822
823 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
824 kernel->height*sizeof(double));
825 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000826 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000827
anthonycc6c8362010-01-25 04:14:01 +0000828 /* set all kernel values within disk area to 1.0 */
cristyc99304f2010-02-01 15:26:27 +0000829 for ( i=0, v= -kernel->y; v <= (long)kernel->y; v++)
830 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony602ab9b2010-01-05 08:06:50 +0000831 if ((u*u+v*v) <= limit)
anthony4fd27e22010-02-07 08:17:18 +0000832 kernel->positive_range += kernel->values[i] = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +0000833 else
834 kernel->values[i] = nan;
anthony4fd27e22010-02-07 08:17:18 +0000835 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
anthony602ab9b2010-01-05 08:06:50 +0000836 break;
837 }
838 case PlusKernel:
839 {
840 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000841 kernel->width = kernel->height = 5; /* default radius 2 */
anthony602ab9b2010-01-05 08:06:50 +0000842 else
843 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000844 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000845
846 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
847 kernel->height*sizeof(double));
848 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000849 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000850
anthonycc6c8362010-01-25 04:14:01 +0000851 /* set all kernel values along axises to 1.0 */
cristyc99304f2010-02-01 15:26:27 +0000852 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
853 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony4fd27e22010-02-07 08:17:18 +0000854 kernel->values[i] = (u == 0 || v == 0) ? args->sigma : nan;
855 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
856 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
anthony602ab9b2010-01-05 08:06:50 +0000857 break;
858 }
859 /* Distance Measuring Kernels */
860 case ChebyshevKernel:
861 {
anthony602ab9b2010-01-05 08:06:50 +0000862 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000863 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000864 else
865 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000866 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000867
868 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
869 kernel->height*sizeof(double));
870 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000871 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000872
cristyc99304f2010-02-01 15:26:27 +0000873 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
874 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
875 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +0000876 args->sigma*((labs(u)>labs(v)) ? labs(u) : labs(v)) );
cristyc99304f2010-02-01 15:26:27 +0000877 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000878 break;
879 }
880 case ManhattenKernel:
881 {
anthony602ab9b2010-01-05 08:06:50 +0000882 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000883 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000884 else
885 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000886 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000887
888 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
889 kernel->height*sizeof(double));
890 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000891 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000892
cristyc99304f2010-02-01 15:26:27 +0000893 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
894 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
895 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +0000896 args->sigma*(labs(u)+labs(v)) );
cristyc99304f2010-02-01 15:26:27 +0000897 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000898 break;
899 }
900 case EuclideanKernel:
901 {
anthony602ab9b2010-01-05 08:06:50 +0000902 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000903 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000904 else
905 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000906 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000907
908 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
909 kernel->height*sizeof(double));
910 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000911 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000912
cristyc99304f2010-02-01 15:26:27 +0000913 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
914 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
915 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +0000916 args->sigma*sqrt((double)(u*u+v*v)) );
cristyc99304f2010-02-01 15:26:27 +0000917 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000918 break;
919 }
920 /* Undefined Kernels */
921 case LaplacianKernel:
922 case LOGKernel:
923 case DOGKernel:
cristy150989e2010-02-01 14:59:39 +0000924 perror("Kernel Type has not been defined yet");
anthony602ab9b2010-01-05 08:06:50 +0000925 /* FALL THRU */
926 default:
927 /* Generate a No-Op minimal kernel - 1x1 pixel */
928 kernel->values=(double *)AcquireQuantumMemory((size_t)1,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 kernel->width = kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +0000932 kernel->x = kernel->x = 0;
anthony602ab9b2010-01-05 08:06:50 +0000933 kernel->type = UndefinedKernel;
cristyc99304f2010-02-01 15:26:27 +0000934 kernel->maximum =
935 kernel->positive_range =
anthonyc94cdb02010-01-06 08:15:29 +0000936 kernel->values[0] = 1.0; /* a flat single-point no-op kernel! */
anthony602ab9b2010-01-05 08:06:50 +0000937 break;
938 }
939
940 return(kernel);
941}
anthonyc94cdb02010-01-06 08:15:29 +0000942
anthony602ab9b2010-01-05 08:06:50 +0000943/*
944%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
945% %
946% %
947% %
cristy6771f1e2010-03-05 19:43:39 +0000948% C l o n e K e r n e l I n f o %
anthony4fd27e22010-02-07 08:17:18 +0000949% %
950% %
951% %
952%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
953%
954% CloneKernelInfo() creates a new clone of the given Kernel so that its can
955% be modified without effecting the original. The cloned kernel should be
956% destroyed using DestoryKernelInfo() when no longer needed.
957%
cristye6365592010-04-02 17:31:23 +0000958% The format of the CloneKernelInfo method is:
anthony4fd27e22010-02-07 08:17:18 +0000959%
anthony930be612010-02-08 04:26:15 +0000960% KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +0000961%
962% A description of each parameter follows:
963%
964% o kernel: the Morphology/Convolution kernel to be cloned
965%
966*/
cristyef656912010-03-05 19:54:59 +0000967MagickExport KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +0000968{
969 register long
970 i;
971
cristy19eb6412010-04-23 14:42:29 +0000972 KernelInfo
973 *kernel_info;
anthony4fd27e22010-02-07 08:17:18 +0000974
975 assert(kernel != (KernelInfo *) NULL);
cristy19eb6412010-04-23 14:42:29 +0000976 kernel_info=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
977 if (kernel_info == (KernelInfo *) NULL)
978 return(kernel_info);
979 *kernel_info=(*kernel); /* copy values in structure */
980 kernel_info->values=(double *) AcquireQuantumMemory(kernel->width,
981 kernel->height*sizeof(double));
982 if (kernel_info->values == (double *) NULL)
983 return(DestroyKernelInfo(kernel_info));
anthony4fd27e22010-02-07 08:17:18 +0000984 for (i=0; i < (long) (kernel->width*kernel->height); i++)
cristy19eb6412010-04-23 14:42:29 +0000985 kernel_info->values[i]=kernel->values[i];
986 return(kernel_info);
anthony4fd27e22010-02-07 08:17:18 +0000987}
988
989/*
990%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
991% %
992% %
993% %
anthony83ba99b2010-01-24 08:48:15 +0000994% D e s t r o y K e r n e l I n f o %
anthony602ab9b2010-01-05 08:06:50 +0000995% %
996% %
997% %
998%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
999%
anthony83ba99b2010-01-24 08:48:15 +00001000% DestroyKernelInfo() frees the memory used by a Convolution/Morphology
1001% kernel.
anthony602ab9b2010-01-05 08:06:50 +00001002%
anthony83ba99b2010-01-24 08:48:15 +00001003% The format of the DestroyKernelInfo method is:
anthony602ab9b2010-01-05 08:06:50 +00001004%
anthony83ba99b2010-01-24 08:48:15 +00001005% KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +00001006%
1007% A description of each parameter follows:
1008%
1009% o kernel: the Morphology/Convolution kernel to be destroyed
1010%
1011*/
1012
anthony83ba99b2010-01-24 08:48:15 +00001013MagickExport KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +00001014{
cristy2be15382010-01-21 02:38:03 +00001015 assert(kernel != (KernelInfo *) NULL);
anthony4fd27e22010-02-07 08:17:18 +00001016
1017 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1018 kernel->height*sizeof(double));
anthony602ab9b2010-01-05 08:06:50 +00001019 kernel->values=(double *)RelinquishMagickMemory(kernel->values);
cristy2be15382010-01-21 02:38:03 +00001020 kernel=(KernelInfo *) RelinquishMagickMemory(kernel);
anthony602ab9b2010-01-05 08:06:50 +00001021 return(kernel);
1022}
anthonyc94cdb02010-01-06 08:15:29 +00001023
1024/*
1025%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1026% %
1027% %
1028% %
anthony29188a82010-01-22 10:12:34 +00001029% 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 +00001030% %
1031% %
1032% %
1033%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1034%
anthony29188a82010-01-22 10:12:34 +00001035% MorphologyImageChannel() applies a user supplied kernel to the image
1036% according to the given mophology method.
anthony602ab9b2010-01-05 08:06:50 +00001037%
1038% The given kernel is assumed to have been pre-scaled appropriatally, usally
1039% by the kernel generator.
1040%
1041% The format of the MorphologyImage method is:
1042%
cristyef656912010-03-05 19:54:59 +00001043% Image *MorphologyImage(const Image *image,MorphologyMethod method,
1044% const long iterations,KernelInfo *kernel,ExceptionInfo *exception)
anthony29188a82010-01-22 10:12:34 +00001045% Image *MorphologyImageChannel(const Image *image, const ChannelType
cristyef656912010-03-05 19:54:59 +00001046% channel,MorphologyMethod method,const long iterations,
1047% KernelInfo *kernel,ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001048%
1049% A description of each parameter follows:
1050%
1051% o image: the image.
1052%
1053% o method: the morphology method to be applied.
1054%
1055% o iterations: apply the operation this many times (or no change).
1056% A value of -1 means loop until no change found.
1057% How this is applied may depend on the morphology method.
1058% Typically this is a value of 1.
1059%
1060% o channel: the channel type.
1061%
1062% o kernel: An array of double representing the morphology kernel.
anthony29188a82010-01-22 10:12:34 +00001063% Warning: kernel may be normalized for the Convolve method.
anthony602ab9b2010-01-05 08:06:50 +00001064%
1065% o exception: return any errors or warnings in this structure.
1066%
1067%
1068% TODO: bias and auto-scale handling of the kernel for convolution
1069% The given kernel is assumed to have been pre-scaled appropriatally, usally
1070% by the kernel generator.
1071%
1072*/
1073
anthony930be612010-02-08 04:26:15 +00001074
anthony602ab9b2010-01-05 08:06:50 +00001075/* Internal function
anthony930be612010-02-08 04:26:15 +00001076 * Apply the Low-Level Morphology Method using the given Kernel
1077 * Returning the number of pixels that changed.
1078 * Two pre-created images must be provided, no image is created.
anthony602ab9b2010-01-05 08:06:50 +00001079 */
1080static unsigned long MorphologyApply(const Image *image, Image
1081 *result_image, const MorphologyMethod method, const ChannelType channel,
cristy2be15382010-01-21 02:38:03 +00001082 const KernelInfo *kernel, ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001083{
cristy2be15382010-01-21 02:38:03 +00001084#define MorphologyTag "Morphology/Image"
anthony602ab9b2010-01-05 08:06:50 +00001085
1086 long
cristy150989e2010-02-01 14:59:39 +00001087 progress,
anthony29188a82010-01-22 10:12:34 +00001088 y, offx, offy,
anthony602ab9b2010-01-05 08:06:50 +00001089 changed;
1090
1091 MagickBooleanType
1092 status;
1093
1094 MagickPixelPacket
1095 bias;
1096
1097 CacheView
1098 *p_view,
1099 *q_view;
1100
anthony4fd27e22010-02-07 08:17:18 +00001101 /* Only the most basic morphology is actually performed by this routine */
anthony4fd27e22010-02-07 08:17:18 +00001102
anthony602ab9b2010-01-05 08:06:50 +00001103 /*
anthony4fd27e22010-02-07 08:17:18 +00001104 Apply Basic Morphology to Image.
anthony602ab9b2010-01-05 08:06:50 +00001105 */
1106 status=MagickTrue;
1107 changed=0;
1108 progress=0;
1109
1110 GetMagickPixelPacket(image,&bias);
1111 SetMagickPixelPacketBias(image,&bias);
anthonycc6c8362010-01-25 04:14:01 +00001112 /* Future: handle auto-bias from user, based on kernel input */
anthony602ab9b2010-01-05 08:06:50 +00001113
1114 p_view=AcquireCacheView(image);
1115 q_view=AcquireCacheView(result_image);
anthony29188a82010-01-22 10:12:34 +00001116
anthonycc6c8362010-01-25 04:14:01 +00001117 /* Some methods (including convolve) needs use a reflected kernel.
1118 * Adjust 'origin' offsets for this reflected kernel.
anthony29188a82010-01-22 10:12:34 +00001119 */
cristyc99304f2010-02-01 15:26:27 +00001120 offx = kernel->x;
1121 offy = kernel->y;
anthony29188a82010-01-22 10:12:34 +00001122 switch(method) {
1123 case ErodeMorphology:
1124 case ErodeIntensityMorphology:
anthony999bb2c2010-02-18 12:38:01 +00001125 /* kernel is user as is, without reflection */
anthony29188a82010-01-22 10:12:34 +00001126 break;
anthony930be612010-02-08 04:26:15 +00001127 case ConvolveMorphology:
1128 case DilateMorphology:
1129 case DilateIntensityMorphology:
1130 case DistanceMorphology:
anthony999bb2c2010-02-18 12:38:01 +00001131 /* kernel needs to used with reflection */
cristy150989e2010-02-01 14:59:39 +00001132 offx = (long) kernel->width-offx-1;
1133 offy = (long) kernel->height-offy-1;
anthony29188a82010-01-22 10:12:34 +00001134 break;
anthony930be612010-02-08 04:26:15 +00001135 default:
1136 perror("Not a low level Morpholgy Method");
1137 break;
anthony29188a82010-01-22 10:12:34 +00001138 }
1139
anthony602ab9b2010-01-05 08:06:50 +00001140#if defined(MAGICKCORE_OPENMP_SUPPORT)
1141 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1142#endif
cristy150989e2010-02-01 14:59:39 +00001143 for (y=0; y < (long) image->rows; y++)
anthony602ab9b2010-01-05 08:06:50 +00001144 {
1145 MagickBooleanType
1146 sync;
1147
1148 register const PixelPacket
1149 *restrict p;
1150
1151 register const IndexPacket
1152 *restrict p_indexes;
1153
1154 register PixelPacket
1155 *restrict q;
1156
1157 register IndexPacket
1158 *restrict q_indexes;
1159
cristy150989e2010-02-01 14:59:39 +00001160 register long
anthony602ab9b2010-01-05 08:06:50 +00001161 x;
1162
anthony29188a82010-01-22 10:12:34 +00001163 unsigned long
anthony602ab9b2010-01-05 08:06:50 +00001164 r;
1165
1166 if (status == MagickFalse)
1167 continue;
anthony29188a82010-01-22 10:12:34 +00001168 p=GetCacheViewVirtualPixels(p_view, -offx, y-offy,
1169 image->columns+kernel->width, kernel->height, exception);
anthony602ab9b2010-01-05 08:06:50 +00001170 q=GetCacheViewAuthenticPixels(q_view,0,y,result_image->columns,1,
1171 exception);
1172 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1173 {
1174 status=MagickFalse;
1175 continue;
1176 }
1177 p_indexes=GetCacheViewVirtualIndexQueue(p_view);
1178 q_indexes=GetCacheViewAuthenticIndexQueue(q_view);
anthony29188a82010-01-22 10:12:34 +00001179 r = (image->columns+kernel->width)*offy+offx; /* constant */
1180
cristy150989e2010-02-01 14:59:39 +00001181 for (x=0; x < (long) image->columns; x++)
anthony602ab9b2010-01-05 08:06:50 +00001182 {
cristy150989e2010-02-01 14:59:39 +00001183 long
anthony602ab9b2010-01-05 08:06:50 +00001184 v;
1185
cristy150989e2010-02-01 14:59:39 +00001186 register long
anthony602ab9b2010-01-05 08:06:50 +00001187 u;
1188
1189 register const double
1190 *restrict k;
1191
1192 register const PixelPacket
1193 *restrict k_pixels;
1194
1195 register const IndexPacket
1196 *restrict k_indexes;
1197
1198 MagickPixelPacket
1199 result;
1200
anthony29188a82010-01-22 10:12:34 +00001201 /* Copy input to ouput image for unused channels
anthony83ba99b2010-01-24 08:48:15 +00001202 * This removes need for 'cloning' a new image every iteration
anthony29188a82010-01-22 10:12:34 +00001203 */
anthony602ab9b2010-01-05 08:06:50 +00001204 *q = p[r];
1205 if (image->colorspace == CMYKColorspace)
1206 q_indexes[x] = p_indexes[r];
1207
cristy5ee247a2010-02-12 15:42:34 +00001208 result.green=(MagickRealType) 0;
1209 result.blue=(MagickRealType) 0;
1210 result.opacity=(MagickRealType) 0;
1211 result.index=(MagickRealType) 0;
anthony602ab9b2010-01-05 08:06:50 +00001212 switch (method) {
1213 case ConvolveMorphology:
anthony930be612010-02-08 04:26:15 +00001214 /* Set the user defined bias of the weighted average output
1215 **
1216 ** FUTURE: provide some way for internal functions to disable
1217 ** user defined bias and scaling effects.
1218 */
anthony602ab9b2010-01-05 08:06:50 +00001219 result=bias;
anthony930be612010-02-08 04:26:15 +00001220 break;
anthony83ba99b2010-01-24 08:48:15 +00001221 case DilateMorphology:
anthony29188a82010-01-22 10:12:34 +00001222 result.red =
1223 result.green =
1224 result.blue =
1225 result.opacity =
1226 result.index = -MagickHuge;
1227 break;
1228 case ErodeMorphology:
1229 result.red =
1230 result.green =
1231 result.blue =
1232 result.opacity =
1233 result.index = +MagickHuge;
1234 break;
anthony4fd27e22010-02-07 08:17:18 +00001235 case DilateIntensityMorphology:
1236 case ErodeIntensityMorphology:
1237 result.red = 0.0; /* flag indicating first match found */
1238 break;
anthony602ab9b2010-01-05 08:06:50 +00001239 default:
anthony29188a82010-01-22 10:12:34 +00001240 /* Otherwise just start with the original pixel value */
cristy150989e2010-02-01 14:59:39 +00001241 result.red = (MagickRealType) p[r].red;
1242 result.green = (MagickRealType) p[r].green;
1243 result.blue = (MagickRealType) p[r].blue;
1244 result.opacity = QuantumRange - (MagickRealType) p[r].opacity;
anthony602ab9b2010-01-05 08:06:50 +00001245 if ( image->colorspace == CMYKColorspace)
cristy150989e2010-02-01 14:59:39 +00001246 result.index = (MagickRealType) p_indexes[r];
anthony602ab9b2010-01-05 08:06:50 +00001247 break;
1248 }
1249
1250 switch ( method ) {
1251 case ConvolveMorphology:
anthony930be612010-02-08 04:26:15 +00001252 /* Weighted Average of pixels using reflected kernel
1253 **
1254 ** NOTE for correct working of this operation for asymetrical
1255 ** kernels, the kernel needs to be applied in its reflected form.
1256 ** That is its values needs to be reversed.
1257 **
1258 ** Correlation is actually the same as this but without reflecting
1259 ** the kernel, and thus 'lower-level' that Convolution. However
1260 ** as Convolution is the more common method used, and it does not
1261 ** really cost us much in terms of processing to use a reflected
1262 ** kernel it is Convolution that is implemented.
1263 **
1264 ** Correlation will have its kernel reflected before calling
1265 ** this function to do a Convolve.
1266 **
1267 ** For more details of Correlation vs Convolution see
1268 ** http://www.cs.umd.edu/~djacobs/CMSC426/Convolution.pdf
1269 */
anthony602ab9b2010-01-05 08:06:50 +00001270 if (((channel & OpacityChannel) == 0) ||
1271 (image->matte == MagickFalse))
1272 {
anthony930be612010-02-08 04:26:15 +00001273 /* Convolution without transparency effects */
anthony29188a82010-01-22 10:12:34 +00001274 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001275 k_pixels = p;
1276 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001277 for (v=0; v < (long) kernel->height; v++) {
1278 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001279 if ( IsNan(*k) ) continue;
1280 result.red += (*k)*k_pixels[u].red;
1281 result.green += (*k)*k_pixels[u].green;
1282 result.blue += (*k)*k_pixels[u].blue;
anthony83ba99b2010-01-24 08:48:15 +00001283 /* result.opacity += not involved here */
anthony602ab9b2010-01-05 08:06:50 +00001284 if ( image->colorspace == CMYKColorspace)
1285 result.index += (*k)*k_indexes[u];
1286 }
1287 k_pixels += image->columns+kernel->width;
1288 k_indexes += image->columns+kernel->width;
1289 }
anthony602ab9b2010-01-05 08:06:50 +00001290 }
1291 else
1292 { /* Kernel & Alpha weighted Convolution */
1293 MagickRealType
1294 alpha, /* alpha value * kernel weighting */
1295 gamma; /* weighting divisor */
1296
1297 gamma=0.0;
anthony29188a82010-01-22 10:12:34 +00001298 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001299 k_pixels = p;
1300 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001301 for (v=0; v < (long) kernel->height; v++) {
1302 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001303 if ( IsNan(*k) ) continue;
1304 alpha=(*k)*(QuantumScale*(QuantumRange-
1305 k_pixels[u].opacity));
1306 gamma += alpha;
1307 result.red += alpha*k_pixels[u].red;
1308 result.green += alpha*k_pixels[u].green;
1309 result.blue += alpha*k_pixels[u].blue;
anthony83ba99b2010-01-24 08:48:15 +00001310 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00001311 if ( image->colorspace == CMYKColorspace)
1312 result.index += alpha*k_indexes[u];
1313 }
1314 k_pixels += image->columns+kernel->width;
1315 k_indexes += image->columns+kernel->width;
1316 }
1317 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
anthony83ba99b2010-01-24 08:48:15 +00001318 result.red *= gamma;
1319 result.green *= gamma;
1320 result.blue *= gamma;
1321 result.opacity *= gamma;
1322 result.index *= gamma;
anthony602ab9b2010-01-05 08:06:50 +00001323 }
1324 break;
1325
anthony4fd27e22010-02-07 08:17:18 +00001326 case ErodeMorphology:
anthony930be612010-02-08 04:26:15 +00001327 /* Minimize Value within kernel neighbourhood
1328 **
1329 ** NOTE that the kernel is not reflected for this operation!
1330 **
1331 ** NOTE: in normal Greyscale Morphology, the kernel value should
1332 ** be added to the real value, this is currently not done, due to
1333 ** the nature of the boolean kernels being used.
1334 */
anthony4fd27e22010-02-07 08:17:18 +00001335 k = kernel->values;
1336 k_pixels = p;
1337 k_indexes = p_indexes;
1338 for (v=0; v < (long) kernel->height; v++) {
1339 for (u=0; u < (long) kernel->width; u++, k++) {
1340 if ( IsNan(*k) || (*k) < 0.5 ) continue;
1341 Minimize(result.red, (double) k_pixels[u].red);
1342 Minimize(result.green, (double) k_pixels[u].green);
1343 Minimize(result.blue, (double) k_pixels[u].blue);
anthonyd37a5cb2010-05-07 06:37:03 +00001344 Minimize(result.opacity,
1345 QuantumRange-(double) k_pixels[u].opacity);
anthony4fd27e22010-02-07 08:17:18 +00001346 if ( image->colorspace == CMYKColorspace)
1347 Minimize(result.index, (double) k_indexes[u]);
1348 }
1349 k_pixels += image->columns+kernel->width;
1350 k_indexes += image->columns+kernel->width;
1351 }
1352 break;
1353
anthony83ba99b2010-01-24 08:48:15 +00001354 case DilateMorphology:
anthony930be612010-02-08 04:26:15 +00001355 /* Maximize Value within kernel neighbourhood
1356 **
1357 ** NOTE for correct working of this operation for asymetrical
1358 ** kernels, the kernel needs to be applied in its reflected form.
1359 ** That is its values needs to be reversed.
1360 **
1361 ** NOTE: in normal Greyscale Morphology, the kernel value should
1362 ** be added to the real value, this is currently not done, due to
1363 ** the nature of the boolean kernels being used.
1364 **
1365 */
anthony29188a82010-01-22 10:12:34 +00001366 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001367 k_pixels = p;
1368 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001369 for (v=0; v < (long) kernel->height; v++) {
1370 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001371 if ( IsNan(*k) || (*k) < 0.5 ) continue;
cristy150989e2010-02-01 14:59:39 +00001372 Maximize(result.red, (double) k_pixels[u].red);
1373 Maximize(result.green, (double) k_pixels[u].green);
1374 Maximize(result.blue, (double) k_pixels[u].blue);
anthonyd37a5cb2010-05-07 06:37:03 +00001375 Maximize(result.opacity,
1376 QuantumRange-(double) k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00001377 if ( image->colorspace == CMYKColorspace)
cristy150989e2010-02-01 14:59:39 +00001378 Maximize(result.index, (double) k_indexes[u]);
anthony602ab9b2010-01-05 08:06:50 +00001379 }
1380 k_pixels += image->columns+kernel->width;
1381 k_indexes += image->columns+kernel->width;
1382 }
anthony602ab9b2010-01-05 08:06:50 +00001383 break;
1384
anthony4fd27e22010-02-07 08:17:18 +00001385 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001386 /* Select Pixel with Minimum Intensity within kernel neighbourhood
1387 **
1388 ** WARNING: the intensity test fails for CMYK and does not
1389 ** take into account the moderating effect of teh alpha channel
1390 ** on the intensity.
1391 **
1392 ** NOTE that the kernel is not reflected for this operation!
1393 */
anthony602ab9b2010-01-05 08:06:50 +00001394 k = kernel->values;
1395 k_pixels = p;
1396 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001397 for (v=0; v < (long) kernel->height; v++) {
1398 for (u=0; u < (long) kernel->width; u++, k++) {
anthony602ab9b2010-01-05 08:06:50 +00001399 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony4fd27e22010-02-07 08:17:18 +00001400 if ( result.red == 0.0 ||
1401 PixelIntensity(&(k_pixels[u])) < PixelIntensity(q) ) {
1402 /* copy the whole pixel - no channel selection */
1403 *q = k_pixels[u];
1404 if ( result.red > 0.0 ) changed++;
1405 result.red = 1.0;
1406 }
anthony602ab9b2010-01-05 08:06:50 +00001407 }
1408 k_pixels += image->columns+kernel->width;
1409 k_indexes += image->columns+kernel->width;
1410 }
anthony602ab9b2010-01-05 08:06:50 +00001411 break;
1412
anthony83ba99b2010-01-24 08:48:15 +00001413 case DilateIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001414 /* Select Pixel with Maximum Intensity within kernel neighbourhood
1415 **
1416 ** WARNING: the intensity test fails for CMYK and does not
1417 ** take into account the moderating effect of teh alpha channel
1418 ** on the intensity.
1419 **
1420 ** NOTE for correct working of this operation for asymetrical
1421 ** kernels, the kernel needs to be applied in its reflected form.
1422 ** That is its values needs to be reversed.
1423 */
anthony29188a82010-01-22 10:12:34 +00001424 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001425 k_pixels = p;
1426 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001427 for (v=0; v < (long) kernel->height; v++) {
1428 for (u=0; u < (long) kernel->width; u++, k--) {
anthony29188a82010-01-22 10:12:34 +00001429 if ( IsNan(*k) || (*k) < 0.5 ) continue; /* boolean kernel */
1430 if ( result.red == 0.0 ||
1431 PixelIntensity(&(k_pixels[u])) > PixelIntensity(q) ) {
1432 /* copy the whole pixel - no channel selection */
1433 *q = k_pixels[u];
1434 if ( result.red > 0.0 ) changed++;
1435 result.red = 1.0;
1436 }
anthony602ab9b2010-01-05 08:06:50 +00001437 }
1438 k_pixels += image->columns+kernel->width;
1439 k_indexes += image->columns+kernel->width;
1440 }
anthony602ab9b2010-01-05 08:06:50 +00001441 break;
1442
anthony602ab9b2010-01-05 08:06:50 +00001443 case DistanceMorphology:
anthony930be612010-02-08 04:26:15 +00001444 /* Add kernel Value and select the minimum value found.
1445 ** The result is a iterative distance from edge of image shape.
1446 **
1447 ** All Distance Kernels are symetrical, but that may not always
1448 ** be the case. For example how about a distance from left edges?
1449 ** To work correctly with asymetrical kernels the reflected kernel
1450 ** needs to be applied.
1451 */
anthony602ab9b2010-01-05 08:06:50 +00001452#if 0
anthony930be612010-02-08 04:26:15 +00001453 /* No need to do distance morphology if original value is zero
1454 ** Unfortunatally I have not been able to get this right
1455 ** when channel selection also becomes involved. -- Arrgghhh
1456 */
1457 if ( ((channel & RedChannel) == 0 && p[r].red == 0)
1458 || ((channel & GreenChannel) == 0 && p[r].green == 0)
1459 || ((channel & BlueChannel) == 0 && p[r].blue == 0)
1460 || ((channel & OpacityChannel) == 0 && p[r].opacity == 0)
1461 || (( (channel & IndexChannel) == 0
1462 || image->colorspace != CMYKColorspace
1463 ) && p_indexes[x] ==0 )
1464 )
1465 break;
anthony602ab9b2010-01-05 08:06:50 +00001466#endif
anthony29188a82010-01-22 10:12:34 +00001467 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001468 k_pixels = p;
1469 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001470 for (v=0; v < (long) kernel->height; v++) {
1471 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001472 if ( IsNan(*k) ) continue;
1473 Minimize(result.red, (*k)+k_pixels[u].red);
1474 Minimize(result.green, (*k)+k_pixels[u].green);
1475 Minimize(result.blue, (*k)+k_pixels[u].blue);
1476 Minimize(result.opacity, (*k)+QuantumRange-k_pixels[u].opacity);
1477 if ( image->colorspace == CMYKColorspace)
1478 Minimize(result.index, (*k)+k_indexes[u]);
1479 }
1480 k_pixels += image->columns+kernel->width;
1481 k_indexes += image->columns+kernel->width;
1482 }
anthony602ab9b2010-01-05 08:06:50 +00001483 break;
1484
1485 case UndefinedMorphology:
1486 default:
1487 break; /* Do nothing */
anthony83ba99b2010-01-24 08:48:15 +00001488 }
1489 switch ( method ) {
1490 case UndefinedMorphology:
1491 case DilateIntensityMorphology:
1492 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001493 break; /* full pixel was directly assigned - not a channel method */
anthony83ba99b2010-01-24 08:48:15 +00001494 default:
1495 /* Assign the results */
1496 if ((channel & RedChannel) != 0)
1497 q->red = ClampToQuantum(result.red);
1498 if ((channel & GreenChannel) != 0)
1499 q->green = ClampToQuantum(result.green);
1500 if ((channel & BlueChannel) != 0)
1501 q->blue = ClampToQuantum(result.blue);
1502 if ((channel & OpacityChannel) != 0
1503 && image->matte == MagickTrue )
1504 q->opacity = ClampToQuantum(QuantumRange-result.opacity);
1505 if ((channel & IndexChannel) != 0
1506 && image->colorspace == CMYKColorspace)
1507 q_indexes[x] = ClampToQuantum(result.index);
1508 break;
1509 }
1510 if ( ( p[r].red != q->red )
1511 || ( p[r].green != q->green )
1512 || ( p[r].blue != q->blue )
1513 || ( p[r].opacity != q->opacity )
1514 || ( image->colorspace == CMYKColorspace &&
1515 p_indexes[r] != q_indexes[x] ) )
1516 changed++; /* The pixel had some value changed! */
anthony602ab9b2010-01-05 08:06:50 +00001517 p++;
1518 q++;
anthony83ba99b2010-01-24 08:48:15 +00001519 } /* x */
anthony602ab9b2010-01-05 08:06:50 +00001520 sync=SyncCacheViewAuthenticPixels(q_view,exception);
1521 if (sync == MagickFalse)
1522 status=MagickFalse;
1523 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1524 {
1525 MagickBooleanType
1526 proceed;
1527
1528#if defined(MAGICKCORE_OPENMP_SUPPORT)
1529 #pragma omp critical (MagickCore_MorphologyImage)
1530#endif
1531 proceed=SetImageProgress(image,MorphologyTag,progress++,image->rows);
1532 if (proceed == MagickFalse)
1533 status=MagickFalse;
1534 }
anthony83ba99b2010-01-24 08:48:15 +00001535 } /* y */
anthony602ab9b2010-01-05 08:06:50 +00001536 result_image->type=image->type;
1537 q_view=DestroyCacheView(q_view);
1538 p_view=DestroyCacheView(p_view);
cristy150989e2010-02-01 14:59:39 +00001539 return(status ? (unsigned long) changed : 0);
anthony602ab9b2010-01-05 08:06:50 +00001540}
1541
anthony4fd27e22010-02-07 08:17:18 +00001542
1543MagickExport Image *MorphologyImage(const Image *image, const MorphologyMethod
anthony930be612010-02-08 04:26:15 +00001544 method, const long iterations,const KernelInfo *kernel, ExceptionInfo
1545 *exception)
cristy2be15382010-01-21 02:38:03 +00001546{
1547 Image
1548 *morphology_image;
1549
1550 morphology_image=MorphologyImageChannel(image,DefaultChannels,method,
1551 iterations,kernel,exception);
1552 return(morphology_image);
1553}
1554
anthony4fd27e22010-02-07 08:17:18 +00001555
cristyef656912010-03-05 19:54:59 +00001556MagickExport Image *MorphologyImageChannel(const Image *image,
1557 const ChannelType channel,const MorphologyMethod method,
1558 const long iterations,const KernelInfo *kernel,ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001559{
cristy150989e2010-02-01 14:59:39 +00001560 long
1561 count;
anthony602ab9b2010-01-05 08:06:50 +00001562
1563 Image
1564 *new_image,
anthony4fd27e22010-02-07 08:17:18 +00001565 *old_image,
1566 *grad_image;
anthony602ab9b2010-01-05 08:06:50 +00001567
anthonycc6c8362010-01-25 04:14:01 +00001568 const char
1569 *artifact;
1570
cristy150989e2010-02-01 14:59:39 +00001571 unsigned long
1572 changed,
1573 limit;
1574
anthony4fd27e22010-02-07 08:17:18 +00001575 KernelInfo
1576 *curr_kernel;
1577
1578 MorphologyMethod
1579 curr_method;
1580
anthony602ab9b2010-01-05 08:06:50 +00001581 assert(image != (Image *) NULL);
1582 assert(image->signature == MagickSignature);
anthony4fd27e22010-02-07 08:17:18 +00001583 assert(kernel != (KernelInfo *) NULL);
1584 assert(kernel->signature == MagickSignature);
anthony602ab9b2010-01-05 08:06:50 +00001585 assert(exception != (ExceptionInfo *) NULL);
1586 assert(exception->signature == MagickSignature);
1587
anthony602ab9b2010-01-05 08:06:50 +00001588 if ( iterations == 0 )
1589 return((Image *)NULL); /* null operation - nothing to do! */
1590
1591 /* kernel must be valid at this point
1592 * (except maybe for posible future morphology methods like "Prune"
1593 */
cristy2be15382010-01-21 02:38:03 +00001594 assert(kernel != (KernelInfo *)NULL);
anthony602ab9b2010-01-05 08:06:50 +00001595
anthony4fd27e22010-02-07 08:17:18 +00001596 count = 0; /* interation count */
1597 changed = 1; /* if compound method assume image was changed */
anthony930be612010-02-08 04:26:15 +00001598 curr_kernel = (KernelInfo *)kernel; /* allow kernel and method */
1599 curr_method = method; /* to be changed as nessary */
anthony4fd27e22010-02-07 08:17:18 +00001600
cristy150989e2010-02-01 14:59:39 +00001601 limit = (unsigned long) iterations;
anthony602ab9b2010-01-05 08:06:50 +00001602 if ( iterations < 0 )
1603 limit = image->columns > image->rows ? image->columns : image->rows;
1604
anthony4fd27e22010-02-07 08:17:18 +00001605 /* Third-level morphology methods */
cristy5ee247a2010-02-12 15:42:34 +00001606 grad_image=(Image *) NULL;
anthony4fd27e22010-02-07 08:17:18 +00001607 switch( curr_method ) {
1608 case EdgeMorphology:
1609 grad_image = MorphologyImageChannel(image, channel,
1610 DilateMorphology, iterations, curr_kernel, exception);
1611 /* FALL-THRU */
1612 case EdgeInMorphology:
1613 curr_method = ErodeMorphology;
anthony602ab9b2010-01-05 08:06:50 +00001614 break;
anthony4fd27e22010-02-07 08:17:18 +00001615 case EdgeOutMorphology:
1616 curr_method = DilateMorphology;
anthony602ab9b2010-01-05 08:06:50 +00001617 break;
anthony4fd27e22010-02-07 08:17:18 +00001618 case TopHatMorphology:
1619 curr_method = OpenMorphology;
1620 break;
1621 case BottomHatMorphology:
1622 curr_method = CloseMorphology;
1623 break;
1624 default:
anthony930be612010-02-08 04:26:15 +00001625 break; /* not a third-level method */
anthony4fd27e22010-02-07 08:17:18 +00001626 }
1627
1628 /* Second-level morphology methods */
1629 switch( curr_method ) {
anthony930be612010-02-08 04:26:15 +00001630 case OpenMorphology:
1631 /* Open is a Erode then a Dilate without reflection */
anthony4fd27e22010-02-07 08:17:18 +00001632 new_image = MorphologyImageChannel(image, channel,
1633 ErodeMorphology, iterations, curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001634 if (new_image == (Image *) NULL)
1635 return((Image *) NULL);
anthony4fd27e22010-02-07 08:17:18 +00001636 curr_method = DilateMorphology;
1637 break;
anthony602ab9b2010-01-05 08:06:50 +00001638 case OpenIntensityMorphology:
anthony4fd27e22010-02-07 08:17:18 +00001639 new_image = MorphologyImageChannel(image, channel,
1640 ErodeIntensityMorphology, iterations, curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001641 if (new_image == (Image *) NULL)
1642 return((Image *) NULL);
anthony4fd27e22010-02-07 08:17:18 +00001643 curr_method = DilateIntensityMorphology;
1644 break;
anthony930be612010-02-08 04:26:15 +00001645
1646 case CloseMorphology:
1647 /* Close is a Dilate then Erode using reflected kernel */
1648 /* A reflected kernel is needed for a Close */
1649 if ( curr_kernel == kernel )
1650 curr_kernel = CloneKernelInfo(kernel);
1651 RotateKernelInfo(curr_kernel,180);
1652 new_image = MorphologyImageChannel(image, channel,
1653 DilateMorphology, iterations, curr_kernel, exception);
1654 if (new_image == (Image *) NULL)
1655 return((Image *) NULL);
1656 curr_method = ErodeMorphology;
1657 break;
anthony4fd27e22010-02-07 08:17:18 +00001658 case CloseIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001659 /* A reflected kernel is needed for a Close */
1660 if ( curr_kernel == kernel )
1661 curr_kernel = CloneKernelInfo(kernel);
anthony4fd27e22010-02-07 08:17:18 +00001662 RotateKernelInfo(curr_kernel,180);
1663 new_image = MorphologyImageChannel(image, channel,
1664 DilateIntensityMorphology, iterations, curr_kernel, exception);
1665 if (new_image == (Image *) NULL)
1666 return((Image *) NULL);
1667 curr_method = ErodeIntensityMorphology;
anthony602ab9b2010-01-05 08:06:50 +00001668 break;
1669
anthony930be612010-02-08 04:26:15 +00001670 case CorrelateMorphology:
1671 /* A Correlation is actually a Convolution with a reflected kernel.
1672 ** However a Convolution is a weighted sum with a reflected kernel.
1673 ** It may seem stange to convert a Correlation into a Convolution
1674 ** as the Correleation is the simplier method, but Convolution is
1675 ** much more commonly used, and it makes sense to implement it directly
1676 ** so as to avoid the need to duplicate the kernel when it is not
1677 ** required (which is typically the default).
1678 */
1679 if ( curr_kernel == kernel )
1680 curr_kernel = CloneKernelInfo(kernel);
1681 RotateKernelInfo(curr_kernel,180);
1682 curr_method = ConvolveMorphology;
1683 /* FALL-THRU into Correlate (weigthed sum without reflection) */
1684
anthonyc94cdb02010-01-06 08:15:29 +00001685 case ConvolveMorphology:
anthony4fd27e22010-02-07 08:17:18 +00001686 /* Scale or Normalize kernel, according to user wishes
anthony930be612010-02-08 04:26:15 +00001687 ** before using it for the Convolve/Correlate method.
1688 **
1689 ** FUTURE: provide some way for internal functions to disable
1690 ** user bias and scaling effects.
anthonycc6c8362010-01-25 04:14:01 +00001691 */
1692 artifact = GetImageArtifact(image,"convolve:scale");
anthony4fd27e22010-02-07 08:17:18 +00001693 if ( artifact != (char *)NULL ) {
cristy19eb6412010-04-23 14:42:29 +00001694 GeometryFlags
anthony999bb2c2010-02-18 12:38:01 +00001695 flags;
1696 GeometryInfo
1697 args;
1698
anthony930be612010-02-08 04:26:15 +00001699 if ( curr_kernel == kernel )
1700 curr_kernel = CloneKernelInfo(kernel);
anthony999bb2c2010-02-18 12:38:01 +00001701
1702 args.rho = 1.0;
cristy19eb6412010-04-23 14:42:29 +00001703 flags = (GeometryFlags) ParseGeometry(artifact, &args);
anthony999bb2c2010-02-18 12:38:01 +00001704 ScaleKernelInfo(curr_kernel, args.rho, flags);
anthony4fd27e22010-02-07 08:17:18 +00001705 }
anthony930be612010-02-08 04:26:15 +00001706 /* FALL-THRU to do the first, and typically the only iteration */
anthony4fd27e22010-02-07 08:17:18 +00001707
anthony602ab9b2010-01-05 08:06:50 +00001708 default:
anthony930be612010-02-08 04:26:15 +00001709 /* Do a single iteration using the Low-Level Morphology method!
1710 ** This ensures a "new_image" has been generated, but allows us to skip
1711 ** the creation of 'old_image' if no more iterations are needed.
1712 **
1713 ** The "curr_method" should also be set to a low-level method that is
1714 ** understood by the MorphologyApply() internal function.
anthony602ab9b2010-01-05 08:06:50 +00001715 */
1716 new_image=CloneImage(image,0,0,MagickTrue,exception);
1717 if (new_image == (Image *) NULL)
1718 return((Image *) NULL);
1719 if (SetImageStorageClass(new_image,DirectClass) == MagickFalse)
1720 {
1721 InheritException(exception,&new_image->exception);
1722 new_image=DestroyImage(new_image);
1723 return((Image *) NULL);
1724 }
anthony4fd27e22010-02-07 08:17:18 +00001725 changed = MorphologyApply(image,new_image,curr_method,channel,curr_kernel,
anthony602ab9b2010-01-05 08:06:50 +00001726 exception);
1727 count++;
1728 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
cristy150989e2010-02-01 14:59:39 +00001729 fprintf(stderr, "Morphology %s:%ld => Changed %lu\n",
anthony4fd27e22010-02-07 08:17:18 +00001730 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
anthony602ab9b2010-01-05 08:06:50 +00001731 count, changed);
anthony930be612010-02-08 04:26:15 +00001732 break;
anthony602ab9b2010-01-05 08:06:50 +00001733 }
1734
anthony930be612010-02-08 04:26:15 +00001735 /* At this point the "curr_method" should not only be set to a low-level
1736 ** method that is understood by the MorphologyApply() internal function,
1737 ** but "new_image" should now be defined, as the image to apply the
1738 ** "curr_method" to.
1739 */
1740
1741 /* Repeat the low-level morphology until count or no change reached */
cristy150989e2010-02-01 14:59:39 +00001742 if ( count < (long) limit && changed > 0 ) {
anthony602ab9b2010-01-05 08:06:50 +00001743 old_image = CloneImage(new_image,0,0,MagickTrue,exception);
1744 if (old_image == (Image *) NULL)
1745 return(DestroyImage(new_image));
1746 if (SetImageStorageClass(old_image,DirectClass) == MagickFalse)
1747 {
1748 InheritException(exception,&old_image->exception);
1749 old_image=DestroyImage(old_image);
1750 return(DestroyImage(new_image));
1751 }
cristy150989e2010-02-01 14:59:39 +00001752 while( count < (long) limit && changed != 0 )
anthony602ab9b2010-01-05 08:06:50 +00001753 {
1754 Image *tmp = old_image;
1755 old_image = new_image;
1756 new_image = tmp;
anthony4fd27e22010-02-07 08:17:18 +00001757 changed = MorphologyApply(old_image,new_image,curr_method,channel,
1758 curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001759 count++;
1760 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
cristy150989e2010-02-01 14:59:39 +00001761 fprintf(stderr, "Morphology %s:%ld => Changed %lu\n",
anthony4fd27e22010-02-07 08:17:18 +00001762 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
anthony602ab9b2010-01-05 08:06:50 +00001763 count, changed);
1764 }
cristy150989e2010-02-01 14:59:39 +00001765 old_image=DestroyImage(old_image);
anthony602ab9b2010-01-05 08:06:50 +00001766 }
anthony930be612010-02-08 04:26:15 +00001767
1768 /* We are finished with kernel - destroy it if we made a clone */
anthony4fd27e22010-02-07 08:17:18 +00001769 if ( curr_kernel != kernel )
1770 curr_kernel=DestroyKernelInfo(curr_kernel);
1771
anthony7d10d742010-05-06 07:05:29 +00001772 /* Third-level Subtractive methods post-processing
1773 **
1774 ** The removal of any 'Sync' channel flag in the Image Compositon below
1775 ** ensures the compose method is applied in a purely mathematical way, only
1776 ** the selected channels, without any normal 'alpha blending' normally
1777 ** associated with the compose method.
1778 **
1779 ** Note "method" here is the 'original' morphological method, and not the
1780 ** 'current' morphological method used above to generate "new_image".
1781 */
anthony4fd27e22010-02-07 08:17:18 +00001782 switch( method ) {
1783 case EdgeOutMorphology:
1784 case EdgeInMorphology:
1785 case TopHatMorphology:
1786 case BottomHatMorphology:
anthony930be612010-02-08 04:26:15 +00001787 /* Get Difference relative to the original image */
cristyaeb2cbc2010-05-07 13:28:58 +00001788 (void) CompositeImageChannel(new_image, (ChannelType) (channel & ~SyncChannels),
anthony7d10d742010-05-06 07:05:29 +00001789 DifferenceCompositeOp, image, 0, 0);
anthony4fd27e22010-02-07 08:17:18 +00001790 break;
anthony7d10d742010-05-06 07:05:29 +00001791 case EdgeMorphology:
1792 /* Difference the Eroded image from the saved Dilated image */
cristyaeb2cbc2010-05-07 13:28:58 +00001793 (void) CompositeImageChannel(new_image, (ChannelType) (channel & ~SyncChannels),
anthony7d10d742010-05-06 07:05:29 +00001794 DifferenceCompositeOp, grad_image, 0, 0);
anthony4fd27e22010-02-07 08:17:18 +00001795 grad_image=DestroyImage(grad_image);
1796 break;
1797 default:
1798 break;
1799 }
anthony602ab9b2010-01-05 08:06:50 +00001800
1801 return(new_image);
1802}
anthony83ba99b2010-01-24 08:48:15 +00001803
1804/*
1805%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1806% %
1807% %
1808% %
anthony4fd27e22010-02-07 08:17:18 +00001809+ R o t a t e K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00001810% %
1811% %
1812% %
1813%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1814%
anthony4fd27e22010-02-07 08:17:18 +00001815% RotateKernelInfo() rotates the kernel by the angle given. Currently it is
anthony83ba99b2010-01-24 08:48:15 +00001816% restricted to 90 degree angles, but this may be improved in the future.
1817%
anthony4fd27e22010-02-07 08:17:18 +00001818% The format of the RotateKernelInfo method is:
anthony83ba99b2010-01-24 08:48:15 +00001819%
anthony4fd27e22010-02-07 08:17:18 +00001820% void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00001821%
1822% A description of each parameter follows:
1823%
1824% o kernel: the Morphology/Convolution kernel
1825%
1826% o angle: angle to rotate in degrees
1827%
anthonyc4c86e02010-01-27 09:30:32 +00001828% This function is only internel to this module, as it is not finalized,
1829% especially with regard to non-orthogonal angles, and rotation of larger
1830% 2D kernels.
anthony83ba99b2010-01-24 08:48:15 +00001831*/
anthony4fd27e22010-02-07 08:17:18 +00001832static void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00001833{
1834 /* WARNING: Currently assumes the kernel (rightly) is horizontally symetrical
1835 **
1836 ** TODO: expand beyond simple 90 degree rotates, flips and flops
1837 */
1838
1839 /* Modulus the angle */
1840 angle = fmod(angle, 360.0);
1841 if ( angle < 0 )
1842 angle += 360.0;
1843
1844 if ( 315.0 < angle || angle <= 45.0 )
1845 return; /* no change! - At least at this time */
1846
1847 switch (kernel->type) {
1848 /* These built-in kernels are cylindrical kernels, rotating is useless */
1849 case GaussianKernel:
1850 case LaplacianKernel:
1851 case LOGKernel:
1852 case DOGKernel:
1853 case DiskKernel:
1854 case ChebyshevKernel:
1855 case ManhattenKernel:
1856 case EuclideanKernel:
1857 return;
1858
1859 /* These may be rotatable at non-90 angles in the future */
1860 /* but simply rotating them in multiples of 90 degrees is useless */
1861 case SquareKernel:
1862 case DiamondKernel:
1863 case PlusKernel:
1864 return;
1865
1866 /* These only allows a +/-90 degree rotation (by transpose) */
1867 /* A 180 degree rotation is useless */
1868 case BlurKernel:
1869 case RectangleKernel:
1870 if ( 135.0 < angle && angle <= 225.0 )
1871 return;
1872 if ( 225.0 < angle && angle <= 315.0 )
1873 angle -= 180;
1874 break;
1875
1876 /* these are freely rotatable in 90 degree units */
1877 case CometKernel:
1878 case UndefinedKernel:
1879 case UserDefinedKernel:
1880 break;
1881 }
1882 if ( 135.0 < angle && angle <= 225.0 )
1883 {
1884 /* For a 180 degree rotation - also know as a reflection */
1885 /* This is actually a very very common operation! */
1886 /* Basically all that is needed is a reversal of the kernel data! */
1887 unsigned long
1888 i,j;
1889 register double
1890 *k,t;
1891
1892 k=kernel->values;
1893 for ( i=0, j=kernel->width*kernel->height-1; i<j; i++, j--)
1894 t=k[i], k[i]=k[j], k[j]=t;
1895
anthony930be612010-02-08 04:26:15 +00001896 kernel->x = (long) kernel->width - kernel->x - 1;
1897 kernel->y = (long) kernel->height - kernel->y - 1;
anthony83ba99b2010-01-24 08:48:15 +00001898 angle = fmod(angle+180.0, 360.0);
1899 }
1900 if ( 45.0 < angle && angle <= 135.0 )
1901 { /* Do a transpose and a flop, of the image, which results in a 90
1902 * degree rotation using two mirror operations.
1903 *
1904 * WARNING: this assumes the original image was a 1 dimentional image
1905 * but currently that is the only built-ins it is applied to.
1906 */
cristy150989e2010-02-01 14:59:39 +00001907 long
anthony83ba99b2010-01-24 08:48:15 +00001908 t;
cristy150989e2010-02-01 14:59:39 +00001909 t = (long) kernel->width;
anthony83ba99b2010-01-24 08:48:15 +00001910 kernel->width = kernel->height;
cristy150989e2010-02-01 14:59:39 +00001911 kernel->height = (unsigned long) t;
cristyc99304f2010-02-01 15:26:27 +00001912 t = kernel->x;
1913 kernel->x = kernel->y;
1914 kernel->y = t;
anthony83ba99b2010-01-24 08:48:15 +00001915 angle = fmod(450.0 - angle, 360.0);
1916 }
1917 /* At this point angle should be between -45 (315) and +45 degrees
1918 * In the future some form of non-orthogonal angled rotates could be
1919 * performed here, posibily with a linear kernel restriction.
1920 */
1921
1922#if 0
1923 Not currently in use!
1924 { /* Do a flop, this assumes kernel is horizontally symetrical.
1925 * Each row of the kernel needs to be reversed!
1926 */
1927 unsigned long
1928 y;
cristy150989e2010-02-01 14:59:39 +00001929 register long
anthony83ba99b2010-01-24 08:48:15 +00001930 x,r;
1931 register double
1932 *k,t;
1933
1934 for ( y=0, k=kernel->values; y < kernel->height; y++, k+=kernel->width)
1935 for ( x=0, r=kernel->width-1; x<kernel->width/2; x++, r--)
1936 t=k[x], k[x]=k[r], k[r]=t;
1937
cristyc99304f2010-02-01 15:26:27 +00001938 kernel->x = kernel->width - kernel->x - 1;
anthony83ba99b2010-01-24 08:48:15 +00001939 angle = fmod(angle+180.0, 360.0);
1940 }
1941#endif
1942 return;
1943}
1944
1945/*
1946%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1947% %
1948% %
1949% %
cristy6771f1e2010-03-05 19:43:39 +00001950% S c a l e K e r n e l I n f o %
anthonycc6c8362010-01-25 04:14:01 +00001951% %
1952% %
1953% %
1954%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1955%
anthony999bb2c2010-02-18 12:38:01 +00001956% ScaleKernelInfo() scales the kernel by the given amount, with or without
1957% normalization of the sum of the kernel values.
anthonycc6c8362010-01-25 04:14:01 +00001958%
anthony999bb2c2010-02-18 12:38:01 +00001959% By default (no flags given) the values within the kernel is scaled
1960% according the given scaling factor.
anthonycc6c8362010-01-25 04:14:01 +00001961%
anthony999bb2c2010-02-18 12:38:01 +00001962% If any 'normalize_flags' are given the kernel will be normalized and then
1963% further scaled by the scaleing factor value given. A 'PercentValue' flag
1964% will cause the given scaling factor to be divided by one hundred percent.
1965%
1966% Kernel normalization ('normalize_flags' given) is designed to ensure that
1967% any use of the kernel scaling factor with 'Convolve' or 'Correlate'
1968% morphology methods will fall into -1.0 to +1.0 range. Note however that
1969% for non-HDRI versions of IM this may cause images to have any negative
1970% results clipped, unless some 'clip' any negative output from 'Convolve'
1971% with the use of some kernels.
1972%
1973% More specifically. Kernels which only contain positive values (such as a
1974% 'Gaussian' kernel) will be scaled so that those values sum to +1.0,
1975% ensuring a 0.0 to +1.0 convolution output range for non-HDRI images.
1976%
1977% For Kernels that contain some negative values, (such as 'Sharpen' kernels)
1978% the kernel will be scaled by the absolute of the sum of kernel values, so
1979% that it will generally fall within the +/- 1.0 range.
1980%
1981% For kernels whose values sum to zero, (such as 'Laplician' kernels) kernel
1982% will be scaled by just the sum of the postive values, so that its output
1983% range will again fall into the +/- 1.0 range.
1984%
1985% For special kernels designed for locating shapes using 'Correlate', (often
1986% only containing +1 and -1 values, representing foreground/brackground
1987% matching) a special normalization method is provided to scale the positive
1988% values seperatally to those of the negative values, so the kernel will be
1989% forced to become a zero-sum kernel better suited to such searches.
1990%
1991% WARNING: Correct normalization of the kernal assumes that the '*_range'
1992% attributes within the kernel structure have been correctly set during the
1993% kernels creation.
1994%
1995% NOTE: The values used for 'normalize_flags' have been selected specifically
1996% to match the use of geometry options, so that '!' means NormalizeValue, '^'
1997% means CorrelateNormalizeValue, and '%' means PercentValue. All other
1998% GeometryFlags values are ignored.
anthonycc6c8362010-01-25 04:14:01 +00001999%
anthony4fd27e22010-02-07 08:17:18 +00002000% The format of the ScaleKernelInfo method is:
anthonycc6c8362010-01-25 04:14:01 +00002001%
anthony999bb2c2010-02-18 12:38:01 +00002002% void ScaleKernelInfo(KernelInfo *kernel, const double scaling_factor,
2003% const MagickStatusType normalize_flags )
anthonycc6c8362010-01-25 04:14:01 +00002004%
2005% A description of each parameter follows:
2006%
2007% o kernel: the Morphology/Convolution kernel
2008%
anthony999bb2c2010-02-18 12:38:01 +00002009% o scaling_factor:
2010% multiply all values (after normalization) by this factor if not
2011% zero. If the kernel is normalized regardless of any flags.
2012%
2013% o normalize_flags:
2014% GeometryFlags defining normalization method to use.
2015% specifically: NormalizeValue, CorrelateNormalizeValue,
2016% and/or PercentValue
anthonycc6c8362010-01-25 04:14:01 +00002017%
anthonyc4c86e02010-01-27 09:30:32 +00002018% This function is internal to this module only at this time, but can be
2019% exported to other modules if needed.
anthonycc6c8362010-01-25 04:14:01 +00002020*/
cristy6771f1e2010-03-05 19:43:39 +00002021MagickExport void ScaleKernelInfo(KernelInfo *kernel,
2022 const double scaling_factor,const GeometryFlags normalize_flags)
anthonycc6c8362010-01-25 04:14:01 +00002023{
cristy150989e2010-02-01 14:59:39 +00002024 register long
anthonycc6c8362010-01-25 04:14:01 +00002025 i;
2026
anthony999bb2c2010-02-18 12:38:01 +00002027 register double
2028 pos_scale,
2029 neg_scale;
2030
2031 pos_scale = 1.0;
2032 if ( (normalize_flags&NormalizeValue) != 0 ) {
2033 /* normalize kernel appropriately */
2034 if ( fabs(kernel->positive_range + kernel->negative_range) > MagickEpsilon )
2035 pos_scale = fabs(kernel->positive_range + kernel->negative_range);
anthonycc6c8362010-01-25 04:14:01 +00002036 else
anthony999bb2c2010-02-18 12:38:01 +00002037 pos_scale = kernel->positive_range; /* special zero-summing kernel */
2038 }
2039 /* force kernel into being a normalized zero-summing kernel */
2040 if ( (normalize_flags&CorrelateNormalizeValue) != 0 ) {
2041 pos_scale = ( fabs(kernel->positive_range) > MagickEpsilon )
2042 ? kernel->positive_range : 1.0;
2043 neg_scale = ( fabs(kernel->negative_range) > MagickEpsilon )
2044 ? -kernel->negative_range : 1.0;
2045 }
2046 else
2047 neg_scale = pos_scale;
2048
2049 /* finialize scaling_factor for positive and negative components */
2050 pos_scale = scaling_factor/pos_scale;
2051 neg_scale = scaling_factor/neg_scale;
2052 if ( (normalize_flags&PercentValue) != 0 ) {
2053 pos_scale /= 100.0;
2054 neg_scale /= 100.0;
anthonycc6c8362010-01-25 04:14:01 +00002055 }
2056
cristy150989e2010-02-01 14:59:39 +00002057 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00002058 if ( ! IsNan(kernel->values[i]) )
anthony999bb2c2010-02-18 12:38:01 +00002059 kernel->values[i] *= (kernel->values[i] >= 0) ? pos_scale : neg_scale;
anthonycc6c8362010-01-25 04:14:01 +00002060
anthony999bb2c2010-02-18 12:38:01 +00002061 /* convolution output range */
2062 kernel->positive_range *= pos_scale;
2063 kernel->negative_range *= neg_scale;
2064 /* maximum and minimum values in kernel */
2065 kernel->maximum *= (kernel->maximum >= 0.0) ? pos_scale : neg_scale;
2066 kernel->minimum *= (kernel->minimum >= 0.0) ? pos_scale : neg_scale;
2067
2068 /* swap kernel settings if user scaling factor is negative */
2069 if ( scaling_factor < MagickEpsilon ) {
2070 double t;
2071 t = kernel->positive_range;
2072 kernel->positive_range = kernel->negative_range;
2073 kernel->negative_range = t;
2074 t = kernel->maximum;
2075 kernel->maximum = kernel->minimum;
2076 kernel->minimum = 1;
2077 }
anthonycc6c8362010-01-25 04:14:01 +00002078
2079 return;
2080}
2081
2082/*
2083%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2084% %
2085% %
2086% %
anthony4fd27e22010-02-07 08:17:18 +00002087+ S h o w K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00002088% %
2089% %
2090% %
2091%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2092%
anthony4fd27e22010-02-07 08:17:18 +00002093% ShowKernelInfo() outputs the details of the given kernel defination to
2094% standard error, generally due to a users 'showkernel' option request.
anthony83ba99b2010-01-24 08:48:15 +00002095%
2096% The format of the ShowKernel method is:
2097%
anthony4fd27e22010-02-07 08:17:18 +00002098% void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00002099%
2100% A description of each parameter follows:
2101%
2102% o kernel: the Morphology/Convolution kernel
2103%
anthonyc4c86e02010-01-27 09:30:32 +00002104% This function is internal to this module only at this time. That may change
2105% in the future.
anthony83ba99b2010-01-24 08:48:15 +00002106*/
anthony4fd27e22010-02-07 08:17:18 +00002107MagickExport void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00002108{
cristy150989e2010-02-01 14:59:39 +00002109 long
anthony83ba99b2010-01-24 08:48:15 +00002110 i, u, v;
2111
2112 fprintf(stderr,
anthonycc6c8362010-01-25 04:14:01 +00002113 "Kernel \"%s\" of size %lux%lu%+ld%+ld with values from %.*lg to %.*lg\n",
anthony83ba99b2010-01-24 08:48:15 +00002114 MagickOptionToMnemonic(MagickKernelOptions, kernel->type),
2115 kernel->width, kernel->height,
cristyc99304f2010-02-01 15:26:27 +00002116 kernel->x, kernel->y,
2117 GetMagickPrecision(), kernel->minimum,
2118 GetMagickPrecision(), kernel->maximum);
anthonycc6c8362010-01-25 04:14:01 +00002119 fprintf(stderr, "Forming convolution output range from %.*lg to %.*lg%s\n",
cristyc99304f2010-02-01 15:26:27 +00002120 GetMagickPrecision(), kernel->negative_range,
2121 GetMagickPrecision(), kernel->positive_range,
anthonycc6c8362010-01-25 04:14:01 +00002122 /*kernel->normalized == MagickTrue ? " (normalized)" : */ "" );
cristy150989e2010-02-01 14:59:39 +00002123 for (i=v=0; v < (long) kernel->height; v++) {
anthony83ba99b2010-01-24 08:48:15 +00002124 fprintf(stderr,"%2ld:",v);
cristy150989e2010-02-01 14:59:39 +00002125 for (u=0; u < (long) kernel->width; u++, i++)
anthony83ba99b2010-01-24 08:48:15 +00002126 if ( IsNan(kernel->values[i]) )
2127 fprintf(stderr," %*s", GetMagickPrecision()+2, "nan");
2128 else
2129 fprintf(stderr," %*.*lg", GetMagickPrecision()+2,
2130 GetMagickPrecision(), kernel->values[i]);
2131 fprintf(stderr,"\n");
2132 }
2133}
anthonycc6c8362010-01-25 04:14:01 +00002134
2135/*
2136%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2137% %
2138% %
2139% %
anthony4fd27e22010-02-07 08:17:18 +00002140+ Z e r o K e r n e l N a n s %
anthonycc6c8362010-01-25 04:14:01 +00002141% %
2142% %
2143% %
2144%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2145%
2146% ZeroKernelNans() replaces any special 'nan' value that may be present in
2147% the kernel with a zero value. This is typically done when the kernel will
2148% be used in special hardware (GPU) convolution processors, to simply
2149% matters.
2150%
2151% The format of the ZeroKernelNans method is:
2152%
2153% voidZeroKernelNans (KernelInfo *kernel)
2154%
2155% A description of each parameter follows:
2156%
2157% o kernel: the Morphology/Convolution kernel
2158%
2159% FUTURE: return the information in a string for API usage.
2160*/
anthonyc4c86e02010-01-27 09:30:32 +00002161MagickExport void ZeroKernelNans(KernelInfo *kernel)
anthonycc6c8362010-01-25 04:14:01 +00002162{
cristy150989e2010-02-01 14:59:39 +00002163 register long
anthonycc6c8362010-01-25 04:14:01 +00002164 i;
2165
cristy150989e2010-02-01 14:59:39 +00002166 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00002167 if ( IsNan(kernel->values[i]) )
2168 kernel->values[i] = 0.0;
2169
2170 return;
2171}