blob: 36f2a7d6c44f5884ac3df14e6647c8613531c382 [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
cristy2be15382010-01-21 02:38:03 +0000182MagickExport KernelInfo *AcquireKernelInfo(const char *kernel_string)
anthony602ab9b2010-01-05 08:06:50 +0000183{
cristy2be15382010-01-21 02:38:03 +0000184 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000185 *kernel;
186
187 char
188 token[MaxTextExtent];
189
cristy150989e2010-02-01 14:59:39 +0000190 register long
anthony602ab9b2010-01-05 08:06:50 +0000191 i;
192
193 const char
194 *p;
195
196 MagickStatusType
197 flags;
198
199 GeometryInfo
200 args;
201
anthony29188a82010-01-22 10:12:34 +0000202 double
203 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
204
cristya2175d32010-04-03 02:25:17 +0000205 if (kernel_string == (const char *) NULL)
206 {
207 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
208 if (kernel == (KernelInfo *)NULL)
209 return(kernel);
210 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
211 kernel->type=UserDefinedKernel;
212 kernel->signature=MagickSignature;
213 return(kernel);
214 }
anthony602ab9b2010-01-05 08:06:50 +0000215 SetGeometryInfo(&args);
216
217 /* does it start with an alpha - Return a builtin kernel */
218 GetMagickToken(kernel_string,&p,token);
cristya2175d32010-04-03 02:25:17 +0000219 if (isalpha((int) ((unsigned char) *token)) != 0)
anthony602ab9b2010-01-05 08:06:50 +0000220 {
221 long
222 type;
223
anthony29188a82010-01-22 10:12:34 +0000224 type=ParseMagickOption(MagickKernelOptions,MagickFalse,token);
anthony602ab9b2010-01-05 08:06:50 +0000225 if ( type < 0 || type == UserDefinedKernel )
cristy2be15382010-01-21 02:38:03 +0000226 return((KernelInfo *)NULL);
anthony602ab9b2010-01-05 08:06:50 +0000227
228 while (((isspace((int) ((unsigned char) *p)) != 0) ||
229 (*p == ',') || (*p == ':' )) && (*p != '\0'))
230 p++;
231 flags = ParseGeometry(p, &args);
232
233 /* special handling of missing values in input string */
anthony4fd27e22010-02-07 08:17:18 +0000234 switch( type ) {
235 case RectangleKernel:
anthony602ab9b2010-01-05 08:06:50 +0000236 if ( (flags & WidthValue) == 0 ) /* if no width then */
237 args.rho = args.sigma; /* then width = height */
238 if ( args.rho < 1.0 ) /* if width too small */
239 args.rho = 3; /* then width = 3 */
240 if ( args.sigma < 1.0 ) /* if height too small */
241 args.sigma = args.rho; /* then height = width */
242 if ( (flags & XValue) == 0 ) /* center offset if not defined */
243 args.xi = (double)(((long)args.rho-1)/2);
244 if ( (flags & YValue) == 0 )
245 args.psi = (double)(((long)args.sigma-1)/2);
anthony4fd27e22010-02-07 08:17:18 +0000246 break;
247 case SquareKernel:
248 case DiamondKernel:
249 case DiskKernel:
250 case PlusKernel:
251 if ( (flags & HeightValue) == 0 ) /* if no scale */
252 args.sigma = 1.0; /* then scale = 1.0 */
253 break;
254 default:
255 break;
anthony602ab9b2010-01-05 08:06:50 +0000256 }
257
cristy2be15382010-01-21 02:38:03 +0000258 return(AcquireKernelBuiltIn((KernelInfoType)type, &args));
anthony602ab9b2010-01-05 08:06:50 +0000259 }
260
cristy2be15382010-01-21 02:38:03 +0000261 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
262 if (kernel == (KernelInfo *)NULL)
anthony602ab9b2010-01-05 08:06:50 +0000263 return(kernel);
264 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
265 kernel->type = UserDefinedKernel;
cristyd43a46b2010-01-21 02:13:41 +0000266 kernel->signature = MagickSignature;
anthony602ab9b2010-01-05 08:06:50 +0000267
268 /* Has a ':' in argument - New user kernel specification */
269 p = strchr(kernel_string, ':');
270 if ( p != (char *) NULL)
271 {
anthony602ab9b2010-01-05 08:06:50 +0000272 /* ParseGeometry() needs the geometry separated! -- Arrgghh */
cristy150989e2010-02-01 14:59:39 +0000273 memcpy(token, kernel_string, (size_t) (p-kernel_string));
anthony602ab9b2010-01-05 08:06:50 +0000274 token[p-kernel_string] = '\0';
275 flags = ParseGeometry(token, &args);
anthony602ab9b2010-01-05 08:06:50 +0000276
anthony29188a82010-01-22 10:12:34 +0000277 /* Size handling and checks of geometry settings */
anthony602ab9b2010-01-05 08:06:50 +0000278 if ( (flags & WidthValue) == 0 ) /* if no width then */
279 args.rho = args.sigma; /* then width = height */
280 if ( args.rho < 1.0 ) /* if width too small */
281 args.rho = 1.0; /* then width = 1 */
282 if ( args.sigma < 1.0 ) /* if height too small */
283 args.sigma = args.rho; /* then height = width */
284 kernel->width = (unsigned long)args.rho;
285 kernel->height = (unsigned long)args.sigma;
286
287 /* Offset Handling and Checks */
288 if ( args.xi < 0.0 || args.psi < 0.0 )
anthony83ba99b2010-01-24 08:48:15 +0000289 return(DestroyKernelInfo(kernel));
cristyc99304f2010-02-01 15:26:27 +0000290 kernel->x = ((flags & XValue)!=0) ? (long)args.xi
cristy150989e2010-02-01 14:59:39 +0000291 : (long) (kernel->width-1)/2;
cristyc99304f2010-02-01 15:26:27 +0000292 kernel->y = ((flags & YValue)!=0) ? (long)args.psi
cristy150989e2010-02-01 14:59:39 +0000293 : (long) (kernel->height-1)/2;
cristyc99304f2010-02-01 15:26:27 +0000294 if ( kernel->x >= (long) kernel->width ||
295 kernel->y >= (long) kernel->height )
anthony83ba99b2010-01-24 08:48:15 +0000296 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000297
298 p++; /* advance beyond the ':' */
299 }
300 else
301 { /* ELSE - Old old kernel specification, forming odd-square kernel */
302 /* count up number of values given */
303 p=(const char *) kernel_string;
cristya699b172010-01-06 16:48:49 +0000304 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
anthony29188a82010-01-22 10:12:34 +0000305 p++; /* ignore "'" chars for convolve filter usage - Cristy */
anthony602ab9b2010-01-05 08:06:50 +0000306 for (i=0; *p != '\0'; i++)
307 {
308 GetMagickToken(p,&p,token);
309 if (*token == ',')
310 GetMagickToken(p,&p,token);
311 }
312 /* set the size of the kernel - old sized square */
313 kernel->width = kernel->height= (unsigned long) sqrt((double) i+1.0);
cristyc99304f2010-02-01 15:26:27 +0000314 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000315 p=(const char *) kernel_string;
anthony29188a82010-01-22 10:12:34 +0000316 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
317 p++; /* ignore "'" chars for convolve filter usage - Cristy */
anthony602ab9b2010-01-05 08:06:50 +0000318 }
319
320 /* Read in the kernel values from rest of input string argument */
321 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
322 kernel->height*sizeof(double));
323 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000324 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000325
cristyc99304f2010-02-01 15:26:27 +0000326 kernel->minimum = +MagickHuge;
327 kernel->maximum = -MagickHuge;
328 kernel->negative_range = kernel->positive_range = 0.0;
cristy150989e2010-02-01 14:59:39 +0000329 for (i=0; (i < (long) (kernel->width*kernel->height)) && (*p != '\0'); i++)
anthony602ab9b2010-01-05 08:06:50 +0000330 {
331 GetMagickToken(p,&p,token);
332 if (*token == ',')
333 GetMagickToken(p,&p,token);
anthony29188a82010-01-22 10:12:34 +0000334 if ( LocaleCompare("nan",token) == 0
335 || LocaleCompare("-",token) == 0 ) {
336 kernel->values[i] = nan; /* do not include this value in kernel */
337 }
338 else {
339 kernel->values[i] = StringToDouble(token);
340 ( kernel->values[i] < 0)
cristyc99304f2010-02-01 15:26:27 +0000341 ? ( kernel->negative_range += kernel->values[i] )
342 : ( kernel->positive_range += kernel->values[i] );
343 Minimize(kernel->minimum, kernel->values[i]);
344 Maximize(kernel->maximum, kernel->values[i]);
anthony29188a82010-01-22 10:12:34 +0000345 }
anthony602ab9b2010-01-05 08:06:50 +0000346 }
anthonycc6c8362010-01-25 04:14:01 +0000347 /* check that we recieved at least one real (non-nan) value! */
cristyc99304f2010-02-01 15:26:27 +0000348 if ( kernel->minimum == MagickHuge )
anthonycc6c8362010-01-25 04:14:01 +0000349 return(DestroyKernelInfo(kernel));
anthony29188a82010-01-22 10:12:34 +0000350
anthonycc6c8362010-01-25 04:14:01 +0000351 /* This should not be needed for a fully defined kernel
anthony29188a82010-01-22 10:12:34 +0000352 * Perhaps an error should be reported instead!
anthonycc6c8362010-01-25 04:14:01 +0000353 * Kept for backward compatibility.
anthony29188a82010-01-22 10:12:34 +0000354 */
cristy150989e2010-02-01 14:59:39 +0000355 if ( i < (long) (kernel->width*kernel->height) ) {
cristyc99304f2010-02-01 15:26:27 +0000356 Minimize(kernel->minimum, kernel->values[i]);
357 Maximize(kernel->maximum, kernel->values[i]);
cristy150989e2010-02-01 14:59:39 +0000358 for ( ; i < (long) (kernel->width*kernel->height); i++)
anthony29188a82010-01-22 10:12:34 +0000359 kernel->values[i]=0.0;
360 }
anthony602ab9b2010-01-05 08:06:50 +0000361
362 return(kernel);
363}
364
365/*
366%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
367% %
368% %
369% %
370% A c q u i r e K e r n e l B u i l t I n %
371% %
372% %
373% %
374%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
375%
376% AcquireKernelBuiltIn() returned one of the 'named' built-in types of
377% kernels used for special purposes such as gaussian blurring, skeleton
378% pruning, and edge distance determination.
379%
380% They take a KernelType, and a set of geometry style arguments, which were
381% typically decoded from a user supplied string, or from a more complex
382% Morphology Method that was requested.
383%
384% The format of the AcquireKernalBuiltIn method is:
385%
cristy2be15382010-01-21 02:38:03 +0000386% KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000387% const GeometryInfo args)
388%
389% A description of each parameter follows:
390%
391% o type: the pre-defined type of kernel wanted
392%
393% o args: arguments defining or modifying the kernel
394%
395% Convolution Kernels
396%
anthony4fd27e22010-02-07 08:17:18 +0000397% Gaussian "{radius},{sigma}"
anthony602ab9b2010-01-05 08:06:50 +0000398% Generate a two-dimentional gaussian kernel, as used by -gaussian
399% A sigma is required, (with the 'x'), due to historical reasons.
400%
401% NOTE: that the 'radius' is optional, but if provided can limit (clip)
402% the final size of the resulting kernel to a square 2*radius+1 in size.
403% The radius should be at least 2 times that of the sigma value, or
404% sever clipping and aliasing may result. If not given or set to 0 the
405% radius will be determined so as to produce the best minimal error
406% result, which is usally much larger than is normally needed.
407%
anthony4fd27e22010-02-07 08:17:18 +0000408% Blur "{radius},{sigma},{angle}"
anthony602ab9b2010-01-05 08:06:50 +0000409% As per Gaussian, but generates a 1 dimensional or linear gaussian
410% blur, at the angle given (current restricted to orthogonal angles).
411% If a 'radius' is given the kernel is clipped to a width of 2*radius+1.
412%
413% NOTE that two such blurs perpendicular to each other is equivelent to
414% -blur and the previous gaussian, but is often 10 or more times faster.
415%
anthony4fd27e22010-02-07 08:17:18 +0000416% Comet "{width},{sigma},{angle}"
anthony602ab9b2010-01-05 08:06:50 +0000417% Blur in one direction only, mush like how a bright object leaves
418% a comet like trail. The Kernel is actually half a gaussian curve,
419% Adding two such blurs in oppiste directions produces a Linear Blur.
420%
421% NOTE: that the first argument is the width of the kernel and not the
422% radius of the kernel.
423%
424% # Still to be implemented...
425% #
anthony4fd27e22010-02-07 08:17:18 +0000426% # Sharpen "{radius},{sigma}
427% # Negated Gaussian (center zeroed and re-normalized),
428% # with a 2 unit positive peak. -- Check On line documentation
429% #
430% # Laplacian "{radius},{sigma}"
anthony602ab9b2010-01-05 08:06:50 +0000431% # Laplacian (a mexican hat like) Function
432% #
433% # LOG "{radius},{sigma1},{sigma2}
434% # Laplacian of Gaussian
435% #
436% # DOG "{radius},{sigma1},{sigma2}
anthony4fd27e22010-02-07 08:17:18 +0000437% # Difference of two Gaussians
438% #
439% # Filter2D
440% # Filter1D
441% # Set kernel values using a resize filter, and given scale (sigma)
442% # Cylindrical or Linear. Is this posible with an image?
443% #
anthony602ab9b2010-01-05 08:06:50 +0000444%
445% Boolean Kernels
446%
447% Rectangle "{geometry}"
448% Simply generate a rectangle of 1's with the size given. You can also
449% specify the location of the 'control point', otherwise the closest
450% pixel to the center of the rectangle is selected.
451%
452% Properly centered and odd sized rectangles work the best.
453%
anthony4fd27e22010-02-07 08:17:18 +0000454% Diamond "[{radius}[,{scale}]]"
anthony602ab9b2010-01-05 08:06:50 +0000455% Generate a diamond shaped kernal with given radius to the points.
456% Kernel size will again be radius*2+1 square and defaults to radius 1,
457% generating a 3x3 kernel that is slightly larger than a square.
458%
anthony4fd27e22010-02-07 08:17:18 +0000459% Square "[{radius}[,{scale}]]"
anthony602ab9b2010-01-05 08:06:50 +0000460% Generate a square shaped kernel of size radius*2+1, and defaulting
461% to a 3x3 (radius 1).
462%
463% Note that using a larger radius for the "Square" or the "Diamond"
464% is also equivelent to iterating the basic morphological method
465% that many times. However However iterating with the smaller radius 1
466% default is actually faster than using a larger kernel radius.
467%
anthony4fd27e22010-02-07 08:17:18 +0000468% Disk "[{radius}[,{scale}]]
anthony602ab9b2010-01-05 08:06:50 +0000469% Generate a binary disk of the radius given, radius may be a float.
470% Kernel size will be ceil(radius)*2+1 square.
471% NOTE: Here are some disk shapes of specific interest
472% "disk:1" => "diamond" or "cross:1"
473% "disk:1.5" => "square"
474% "disk:2" => "diamond:2"
anthony83ba99b2010-01-24 08:48:15 +0000475% "disk:2.5" => a general disk shape of radius 2
anthony602ab9b2010-01-05 08:06:50 +0000476% "disk:2.9" => "square:2"
anthony83ba99b2010-01-24 08:48:15 +0000477% "disk:3.5" => default - octagonal/disk shape of radius 3
anthony602ab9b2010-01-05 08:06:50 +0000478% "disk:4.2" => roughly octagonal shape of radius 4
anthony83ba99b2010-01-24 08:48:15 +0000479% "disk:4.3" => a general disk shape of radius 4
anthony602ab9b2010-01-05 08:06:50 +0000480% After this all the kernel shape becomes more and more circular.
481%
482% Because a "disk" is more circular when using a larger radius, using a
483% larger radius is preferred over iterating the morphological operation.
484%
anthony4fd27e22010-02-07 08:17:18 +0000485% Plus "[{radius}[,{scale}]]"
anthony602ab9b2010-01-05 08:06:50 +0000486% Generate a kernel in the shape of a 'plus' sign. The length of each
487% arm is also the radius, which defaults to 2.
488%
489% This kernel is not a good general morphological kernel, but is used
490% more for highlighting and marking any single pixels in an image using,
491% a "Dilate" or "Erode" method as appropriate.
anthonyc94cdb02010-01-06 08:15:29 +0000492%
anthony602ab9b2010-01-05 08:06:50 +0000493% NOTE: "plus:1" is equivelent to a "Diamond" kernel.
494%
495% Note that unlike other kernels iterating a plus does not produce the
496% same result as using a larger radius for the cross.
497%
498% Distance Measuring Kernels
499%
500% Chebyshev "[{radius}][x{scale}]" largest x or y distance (default r=1)
501% Manhatten "[{radius}][x{scale}]" square grid distance (default r=1)
anthonyc94cdb02010-01-06 08:15:29 +0000502% Euclidean "[{radius}][x{scale}]" direct distance (default r=1)
anthony602ab9b2010-01-05 08:06:50 +0000503%
504% Different types of distance measuring methods, which are used with the
505% a 'Distance' morphology method for generating a gradient based on
506% distance from an edge of a binary shape, though there is a technique
507% for handling a anti-aliased shape.
508%
anthonyc94cdb02010-01-06 08:15:29 +0000509% Chebyshev Distance (also known as Tchebychev Distance) is a value of
510% one to any neighbour, orthogonal or diagonal. One why of thinking of
511% it is the number of squares a 'King' or 'Queen' in chess needs to
512% traverse reach any other position on a chess board. It results in a
513% 'square' like distance function, but one where diagonals are closer
514% than expected.
anthony602ab9b2010-01-05 08:06:50 +0000515%
anthonyc94cdb02010-01-06 08:15:29 +0000516% Manhatten Distance (also known as Rectilinear Distance, or the Taxi
517% Cab metric), is the distance needed when you can only travel in
518% orthogonal (horizontal or vertical) only. It is the distance a 'Rook'
519% in chess would travel. It results in a diamond like distances, where
520% diagonals are further than expected.
anthony602ab9b2010-01-05 08:06:50 +0000521%
anthonyc94cdb02010-01-06 08:15:29 +0000522% Euclidean Distance is the 'direct' or 'as the crow flys distance.
523% However by default the kernel size only has a radius of 1, which
524% limits the distance to 'Knight' like moves, with only orthogonal and
525% diagonal measurements being correct. As such for the default kernel
526% you will get octagonal like distance function, which is reasonally
527% accurate.
528%
529% However if you use a larger radius such as "Euclidean:4" you will
530% get a much smoother distance gradient from the edge of the shape.
531% Of course a larger kernel is slower to use, and generally not needed.
532%
533% To allow the use of fractional distances that you get with diagonals
534% the actual distance is scaled by a fixed value which the user can
535% provide. This is not actually nessary for either ""Chebyshev" or
536% "Manhatten" distance kernels, but is done for all three distance
537% kernels. If no scale is provided it is set to a value of 100,
538% allowing for a maximum distance measurement of 655 pixels using a Q16
539% version of IM, from any edge. However for small images this can
540% result in quite a dark gradient.
541%
542% See the 'Distance' Morphological Method, for information of how it is
543% applied.
anthony602ab9b2010-01-05 08:06:50 +0000544%
anthony4fd27e22010-02-07 08:17:18 +0000545% # Hit-n-Miss Kernel-Lists -- Still to be implemented
546% #
547% # specifically for Pruning, Thinning, Thickening
548% #
anthony602ab9b2010-01-05 08:06:50 +0000549*/
550
cristy2be15382010-01-21 02:38:03 +0000551MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000552 const GeometryInfo *args)
553{
cristy2be15382010-01-21 02:38:03 +0000554 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000555 *kernel;
556
cristy150989e2010-02-01 14:59:39 +0000557 register long
anthony602ab9b2010-01-05 08:06:50 +0000558 i;
559
560 register long
561 u,
562 v;
563
564 double
565 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
566
cristy2be15382010-01-21 02:38:03 +0000567 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
568 if (kernel == (KernelInfo *) NULL)
anthony602ab9b2010-01-05 08:06:50 +0000569 return(kernel);
570 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
cristyc99304f2010-02-01 15:26:27 +0000571 kernel->minimum = kernel->maximum = 0.0;
572 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000573 kernel->type = type;
cristyd43a46b2010-01-21 02:13:41 +0000574 kernel->signature = MagickSignature;
anthony602ab9b2010-01-05 08:06:50 +0000575
576 switch(type) {
577 /* Convolution Kernels */
578 case GaussianKernel:
579 { double
580 sigma = fabs(args->sigma);
581
582 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
583
584 kernel->width = kernel->height =
585 GetOptimalKernelWidth2D(args->rho,sigma);
cristyc99304f2010-02-01 15:26:27 +0000586 kernel->x = kernel->y = (long) (kernel->width-1)/2;
587 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000588 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
589 kernel->height*sizeof(double));
590 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000591 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000592
593 sigma = 2.0*sigma*sigma; /* simplify the expression */
cristyc99304f2010-02-01 15:26:27 +0000594 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
595 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
596 kernel->positive_range += (
anthony602ab9b2010-01-05 08:06:50 +0000597 kernel->values[i] =
598 exp(-((double)(u*u+v*v))/sigma)
599 /* / (MagickPI*sigma) */ );
cristyc99304f2010-02-01 15:26:27 +0000600 kernel->minimum = 0;
601 kernel->maximum = kernel->values[
602 kernel->y*kernel->width+kernel->x ];
anthony602ab9b2010-01-05 08:06:50 +0000603
anthony999bb2c2010-02-18 12:38:01 +0000604 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
anthony602ab9b2010-01-05 08:06:50 +0000605
606 break;
607 }
608 case BlurKernel:
609 { double
610 sigma = fabs(args->sigma);
611
612 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
613
614 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
cristyc99304f2010-02-01 15:26:27 +0000615 kernel->x = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000616 kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +0000617 kernel->y = 0;
618 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000619 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
620 kernel->height*sizeof(double));
621 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000622 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000623
624#if 1
625#define KernelRank 3
626 /* Formula derived from GetBlurKernel() in "effect.c" (plus bug fix).
627 ** It generates a gaussian 3 times the width, and compresses it into
628 ** the expected range. This produces a closer normalization of the
629 ** resulting kernel, especially for very low sigma values.
630 ** As such while wierd it is prefered.
631 **
632 ** I am told this method originally came from Photoshop.
633 */
634 sigma *= KernelRank; /* simplify expanded curve */
cristy150989e2010-02-01 14:59:39 +0000635 v = (long) (kernel->width*KernelRank-1)/2; /* start/end points to fit range */
anthony602ab9b2010-01-05 08:06:50 +0000636 (void) ResetMagickMemory(kernel->values,0, (size_t)
637 kernel->width*sizeof(double));
638 for ( u=-v; u <= v; u++) {
639 kernel->values[(u+v)/KernelRank] +=
640 exp(-((double)(u*u))/(2.0*sigma*sigma))
641 /* / (MagickSQ2PI*sigma/KernelRank) */ ;
642 }
cristy150989e2010-02-01 14:59:39 +0000643 for (i=0; i < (long) kernel->width; i++)
cristyc99304f2010-02-01 15:26:27 +0000644 kernel->positive_range += kernel->values[i];
anthony602ab9b2010-01-05 08:06:50 +0000645#else
cristyc99304f2010-02-01 15:26:27 +0000646 for ( i=0, u=-kernel->x; i < kernel->width; i++, u++)
647 kernel->positive_range += (
anthony602ab9b2010-01-05 08:06:50 +0000648 kernel->values[i] =
649 exp(-((double)(u*u))/(2.0*sigma*sigma))
650 /* / (MagickSQ2PI*sigma) */ );
651#endif
cristyc99304f2010-02-01 15:26:27 +0000652 kernel->minimum = 0;
653 kernel->maximum = kernel->values[ kernel->x ];
anthonycc6c8362010-01-25 04:14:01 +0000654 /* Note that neither methods above generate a normalized kernel,
655 ** though it gets close. The kernel may be 'clipped' by a user defined
656 ** radius, producing a smaller (darker) kernel. Also for very small
657 ** sigma's (> 0.1) the central value becomes larger than one, and thus
658 ** producing a very bright kernel.
anthony602ab9b2010-01-05 08:06:50 +0000659 */
anthonycc6c8362010-01-25 04:14:01 +0000660
anthony602ab9b2010-01-05 08:06:50 +0000661 /* Normalize the 1D Gaussian Kernel
662 **
663 ** Because of this the divisor in the above kernel generator is
anthonyc94cdb02010-01-06 08:15:29 +0000664 ** not needed, so is not done above.
anthony602ab9b2010-01-05 08:06:50 +0000665 */
anthony999bb2c2010-02-18 12:38:01 +0000666 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
anthonycc6c8362010-01-25 04:14:01 +0000667
anthony602ab9b2010-01-05 08:06:50 +0000668 /* rotate the kernel by given angle */
anthony4fd27e22010-02-07 08:17:18 +0000669 RotateKernelInfo(kernel, args->xi);
anthony602ab9b2010-01-05 08:06:50 +0000670 break;
671 }
672 case CometKernel:
673 { double
674 sigma = fabs(args->sigma);
675
676 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
677
678 if ( args->rho < 1.0 )
679 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
680 else
681 kernel->width = (unsigned long)args->rho;
cristyc99304f2010-02-01 15:26:27 +0000682 kernel->x = kernel->y = 0;
anthony602ab9b2010-01-05 08:06:50 +0000683 kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +0000684 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000685 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
686 kernel->height*sizeof(double));
687 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000688 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000689
690 /* A comet blur is half a gaussian curve, so that the object is
691 ** blurred in one direction only. This may not be quite the right
692 ** curve so may change in the future. The function must be normalised.
693 */
694#if 1
695#define KernelRank 3
696 sigma *= KernelRank; /* simplify expanded curve */
cristy150989e2010-02-01 14:59:39 +0000697 v = (long) kernel->width*KernelRank; /* start/end points to fit range */
anthony602ab9b2010-01-05 08:06:50 +0000698 (void) ResetMagickMemory(kernel->values,0, (size_t)
699 kernel->width*sizeof(double));
700 for ( u=0; u < v; u++) {
701 kernel->values[u/KernelRank] +=
702 exp(-((double)(u*u))/(2.0*sigma*sigma))
703 /* / (MagickSQ2PI*sigma/KernelRank) */ ;
704 }
cristy150989e2010-02-01 14:59:39 +0000705 for (i=0; i < (long) kernel->width; i++)
cristyc99304f2010-02-01 15:26:27 +0000706 kernel->positive_range += kernel->values[i];
anthony602ab9b2010-01-05 08:06:50 +0000707#else
cristy150989e2010-02-01 14:59:39 +0000708 for ( i=0; i < (long) kernel->width; i++)
cristyc99304f2010-02-01 15:26:27 +0000709 kernel->positive_range += (
anthony602ab9b2010-01-05 08:06:50 +0000710 kernel->values[i] =
711 exp(-((double)(i*i))/(2.0*sigma*sigma))
712 /* / (MagickSQ2PI*sigma) */ );
713#endif
cristyc99304f2010-02-01 15:26:27 +0000714 kernel->minimum = 0;
715 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000716
anthony999bb2c2010-02-18 12:38:01 +0000717 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
718 RotateKernelInfo(kernel, args->xi); /* Rotate by angle */
anthony602ab9b2010-01-05 08:06:50 +0000719 break;
720 }
721 /* Boolean Kernels */
722 case RectangleKernel:
723 case SquareKernel:
724 {
anthony4fd27e22010-02-07 08:17:18 +0000725 double scale;
anthony602ab9b2010-01-05 08:06:50 +0000726 if ( type == SquareKernel )
727 {
728 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000729 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000730 else
cristy150989e2010-02-01 14:59:39 +0000731 kernel->width = kernel->height = (unsigned long) (2*args->rho+1);
cristyc99304f2010-02-01 15:26:27 +0000732 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony4fd27e22010-02-07 08:17:18 +0000733 scale = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +0000734 }
735 else {
cristy2be15382010-01-21 02:38:03 +0000736 /* NOTE: user defaults set in "AcquireKernelInfo()" */
anthony602ab9b2010-01-05 08:06:50 +0000737 if ( args->rho < 1.0 || args->sigma < 1.0 )
anthony83ba99b2010-01-24 08:48:15 +0000738 return(DestroyKernelInfo(kernel)); /* invalid args given */
anthony602ab9b2010-01-05 08:06:50 +0000739 kernel->width = (unsigned long)args->rho;
740 kernel->height = (unsigned long)args->sigma;
741 if ( args->xi < 0.0 || args->xi > (double)kernel->width ||
742 args->psi < 0.0 || args->psi > (double)kernel->height )
anthony83ba99b2010-01-24 08:48:15 +0000743 return(DestroyKernelInfo(kernel)); /* invalid args given */
cristyc99304f2010-02-01 15:26:27 +0000744 kernel->x = (long) args->xi;
745 kernel->y = (long) args->psi;
anthony4fd27e22010-02-07 08:17:18 +0000746 scale = 1.0;
anthony602ab9b2010-01-05 08:06:50 +0000747 }
748 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
749 kernel->height*sizeof(double));
750 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000751 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000752
anthonycc6c8362010-01-25 04:14:01 +0000753 /* set all kernel values to 1.0 */
cristy150989e2010-02-01 14:59:39 +0000754 u=(long) kernel->width*kernel->height;
755 for ( i=0; i < u; i++)
anthony4fd27e22010-02-07 08:17:18 +0000756 kernel->values[i] = scale;
757 kernel->minimum = kernel->maximum = scale; /* a flat shape */
758 kernel->positive_range = scale*u;
anthonycc6c8362010-01-25 04:14:01 +0000759 break;
anthony602ab9b2010-01-05 08:06:50 +0000760 }
761 case DiamondKernel:
762 {
763 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000764 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000765 else
766 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000767 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000768
769 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
770 kernel->height*sizeof(double));
771 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000772 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000773
anthony4fd27e22010-02-07 08:17:18 +0000774 /* set all kernel values within diamond area to scale given */
cristyc99304f2010-02-01 15:26:27 +0000775 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
776 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
777 if ((labs(u)+labs(v)) <= (long)kernel->x)
anthony4fd27e22010-02-07 08:17:18 +0000778 kernel->positive_range += kernel->values[i] = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +0000779 else
780 kernel->values[i] = nan;
anthony4fd27e22010-02-07 08:17:18 +0000781 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
anthony602ab9b2010-01-05 08:06:50 +0000782 break;
783 }
784 case DiskKernel:
785 {
786 long
787 limit;
788
789 limit = (long)(args->rho*args->rho);
anthony83ba99b2010-01-24 08:48:15 +0000790 if (args->rho < 0.1) /* default radius approx 3.5 */
791 kernel->width = kernel->height = 7L, limit = 10L;
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
anthonycc6c8362010-01-25 04:14:01 +0000801 /* set all kernel values within disk area to 1.0 */
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++)
anthony602ab9b2010-01-05 08:06:50 +0000804 if ((u*u+v*v) <= limit)
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 PlusKernel:
812 {
813 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000814 kernel->width = kernel->height = 5; /* default radius 2 */
anthony602ab9b2010-01-05 08:06:50 +0000815 else
816 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000817 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000818
819 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
820 kernel->height*sizeof(double));
821 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000822 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000823
anthonycc6c8362010-01-25 04:14:01 +0000824 /* set all kernel values along axises to 1.0 */
cristyc99304f2010-02-01 15:26:27 +0000825 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
826 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony4fd27e22010-02-07 08:17:18 +0000827 kernel->values[i] = (u == 0 || v == 0) ? args->sigma : nan;
828 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
829 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
anthony602ab9b2010-01-05 08:06:50 +0000830 break;
831 }
832 /* Distance Measuring Kernels */
833 case ChebyshevKernel:
834 {
835 double
836 scale;
837
838 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000839 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000840 else
841 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000842 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000843
844 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
845 kernel->height*sizeof(double));
846 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000847 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000848
849 scale = (args->sigma < 1.0) ? 100.0 : args->sigma;
cristyc99304f2010-02-01 15:26:27 +0000850 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
851 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
852 kernel->positive_range += ( kernel->values[i] =
anthony602ab9b2010-01-05 08:06:50 +0000853 scale*((labs(u)>labs(v)) ? labs(u) : labs(v)) );
cristyc99304f2010-02-01 15:26:27 +0000854 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000855 break;
856 }
857 case ManhattenKernel:
858 {
859 double
860 scale;
861
862 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
873 scale = (args->sigma < 1.0) ? 100.0 : args->sigma;
cristyc99304f2010-02-01 15:26:27 +0000874 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
875 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
876 kernel->positive_range += ( kernel->values[i] =
anthony602ab9b2010-01-05 08:06:50 +0000877 scale*(labs(u)+labs(v)) );
cristyc99304f2010-02-01 15:26:27 +0000878 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000879 break;
880 }
881 case EuclideanKernel:
882 {
883 double
884 scale;
885
886 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000887 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000888 else
889 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000890 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000891
892 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
893 kernel->height*sizeof(double));
894 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000895 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000896
897 scale = (args->sigma < 1.0) ? 100.0 : args->sigma;
cristyc99304f2010-02-01 15:26:27 +0000898 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
899 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
900 kernel->positive_range += ( kernel->values[i] =
anthony602ab9b2010-01-05 08:06:50 +0000901 scale*sqrt((double)(u*u+v*v)) );
cristyc99304f2010-02-01 15:26:27 +0000902 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000903 break;
904 }
905 /* Undefined Kernels */
906 case LaplacianKernel:
907 case LOGKernel:
908 case DOGKernel:
cristy150989e2010-02-01 14:59:39 +0000909 perror("Kernel Type has not been defined yet");
anthony602ab9b2010-01-05 08:06:50 +0000910 /* FALL THRU */
911 default:
912 /* Generate a No-Op minimal kernel - 1x1 pixel */
913 kernel->values=(double *)AcquireQuantumMemory((size_t)1,sizeof(double));
914 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000915 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000916 kernel->width = kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +0000917 kernel->x = kernel->x = 0;
anthony602ab9b2010-01-05 08:06:50 +0000918 kernel->type = UndefinedKernel;
cristyc99304f2010-02-01 15:26:27 +0000919 kernel->maximum =
920 kernel->positive_range =
anthonyc94cdb02010-01-06 08:15:29 +0000921 kernel->values[0] = 1.0; /* a flat single-point no-op kernel! */
anthony602ab9b2010-01-05 08:06:50 +0000922 break;
923 }
924
925 return(kernel);
926}
anthonyc94cdb02010-01-06 08:15:29 +0000927
anthony602ab9b2010-01-05 08:06:50 +0000928/*
929%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
930% %
931% %
932% %
cristy6771f1e2010-03-05 19:43:39 +0000933% C l o n e K e r n e l I n f o %
anthony4fd27e22010-02-07 08:17:18 +0000934% %
935% %
936% %
937%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
938%
939% CloneKernelInfo() creates a new clone of the given Kernel so that its can
940% be modified without effecting the original. The cloned kernel should be
941% destroyed using DestoryKernelInfo() when no longer needed.
942%
cristye6365592010-04-02 17:31:23 +0000943% The format of the CloneKernelInfo method is:
anthony4fd27e22010-02-07 08:17:18 +0000944%
anthony930be612010-02-08 04:26:15 +0000945% KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +0000946%
947% A description of each parameter follows:
948%
949% o kernel: the Morphology/Convolution kernel to be cloned
950%
951*/
cristyef656912010-03-05 19:54:59 +0000952MagickExport KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +0000953{
954 register long
955 i;
956
cristy19eb6412010-04-23 14:42:29 +0000957 KernelInfo
958 *kernel_info;
anthony4fd27e22010-02-07 08:17:18 +0000959
960 assert(kernel != (KernelInfo *) NULL);
cristy19eb6412010-04-23 14:42:29 +0000961 kernel_info=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
962 if (kernel_info == (KernelInfo *) NULL)
963 return(kernel_info);
964 *kernel_info=(*kernel); /* copy values in structure */
965 kernel_info->values=(double *) AcquireQuantumMemory(kernel->width,
966 kernel->height*sizeof(double));
967 if (kernel_info->values == (double *) NULL)
968 return(DestroyKernelInfo(kernel_info));
anthony4fd27e22010-02-07 08:17:18 +0000969 for (i=0; i < (long) (kernel->width*kernel->height); i++)
cristy19eb6412010-04-23 14:42:29 +0000970 kernel_info->values[i]=kernel->values[i];
971 return(kernel_info);
anthony4fd27e22010-02-07 08:17:18 +0000972}
973
974/*
975%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
976% %
977% %
978% %
anthony83ba99b2010-01-24 08:48:15 +0000979% D e s t r o y K e r n e l I n f o %
anthony602ab9b2010-01-05 08:06:50 +0000980% %
981% %
982% %
983%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
984%
anthony83ba99b2010-01-24 08:48:15 +0000985% DestroyKernelInfo() frees the memory used by a Convolution/Morphology
986% kernel.
anthony602ab9b2010-01-05 08:06:50 +0000987%
anthony83ba99b2010-01-24 08:48:15 +0000988% The format of the DestroyKernelInfo method is:
anthony602ab9b2010-01-05 08:06:50 +0000989%
anthony83ba99b2010-01-24 08:48:15 +0000990% KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +0000991%
992% A description of each parameter follows:
993%
994% o kernel: the Morphology/Convolution kernel to be destroyed
995%
996*/
997
anthony83ba99b2010-01-24 08:48:15 +0000998MagickExport KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +0000999{
cristy2be15382010-01-21 02:38:03 +00001000 assert(kernel != (KernelInfo *) NULL);
anthony4fd27e22010-02-07 08:17:18 +00001001
1002 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1003 kernel->height*sizeof(double));
anthony602ab9b2010-01-05 08:06:50 +00001004 kernel->values=(double *)RelinquishMagickMemory(kernel->values);
cristy2be15382010-01-21 02:38:03 +00001005 kernel=(KernelInfo *) RelinquishMagickMemory(kernel);
anthony602ab9b2010-01-05 08:06:50 +00001006 return(kernel);
1007}
anthonyc94cdb02010-01-06 08:15:29 +00001008
1009/*
1010%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1011% %
1012% %
1013% %
anthony29188a82010-01-22 10:12:34 +00001014% 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 +00001015% %
1016% %
1017% %
1018%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1019%
anthony29188a82010-01-22 10:12:34 +00001020% MorphologyImageChannel() applies a user supplied kernel to the image
1021% according to the given mophology method.
anthony602ab9b2010-01-05 08:06:50 +00001022%
1023% The given kernel is assumed to have been pre-scaled appropriatally, usally
1024% by the kernel generator.
1025%
1026% The format of the MorphologyImage method is:
1027%
cristyef656912010-03-05 19:54:59 +00001028% Image *MorphologyImage(const Image *image,MorphologyMethod method,
1029% const long iterations,KernelInfo *kernel,ExceptionInfo *exception)
anthony29188a82010-01-22 10:12:34 +00001030% Image *MorphologyImageChannel(const Image *image, const ChannelType
cristyef656912010-03-05 19:54:59 +00001031% channel,MorphologyMethod method,const long iterations,
1032% KernelInfo *kernel,ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001033%
1034% A description of each parameter follows:
1035%
1036% o image: the image.
1037%
1038% o method: the morphology method to be applied.
1039%
1040% o iterations: apply the operation this many times (or no change).
1041% A value of -1 means loop until no change found.
1042% How this is applied may depend on the morphology method.
1043% Typically this is a value of 1.
1044%
1045% o channel: the channel type.
1046%
1047% o kernel: An array of double representing the morphology kernel.
anthony29188a82010-01-22 10:12:34 +00001048% Warning: kernel may be normalized for the Convolve method.
anthony602ab9b2010-01-05 08:06:50 +00001049%
1050% o exception: return any errors or warnings in this structure.
1051%
1052%
1053% TODO: bias and auto-scale handling of the kernel for convolution
1054% The given kernel is assumed to have been pre-scaled appropriatally, usally
1055% by the kernel generator.
1056%
1057*/
1058
anthony930be612010-02-08 04:26:15 +00001059
anthony602ab9b2010-01-05 08:06:50 +00001060/* Internal function
anthony930be612010-02-08 04:26:15 +00001061 * Apply the Low-Level Morphology Method using the given Kernel
1062 * Returning the number of pixels that changed.
1063 * Two pre-created images must be provided, no image is created.
anthony602ab9b2010-01-05 08:06:50 +00001064 */
1065static unsigned long MorphologyApply(const Image *image, Image
1066 *result_image, const MorphologyMethod method, const ChannelType channel,
cristy2be15382010-01-21 02:38:03 +00001067 const KernelInfo *kernel, ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001068{
cristy2be15382010-01-21 02:38:03 +00001069#define MorphologyTag "Morphology/Image"
anthony602ab9b2010-01-05 08:06:50 +00001070
1071 long
cristy150989e2010-02-01 14:59:39 +00001072 progress,
anthony29188a82010-01-22 10:12:34 +00001073 y, offx, offy,
anthony602ab9b2010-01-05 08:06:50 +00001074 changed;
1075
1076 MagickBooleanType
1077 status;
1078
1079 MagickPixelPacket
1080 bias;
1081
1082 CacheView
1083 *p_view,
1084 *q_view;
1085
anthony4fd27e22010-02-07 08:17:18 +00001086 /* Only the most basic morphology is actually performed by this routine */
anthony4fd27e22010-02-07 08:17:18 +00001087
anthony602ab9b2010-01-05 08:06:50 +00001088 /*
anthony4fd27e22010-02-07 08:17:18 +00001089 Apply Basic Morphology to Image.
anthony602ab9b2010-01-05 08:06:50 +00001090 */
1091 status=MagickTrue;
1092 changed=0;
1093 progress=0;
1094
1095 GetMagickPixelPacket(image,&bias);
1096 SetMagickPixelPacketBias(image,&bias);
anthonycc6c8362010-01-25 04:14:01 +00001097 /* Future: handle auto-bias from user, based on kernel input */
anthony602ab9b2010-01-05 08:06:50 +00001098
1099 p_view=AcquireCacheView(image);
1100 q_view=AcquireCacheView(result_image);
anthony29188a82010-01-22 10:12:34 +00001101
anthonycc6c8362010-01-25 04:14:01 +00001102 /* Some methods (including convolve) needs use a reflected kernel.
1103 * Adjust 'origin' offsets for this reflected kernel.
anthony29188a82010-01-22 10:12:34 +00001104 */
cristyc99304f2010-02-01 15:26:27 +00001105 offx = kernel->x;
1106 offy = kernel->y;
anthony29188a82010-01-22 10:12:34 +00001107 switch(method) {
1108 case ErodeMorphology:
1109 case ErodeIntensityMorphology:
anthony999bb2c2010-02-18 12:38:01 +00001110 /* kernel is user as is, without reflection */
anthony29188a82010-01-22 10:12:34 +00001111 break;
anthony930be612010-02-08 04:26:15 +00001112 case ConvolveMorphology:
1113 case DilateMorphology:
1114 case DilateIntensityMorphology:
1115 case DistanceMorphology:
anthony999bb2c2010-02-18 12:38:01 +00001116 /* kernel needs to used with reflection */
cristy150989e2010-02-01 14:59:39 +00001117 offx = (long) kernel->width-offx-1;
1118 offy = (long) kernel->height-offy-1;
anthony29188a82010-01-22 10:12:34 +00001119 break;
anthony930be612010-02-08 04:26:15 +00001120 default:
1121 perror("Not a low level Morpholgy Method");
1122 break;
anthony29188a82010-01-22 10:12:34 +00001123 }
1124
anthony602ab9b2010-01-05 08:06:50 +00001125#if defined(MAGICKCORE_OPENMP_SUPPORT)
1126 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1127#endif
cristy150989e2010-02-01 14:59:39 +00001128 for (y=0; y < (long) image->rows; y++)
anthony602ab9b2010-01-05 08:06:50 +00001129 {
1130 MagickBooleanType
1131 sync;
1132
1133 register const PixelPacket
1134 *restrict p;
1135
1136 register const IndexPacket
1137 *restrict p_indexes;
1138
1139 register PixelPacket
1140 *restrict q;
1141
1142 register IndexPacket
1143 *restrict q_indexes;
1144
cristy150989e2010-02-01 14:59:39 +00001145 register long
anthony602ab9b2010-01-05 08:06:50 +00001146 x;
1147
anthony29188a82010-01-22 10:12:34 +00001148 unsigned long
anthony602ab9b2010-01-05 08:06:50 +00001149 r;
1150
1151 if (status == MagickFalse)
1152 continue;
anthony29188a82010-01-22 10:12:34 +00001153 p=GetCacheViewVirtualPixels(p_view, -offx, y-offy,
1154 image->columns+kernel->width, kernel->height, exception);
anthony602ab9b2010-01-05 08:06:50 +00001155 q=GetCacheViewAuthenticPixels(q_view,0,y,result_image->columns,1,
1156 exception);
1157 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1158 {
1159 status=MagickFalse;
1160 continue;
1161 }
1162 p_indexes=GetCacheViewVirtualIndexQueue(p_view);
1163 q_indexes=GetCacheViewAuthenticIndexQueue(q_view);
anthony29188a82010-01-22 10:12:34 +00001164 r = (image->columns+kernel->width)*offy+offx; /* constant */
1165
cristy150989e2010-02-01 14:59:39 +00001166 for (x=0; x < (long) image->columns; x++)
anthony602ab9b2010-01-05 08:06:50 +00001167 {
cristy150989e2010-02-01 14:59:39 +00001168 long
anthony602ab9b2010-01-05 08:06:50 +00001169 v;
1170
cristy150989e2010-02-01 14:59:39 +00001171 register long
anthony602ab9b2010-01-05 08:06:50 +00001172 u;
1173
1174 register const double
1175 *restrict k;
1176
1177 register const PixelPacket
1178 *restrict k_pixels;
1179
1180 register const IndexPacket
1181 *restrict k_indexes;
1182
1183 MagickPixelPacket
1184 result;
1185
anthony29188a82010-01-22 10:12:34 +00001186 /* Copy input to ouput image for unused channels
anthony83ba99b2010-01-24 08:48:15 +00001187 * This removes need for 'cloning' a new image every iteration
anthony29188a82010-01-22 10:12:34 +00001188 */
anthony602ab9b2010-01-05 08:06:50 +00001189 *q = p[r];
1190 if (image->colorspace == CMYKColorspace)
1191 q_indexes[x] = p_indexes[r];
1192
cristy5ee247a2010-02-12 15:42:34 +00001193 result.green=(MagickRealType) 0;
1194 result.blue=(MagickRealType) 0;
1195 result.opacity=(MagickRealType) 0;
1196 result.index=(MagickRealType) 0;
anthony602ab9b2010-01-05 08:06:50 +00001197 switch (method) {
1198 case ConvolveMorphology:
anthony930be612010-02-08 04:26:15 +00001199 /* Set the user defined bias of the weighted average output
1200 **
1201 ** FUTURE: provide some way for internal functions to disable
1202 ** user defined bias and scaling effects.
1203 */
anthony602ab9b2010-01-05 08:06:50 +00001204 result=bias;
anthony930be612010-02-08 04:26:15 +00001205 break;
anthony83ba99b2010-01-24 08:48:15 +00001206 case DilateMorphology:
anthony29188a82010-01-22 10:12:34 +00001207 result.red =
1208 result.green =
1209 result.blue =
1210 result.opacity =
1211 result.index = -MagickHuge;
1212 break;
1213 case ErodeMorphology:
1214 result.red =
1215 result.green =
1216 result.blue =
1217 result.opacity =
1218 result.index = +MagickHuge;
1219 break;
anthony4fd27e22010-02-07 08:17:18 +00001220 case DilateIntensityMorphology:
1221 case ErodeIntensityMorphology:
1222 result.red = 0.0; /* flag indicating first match found */
1223 break;
anthony602ab9b2010-01-05 08:06:50 +00001224 default:
anthony29188a82010-01-22 10:12:34 +00001225 /* Otherwise just start with the original pixel value */
cristy150989e2010-02-01 14:59:39 +00001226 result.red = (MagickRealType) p[r].red;
1227 result.green = (MagickRealType) p[r].green;
1228 result.blue = (MagickRealType) p[r].blue;
1229 result.opacity = QuantumRange - (MagickRealType) p[r].opacity;
anthony602ab9b2010-01-05 08:06:50 +00001230 if ( image->colorspace == CMYKColorspace)
cristy150989e2010-02-01 14:59:39 +00001231 result.index = (MagickRealType) p_indexes[r];
anthony602ab9b2010-01-05 08:06:50 +00001232 break;
1233 }
1234
1235 switch ( method ) {
1236 case ConvolveMorphology:
anthony930be612010-02-08 04:26:15 +00001237 /* Weighted Average of pixels using reflected kernel
1238 **
1239 ** NOTE for correct working of this operation for asymetrical
1240 ** kernels, the kernel needs to be applied in its reflected form.
1241 ** That is its values needs to be reversed.
1242 **
1243 ** Correlation is actually the same as this but without reflecting
1244 ** the kernel, and thus 'lower-level' that Convolution. However
1245 ** as Convolution is the more common method used, and it does not
1246 ** really cost us much in terms of processing to use a reflected
1247 ** kernel it is Convolution that is implemented.
1248 **
1249 ** Correlation will have its kernel reflected before calling
1250 ** this function to do a Convolve.
1251 **
1252 ** For more details of Correlation vs Convolution see
1253 ** http://www.cs.umd.edu/~djacobs/CMSC426/Convolution.pdf
1254 */
anthony602ab9b2010-01-05 08:06:50 +00001255 if (((channel & OpacityChannel) == 0) ||
1256 (image->matte == MagickFalse))
1257 {
anthony930be612010-02-08 04:26:15 +00001258 /* Convolution without transparency effects */
anthony29188a82010-01-22 10:12:34 +00001259 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001260 k_pixels = p;
1261 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001262 for (v=0; v < (long) kernel->height; v++) {
1263 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001264 if ( IsNan(*k) ) continue;
1265 result.red += (*k)*k_pixels[u].red;
1266 result.green += (*k)*k_pixels[u].green;
1267 result.blue += (*k)*k_pixels[u].blue;
anthony83ba99b2010-01-24 08:48:15 +00001268 /* result.opacity += not involved here */
anthony602ab9b2010-01-05 08:06:50 +00001269 if ( image->colorspace == CMYKColorspace)
1270 result.index += (*k)*k_indexes[u];
1271 }
1272 k_pixels += image->columns+kernel->width;
1273 k_indexes += image->columns+kernel->width;
1274 }
anthony602ab9b2010-01-05 08:06:50 +00001275 }
1276 else
1277 { /* Kernel & Alpha weighted Convolution */
1278 MagickRealType
1279 alpha, /* alpha value * kernel weighting */
1280 gamma; /* weighting divisor */
1281
1282 gamma=0.0;
anthony29188a82010-01-22 10:12:34 +00001283 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001284 k_pixels = p;
1285 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001286 for (v=0; v < (long) kernel->height; v++) {
1287 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001288 if ( IsNan(*k) ) continue;
1289 alpha=(*k)*(QuantumScale*(QuantumRange-
1290 k_pixels[u].opacity));
1291 gamma += alpha;
1292 result.red += alpha*k_pixels[u].red;
1293 result.green += alpha*k_pixels[u].green;
1294 result.blue += alpha*k_pixels[u].blue;
anthony83ba99b2010-01-24 08:48:15 +00001295 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00001296 if ( image->colorspace == CMYKColorspace)
1297 result.index += alpha*k_indexes[u];
1298 }
1299 k_pixels += image->columns+kernel->width;
1300 k_indexes += image->columns+kernel->width;
1301 }
1302 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
anthony83ba99b2010-01-24 08:48:15 +00001303 result.red *= gamma;
1304 result.green *= gamma;
1305 result.blue *= gamma;
1306 result.opacity *= gamma;
1307 result.index *= gamma;
anthony602ab9b2010-01-05 08:06:50 +00001308 }
1309 break;
1310
anthony4fd27e22010-02-07 08:17:18 +00001311 case ErodeMorphology:
anthony930be612010-02-08 04:26:15 +00001312 /* Minimize Value within kernel neighbourhood
1313 **
1314 ** NOTE that the kernel is not reflected for this operation!
1315 **
1316 ** NOTE: in normal Greyscale Morphology, the kernel value should
1317 ** be added to the real value, this is currently not done, due to
1318 ** the nature of the boolean kernels being used.
1319 */
anthony4fd27e22010-02-07 08:17:18 +00001320 k = kernel->values;
1321 k_pixels = p;
1322 k_indexes = p_indexes;
1323 for (v=0; v < (long) kernel->height; v++) {
1324 for (u=0; u < (long) kernel->width; u++, k++) {
1325 if ( IsNan(*k) || (*k) < 0.5 ) continue;
1326 Minimize(result.red, (double) k_pixels[u].red);
1327 Minimize(result.green, (double) k_pixels[u].green);
1328 Minimize(result.blue, (double) k_pixels[u].blue);
1329 Minimize(result.opacity, QuantumRange-(double) k_pixels[u].opacity);
1330 if ( image->colorspace == CMYKColorspace)
1331 Minimize(result.index, (double) k_indexes[u]);
1332 }
1333 k_pixels += image->columns+kernel->width;
1334 k_indexes += image->columns+kernel->width;
1335 }
1336 break;
1337
anthony83ba99b2010-01-24 08:48:15 +00001338 case DilateMorphology:
anthony930be612010-02-08 04:26:15 +00001339 /* Maximize Value within kernel neighbourhood
1340 **
1341 ** NOTE for correct working of this operation for asymetrical
1342 ** kernels, the kernel needs to be applied in its reflected form.
1343 ** That is its values needs to be reversed.
1344 **
1345 ** NOTE: in normal Greyscale Morphology, the kernel value should
1346 ** be added to the real value, this is currently not done, due to
1347 ** the nature of the boolean kernels being used.
1348 **
1349 */
anthony29188a82010-01-22 10:12:34 +00001350 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001351 k_pixels = p;
1352 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001353 for (v=0; v < (long) kernel->height; v++) {
1354 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001355 if ( IsNan(*k) || (*k) < 0.5 ) continue;
cristy150989e2010-02-01 14:59:39 +00001356 Maximize(result.red, (double) k_pixels[u].red);
1357 Maximize(result.green, (double) k_pixels[u].green);
1358 Maximize(result.blue, (double) k_pixels[u].blue);
1359 Maximize(result.opacity, QuantumRange-(double) k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00001360 if ( image->colorspace == CMYKColorspace)
cristy150989e2010-02-01 14:59:39 +00001361 Maximize(result.index, (double) k_indexes[u]);
anthony602ab9b2010-01-05 08:06:50 +00001362 }
1363 k_pixels += image->columns+kernel->width;
1364 k_indexes += image->columns+kernel->width;
1365 }
anthony602ab9b2010-01-05 08:06:50 +00001366 break;
1367
anthony4fd27e22010-02-07 08:17:18 +00001368 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001369 /* Select Pixel with Minimum Intensity within kernel neighbourhood
1370 **
1371 ** WARNING: the intensity test fails for CMYK and does not
1372 ** take into account the moderating effect of teh alpha channel
1373 ** on the intensity.
1374 **
1375 ** NOTE that the kernel is not reflected for this operation!
1376 */
anthony602ab9b2010-01-05 08:06:50 +00001377 k = kernel->values;
1378 k_pixels = p;
1379 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001380 for (v=0; v < (long) kernel->height; v++) {
1381 for (u=0; u < (long) kernel->width; u++, k++) {
anthony602ab9b2010-01-05 08:06:50 +00001382 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony4fd27e22010-02-07 08:17:18 +00001383 if ( result.red == 0.0 ||
1384 PixelIntensity(&(k_pixels[u])) < PixelIntensity(q) ) {
1385 /* copy the whole pixel - no channel selection */
1386 *q = k_pixels[u];
1387 if ( result.red > 0.0 ) changed++;
1388 result.red = 1.0;
1389 }
anthony602ab9b2010-01-05 08:06:50 +00001390 }
1391 k_pixels += image->columns+kernel->width;
1392 k_indexes += image->columns+kernel->width;
1393 }
anthony602ab9b2010-01-05 08:06:50 +00001394 break;
1395
anthony83ba99b2010-01-24 08:48:15 +00001396 case DilateIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001397 /* Select Pixel with Maximum Intensity within kernel neighbourhood
1398 **
1399 ** WARNING: the intensity test fails for CMYK and does not
1400 ** take into account the moderating effect of teh alpha channel
1401 ** on the intensity.
1402 **
1403 ** NOTE for correct working of this operation for asymetrical
1404 ** kernels, the kernel needs to be applied in its reflected form.
1405 ** That is its values needs to be reversed.
1406 */
anthony29188a82010-01-22 10:12:34 +00001407 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001408 k_pixels = p;
1409 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001410 for (v=0; v < (long) kernel->height; v++) {
1411 for (u=0; u < (long) kernel->width; u++, k--) {
anthony29188a82010-01-22 10:12:34 +00001412 if ( IsNan(*k) || (*k) < 0.5 ) continue; /* boolean kernel */
1413 if ( result.red == 0.0 ||
1414 PixelIntensity(&(k_pixels[u])) > PixelIntensity(q) ) {
1415 /* copy the whole pixel - no channel selection */
1416 *q = k_pixels[u];
1417 if ( result.red > 0.0 ) changed++;
1418 result.red = 1.0;
1419 }
anthony602ab9b2010-01-05 08:06:50 +00001420 }
1421 k_pixels += image->columns+kernel->width;
1422 k_indexes += image->columns+kernel->width;
1423 }
anthony602ab9b2010-01-05 08:06:50 +00001424 break;
1425
anthony602ab9b2010-01-05 08:06:50 +00001426 case DistanceMorphology:
anthony930be612010-02-08 04:26:15 +00001427 /* Add kernel Value and select the minimum value found.
1428 ** The result is a iterative distance from edge of image shape.
1429 **
1430 ** All Distance Kernels are symetrical, but that may not always
1431 ** be the case. For example how about a distance from left edges?
1432 ** To work correctly with asymetrical kernels the reflected kernel
1433 ** needs to be applied.
1434 */
anthony602ab9b2010-01-05 08:06:50 +00001435#if 0
anthony930be612010-02-08 04:26:15 +00001436 /* No need to do distance morphology if original value is zero
1437 ** Unfortunatally I have not been able to get this right
1438 ** when channel selection also becomes involved. -- Arrgghhh
1439 */
1440 if ( ((channel & RedChannel) == 0 && p[r].red == 0)
1441 || ((channel & GreenChannel) == 0 && p[r].green == 0)
1442 || ((channel & BlueChannel) == 0 && p[r].blue == 0)
1443 || ((channel & OpacityChannel) == 0 && p[r].opacity == 0)
1444 || (( (channel & IndexChannel) == 0
1445 || image->colorspace != CMYKColorspace
1446 ) && p_indexes[x] ==0 )
1447 )
1448 break;
anthony602ab9b2010-01-05 08:06:50 +00001449#endif
anthony29188a82010-01-22 10:12:34 +00001450 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001451 k_pixels = p;
1452 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001453 for (v=0; v < (long) kernel->height; v++) {
1454 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001455 if ( IsNan(*k) ) continue;
1456 Minimize(result.red, (*k)+k_pixels[u].red);
1457 Minimize(result.green, (*k)+k_pixels[u].green);
1458 Minimize(result.blue, (*k)+k_pixels[u].blue);
1459 Minimize(result.opacity, (*k)+QuantumRange-k_pixels[u].opacity);
1460 if ( image->colorspace == CMYKColorspace)
1461 Minimize(result.index, (*k)+k_indexes[u]);
1462 }
1463 k_pixels += image->columns+kernel->width;
1464 k_indexes += image->columns+kernel->width;
1465 }
anthony602ab9b2010-01-05 08:06:50 +00001466 break;
1467
1468 case UndefinedMorphology:
1469 default:
1470 break; /* Do nothing */
anthony83ba99b2010-01-24 08:48:15 +00001471 }
1472 switch ( method ) {
1473 case UndefinedMorphology:
1474 case DilateIntensityMorphology:
1475 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001476 break; /* full pixel was directly assigned - not a channel method */
anthony83ba99b2010-01-24 08:48:15 +00001477 default:
1478 /* Assign the results */
1479 if ((channel & RedChannel) != 0)
1480 q->red = ClampToQuantum(result.red);
1481 if ((channel & GreenChannel) != 0)
1482 q->green = ClampToQuantum(result.green);
1483 if ((channel & BlueChannel) != 0)
1484 q->blue = ClampToQuantum(result.blue);
1485 if ((channel & OpacityChannel) != 0
1486 && image->matte == MagickTrue )
1487 q->opacity = ClampToQuantum(QuantumRange-result.opacity);
1488 if ((channel & IndexChannel) != 0
1489 && image->colorspace == CMYKColorspace)
1490 q_indexes[x] = ClampToQuantum(result.index);
1491 break;
1492 }
1493 if ( ( p[r].red != q->red )
1494 || ( p[r].green != q->green )
1495 || ( p[r].blue != q->blue )
1496 || ( p[r].opacity != q->opacity )
1497 || ( image->colorspace == CMYKColorspace &&
1498 p_indexes[r] != q_indexes[x] ) )
1499 changed++; /* The pixel had some value changed! */
anthony602ab9b2010-01-05 08:06:50 +00001500 p++;
1501 q++;
anthony83ba99b2010-01-24 08:48:15 +00001502 } /* x */
anthony602ab9b2010-01-05 08:06:50 +00001503 sync=SyncCacheViewAuthenticPixels(q_view,exception);
1504 if (sync == MagickFalse)
1505 status=MagickFalse;
1506 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1507 {
1508 MagickBooleanType
1509 proceed;
1510
1511#if defined(MAGICKCORE_OPENMP_SUPPORT)
1512 #pragma omp critical (MagickCore_MorphologyImage)
1513#endif
1514 proceed=SetImageProgress(image,MorphologyTag,progress++,image->rows);
1515 if (proceed == MagickFalse)
1516 status=MagickFalse;
1517 }
anthony83ba99b2010-01-24 08:48:15 +00001518 } /* y */
anthony602ab9b2010-01-05 08:06:50 +00001519 result_image->type=image->type;
1520 q_view=DestroyCacheView(q_view);
1521 p_view=DestroyCacheView(p_view);
cristy150989e2010-02-01 14:59:39 +00001522 return(status ? (unsigned long) changed : 0);
anthony602ab9b2010-01-05 08:06:50 +00001523}
1524
anthony4fd27e22010-02-07 08:17:18 +00001525
1526MagickExport Image *MorphologyImage(const Image *image, const MorphologyMethod
anthony930be612010-02-08 04:26:15 +00001527 method, const long iterations,const KernelInfo *kernel, ExceptionInfo
1528 *exception)
cristy2be15382010-01-21 02:38:03 +00001529{
1530 Image
1531 *morphology_image;
1532
1533 morphology_image=MorphologyImageChannel(image,DefaultChannels,method,
1534 iterations,kernel,exception);
1535 return(morphology_image);
1536}
1537
anthony4fd27e22010-02-07 08:17:18 +00001538
cristyef656912010-03-05 19:54:59 +00001539MagickExport Image *MorphologyImageChannel(const Image *image,
1540 const ChannelType channel,const MorphologyMethod method,
1541 const long iterations,const KernelInfo *kernel,ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001542{
cristy150989e2010-02-01 14:59:39 +00001543 long
1544 count;
anthony602ab9b2010-01-05 08:06:50 +00001545
1546 Image
1547 *new_image,
anthony4fd27e22010-02-07 08:17:18 +00001548 *old_image,
1549 *grad_image;
anthony602ab9b2010-01-05 08:06:50 +00001550
anthonycc6c8362010-01-25 04:14:01 +00001551 const char
1552 *artifact;
1553
cristy150989e2010-02-01 14:59:39 +00001554 unsigned long
1555 changed,
1556 limit;
1557
anthony4fd27e22010-02-07 08:17:18 +00001558 KernelInfo
1559 *curr_kernel;
1560
1561 MorphologyMethod
1562 curr_method;
1563
anthony602ab9b2010-01-05 08:06:50 +00001564 assert(image != (Image *) NULL);
1565 assert(image->signature == MagickSignature);
anthony4fd27e22010-02-07 08:17:18 +00001566 assert(kernel != (KernelInfo *) NULL);
1567 assert(kernel->signature == MagickSignature);
anthony602ab9b2010-01-05 08:06:50 +00001568 assert(exception != (ExceptionInfo *) NULL);
1569 assert(exception->signature == MagickSignature);
1570
anthony602ab9b2010-01-05 08:06:50 +00001571 if ( iterations == 0 )
1572 return((Image *)NULL); /* null operation - nothing to do! */
1573
1574 /* kernel must be valid at this point
1575 * (except maybe for posible future morphology methods like "Prune"
1576 */
cristy2be15382010-01-21 02:38:03 +00001577 assert(kernel != (KernelInfo *)NULL);
anthony602ab9b2010-01-05 08:06:50 +00001578
anthony4fd27e22010-02-07 08:17:18 +00001579 count = 0; /* interation count */
1580 changed = 1; /* if compound method assume image was changed */
anthony930be612010-02-08 04:26:15 +00001581 curr_kernel = (KernelInfo *)kernel; /* allow kernel and method */
1582 curr_method = method; /* to be changed as nessary */
anthony4fd27e22010-02-07 08:17:18 +00001583
cristy150989e2010-02-01 14:59:39 +00001584 limit = (unsigned long) iterations;
anthony602ab9b2010-01-05 08:06:50 +00001585 if ( iterations < 0 )
1586 limit = image->columns > image->rows ? image->columns : image->rows;
1587
anthony4fd27e22010-02-07 08:17:18 +00001588 /* Third-level morphology methods */
cristy5ee247a2010-02-12 15:42:34 +00001589 grad_image=(Image *) NULL;
anthony4fd27e22010-02-07 08:17:18 +00001590 switch( curr_method ) {
1591 case EdgeMorphology:
1592 grad_image = MorphologyImageChannel(image, channel,
1593 DilateMorphology, iterations, curr_kernel, exception);
1594 /* FALL-THRU */
1595 case EdgeInMorphology:
1596 curr_method = ErodeMorphology;
anthony602ab9b2010-01-05 08:06:50 +00001597 break;
anthony4fd27e22010-02-07 08:17:18 +00001598 case EdgeOutMorphology:
1599 curr_method = DilateMorphology;
anthony602ab9b2010-01-05 08:06:50 +00001600 break;
anthony4fd27e22010-02-07 08:17:18 +00001601 case TopHatMorphology:
1602 curr_method = OpenMorphology;
1603 break;
1604 case BottomHatMorphology:
1605 curr_method = CloseMorphology;
1606 break;
1607 default:
anthony930be612010-02-08 04:26:15 +00001608 break; /* not a third-level method */
anthony4fd27e22010-02-07 08:17:18 +00001609 }
1610
1611 /* Second-level morphology methods */
1612 switch( curr_method ) {
anthony930be612010-02-08 04:26:15 +00001613 case OpenMorphology:
1614 /* Open is a Erode then a Dilate without reflection */
anthony4fd27e22010-02-07 08:17:18 +00001615 new_image = MorphologyImageChannel(image, channel,
1616 ErodeMorphology, iterations, curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001617 if (new_image == (Image *) NULL)
1618 return((Image *) NULL);
anthony4fd27e22010-02-07 08:17:18 +00001619 curr_method = DilateMorphology;
1620 break;
anthony602ab9b2010-01-05 08:06:50 +00001621 case OpenIntensityMorphology:
anthony4fd27e22010-02-07 08:17:18 +00001622 new_image = MorphologyImageChannel(image, channel,
1623 ErodeIntensityMorphology, iterations, curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001624 if (new_image == (Image *) NULL)
1625 return((Image *) NULL);
anthony4fd27e22010-02-07 08:17:18 +00001626 curr_method = DilateIntensityMorphology;
1627 break;
anthony930be612010-02-08 04:26:15 +00001628
1629 case CloseMorphology:
1630 /* Close is a Dilate then Erode using reflected kernel */
1631 /* A reflected kernel is needed for a Close */
1632 if ( curr_kernel == kernel )
1633 curr_kernel = CloneKernelInfo(kernel);
1634 RotateKernelInfo(curr_kernel,180);
1635 new_image = MorphologyImageChannel(image, channel,
1636 DilateMorphology, iterations, curr_kernel, exception);
1637 if (new_image == (Image *) NULL)
1638 return((Image *) NULL);
1639 curr_method = ErodeMorphology;
1640 break;
anthony4fd27e22010-02-07 08:17:18 +00001641 case CloseIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001642 /* A reflected kernel is needed for a Close */
1643 if ( curr_kernel == kernel )
1644 curr_kernel = CloneKernelInfo(kernel);
anthony4fd27e22010-02-07 08:17:18 +00001645 RotateKernelInfo(curr_kernel,180);
1646 new_image = MorphologyImageChannel(image, channel,
1647 DilateIntensityMorphology, iterations, curr_kernel, exception);
1648 if (new_image == (Image *) NULL)
1649 return((Image *) NULL);
1650 curr_method = ErodeIntensityMorphology;
anthony602ab9b2010-01-05 08:06:50 +00001651 break;
1652
anthony930be612010-02-08 04:26:15 +00001653 case CorrelateMorphology:
1654 /* A Correlation is actually a Convolution with a reflected kernel.
1655 ** However a Convolution is a weighted sum with a reflected kernel.
1656 ** It may seem stange to convert a Correlation into a Convolution
1657 ** as the Correleation is the simplier method, but Convolution is
1658 ** much more commonly used, and it makes sense to implement it directly
1659 ** so as to avoid the need to duplicate the kernel when it is not
1660 ** required (which is typically the default).
1661 */
1662 if ( curr_kernel == kernel )
1663 curr_kernel = CloneKernelInfo(kernel);
1664 RotateKernelInfo(curr_kernel,180);
1665 curr_method = ConvolveMorphology;
1666 /* FALL-THRU into Correlate (weigthed sum without reflection) */
1667
anthonyc94cdb02010-01-06 08:15:29 +00001668 case ConvolveMorphology:
anthony4fd27e22010-02-07 08:17:18 +00001669 /* Scale or Normalize kernel, according to user wishes
anthony930be612010-02-08 04:26:15 +00001670 ** before using it for the Convolve/Correlate method.
1671 **
1672 ** FUTURE: provide some way for internal functions to disable
1673 ** user bias and scaling effects.
anthonycc6c8362010-01-25 04:14:01 +00001674 */
1675 artifact = GetImageArtifact(image,"convolve:scale");
anthony4fd27e22010-02-07 08:17:18 +00001676 if ( artifact != (char *)NULL ) {
cristy19eb6412010-04-23 14:42:29 +00001677 GeometryFlags
anthony999bb2c2010-02-18 12:38:01 +00001678 flags;
1679 GeometryInfo
1680 args;
1681
anthony930be612010-02-08 04:26:15 +00001682 if ( curr_kernel == kernel )
1683 curr_kernel = CloneKernelInfo(kernel);
anthony999bb2c2010-02-18 12:38:01 +00001684
1685 args.rho = 1.0;
cristy19eb6412010-04-23 14:42:29 +00001686 flags = (GeometryFlags) ParseGeometry(artifact, &args);
anthony999bb2c2010-02-18 12:38:01 +00001687 ScaleKernelInfo(curr_kernel, args.rho, flags);
anthony4fd27e22010-02-07 08:17:18 +00001688 }
anthony930be612010-02-08 04:26:15 +00001689 /* FALL-THRU to do the first, and typically the only iteration */
anthony4fd27e22010-02-07 08:17:18 +00001690
anthony602ab9b2010-01-05 08:06:50 +00001691 default:
anthony930be612010-02-08 04:26:15 +00001692 /* Do a single iteration using the Low-Level Morphology method!
1693 ** This ensures a "new_image" has been generated, but allows us to skip
1694 ** the creation of 'old_image' if no more iterations are needed.
1695 **
1696 ** The "curr_method" should also be set to a low-level method that is
1697 ** understood by the MorphologyApply() internal function.
anthony602ab9b2010-01-05 08:06:50 +00001698 */
1699 new_image=CloneImage(image,0,0,MagickTrue,exception);
1700 if (new_image == (Image *) NULL)
1701 return((Image *) NULL);
1702 if (SetImageStorageClass(new_image,DirectClass) == MagickFalse)
1703 {
1704 InheritException(exception,&new_image->exception);
1705 new_image=DestroyImage(new_image);
1706 return((Image *) NULL);
1707 }
anthony4fd27e22010-02-07 08:17:18 +00001708 changed = MorphologyApply(image,new_image,curr_method,channel,curr_kernel,
anthony602ab9b2010-01-05 08:06:50 +00001709 exception);
1710 count++;
1711 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
cristy150989e2010-02-01 14:59:39 +00001712 fprintf(stderr, "Morphology %s:%ld => Changed %lu\n",
anthony4fd27e22010-02-07 08:17:18 +00001713 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
anthony602ab9b2010-01-05 08:06:50 +00001714 count, changed);
anthony930be612010-02-08 04:26:15 +00001715 break;
anthony602ab9b2010-01-05 08:06:50 +00001716 }
1717
anthony930be612010-02-08 04:26:15 +00001718 /* At this point the "curr_method" should not only be set to a low-level
1719 ** method that is understood by the MorphologyApply() internal function,
1720 ** but "new_image" should now be defined, as the image to apply the
1721 ** "curr_method" to.
1722 */
1723
1724 /* Repeat the low-level morphology until count or no change reached */
cristy150989e2010-02-01 14:59:39 +00001725 if ( count < (long) limit && changed > 0 ) {
anthony602ab9b2010-01-05 08:06:50 +00001726 old_image = CloneImage(new_image,0,0,MagickTrue,exception);
1727 if (old_image == (Image *) NULL)
1728 return(DestroyImage(new_image));
1729 if (SetImageStorageClass(old_image,DirectClass) == MagickFalse)
1730 {
1731 InheritException(exception,&old_image->exception);
1732 old_image=DestroyImage(old_image);
1733 return(DestroyImage(new_image));
1734 }
cristy150989e2010-02-01 14:59:39 +00001735 while( count < (long) limit && changed != 0 )
anthony602ab9b2010-01-05 08:06:50 +00001736 {
1737 Image *tmp = old_image;
1738 old_image = new_image;
1739 new_image = tmp;
anthony4fd27e22010-02-07 08:17:18 +00001740 changed = MorphologyApply(old_image,new_image,curr_method,channel,
1741 curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001742 count++;
1743 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
cristy150989e2010-02-01 14:59:39 +00001744 fprintf(stderr, "Morphology %s:%ld => Changed %lu\n",
anthony4fd27e22010-02-07 08:17:18 +00001745 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
anthony602ab9b2010-01-05 08:06:50 +00001746 count, changed);
1747 }
cristy150989e2010-02-01 14:59:39 +00001748 old_image=DestroyImage(old_image);
anthony602ab9b2010-01-05 08:06:50 +00001749 }
anthony930be612010-02-08 04:26:15 +00001750
1751 /* We are finished with kernel - destroy it if we made a clone */
anthony4fd27e22010-02-07 08:17:18 +00001752 if ( curr_kernel != kernel )
1753 curr_kernel=DestroyKernelInfo(curr_kernel);
1754
anthony930be612010-02-08 04:26:15 +00001755 /* Third-level Subtractive methods post-processing */
anthony4fd27e22010-02-07 08:17:18 +00001756 switch( method ) {
1757 case EdgeOutMorphology:
1758 case EdgeInMorphology:
1759 case TopHatMorphology:
1760 case BottomHatMorphology:
anthony930be612010-02-08 04:26:15 +00001761 /* Get Difference relative to the original image */
cristy04ffdba2010-02-18 14:34:47 +00001762 (void) CompositeImageChannel(new_image, channel, DifferenceCompositeOp,
anthony4fd27e22010-02-07 08:17:18 +00001763 image, 0, 0);
1764 break;
anthony930be612010-02-08 04:26:15 +00001765 case EdgeMorphology: /* subtract the Erode from a Dilate */
cristy04ffdba2010-02-18 14:34:47 +00001766 (void) CompositeImageChannel(new_image, channel, DifferenceCompositeOp,
anthony4fd27e22010-02-07 08:17:18 +00001767 grad_image, 0, 0);
1768 grad_image=DestroyImage(grad_image);
1769 break;
1770 default:
1771 break;
1772 }
anthony602ab9b2010-01-05 08:06:50 +00001773
1774 return(new_image);
1775}
anthony83ba99b2010-01-24 08:48:15 +00001776
1777/*
1778%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1779% %
1780% %
1781% %
anthony4fd27e22010-02-07 08:17:18 +00001782+ R o t a t e K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00001783% %
1784% %
1785% %
1786%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1787%
anthony4fd27e22010-02-07 08:17:18 +00001788% RotateKernelInfo() rotates the kernel by the angle given. Currently it is
anthony83ba99b2010-01-24 08:48:15 +00001789% restricted to 90 degree angles, but this may be improved in the future.
1790%
anthony4fd27e22010-02-07 08:17:18 +00001791% The format of the RotateKernelInfo method is:
anthony83ba99b2010-01-24 08:48:15 +00001792%
anthony4fd27e22010-02-07 08:17:18 +00001793% void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00001794%
1795% A description of each parameter follows:
1796%
1797% o kernel: the Morphology/Convolution kernel
1798%
1799% o angle: angle to rotate in degrees
1800%
anthonyc4c86e02010-01-27 09:30:32 +00001801% This function is only internel to this module, as it is not finalized,
1802% especially with regard to non-orthogonal angles, and rotation of larger
1803% 2D kernels.
anthony83ba99b2010-01-24 08:48:15 +00001804*/
anthony4fd27e22010-02-07 08:17:18 +00001805static void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00001806{
1807 /* WARNING: Currently assumes the kernel (rightly) is horizontally symetrical
1808 **
1809 ** TODO: expand beyond simple 90 degree rotates, flips and flops
1810 */
1811
1812 /* Modulus the angle */
1813 angle = fmod(angle, 360.0);
1814 if ( angle < 0 )
1815 angle += 360.0;
1816
1817 if ( 315.0 < angle || angle <= 45.0 )
1818 return; /* no change! - At least at this time */
1819
1820 switch (kernel->type) {
1821 /* These built-in kernels are cylindrical kernels, rotating is useless */
1822 case GaussianKernel:
1823 case LaplacianKernel:
1824 case LOGKernel:
1825 case DOGKernel:
1826 case DiskKernel:
1827 case ChebyshevKernel:
1828 case ManhattenKernel:
1829 case EuclideanKernel:
1830 return;
1831
1832 /* These may be rotatable at non-90 angles in the future */
1833 /* but simply rotating them in multiples of 90 degrees is useless */
1834 case SquareKernel:
1835 case DiamondKernel:
1836 case PlusKernel:
1837 return;
1838
1839 /* These only allows a +/-90 degree rotation (by transpose) */
1840 /* A 180 degree rotation is useless */
1841 case BlurKernel:
1842 case RectangleKernel:
1843 if ( 135.0 < angle && angle <= 225.0 )
1844 return;
1845 if ( 225.0 < angle && angle <= 315.0 )
1846 angle -= 180;
1847 break;
1848
1849 /* these are freely rotatable in 90 degree units */
1850 case CometKernel:
1851 case UndefinedKernel:
1852 case UserDefinedKernel:
1853 break;
1854 }
1855 if ( 135.0 < angle && angle <= 225.0 )
1856 {
1857 /* For a 180 degree rotation - also know as a reflection */
1858 /* This is actually a very very common operation! */
1859 /* Basically all that is needed is a reversal of the kernel data! */
1860 unsigned long
1861 i,j;
1862 register double
1863 *k,t;
1864
1865 k=kernel->values;
1866 for ( i=0, j=kernel->width*kernel->height-1; i<j; i++, j--)
1867 t=k[i], k[i]=k[j], k[j]=t;
1868
anthony930be612010-02-08 04:26:15 +00001869 kernel->x = (long) kernel->width - kernel->x - 1;
1870 kernel->y = (long) kernel->height - kernel->y - 1;
anthony83ba99b2010-01-24 08:48:15 +00001871 angle = fmod(angle+180.0, 360.0);
1872 }
1873 if ( 45.0 < angle && angle <= 135.0 )
1874 { /* Do a transpose and a flop, of the image, which results in a 90
1875 * degree rotation using two mirror operations.
1876 *
1877 * WARNING: this assumes the original image was a 1 dimentional image
1878 * but currently that is the only built-ins it is applied to.
1879 */
cristy150989e2010-02-01 14:59:39 +00001880 long
anthony83ba99b2010-01-24 08:48:15 +00001881 t;
cristy150989e2010-02-01 14:59:39 +00001882 t = (long) kernel->width;
anthony83ba99b2010-01-24 08:48:15 +00001883 kernel->width = kernel->height;
cristy150989e2010-02-01 14:59:39 +00001884 kernel->height = (unsigned long) t;
cristyc99304f2010-02-01 15:26:27 +00001885 t = kernel->x;
1886 kernel->x = kernel->y;
1887 kernel->y = t;
anthony83ba99b2010-01-24 08:48:15 +00001888 angle = fmod(450.0 - angle, 360.0);
1889 }
1890 /* At this point angle should be between -45 (315) and +45 degrees
1891 * In the future some form of non-orthogonal angled rotates could be
1892 * performed here, posibily with a linear kernel restriction.
1893 */
1894
1895#if 0
1896 Not currently in use!
1897 { /* Do a flop, this assumes kernel is horizontally symetrical.
1898 * Each row of the kernel needs to be reversed!
1899 */
1900 unsigned long
1901 y;
cristy150989e2010-02-01 14:59:39 +00001902 register long
anthony83ba99b2010-01-24 08:48:15 +00001903 x,r;
1904 register double
1905 *k,t;
1906
1907 for ( y=0, k=kernel->values; y < kernel->height; y++, k+=kernel->width)
1908 for ( x=0, r=kernel->width-1; x<kernel->width/2; x++, r--)
1909 t=k[x], k[x]=k[r], k[r]=t;
1910
cristyc99304f2010-02-01 15:26:27 +00001911 kernel->x = kernel->width - kernel->x - 1;
anthony83ba99b2010-01-24 08:48:15 +00001912 angle = fmod(angle+180.0, 360.0);
1913 }
1914#endif
1915 return;
1916}
1917
1918/*
1919%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1920% %
1921% %
1922% %
cristy6771f1e2010-03-05 19:43:39 +00001923% S c a l e K e r n e l I n f o %
anthonycc6c8362010-01-25 04:14:01 +00001924% %
1925% %
1926% %
1927%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1928%
anthony999bb2c2010-02-18 12:38:01 +00001929% ScaleKernelInfo() scales the kernel by the given amount, with or without
1930% normalization of the sum of the kernel values.
anthonycc6c8362010-01-25 04:14:01 +00001931%
anthony999bb2c2010-02-18 12:38:01 +00001932% By default (no flags given) the values within the kernel is scaled
1933% according the given scaling factor.
anthonycc6c8362010-01-25 04:14:01 +00001934%
anthony999bb2c2010-02-18 12:38:01 +00001935% If any 'normalize_flags' are given the kernel will be normalized and then
1936% further scaled by the scaleing factor value given. A 'PercentValue' flag
1937% will cause the given scaling factor to be divided by one hundred percent.
1938%
1939% Kernel normalization ('normalize_flags' given) is designed to ensure that
1940% any use of the kernel scaling factor with 'Convolve' or 'Correlate'
1941% morphology methods will fall into -1.0 to +1.0 range. Note however that
1942% for non-HDRI versions of IM this may cause images to have any negative
1943% results clipped, unless some 'clip' any negative output from 'Convolve'
1944% with the use of some kernels.
1945%
1946% More specifically. Kernels which only contain positive values (such as a
1947% 'Gaussian' kernel) will be scaled so that those values sum to +1.0,
1948% ensuring a 0.0 to +1.0 convolution output range for non-HDRI images.
1949%
1950% For Kernels that contain some negative values, (such as 'Sharpen' kernels)
1951% the kernel will be scaled by the absolute of the sum of kernel values, so
1952% that it will generally fall within the +/- 1.0 range.
1953%
1954% For kernels whose values sum to zero, (such as 'Laplician' kernels) kernel
1955% will be scaled by just the sum of the postive values, so that its output
1956% range will again fall into the +/- 1.0 range.
1957%
1958% For special kernels designed for locating shapes using 'Correlate', (often
1959% only containing +1 and -1 values, representing foreground/brackground
1960% matching) a special normalization method is provided to scale the positive
1961% values seperatally to those of the negative values, so the kernel will be
1962% forced to become a zero-sum kernel better suited to such searches.
1963%
1964% WARNING: Correct normalization of the kernal assumes that the '*_range'
1965% attributes within the kernel structure have been correctly set during the
1966% kernels creation.
1967%
1968% NOTE: The values used for 'normalize_flags' have been selected specifically
1969% to match the use of geometry options, so that '!' means NormalizeValue, '^'
1970% means CorrelateNormalizeValue, and '%' means PercentValue. All other
1971% GeometryFlags values are ignored.
anthonycc6c8362010-01-25 04:14:01 +00001972%
anthony4fd27e22010-02-07 08:17:18 +00001973% The format of the ScaleKernelInfo method is:
anthonycc6c8362010-01-25 04:14:01 +00001974%
anthony999bb2c2010-02-18 12:38:01 +00001975% void ScaleKernelInfo(KernelInfo *kernel, const double scaling_factor,
1976% const MagickStatusType normalize_flags )
anthonycc6c8362010-01-25 04:14:01 +00001977%
1978% A description of each parameter follows:
1979%
1980% o kernel: the Morphology/Convolution kernel
1981%
anthony999bb2c2010-02-18 12:38:01 +00001982% o scaling_factor:
1983% multiply all values (after normalization) by this factor if not
1984% zero. If the kernel is normalized regardless of any flags.
1985%
1986% o normalize_flags:
1987% GeometryFlags defining normalization method to use.
1988% specifically: NormalizeValue, CorrelateNormalizeValue,
1989% and/or PercentValue
anthonycc6c8362010-01-25 04:14:01 +00001990%
anthonyc4c86e02010-01-27 09:30:32 +00001991% This function is internal to this module only at this time, but can be
1992% exported to other modules if needed.
anthonycc6c8362010-01-25 04:14:01 +00001993*/
cristy6771f1e2010-03-05 19:43:39 +00001994MagickExport void ScaleKernelInfo(KernelInfo *kernel,
1995 const double scaling_factor,const GeometryFlags normalize_flags)
anthonycc6c8362010-01-25 04:14:01 +00001996{
cristy150989e2010-02-01 14:59:39 +00001997 register long
anthonycc6c8362010-01-25 04:14:01 +00001998 i;
1999
anthony999bb2c2010-02-18 12:38:01 +00002000 register double
2001 pos_scale,
2002 neg_scale;
2003
2004 pos_scale = 1.0;
2005 if ( (normalize_flags&NormalizeValue) != 0 ) {
2006 /* normalize kernel appropriately */
2007 if ( fabs(kernel->positive_range + kernel->negative_range) > MagickEpsilon )
2008 pos_scale = fabs(kernel->positive_range + kernel->negative_range);
anthonycc6c8362010-01-25 04:14:01 +00002009 else
anthony999bb2c2010-02-18 12:38:01 +00002010 pos_scale = kernel->positive_range; /* special zero-summing kernel */
2011 }
2012 /* force kernel into being a normalized zero-summing kernel */
2013 if ( (normalize_flags&CorrelateNormalizeValue) != 0 ) {
2014 pos_scale = ( fabs(kernel->positive_range) > MagickEpsilon )
2015 ? kernel->positive_range : 1.0;
2016 neg_scale = ( fabs(kernel->negative_range) > MagickEpsilon )
2017 ? -kernel->negative_range : 1.0;
2018 }
2019 else
2020 neg_scale = pos_scale;
2021
2022 /* finialize scaling_factor for positive and negative components */
2023 pos_scale = scaling_factor/pos_scale;
2024 neg_scale = scaling_factor/neg_scale;
2025 if ( (normalize_flags&PercentValue) != 0 ) {
2026 pos_scale /= 100.0;
2027 neg_scale /= 100.0;
anthonycc6c8362010-01-25 04:14:01 +00002028 }
2029
cristy150989e2010-02-01 14:59:39 +00002030 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00002031 if ( ! IsNan(kernel->values[i]) )
anthony999bb2c2010-02-18 12:38:01 +00002032 kernel->values[i] *= (kernel->values[i] >= 0) ? pos_scale : neg_scale;
anthonycc6c8362010-01-25 04:14:01 +00002033
anthony999bb2c2010-02-18 12:38:01 +00002034 /* convolution output range */
2035 kernel->positive_range *= pos_scale;
2036 kernel->negative_range *= neg_scale;
2037 /* maximum and minimum values in kernel */
2038 kernel->maximum *= (kernel->maximum >= 0.0) ? pos_scale : neg_scale;
2039 kernel->minimum *= (kernel->minimum >= 0.0) ? pos_scale : neg_scale;
2040
2041 /* swap kernel settings if user scaling factor is negative */
2042 if ( scaling_factor < MagickEpsilon ) {
2043 double t;
2044 t = kernel->positive_range;
2045 kernel->positive_range = kernel->negative_range;
2046 kernel->negative_range = t;
2047 t = kernel->maximum;
2048 kernel->maximum = kernel->minimum;
2049 kernel->minimum = 1;
2050 }
anthonycc6c8362010-01-25 04:14:01 +00002051
2052 return;
2053}
2054
2055/*
2056%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2057% %
2058% %
2059% %
anthony4fd27e22010-02-07 08:17:18 +00002060+ S h o w K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00002061% %
2062% %
2063% %
2064%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2065%
anthony4fd27e22010-02-07 08:17:18 +00002066% ShowKernelInfo() outputs the details of the given kernel defination to
2067% standard error, generally due to a users 'showkernel' option request.
anthony83ba99b2010-01-24 08:48:15 +00002068%
2069% The format of the ShowKernel method is:
2070%
anthony4fd27e22010-02-07 08:17:18 +00002071% void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00002072%
2073% A description of each parameter follows:
2074%
2075% o kernel: the Morphology/Convolution kernel
2076%
anthonyc4c86e02010-01-27 09:30:32 +00002077% This function is internal to this module only at this time. That may change
2078% in the future.
anthony83ba99b2010-01-24 08:48:15 +00002079*/
anthony4fd27e22010-02-07 08:17:18 +00002080MagickExport void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00002081{
cristy150989e2010-02-01 14:59:39 +00002082 long
anthony83ba99b2010-01-24 08:48:15 +00002083 i, u, v;
2084
2085 fprintf(stderr,
anthonycc6c8362010-01-25 04:14:01 +00002086 "Kernel \"%s\" of size %lux%lu%+ld%+ld with values from %.*lg to %.*lg\n",
anthony83ba99b2010-01-24 08:48:15 +00002087 MagickOptionToMnemonic(MagickKernelOptions, kernel->type),
2088 kernel->width, kernel->height,
cristyc99304f2010-02-01 15:26:27 +00002089 kernel->x, kernel->y,
2090 GetMagickPrecision(), kernel->minimum,
2091 GetMagickPrecision(), kernel->maximum);
anthonycc6c8362010-01-25 04:14:01 +00002092 fprintf(stderr, "Forming convolution output range from %.*lg to %.*lg%s\n",
cristyc99304f2010-02-01 15:26:27 +00002093 GetMagickPrecision(), kernel->negative_range,
2094 GetMagickPrecision(), kernel->positive_range,
anthonycc6c8362010-01-25 04:14:01 +00002095 /*kernel->normalized == MagickTrue ? " (normalized)" : */ "" );
cristy150989e2010-02-01 14:59:39 +00002096 for (i=v=0; v < (long) kernel->height; v++) {
anthony83ba99b2010-01-24 08:48:15 +00002097 fprintf(stderr,"%2ld:",v);
cristy150989e2010-02-01 14:59:39 +00002098 for (u=0; u < (long) kernel->width; u++, i++)
anthony83ba99b2010-01-24 08:48:15 +00002099 if ( IsNan(kernel->values[i]) )
2100 fprintf(stderr," %*s", GetMagickPrecision()+2, "nan");
2101 else
2102 fprintf(stderr," %*.*lg", GetMagickPrecision()+2,
2103 GetMagickPrecision(), kernel->values[i]);
2104 fprintf(stderr,"\n");
2105 }
2106}
anthonycc6c8362010-01-25 04:14:01 +00002107
2108/*
2109%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2110% %
2111% %
2112% %
anthony4fd27e22010-02-07 08:17:18 +00002113+ Z e r o K e r n e l N a n s %
anthonycc6c8362010-01-25 04:14:01 +00002114% %
2115% %
2116% %
2117%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2118%
2119% ZeroKernelNans() replaces any special 'nan' value that may be present in
2120% the kernel with a zero value. This is typically done when the kernel will
2121% be used in special hardware (GPU) convolution processors, to simply
2122% matters.
2123%
2124% The format of the ZeroKernelNans method is:
2125%
2126% voidZeroKernelNans (KernelInfo *kernel)
2127%
2128% A description of each parameter follows:
2129%
2130% o kernel: the Morphology/Convolution kernel
2131%
2132% FUTURE: return the information in a string for API usage.
2133*/
anthonyc4c86e02010-01-27 09:30:32 +00002134MagickExport void ZeroKernelNans(KernelInfo *kernel)
anthonycc6c8362010-01-25 04:14:01 +00002135{
cristy150989e2010-02-01 14:59:39 +00002136 register long
anthonycc6c8362010-01-25 04:14:01 +00002137 i;
2138
cristy150989e2010-02-01 14:59:39 +00002139 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00002140 if ( IsNan(kernel->values[i]) )
2141 kernel->values[i] = 0.0;
2142
2143 return;
2144}