blob: e49af0c264e3c5fc77ff71a345409ba1cf10ae3b [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"
79
anthonyc94cdb02010-01-06 08:15:29 +000080
anthony602ab9b2010-01-05 08:06:50 +000081/*
anthonyc94cdb02010-01-06 08:15:29 +000082 * The following test is for special floating point numbers of value NaN (not
83 * a number), that may be used within a Kernel Definition. NaN's are defined
84 * as part of the IEEE standard for floating point number representation.
anthony602ab9b2010-01-05 08:06:50 +000085 *
anthonyc94cdb02010-01-06 08:15:29 +000086 * These are used a Kernel value of NaN means that that kernal position is not
87 * part of the normal convolution or morphology process, and thus allowing the
88 * use of 'shaped' kernels.
anthony602ab9b2010-01-05 08:06:50 +000089 *
anthonyc94cdb02010-01-06 08:15:29 +000090 * Special Properities Two NaN's are never equal, even if they are from the
91 * same variable That is the IsNaN() macro is only true if the value is NaN.
anthony602ab9b2010-01-05 08:06:50 +000092 */
93#define IsNan(a) ((a)!=(a))
94
anthony29188a82010-01-22 10:12:34 +000095/*
96 * Other global definitions used by module
97 */
98static inline double MagickMin(const double x,const double y)
99{
100 return( x < y ? x : y);
101}
102static inline double MagickMax(const double x,const double y)
103{
104 return( x > y ? x : y);
105}
106#define Minimize(assign,value) assign=MagickMin(assign,value)
107#define Maximize(assign,value) assign=MagickMax(assign,value)
108
anthonyc4c86e02010-01-27 09:30:32 +0000109/* Currently these are only internal to this module */
110static void
cristyef656912010-03-05 19:54:59 +0000111 RotateKernelInfo(KernelInfo *, double);
anthony602ab9b2010-01-05 08:06:50 +0000112
113/*
114%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
115% %
116% %
117% %
anthony83ba99b2010-01-24 08:48:15 +0000118% A c q u i r e K e r n e l I n f o %
anthony602ab9b2010-01-05 08:06:50 +0000119% %
120% %
121% %
122%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
123%
cristy2be15382010-01-21 02:38:03 +0000124% AcquireKernelInfo() takes the given string (generally supplied by the
anthony602ab9b2010-01-05 08:06:50 +0000125% user) and converts it into a Morphology/Convolution Kernel. This allows
126% users to specify a kernel from a number of pre-defined kernels, or to fully
127% specify their own kernel for a specific Convolution or Morphology
128% Operation.
129%
130% The kernel so generated can be any rectangular array of floating point
131% values (doubles) with the 'control point' or 'pixel being affected'
132% anywhere within that array of values.
133%
anthony83ba99b2010-01-24 08:48:15 +0000134% Previously IM was restricted to a square of odd size using the exact
135% center as origin, this is no longer the case, and any rectangular kernel
136% with any value being declared the origin. This in turn allows the use of
137% highly asymmetrical kernels.
anthony602ab9b2010-01-05 08:06:50 +0000138%
139% The floating point values in the kernel can also include a special value
anthony83ba99b2010-01-24 08:48:15 +0000140% known as 'nan' or 'not a number' to indicate that this value is not part
141% of the kernel array. This allows you to shaped the kernel within its
142% rectangular area. That is 'nan' values provide a 'mask' for the kernel
143% shape. However at least one non-nan value must be provided for correct
144% working of a kernel.
anthony602ab9b2010-01-05 08:06:50 +0000145%
anthony83ba99b2010-01-24 08:48:15 +0000146% The returned kernel should be free using the DestroyKernelInfo() when you
147% are finished with it.
anthony602ab9b2010-01-05 08:06:50 +0000148%
149% Input kernel defintion strings can consist of any of three types.
150%
anthony29188a82010-01-22 10:12:34 +0000151% "name:args"
152% Select from one of the built in kernels, using the name and
153% geometry arguments supplied. See AcquireKernelBuiltIn()
anthony602ab9b2010-01-05 08:06:50 +0000154%
155% "WxH[+X+Y]:num, num, num ..."
156% a kernal of size W by H, with W*H floating point numbers following.
157% the 'center' can be optionally be defined at +X+Y (such that +0+0
anthony29188a82010-01-22 10:12:34 +0000158% is top left corner). If not defined the pixel in the center, for
159% odd sizes, or to the immediate top or left of center for even sizes
160% is automatically selected.
anthony602ab9b2010-01-05 08:06:50 +0000161%
anthony29188a82010-01-22 10:12:34 +0000162% "num, num, num, num, ..."
163% list of floating point numbers defining an 'old style' odd sized
164% square kernel. At least 9 values should be provided for a 3x3
165% square kernel, 25 for a 5x5 square kernel, 49 for 7x7, etc.
166% Values can be space or comma separated. This is not recommended.
anthony602ab9b2010-01-05 08:06:50 +0000167%
anthony83ba99b2010-01-24 08:48:15 +0000168% Note that 'name' kernels will start with an alphabetic character while the
169% new kernel specification has a ':' character in its specification string.
170% If neither is the case, it is assumed an old style of a simple list of
171% numbers generating a odd-sized square kernel has been given.
anthony602ab9b2010-01-05 08:06:50 +0000172%
173% The format of the AcquireKernal method is:
174%
cristy2be15382010-01-21 02:38:03 +0000175% KernelInfo *AcquireKernelInfo(const char *kernel_string)
anthony602ab9b2010-01-05 08:06:50 +0000176%
177% A description of each parameter follows:
178%
179% o kernel_string: the Morphology/Convolution kernel wanted.
180%
181*/
182
cristy2be15382010-01-21 02:38:03 +0000183MagickExport KernelInfo *AcquireKernelInfo(const char *kernel_string)
anthony602ab9b2010-01-05 08:06:50 +0000184{
cristy2be15382010-01-21 02:38:03 +0000185 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000186 *kernel;
187
188 char
189 token[MaxTextExtent];
190
cristy150989e2010-02-01 14:59:39 +0000191 register long
anthony602ab9b2010-01-05 08:06:50 +0000192 i;
193
194 const char
195 *p;
196
197 MagickStatusType
198 flags;
199
200 GeometryInfo
201 args;
202
anthony29188a82010-01-22 10:12:34 +0000203 double
204 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
205
anthony602ab9b2010-01-05 08:06:50 +0000206 assert(kernel_string != (const char *) NULL);
207 SetGeometryInfo(&args);
208
209 /* does it start with an alpha - Return a builtin kernel */
210 GetMagickToken(kernel_string,&p,token);
211 if ( isalpha((int)token[0]) )
212 {
213 long
214 type;
215
anthony29188a82010-01-22 10:12:34 +0000216 type=ParseMagickOption(MagickKernelOptions,MagickFalse,token);
anthony602ab9b2010-01-05 08:06:50 +0000217 if ( type < 0 || type == UserDefinedKernel )
cristy2be15382010-01-21 02:38:03 +0000218 return((KernelInfo *)NULL);
anthony602ab9b2010-01-05 08:06:50 +0000219
220 while (((isspace((int) ((unsigned char) *p)) != 0) ||
221 (*p == ',') || (*p == ':' )) && (*p != '\0'))
222 p++;
223 flags = ParseGeometry(p, &args);
224
225 /* special handling of missing values in input string */
anthony4fd27e22010-02-07 08:17:18 +0000226 switch( type ) {
227 case RectangleKernel:
anthony602ab9b2010-01-05 08:06:50 +0000228 if ( (flags & WidthValue) == 0 ) /* if no width then */
229 args.rho = args.sigma; /* then width = height */
230 if ( args.rho < 1.0 ) /* if width too small */
231 args.rho = 3; /* then width = 3 */
232 if ( args.sigma < 1.0 ) /* if height too small */
233 args.sigma = args.rho; /* then height = width */
234 if ( (flags & XValue) == 0 ) /* center offset if not defined */
235 args.xi = (double)(((long)args.rho-1)/2);
236 if ( (flags & YValue) == 0 )
237 args.psi = (double)(((long)args.sigma-1)/2);
anthony4fd27e22010-02-07 08:17:18 +0000238 break;
239 case SquareKernel:
240 case DiamondKernel:
241 case DiskKernel:
242 case PlusKernel:
243 if ( (flags & HeightValue) == 0 ) /* if no scale */
244 args.sigma = 1.0; /* then scale = 1.0 */
245 break;
246 default:
247 break;
anthony602ab9b2010-01-05 08:06:50 +0000248 }
249
cristy2be15382010-01-21 02:38:03 +0000250 return(AcquireKernelBuiltIn((KernelInfoType)type, &args));
anthony602ab9b2010-01-05 08:06:50 +0000251 }
252
cristy2be15382010-01-21 02:38:03 +0000253 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
254 if (kernel == (KernelInfo *)NULL)
anthony602ab9b2010-01-05 08:06:50 +0000255 return(kernel);
256 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
257 kernel->type = UserDefinedKernel;
cristyd43a46b2010-01-21 02:13:41 +0000258 kernel->signature = MagickSignature;
anthony602ab9b2010-01-05 08:06:50 +0000259
260 /* Has a ':' in argument - New user kernel specification */
261 p = strchr(kernel_string, ':');
262 if ( p != (char *) NULL)
263 {
anthony602ab9b2010-01-05 08:06:50 +0000264 /* ParseGeometry() needs the geometry separated! -- Arrgghh */
cristy150989e2010-02-01 14:59:39 +0000265 memcpy(token, kernel_string, (size_t) (p-kernel_string));
anthony602ab9b2010-01-05 08:06:50 +0000266 token[p-kernel_string] = '\0';
267 flags = ParseGeometry(token, &args);
anthony602ab9b2010-01-05 08:06:50 +0000268
anthony29188a82010-01-22 10:12:34 +0000269 /* Size handling and checks of geometry settings */
anthony602ab9b2010-01-05 08:06:50 +0000270 if ( (flags & WidthValue) == 0 ) /* if no width then */
271 args.rho = args.sigma; /* then width = height */
272 if ( args.rho < 1.0 ) /* if width too small */
273 args.rho = 1.0; /* then width = 1 */
274 if ( args.sigma < 1.0 ) /* if height too small */
275 args.sigma = args.rho; /* then height = width */
276 kernel->width = (unsigned long)args.rho;
277 kernel->height = (unsigned long)args.sigma;
278
279 /* Offset Handling and Checks */
280 if ( args.xi < 0.0 || args.psi < 0.0 )
anthony83ba99b2010-01-24 08:48:15 +0000281 return(DestroyKernelInfo(kernel));
cristyc99304f2010-02-01 15:26:27 +0000282 kernel->x = ((flags & XValue)!=0) ? (long)args.xi
cristy150989e2010-02-01 14:59:39 +0000283 : (long) (kernel->width-1)/2;
cristyc99304f2010-02-01 15:26:27 +0000284 kernel->y = ((flags & YValue)!=0) ? (long)args.psi
cristy150989e2010-02-01 14:59:39 +0000285 : (long) (kernel->height-1)/2;
cristyc99304f2010-02-01 15:26:27 +0000286 if ( kernel->x >= (long) kernel->width ||
287 kernel->y >= (long) kernel->height )
anthony83ba99b2010-01-24 08:48:15 +0000288 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000289
290 p++; /* advance beyond the ':' */
291 }
292 else
293 { /* ELSE - Old old kernel specification, forming odd-square kernel */
294 /* count up number of values given */
295 p=(const char *) kernel_string;
cristya699b172010-01-06 16:48:49 +0000296 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
anthony29188a82010-01-22 10:12:34 +0000297 p++; /* ignore "'" chars for convolve filter usage - Cristy */
anthony602ab9b2010-01-05 08:06:50 +0000298 for (i=0; *p != '\0'; i++)
299 {
300 GetMagickToken(p,&p,token);
301 if (*token == ',')
302 GetMagickToken(p,&p,token);
303 }
304 /* set the size of the kernel - old sized square */
305 kernel->width = kernel->height= (unsigned long) sqrt((double) i+1.0);
cristyc99304f2010-02-01 15:26:27 +0000306 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000307 p=(const char *) kernel_string;
anthony29188a82010-01-22 10:12:34 +0000308 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
309 p++; /* ignore "'" chars for convolve filter usage - Cristy */
anthony602ab9b2010-01-05 08:06:50 +0000310 }
311
312 /* Read in the kernel values from rest of input string argument */
313 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
314 kernel->height*sizeof(double));
315 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000316 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000317
cristyc99304f2010-02-01 15:26:27 +0000318 kernel->minimum = +MagickHuge;
319 kernel->maximum = -MagickHuge;
320 kernel->negative_range = kernel->positive_range = 0.0;
cristy150989e2010-02-01 14:59:39 +0000321 for (i=0; (i < (long) (kernel->width*kernel->height)) && (*p != '\0'); i++)
anthony602ab9b2010-01-05 08:06:50 +0000322 {
323 GetMagickToken(p,&p,token);
324 if (*token == ',')
325 GetMagickToken(p,&p,token);
anthony29188a82010-01-22 10:12:34 +0000326 if ( LocaleCompare("nan",token) == 0
327 || LocaleCompare("-",token) == 0 ) {
328 kernel->values[i] = nan; /* do not include this value in kernel */
329 }
330 else {
331 kernel->values[i] = StringToDouble(token);
332 ( kernel->values[i] < 0)
cristyc99304f2010-02-01 15:26:27 +0000333 ? ( kernel->negative_range += kernel->values[i] )
334 : ( kernel->positive_range += kernel->values[i] );
335 Minimize(kernel->minimum, kernel->values[i]);
336 Maximize(kernel->maximum, kernel->values[i]);
anthony29188a82010-01-22 10:12:34 +0000337 }
anthony602ab9b2010-01-05 08:06:50 +0000338 }
anthonycc6c8362010-01-25 04:14:01 +0000339 /* check that we recieved at least one real (non-nan) value! */
cristyc99304f2010-02-01 15:26:27 +0000340 if ( kernel->minimum == MagickHuge )
anthonycc6c8362010-01-25 04:14:01 +0000341 return(DestroyKernelInfo(kernel));
anthony29188a82010-01-22 10:12:34 +0000342
anthonycc6c8362010-01-25 04:14:01 +0000343 /* This should not be needed for a fully defined kernel
anthony29188a82010-01-22 10:12:34 +0000344 * Perhaps an error should be reported instead!
anthonycc6c8362010-01-25 04:14:01 +0000345 * Kept for backward compatibility.
anthony29188a82010-01-22 10:12:34 +0000346 */
cristy150989e2010-02-01 14:59:39 +0000347 if ( i < (long) (kernel->width*kernel->height) ) {
cristyc99304f2010-02-01 15:26:27 +0000348 Minimize(kernel->minimum, kernel->values[i]);
349 Maximize(kernel->maximum, kernel->values[i]);
cristy150989e2010-02-01 14:59:39 +0000350 for ( ; i < (long) (kernel->width*kernel->height); i++)
anthony29188a82010-01-22 10:12:34 +0000351 kernel->values[i]=0.0;
352 }
anthony602ab9b2010-01-05 08:06:50 +0000353
354 return(kernel);
355}
356
357/*
358%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
359% %
360% %
361% %
362% A c q u i r e K e r n e l B u i l t I n %
363% %
364% %
365% %
366%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
367%
368% AcquireKernelBuiltIn() returned one of the 'named' built-in types of
369% kernels used for special purposes such as gaussian blurring, skeleton
370% pruning, and edge distance determination.
371%
372% They take a KernelType, and a set of geometry style arguments, which were
373% typically decoded from a user supplied string, or from a more complex
374% Morphology Method that was requested.
375%
376% The format of the AcquireKernalBuiltIn method is:
377%
cristy2be15382010-01-21 02:38:03 +0000378% KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000379% const GeometryInfo args)
380%
381% A description of each parameter follows:
382%
383% o type: the pre-defined type of kernel wanted
384%
385% o args: arguments defining or modifying the kernel
386%
387% Convolution Kernels
388%
anthony4fd27e22010-02-07 08:17:18 +0000389% Gaussian "{radius},{sigma}"
anthony602ab9b2010-01-05 08:06:50 +0000390% Generate a two-dimentional gaussian kernel, as used by -gaussian
391% A sigma is required, (with the 'x'), due to historical reasons.
392%
393% NOTE: that the 'radius' is optional, but if provided can limit (clip)
394% the final size of the resulting kernel to a square 2*radius+1 in size.
395% The radius should be at least 2 times that of the sigma value, or
396% sever clipping and aliasing may result. If not given or set to 0 the
397% radius will be determined so as to produce the best minimal error
398% result, which is usally much larger than is normally needed.
399%
anthony4fd27e22010-02-07 08:17:18 +0000400% Blur "{radius},{sigma},{angle}"
anthony602ab9b2010-01-05 08:06:50 +0000401% As per Gaussian, but generates a 1 dimensional or linear gaussian
402% blur, at the angle given (current restricted to orthogonal angles).
403% If a 'radius' is given the kernel is clipped to a width of 2*radius+1.
404%
405% NOTE that two such blurs perpendicular to each other is equivelent to
406% -blur and the previous gaussian, but is often 10 or more times faster.
407%
anthony4fd27e22010-02-07 08:17:18 +0000408% Comet "{width},{sigma},{angle}"
anthony602ab9b2010-01-05 08:06:50 +0000409% Blur in one direction only, mush like how a bright object leaves
410% a comet like trail. The Kernel is actually half a gaussian curve,
411% Adding two such blurs in oppiste directions produces a Linear Blur.
412%
413% NOTE: that the first argument is the width of the kernel and not the
414% radius of the kernel.
415%
416% # Still to be implemented...
417% #
anthony4fd27e22010-02-07 08:17:18 +0000418% # Sharpen "{radius},{sigma}
419% # Negated Gaussian (center zeroed and re-normalized),
420% # with a 2 unit positive peak. -- Check On line documentation
421% #
422% # Laplacian "{radius},{sigma}"
anthony602ab9b2010-01-05 08:06:50 +0000423% # Laplacian (a mexican hat like) Function
424% #
425% # LOG "{radius},{sigma1},{sigma2}
426% # Laplacian of Gaussian
427% #
428% # DOG "{radius},{sigma1},{sigma2}
anthony4fd27e22010-02-07 08:17:18 +0000429% # Difference of two Gaussians
430% #
431% # Filter2D
432% # Filter1D
433% # Set kernel values using a resize filter, and given scale (sigma)
434% # Cylindrical or Linear. Is this posible with an image?
435% #
anthony602ab9b2010-01-05 08:06:50 +0000436%
437% Boolean Kernels
438%
439% Rectangle "{geometry}"
440% Simply generate a rectangle of 1's with the size given. You can also
441% specify the location of the 'control point', otherwise the closest
442% pixel to the center of the rectangle is selected.
443%
444% Properly centered and odd sized rectangles work the best.
445%
anthony4fd27e22010-02-07 08:17:18 +0000446% Diamond "[{radius}[,{scale}]]"
anthony602ab9b2010-01-05 08:06:50 +0000447% Generate a diamond shaped kernal with given radius to the points.
448% Kernel size will again be radius*2+1 square and defaults to radius 1,
449% generating a 3x3 kernel that is slightly larger than a square.
450%
anthony4fd27e22010-02-07 08:17:18 +0000451% Square "[{radius}[,{scale}]]"
anthony602ab9b2010-01-05 08:06:50 +0000452% Generate a square shaped kernel of size radius*2+1, and defaulting
453% to a 3x3 (radius 1).
454%
455% Note that using a larger radius for the "Square" or the "Diamond"
456% is also equivelent to iterating the basic morphological method
457% that many times. However However iterating with the smaller radius 1
458% default is actually faster than using a larger kernel radius.
459%
anthony4fd27e22010-02-07 08:17:18 +0000460% Disk "[{radius}[,{scale}]]
anthony602ab9b2010-01-05 08:06:50 +0000461% Generate a binary disk of the radius given, radius may be a float.
462% Kernel size will be ceil(radius)*2+1 square.
463% NOTE: Here are some disk shapes of specific interest
464% "disk:1" => "diamond" or "cross:1"
465% "disk:1.5" => "square"
466% "disk:2" => "diamond:2"
anthony83ba99b2010-01-24 08:48:15 +0000467% "disk:2.5" => a general disk shape of radius 2
anthony602ab9b2010-01-05 08:06:50 +0000468% "disk:2.9" => "square:2"
anthony83ba99b2010-01-24 08:48:15 +0000469% "disk:3.5" => default - octagonal/disk shape of radius 3
anthony602ab9b2010-01-05 08:06:50 +0000470% "disk:4.2" => roughly octagonal shape of radius 4
anthony83ba99b2010-01-24 08:48:15 +0000471% "disk:4.3" => a general disk shape of radius 4
anthony602ab9b2010-01-05 08:06:50 +0000472% After this all the kernel shape becomes more and more circular.
473%
474% Because a "disk" is more circular when using a larger radius, using a
475% larger radius is preferred over iterating the morphological operation.
476%
anthony4fd27e22010-02-07 08:17:18 +0000477% Plus "[{radius}[,{scale}]]"
anthony602ab9b2010-01-05 08:06:50 +0000478% Generate a kernel in the shape of a 'plus' sign. The length of each
479% arm is also the radius, which defaults to 2.
480%
481% This kernel is not a good general morphological kernel, but is used
482% more for highlighting and marking any single pixels in an image using,
483% a "Dilate" or "Erode" method as appropriate.
anthonyc94cdb02010-01-06 08:15:29 +0000484%
anthony602ab9b2010-01-05 08:06:50 +0000485% NOTE: "plus:1" is equivelent to a "Diamond" kernel.
486%
487% Note that unlike other kernels iterating a plus does not produce the
488% same result as using a larger radius for the cross.
489%
490% Distance Measuring Kernels
491%
492% Chebyshev "[{radius}][x{scale}]" largest x or y distance (default r=1)
493% Manhatten "[{radius}][x{scale}]" square grid distance (default r=1)
anthonyc94cdb02010-01-06 08:15:29 +0000494% Euclidean "[{radius}][x{scale}]" direct distance (default r=1)
anthony602ab9b2010-01-05 08:06:50 +0000495%
496% Different types of distance measuring methods, which are used with the
497% a 'Distance' morphology method for generating a gradient based on
498% distance from an edge of a binary shape, though there is a technique
499% for handling a anti-aliased shape.
500%
anthonyc94cdb02010-01-06 08:15:29 +0000501% Chebyshev Distance (also known as Tchebychev Distance) is a value of
502% one to any neighbour, orthogonal or diagonal. One why of thinking of
503% it is the number of squares a 'King' or 'Queen' in chess needs to
504% traverse reach any other position on a chess board. It results in a
505% 'square' like distance function, but one where diagonals are closer
506% than expected.
anthony602ab9b2010-01-05 08:06:50 +0000507%
anthonyc94cdb02010-01-06 08:15:29 +0000508% Manhatten Distance (also known as Rectilinear Distance, or the Taxi
509% Cab metric), is the distance needed when you can only travel in
510% orthogonal (horizontal or vertical) only. It is the distance a 'Rook'
511% in chess would travel. It results in a diamond like distances, where
512% diagonals are further than expected.
anthony602ab9b2010-01-05 08:06:50 +0000513%
anthonyc94cdb02010-01-06 08:15:29 +0000514% Euclidean Distance is the 'direct' or 'as the crow flys distance.
515% However by default the kernel size only has a radius of 1, which
516% limits the distance to 'Knight' like moves, with only orthogonal and
517% diagonal measurements being correct. As such for the default kernel
518% you will get octagonal like distance function, which is reasonally
519% accurate.
520%
521% However if you use a larger radius such as "Euclidean:4" you will
522% get a much smoother distance gradient from the edge of the shape.
523% Of course a larger kernel is slower to use, and generally not needed.
524%
525% To allow the use of fractional distances that you get with diagonals
526% the actual distance is scaled by a fixed value which the user can
527% provide. This is not actually nessary for either ""Chebyshev" or
528% "Manhatten" distance kernels, but is done for all three distance
529% kernels. If no scale is provided it is set to a value of 100,
530% allowing for a maximum distance measurement of 655 pixels using a Q16
531% version of IM, from any edge. However for small images this can
532% result in quite a dark gradient.
533%
534% See the 'Distance' Morphological Method, for information of how it is
535% applied.
anthony602ab9b2010-01-05 08:06:50 +0000536%
anthony4fd27e22010-02-07 08:17:18 +0000537% # Hit-n-Miss Kernel-Lists -- Still to be implemented
538% #
539% # specifically for Pruning, Thinning, Thickening
540% #
anthony602ab9b2010-01-05 08:06:50 +0000541*/
542
cristy2be15382010-01-21 02:38:03 +0000543MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000544 const GeometryInfo *args)
545{
cristy2be15382010-01-21 02:38:03 +0000546 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000547 *kernel;
548
cristy150989e2010-02-01 14:59:39 +0000549 register long
anthony602ab9b2010-01-05 08:06:50 +0000550 i;
551
552 register long
553 u,
554 v;
555
556 double
557 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
558
cristy2be15382010-01-21 02:38:03 +0000559 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
560 if (kernel == (KernelInfo *) NULL)
anthony602ab9b2010-01-05 08:06:50 +0000561 return(kernel);
562 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
cristyc99304f2010-02-01 15:26:27 +0000563 kernel->minimum = kernel->maximum = 0.0;
564 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000565 kernel->type = type;
cristyd43a46b2010-01-21 02:13:41 +0000566 kernel->signature = MagickSignature;
anthony602ab9b2010-01-05 08:06:50 +0000567
568 switch(type) {
569 /* Convolution Kernels */
570 case GaussianKernel:
571 { double
572 sigma = fabs(args->sigma);
573
574 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
575
576 kernel->width = kernel->height =
577 GetOptimalKernelWidth2D(args->rho,sigma);
cristyc99304f2010-02-01 15:26:27 +0000578 kernel->x = kernel->y = (long) (kernel->width-1)/2;
579 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000580 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
581 kernel->height*sizeof(double));
582 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000583 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000584
585 sigma = 2.0*sigma*sigma; /* simplify the expression */
cristyc99304f2010-02-01 15:26:27 +0000586 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
587 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
588 kernel->positive_range += (
anthony602ab9b2010-01-05 08:06:50 +0000589 kernel->values[i] =
590 exp(-((double)(u*u+v*v))/sigma)
591 /* / (MagickPI*sigma) */ );
cristyc99304f2010-02-01 15:26:27 +0000592 kernel->minimum = 0;
593 kernel->maximum = kernel->values[
594 kernel->y*kernel->width+kernel->x ];
anthony602ab9b2010-01-05 08:06:50 +0000595
anthony999bb2c2010-02-18 12:38:01 +0000596 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
anthony602ab9b2010-01-05 08:06:50 +0000597
598 break;
599 }
600 case BlurKernel:
601 { double
602 sigma = fabs(args->sigma);
603
604 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
605
606 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
cristyc99304f2010-02-01 15:26:27 +0000607 kernel->x = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000608 kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +0000609 kernel->y = 0;
610 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000611 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
612 kernel->height*sizeof(double));
613 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000614 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000615
616#if 1
617#define KernelRank 3
618 /* Formula derived from GetBlurKernel() in "effect.c" (plus bug fix).
619 ** It generates a gaussian 3 times the width, and compresses it into
620 ** the expected range. This produces a closer normalization of the
621 ** resulting kernel, especially for very low sigma values.
622 ** As such while wierd it is prefered.
623 **
624 ** I am told this method originally came from Photoshop.
625 */
626 sigma *= KernelRank; /* simplify expanded curve */
cristy150989e2010-02-01 14:59:39 +0000627 v = (long) (kernel->width*KernelRank-1)/2; /* start/end points to fit range */
anthony602ab9b2010-01-05 08:06:50 +0000628 (void) ResetMagickMemory(kernel->values,0, (size_t)
629 kernel->width*sizeof(double));
630 for ( u=-v; u <= v; u++) {
631 kernel->values[(u+v)/KernelRank] +=
632 exp(-((double)(u*u))/(2.0*sigma*sigma))
633 /* / (MagickSQ2PI*sigma/KernelRank) */ ;
634 }
cristy150989e2010-02-01 14:59:39 +0000635 for (i=0; i < (long) kernel->width; i++)
cristyc99304f2010-02-01 15:26:27 +0000636 kernel->positive_range += kernel->values[i];
anthony602ab9b2010-01-05 08:06:50 +0000637#else
cristyc99304f2010-02-01 15:26:27 +0000638 for ( i=0, u=-kernel->x; i < kernel->width; i++, u++)
639 kernel->positive_range += (
anthony602ab9b2010-01-05 08:06:50 +0000640 kernel->values[i] =
641 exp(-((double)(u*u))/(2.0*sigma*sigma))
642 /* / (MagickSQ2PI*sigma) */ );
643#endif
cristyc99304f2010-02-01 15:26:27 +0000644 kernel->minimum = 0;
645 kernel->maximum = kernel->values[ kernel->x ];
anthonycc6c8362010-01-25 04:14:01 +0000646 /* Note that neither methods above generate a normalized kernel,
647 ** though it gets close. The kernel may be 'clipped' by a user defined
648 ** radius, producing a smaller (darker) kernel. Also for very small
649 ** sigma's (> 0.1) the central value becomes larger than one, and thus
650 ** producing a very bright kernel.
anthony602ab9b2010-01-05 08:06:50 +0000651 */
anthonycc6c8362010-01-25 04:14:01 +0000652
anthony602ab9b2010-01-05 08:06:50 +0000653 /* Normalize the 1D Gaussian Kernel
654 **
655 ** Because of this the divisor in the above kernel generator is
anthonyc94cdb02010-01-06 08:15:29 +0000656 ** not needed, so is not done above.
anthony602ab9b2010-01-05 08:06:50 +0000657 */
anthony999bb2c2010-02-18 12:38:01 +0000658 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
anthonycc6c8362010-01-25 04:14:01 +0000659
anthony602ab9b2010-01-05 08:06:50 +0000660 /* rotate the kernel by given angle */
anthony4fd27e22010-02-07 08:17:18 +0000661 RotateKernelInfo(kernel, args->xi);
anthony602ab9b2010-01-05 08:06:50 +0000662 break;
663 }
664 case CometKernel:
665 { double
666 sigma = fabs(args->sigma);
667
668 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
669
670 if ( args->rho < 1.0 )
671 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
672 else
673 kernel->width = (unsigned long)args->rho;
cristyc99304f2010-02-01 15:26:27 +0000674 kernel->x = kernel->y = 0;
anthony602ab9b2010-01-05 08:06:50 +0000675 kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +0000676 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000677 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
678 kernel->height*sizeof(double));
679 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000680 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000681
682 /* A comet blur is half a gaussian curve, so that the object is
683 ** blurred in one direction only. This may not be quite the right
684 ** curve so may change in the future. The function must be normalised.
685 */
686#if 1
687#define KernelRank 3
688 sigma *= KernelRank; /* simplify expanded curve */
cristy150989e2010-02-01 14:59:39 +0000689 v = (long) kernel->width*KernelRank; /* start/end points to fit range */
anthony602ab9b2010-01-05 08:06:50 +0000690 (void) ResetMagickMemory(kernel->values,0, (size_t)
691 kernel->width*sizeof(double));
692 for ( u=0; u < v; u++) {
693 kernel->values[u/KernelRank] +=
694 exp(-((double)(u*u))/(2.0*sigma*sigma))
695 /* / (MagickSQ2PI*sigma/KernelRank) */ ;
696 }
cristy150989e2010-02-01 14:59:39 +0000697 for (i=0; i < (long) kernel->width; i++)
cristyc99304f2010-02-01 15:26:27 +0000698 kernel->positive_range += kernel->values[i];
anthony602ab9b2010-01-05 08:06:50 +0000699#else
cristy150989e2010-02-01 14:59:39 +0000700 for ( i=0; i < (long) kernel->width; i++)
cristyc99304f2010-02-01 15:26:27 +0000701 kernel->positive_range += (
anthony602ab9b2010-01-05 08:06:50 +0000702 kernel->values[i] =
703 exp(-((double)(i*i))/(2.0*sigma*sigma))
704 /* / (MagickSQ2PI*sigma) */ );
705#endif
cristyc99304f2010-02-01 15:26:27 +0000706 kernel->minimum = 0;
707 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000708
anthony999bb2c2010-02-18 12:38:01 +0000709 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
710 RotateKernelInfo(kernel, args->xi); /* Rotate by angle */
anthony602ab9b2010-01-05 08:06:50 +0000711 break;
712 }
713 /* Boolean Kernels */
714 case RectangleKernel:
715 case SquareKernel:
716 {
anthony4fd27e22010-02-07 08:17:18 +0000717 double scale;
anthony602ab9b2010-01-05 08:06:50 +0000718 if ( type == SquareKernel )
719 {
720 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000721 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000722 else
cristy150989e2010-02-01 14:59:39 +0000723 kernel->width = kernel->height = (unsigned long) (2*args->rho+1);
cristyc99304f2010-02-01 15:26:27 +0000724 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony4fd27e22010-02-07 08:17:18 +0000725 scale = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +0000726 }
727 else {
cristy2be15382010-01-21 02:38:03 +0000728 /* NOTE: user defaults set in "AcquireKernelInfo()" */
anthony602ab9b2010-01-05 08:06:50 +0000729 if ( args->rho < 1.0 || args->sigma < 1.0 )
anthony83ba99b2010-01-24 08:48:15 +0000730 return(DestroyKernelInfo(kernel)); /* invalid args given */
anthony602ab9b2010-01-05 08:06:50 +0000731 kernel->width = (unsigned long)args->rho;
732 kernel->height = (unsigned long)args->sigma;
733 if ( args->xi < 0.0 || args->xi > (double)kernel->width ||
734 args->psi < 0.0 || args->psi > (double)kernel->height )
anthony83ba99b2010-01-24 08:48:15 +0000735 return(DestroyKernelInfo(kernel)); /* invalid args given */
cristyc99304f2010-02-01 15:26:27 +0000736 kernel->x = (long) args->xi;
737 kernel->y = (long) args->psi;
anthony4fd27e22010-02-07 08:17:18 +0000738 scale = 1.0;
anthony602ab9b2010-01-05 08:06:50 +0000739 }
740 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
741 kernel->height*sizeof(double));
742 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000743 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000744
anthonycc6c8362010-01-25 04:14:01 +0000745 /* set all kernel values to 1.0 */
cristy150989e2010-02-01 14:59:39 +0000746 u=(long) kernel->width*kernel->height;
747 for ( i=0; i < u; i++)
anthony4fd27e22010-02-07 08:17:18 +0000748 kernel->values[i] = scale;
749 kernel->minimum = kernel->maximum = scale; /* a flat shape */
750 kernel->positive_range = scale*u;
anthonycc6c8362010-01-25 04:14:01 +0000751 break;
anthony602ab9b2010-01-05 08:06:50 +0000752 }
753 case DiamondKernel:
754 {
755 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000756 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000757 else
758 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000759 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000760
761 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
762 kernel->height*sizeof(double));
763 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000764 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000765
anthony4fd27e22010-02-07 08:17:18 +0000766 /* set all kernel values within diamond area to scale given */
cristyc99304f2010-02-01 15:26:27 +0000767 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
768 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
769 if ((labs(u)+labs(v)) <= (long)kernel->x)
anthony4fd27e22010-02-07 08:17:18 +0000770 kernel->positive_range += kernel->values[i] = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +0000771 else
772 kernel->values[i] = nan;
anthony4fd27e22010-02-07 08:17:18 +0000773 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
anthony602ab9b2010-01-05 08:06:50 +0000774 break;
775 }
776 case DiskKernel:
777 {
778 long
779 limit;
780
781 limit = (long)(args->rho*args->rho);
anthony83ba99b2010-01-24 08:48:15 +0000782 if (args->rho < 0.1) /* default radius approx 3.5 */
783 kernel->width = kernel->height = 7L, limit = 10L;
anthony602ab9b2010-01-05 08:06:50 +0000784 else
785 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000786 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000787
788 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
789 kernel->height*sizeof(double));
790 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000791 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000792
anthonycc6c8362010-01-25 04:14:01 +0000793 /* set all kernel values within disk area to 1.0 */
cristyc99304f2010-02-01 15:26:27 +0000794 for ( i=0, v= -kernel->y; v <= (long)kernel->y; v++)
795 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony602ab9b2010-01-05 08:06:50 +0000796 if ((u*u+v*v) <= limit)
anthony4fd27e22010-02-07 08:17:18 +0000797 kernel->positive_range += kernel->values[i] = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +0000798 else
799 kernel->values[i] = nan;
anthony4fd27e22010-02-07 08:17:18 +0000800 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
anthony602ab9b2010-01-05 08:06:50 +0000801 break;
802 }
803 case PlusKernel:
804 {
805 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000806 kernel->width = kernel->height = 5; /* default radius 2 */
anthony602ab9b2010-01-05 08:06:50 +0000807 else
808 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000809 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000810
811 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
812 kernel->height*sizeof(double));
813 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000814 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000815
anthonycc6c8362010-01-25 04:14:01 +0000816 /* set all kernel values along axises to 1.0 */
cristyc99304f2010-02-01 15:26:27 +0000817 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
818 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony4fd27e22010-02-07 08:17:18 +0000819 kernel->values[i] = (u == 0 || v == 0) ? args->sigma : nan;
820 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
821 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
anthony602ab9b2010-01-05 08:06:50 +0000822 break;
823 }
824 /* Distance Measuring Kernels */
825 case ChebyshevKernel:
826 {
827 double
828 scale;
829
830 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000831 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000832 else
833 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000834 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000835
836 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
837 kernel->height*sizeof(double));
838 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000839 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000840
841 scale = (args->sigma < 1.0) ? 100.0 : args->sigma;
cristyc99304f2010-02-01 15:26:27 +0000842 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
843 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
844 kernel->positive_range += ( kernel->values[i] =
anthony602ab9b2010-01-05 08:06:50 +0000845 scale*((labs(u)>labs(v)) ? labs(u) : labs(v)) );
cristyc99304f2010-02-01 15:26:27 +0000846 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000847 break;
848 }
849 case ManhattenKernel:
850 {
851 double
852 scale;
853
854 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000855 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000856 else
857 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000858 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000859
860 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
861 kernel->height*sizeof(double));
862 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000863 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000864
865 scale = (args->sigma < 1.0) ? 100.0 : args->sigma;
cristyc99304f2010-02-01 15:26:27 +0000866 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
867 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
868 kernel->positive_range += ( kernel->values[i] =
anthony602ab9b2010-01-05 08:06:50 +0000869 scale*(labs(u)+labs(v)) );
cristyc99304f2010-02-01 15:26:27 +0000870 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000871 break;
872 }
873 case EuclideanKernel:
874 {
875 double
876 scale;
877
878 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000879 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000880 else
881 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000882 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000883
884 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
885 kernel->height*sizeof(double));
886 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000887 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000888
889 scale = (args->sigma < 1.0) ? 100.0 : args->sigma;
cristyc99304f2010-02-01 15:26:27 +0000890 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
891 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
892 kernel->positive_range += ( kernel->values[i] =
anthony602ab9b2010-01-05 08:06:50 +0000893 scale*sqrt((double)(u*u+v*v)) );
cristyc99304f2010-02-01 15:26:27 +0000894 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000895 break;
896 }
897 /* Undefined Kernels */
898 case LaplacianKernel:
899 case LOGKernel:
900 case DOGKernel:
cristy150989e2010-02-01 14:59:39 +0000901 perror("Kernel Type has not been defined yet");
anthony602ab9b2010-01-05 08:06:50 +0000902 /* FALL THRU */
903 default:
904 /* Generate a No-Op minimal kernel - 1x1 pixel */
905 kernel->values=(double *)AcquireQuantumMemory((size_t)1,sizeof(double));
906 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000907 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000908 kernel->width = kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +0000909 kernel->x = kernel->x = 0;
anthony602ab9b2010-01-05 08:06:50 +0000910 kernel->type = UndefinedKernel;
cristyc99304f2010-02-01 15:26:27 +0000911 kernel->maximum =
912 kernel->positive_range =
anthonyc94cdb02010-01-06 08:15:29 +0000913 kernel->values[0] = 1.0; /* a flat single-point no-op kernel! */
anthony602ab9b2010-01-05 08:06:50 +0000914 break;
915 }
916
917 return(kernel);
918}
anthonyc94cdb02010-01-06 08:15:29 +0000919
anthony602ab9b2010-01-05 08:06:50 +0000920/*
921%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
922% %
923% %
924% %
cristy6771f1e2010-03-05 19:43:39 +0000925% C l o n e K e r n e l I n f o %
anthony4fd27e22010-02-07 08:17:18 +0000926% %
927% %
928% %
929%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
930%
931% CloneKernelInfo() creates a new clone of the given Kernel so that its can
932% be modified without effecting the original. The cloned kernel should be
933% destroyed using DestoryKernelInfo() when no longer needed.
934%
935% The format of the DestroyKernelInfo method is:
936%
anthony930be612010-02-08 04:26:15 +0000937% KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +0000938%
939% A description of each parameter follows:
940%
941% o kernel: the Morphology/Convolution kernel to be cloned
942%
943*/
cristyef656912010-03-05 19:54:59 +0000944MagickExport KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +0000945{
946 register long
947 i;
948
949 KernelInfo *
950 new;
951
952 assert(kernel != (KernelInfo *) NULL);
953
954 new=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
955 if (new == (KernelInfo *) NULL)
956 return(new);
957 *new = *kernel; /* copy values in structure */
958
959 new->values=(double *) AcquireQuantumMemory(kernel->width,
960 kernel->height*sizeof(double));
961 if (new->values == (double *) NULL)
962 return(DestroyKernelInfo(new));
963
964 for (i=0; i < (long) (kernel->width*kernel->height); i++)
965 new->values[i] = kernel->values[i];
966
967 return(new);
968}
969
970/*
971%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
972% %
973% %
974% %
anthony83ba99b2010-01-24 08:48:15 +0000975% D e s t r o y K e r n e l I n f o %
anthony602ab9b2010-01-05 08:06:50 +0000976% %
977% %
978% %
979%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
980%
anthony83ba99b2010-01-24 08:48:15 +0000981% DestroyKernelInfo() frees the memory used by a Convolution/Morphology
982% kernel.
anthony602ab9b2010-01-05 08:06:50 +0000983%
anthony83ba99b2010-01-24 08:48:15 +0000984% The format of the DestroyKernelInfo method is:
anthony602ab9b2010-01-05 08:06:50 +0000985%
anthony83ba99b2010-01-24 08:48:15 +0000986% KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +0000987%
988% A description of each parameter follows:
989%
990% o kernel: the Morphology/Convolution kernel to be destroyed
991%
992*/
993
anthony83ba99b2010-01-24 08:48:15 +0000994MagickExport KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +0000995{
cristy2be15382010-01-21 02:38:03 +0000996 assert(kernel != (KernelInfo *) NULL);
anthony4fd27e22010-02-07 08:17:18 +0000997
998 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
999 kernel->height*sizeof(double));
anthony602ab9b2010-01-05 08:06:50 +00001000 kernel->values=(double *)RelinquishMagickMemory(kernel->values);
cristy2be15382010-01-21 02:38:03 +00001001 kernel=(KernelInfo *) RelinquishMagickMemory(kernel);
anthony602ab9b2010-01-05 08:06:50 +00001002 return(kernel);
1003}
anthonyc94cdb02010-01-06 08:15:29 +00001004
1005/*
1006%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1007% %
1008% %
1009% %
anthony29188a82010-01-22 10:12:34 +00001010% 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 +00001011% %
1012% %
1013% %
1014%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1015%
anthony29188a82010-01-22 10:12:34 +00001016% MorphologyImageChannel() applies a user supplied kernel to the image
1017% according to the given mophology method.
anthony602ab9b2010-01-05 08:06:50 +00001018%
1019% The given kernel is assumed to have been pre-scaled appropriatally, usally
1020% by the kernel generator.
1021%
1022% The format of the MorphologyImage method is:
1023%
cristyef656912010-03-05 19:54:59 +00001024% Image *MorphologyImage(const Image *image,MorphologyMethod method,
1025% const long iterations,KernelInfo *kernel,ExceptionInfo *exception)
anthony29188a82010-01-22 10:12:34 +00001026% Image *MorphologyImageChannel(const Image *image, const ChannelType
cristyef656912010-03-05 19:54:59 +00001027% channel,MorphologyMethod method,const long iterations,
1028% KernelInfo *kernel,ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001029%
1030% A description of each parameter follows:
1031%
1032% o image: the image.
1033%
1034% o method: the morphology method to be applied.
1035%
1036% o iterations: apply the operation this many times (or no change).
1037% A value of -1 means loop until no change found.
1038% How this is applied may depend on the morphology method.
1039% Typically this is a value of 1.
1040%
1041% o channel: the channel type.
1042%
1043% o kernel: An array of double representing the morphology kernel.
anthony29188a82010-01-22 10:12:34 +00001044% Warning: kernel may be normalized for the Convolve method.
anthony602ab9b2010-01-05 08:06:50 +00001045%
1046% o exception: return any errors or warnings in this structure.
1047%
1048%
1049% TODO: bias and auto-scale handling of the kernel for convolution
1050% The given kernel is assumed to have been pre-scaled appropriatally, usally
1051% by the kernel generator.
1052%
1053*/
1054
anthony930be612010-02-08 04:26:15 +00001055
anthony602ab9b2010-01-05 08:06:50 +00001056/* Internal function
anthony930be612010-02-08 04:26:15 +00001057 * Apply the Low-Level Morphology Method using the given Kernel
1058 * Returning the number of pixels that changed.
1059 * Two pre-created images must be provided, no image is created.
anthony602ab9b2010-01-05 08:06:50 +00001060 */
1061static unsigned long MorphologyApply(const Image *image, Image
1062 *result_image, const MorphologyMethod method, const ChannelType channel,
cristy2be15382010-01-21 02:38:03 +00001063 const KernelInfo *kernel, ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001064{
cristy2be15382010-01-21 02:38:03 +00001065#define MorphologyTag "Morphology/Image"
anthony602ab9b2010-01-05 08:06:50 +00001066
1067 long
cristy150989e2010-02-01 14:59:39 +00001068 progress,
anthony29188a82010-01-22 10:12:34 +00001069 y, offx, offy,
anthony602ab9b2010-01-05 08:06:50 +00001070 changed;
1071
1072 MagickBooleanType
1073 status;
1074
1075 MagickPixelPacket
1076 bias;
1077
1078 CacheView
1079 *p_view,
1080 *q_view;
1081
anthony4fd27e22010-02-07 08:17:18 +00001082 /* Only the most basic morphology is actually performed by this routine */
anthony4fd27e22010-02-07 08:17:18 +00001083
anthony602ab9b2010-01-05 08:06:50 +00001084 /*
anthony4fd27e22010-02-07 08:17:18 +00001085 Apply Basic Morphology to Image.
anthony602ab9b2010-01-05 08:06:50 +00001086 */
1087 status=MagickTrue;
1088 changed=0;
1089 progress=0;
1090
1091 GetMagickPixelPacket(image,&bias);
1092 SetMagickPixelPacketBias(image,&bias);
anthonycc6c8362010-01-25 04:14:01 +00001093 /* Future: handle auto-bias from user, based on kernel input */
anthony602ab9b2010-01-05 08:06:50 +00001094
1095 p_view=AcquireCacheView(image);
1096 q_view=AcquireCacheView(result_image);
anthony29188a82010-01-22 10:12:34 +00001097
anthonycc6c8362010-01-25 04:14:01 +00001098 /* Some methods (including convolve) needs use a reflected kernel.
1099 * Adjust 'origin' offsets for this reflected kernel.
anthony29188a82010-01-22 10:12:34 +00001100 */
cristyc99304f2010-02-01 15:26:27 +00001101 offx = kernel->x;
1102 offy = kernel->y;
anthony29188a82010-01-22 10:12:34 +00001103 switch(method) {
1104 case ErodeMorphology:
1105 case ErodeIntensityMorphology:
anthony999bb2c2010-02-18 12:38:01 +00001106 /* kernel is user as is, without reflection */
anthony29188a82010-01-22 10:12:34 +00001107 break;
anthony930be612010-02-08 04:26:15 +00001108 case ConvolveMorphology:
1109 case DilateMorphology:
1110 case DilateIntensityMorphology:
1111 case DistanceMorphology:
anthony999bb2c2010-02-18 12:38:01 +00001112 /* kernel needs to used with reflection */
cristy150989e2010-02-01 14:59:39 +00001113 offx = (long) kernel->width-offx-1;
1114 offy = (long) kernel->height-offy-1;
anthony29188a82010-01-22 10:12:34 +00001115 break;
anthony930be612010-02-08 04:26:15 +00001116 default:
1117 perror("Not a low level Morpholgy Method");
1118 break;
anthony29188a82010-01-22 10:12:34 +00001119 }
1120
anthony602ab9b2010-01-05 08:06:50 +00001121#if defined(MAGICKCORE_OPENMP_SUPPORT)
1122 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1123#endif
cristy150989e2010-02-01 14:59:39 +00001124 for (y=0; y < (long) image->rows; y++)
anthony602ab9b2010-01-05 08:06:50 +00001125 {
1126 MagickBooleanType
1127 sync;
1128
1129 register const PixelPacket
1130 *restrict p;
1131
1132 register const IndexPacket
1133 *restrict p_indexes;
1134
1135 register PixelPacket
1136 *restrict q;
1137
1138 register IndexPacket
1139 *restrict q_indexes;
1140
cristy150989e2010-02-01 14:59:39 +00001141 register long
anthony602ab9b2010-01-05 08:06:50 +00001142 x;
1143
anthony29188a82010-01-22 10:12:34 +00001144 unsigned long
anthony602ab9b2010-01-05 08:06:50 +00001145 r;
1146
1147 if (status == MagickFalse)
1148 continue;
anthony29188a82010-01-22 10:12:34 +00001149 p=GetCacheViewVirtualPixels(p_view, -offx, y-offy,
1150 image->columns+kernel->width, kernel->height, exception);
anthony602ab9b2010-01-05 08:06:50 +00001151 q=GetCacheViewAuthenticPixels(q_view,0,y,result_image->columns,1,
1152 exception);
1153 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1154 {
1155 status=MagickFalse;
1156 continue;
1157 }
1158 p_indexes=GetCacheViewVirtualIndexQueue(p_view);
1159 q_indexes=GetCacheViewAuthenticIndexQueue(q_view);
anthony29188a82010-01-22 10:12:34 +00001160 r = (image->columns+kernel->width)*offy+offx; /* constant */
1161
cristy150989e2010-02-01 14:59:39 +00001162 for (x=0; x < (long) image->columns; x++)
anthony602ab9b2010-01-05 08:06:50 +00001163 {
cristy150989e2010-02-01 14:59:39 +00001164 long
anthony602ab9b2010-01-05 08:06:50 +00001165 v;
1166
cristy150989e2010-02-01 14:59:39 +00001167 register long
anthony602ab9b2010-01-05 08:06:50 +00001168 u;
1169
1170 register const double
1171 *restrict k;
1172
1173 register const PixelPacket
1174 *restrict k_pixels;
1175
1176 register const IndexPacket
1177 *restrict k_indexes;
1178
1179 MagickPixelPacket
1180 result;
1181
anthony29188a82010-01-22 10:12:34 +00001182 /* Copy input to ouput image for unused channels
anthony83ba99b2010-01-24 08:48:15 +00001183 * This removes need for 'cloning' a new image every iteration
anthony29188a82010-01-22 10:12:34 +00001184 */
anthony602ab9b2010-01-05 08:06:50 +00001185 *q = p[r];
1186 if (image->colorspace == CMYKColorspace)
1187 q_indexes[x] = p_indexes[r];
1188
cristy5ee247a2010-02-12 15:42:34 +00001189 result.green=(MagickRealType) 0;
1190 result.blue=(MagickRealType) 0;
1191 result.opacity=(MagickRealType) 0;
1192 result.index=(MagickRealType) 0;
anthony602ab9b2010-01-05 08:06:50 +00001193 switch (method) {
1194 case ConvolveMorphology:
anthony930be612010-02-08 04:26:15 +00001195 /* Set the user defined bias of the weighted average output
1196 **
1197 ** FUTURE: provide some way for internal functions to disable
1198 ** user defined bias and scaling effects.
1199 */
anthony602ab9b2010-01-05 08:06:50 +00001200 result=bias;
anthony930be612010-02-08 04:26:15 +00001201 break;
anthony83ba99b2010-01-24 08:48:15 +00001202 case DilateMorphology:
anthony29188a82010-01-22 10:12:34 +00001203 result.red =
1204 result.green =
1205 result.blue =
1206 result.opacity =
1207 result.index = -MagickHuge;
1208 break;
1209 case ErodeMorphology:
1210 result.red =
1211 result.green =
1212 result.blue =
1213 result.opacity =
1214 result.index = +MagickHuge;
1215 break;
anthony4fd27e22010-02-07 08:17:18 +00001216 case DilateIntensityMorphology:
1217 case ErodeIntensityMorphology:
1218 result.red = 0.0; /* flag indicating first match found */
1219 break;
anthony602ab9b2010-01-05 08:06:50 +00001220 default:
anthony29188a82010-01-22 10:12:34 +00001221 /* Otherwise just start with the original pixel value */
cristy150989e2010-02-01 14:59:39 +00001222 result.red = (MagickRealType) p[r].red;
1223 result.green = (MagickRealType) p[r].green;
1224 result.blue = (MagickRealType) p[r].blue;
1225 result.opacity = QuantumRange - (MagickRealType) p[r].opacity;
anthony602ab9b2010-01-05 08:06:50 +00001226 if ( image->colorspace == CMYKColorspace)
cristy150989e2010-02-01 14:59:39 +00001227 result.index = (MagickRealType) p_indexes[r];
anthony602ab9b2010-01-05 08:06:50 +00001228 break;
1229 }
1230
1231 switch ( method ) {
1232 case ConvolveMorphology:
anthony930be612010-02-08 04:26:15 +00001233 /* Weighted Average of pixels using reflected kernel
1234 **
1235 ** NOTE for correct working of this operation for asymetrical
1236 ** kernels, the kernel needs to be applied in its reflected form.
1237 ** That is its values needs to be reversed.
1238 **
1239 ** Correlation is actually the same as this but without reflecting
1240 ** the kernel, and thus 'lower-level' that Convolution. However
1241 ** as Convolution is the more common method used, and it does not
1242 ** really cost us much in terms of processing to use a reflected
1243 ** kernel it is Convolution that is implemented.
1244 **
1245 ** Correlation will have its kernel reflected before calling
1246 ** this function to do a Convolve.
1247 **
1248 ** For more details of Correlation vs Convolution see
1249 ** http://www.cs.umd.edu/~djacobs/CMSC426/Convolution.pdf
1250 */
anthony602ab9b2010-01-05 08:06:50 +00001251 if (((channel & OpacityChannel) == 0) ||
1252 (image->matte == MagickFalse))
1253 {
anthony930be612010-02-08 04:26:15 +00001254 /* Convolution without transparency effects */
anthony29188a82010-01-22 10:12:34 +00001255 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001256 k_pixels = p;
1257 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001258 for (v=0; v < (long) kernel->height; v++) {
1259 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001260 if ( IsNan(*k) ) continue;
1261 result.red += (*k)*k_pixels[u].red;
1262 result.green += (*k)*k_pixels[u].green;
1263 result.blue += (*k)*k_pixels[u].blue;
anthony83ba99b2010-01-24 08:48:15 +00001264 /* result.opacity += not involved here */
anthony602ab9b2010-01-05 08:06:50 +00001265 if ( image->colorspace == CMYKColorspace)
1266 result.index += (*k)*k_indexes[u];
1267 }
1268 k_pixels += image->columns+kernel->width;
1269 k_indexes += image->columns+kernel->width;
1270 }
anthony602ab9b2010-01-05 08:06:50 +00001271 }
1272 else
1273 { /* Kernel & Alpha weighted Convolution */
1274 MagickRealType
1275 alpha, /* alpha value * kernel weighting */
1276 gamma; /* weighting divisor */
1277
1278 gamma=0.0;
anthony29188a82010-01-22 10:12:34 +00001279 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001280 k_pixels = p;
1281 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001282 for (v=0; v < (long) kernel->height; v++) {
1283 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001284 if ( IsNan(*k) ) continue;
1285 alpha=(*k)*(QuantumScale*(QuantumRange-
1286 k_pixels[u].opacity));
1287 gamma += alpha;
1288 result.red += alpha*k_pixels[u].red;
1289 result.green += alpha*k_pixels[u].green;
1290 result.blue += alpha*k_pixels[u].blue;
anthony83ba99b2010-01-24 08:48:15 +00001291 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00001292 if ( image->colorspace == CMYKColorspace)
1293 result.index += alpha*k_indexes[u];
1294 }
1295 k_pixels += image->columns+kernel->width;
1296 k_indexes += image->columns+kernel->width;
1297 }
1298 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
anthony83ba99b2010-01-24 08:48:15 +00001299 result.red *= gamma;
1300 result.green *= gamma;
1301 result.blue *= gamma;
1302 result.opacity *= gamma;
1303 result.index *= gamma;
anthony602ab9b2010-01-05 08:06:50 +00001304 }
1305 break;
1306
anthony4fd27e22010-02-07 08:17:18 +00001307 case ErodeMorphology:
anthony930be612010-02-08 04:26:15 +00001308 /* Minimize Value within kernel neighbourhood
1309 **
1310 ** NOTE that the kernel is not reflected for this operation!
1311 **
1312 ** NOTE: in normal Greyscale Morphology, the kernel value should
1313 ** be added to the real value, this is currently not done, due to
1314 ** the nature of the boolean kernels being used.
1315 */
anthony4fd27e22010-02-07 08:17:18 +00001316 k = kernel->values;
1317 k_pixels = p;
1318 k_indexes = p_indexes;
1319 for (v=0; v < (long) kernel->height; v++) {
1320 for (u=0; u < (long) kernel->width; u++, k++) {
1321 if ( IsNan(*k) || (*k) < 0.5 ) continue;
1322 Minimize(result.red, (double) k_pixels[u].red);
1323 Minimize(result.green, (double) k_pixels[u].green);
1324 Minimize(result.blue, (double) k_pixels[u].blue);
1325 Minimize(result.opacity, QuantumRange-(double) k_pixels[u].opacity);
1326 if ( image->colorspace == CMYKColorspace)
1327 Minimize(result.index, (double) k_indexes[u]);
1328 }
1329 k_pixels += image->columns+kernel->width;
1330 k_indexes += image->columns+kernel->width;
1331 }
1332 break;
1333
anthony83ba99b2010-01-24 08:48:15 +00001334 case DilateMorphology:
anthony930be612010-02-08 04:26:15 +00001335 /* Maximize Value within kernel neighbourhood
1336 **
1337 ** NOTE for correct working of this operation for asymetrical
1338 ** kernels, the kernel needs to be applied in its reflected form.
1339 ** That is its values needs to be reversed.
1340 **
1341 ** NOTE: in normal Greyscale Morphology, the kernel value should
1342 ** be added to the real value, this is currently not done, due to
1343 ** the nature of the boolean kernels being used.
1344 **
1345 */
anthony29188a82010-01-22 10:12:34 +00001346 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001347 k_pixels = p;
1348 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001349 for (v=0; v < (long) kernel->height; v++) {
1350 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001351 if ( IsNan(*k) || (*k) < 0.5 ) continue;
cristy150989e2010-02-01 14:59:39 +00001352 Maximize(result.red, (double) k_pixels[u].red);
1353 Maximize(result.green, (double) k_pixels[u].green);
1354 Maximize(result.blue, (double) k_pixels[u].blue);
1355 Maximize(result.opacity, QuantumRange-(double) k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00001356 if ( image->colorspace == CMYKColorspace)
cristy150989e2010-02-01 14:59:39 +00001357 Maximize(result.index, (double) k_indexes[u]);
anthony602ab9b2010-01-05 08:06:50 +00001358 }
1359 k_pixels += image->columns+kernel->width;
1360 k_indexes += image->columns+kernel->width;
1361 }
anthony602ab9b2010-01-05 08:06:50 +00001362 break;
1363
anthony4fd27e22010-02-07 08:17:18 +00001364 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001365 /* Select Pixel with Minimum Intensity within kernel neighbourhood
1366 **
1367 ** WARNING: the intensity test fails for CMYK and does not
1368 ** take into account the moderating effect of teh alpha channel
1369 ** on the intensity.
1370 **
1371 ** NOTE that the kernel is not reflected for this operation!
1372 */
anthony602ab9b2010-01-05 08:06:50 +00001373 k = kernel->values;
1374 k_pixels = p;
1375 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001376 for (v=0; v < (long) kernel->height; v++) {
1377 for (u=0; u < (long) kernel->width; u++, k++) {
anthony602ab9b2010-01-05 08:06:50 +00001378 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony4fd27e22010-02-07 08:17:18 +00001379 if ( result.red == 0.0 ||
1380 PixelIntensity(&(k_pixels[u])) < PixelIntensity(q) ) {
1381 /* copy the whole pixel - no channel selection */
1382 *q = k_pixels[u];
1383 if ( result.red > 0.0 ) changed++;
1384 result.red = 1.0;
1385 }
anthony602ab9b2010-01-05 08:06:50 +00001386 }
1387 k_pixels += image->columns+kernel->width;
1388 k_indexes += image->columns+kernel->width;
1389 }
anthony602ab9b2010-01-05 08:06:50 +00001390 break;
1391
anthony83ba99b2010-01-24 08:48:15 +00001392 case DilateIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001393 /* Select Pixel with Maximum Intensity within kernel neighbourhood
1394 **
1395 ** WARNING: the intensity test fails for CMYK and does not
1396 ** take into account the moderating effect of teh alpha channel
1397 ** on the intensity.
1398 **
1399 ** NOTE for correct working of this operation for asymetrical
1400 ** kernels, the kernel needs to be applied in its reflected form.
1401 ** That is its values needs to be reversed.
1402 */
anthony29188a82010-01-22 10:12:34 +00001403 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001404 k_pixels = p;
1405 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001406 for (v=0; v < (long) kernel->height; v++) {
1407 for (u=0; u < (long) kernel->width; u++, k--) {
anthony29188a82010-01-22 10:12:34 +00001408 if ( IsNan(*k) || (*k) < 0.5 ) continue; /* boolean kernel */
1409 if ( result.red == 0.0 ||
1410 PixelIntensity(&(k_pixels[u])) > PixelIntensity(q) ) {
1411 /* copy the whole pixel - no channel selection */
1412 *q = k_pixels[u];
1413 if ( result.red > 0.0 ) changed++;
1414 result.red = 1.0;
1415 }
anthony602ab9b2010-01-05 08:06:50 +00001416 }
1417 k_pixels += image->columns+kernel->width;
1418 k_indexes += image->columns+kernel->width;
1419 }
anthony602ab9b2010-01-05 08:06:50 +00001420 break;
1421
anthony602ab9b2010-01-05 08:06:50 +00001422 case DistanceMorphology:
anthony930be612010-02-08 04:26:15 +00001423 /* Add kernel Value and select the minimum value found.
1424 ** The result is a iterative distance from edge of image shape.
1425 **
1426 ** All Distance Kernels are symetrical, but that may not always
1427 ** be the case. For example how about a distance from left edges?
1428 ** To work correctly with asymetrical kernels the reflected kernel
1429 ** needs to be applied.
1430 */
anthony602ab9b2010-01-05 08:06:50 +00001431#if 0
anthony930be612010-02-08 04:26:15 +00001432 /* No need to do distance morphology if original value is zero
1433 ** Unfortunatally I have not been able to get this right
1434 ** when channel selection also becomes involved. -- Arrgghhh
1435 */
1436 if ( ((channel & RedChannel) == 0 && p[r].red == 0)
1437 || ((channel & GreenChannel) == 0 && p[r].green == 0)
1438 || ((channel & BlueChannel) == 0 && p[r].blue == 0)
1439 || ((channel & OpacityChannel) == 0 && p[r].opacity == 0)
1440 || (( (channel & IndexChannel) == 0
1441 || image->colorspace != CMYKColorspace
1442 ) && p_indexes[x] ==0 )
1443 )
1444 break;
anthony602ab9b2010-01-05 08:06:50 +00001445#endif
anthony29188a82010-01-22 10:12:34 +00001446 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001447 k_pixels = p;
1448 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001449 for (v=0; v < (long) kernel->height; v++) {
1450 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001451 if ( IsNan(*k) ) continue;
1452 Minimize(result.red, (*k)+k_pixels[u].red);
1453 Minimize(result.green, (*k)+k_pixels[u].green);
1454 Minimize(result.blue, (*k)+k_pixels[u].blue);
1455 Minimize(result.opacity, (*k)+QuantumRange-k_pixels[u].opacity);
1456 if ( image->colorspace == CMYKColorspace)
1457 Minimize(result.index, (*k)+k_indexes[u]);
1458 }
1459 k_pixels += image->columns+kernel->width;
1460 k_indexes += image->columns+kernel->width;
1461 }
anthony602ab9b2010-01-05 08:06:50 +00001462 break;
1463
1464 case UndefinedMorphology:
1465 default:
1466 break; /* Do nothing */
anthony83ba99b2010-01-24 08:48:15 +00001467 }
1468 switch ( method ) {
1469 case UndefinedMorphology:
1470 case DilateIntensityMorphology:
1471 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001472 break; /* full pixel was directly assigned - not a channel method */
anthony83ba99b2010-01-24 08:48:15 +00001473 default:
1474 /* Assign the results */
1475 if ((channel & RedChannel) != 0)
1476 q->red = ClampToQuantum(result.red);
1477 if ((channel & GreenChannel) != 0)
1478 q->green = ClampToQuantum(result.green);
1479 if ((channel & BlueChannel) != 0)
1480 q->blue = ClampToQuantum(result.blue);
1481 if ((channel & OpacityChannel) != 0
1482 && image->matte == MagickTrue )
1483 q->opacity = ClampToQuantum(QuantumRange-result.opacity);
1484 if ((channel & IndexChannel) != 0
1485 && image->colorspace == CMYKColorspace)
1486 q_indexes[x] = ClampToQuantum(result.index);
1487 break;
1488 }
1489 if ( ( p[r].red != q->red )
1490 || ( p[r].green != q->green )
1491 || ( p[r].blue != q->blue )
1492 || ( p[r].opacity != q->opacity )
1493 || ( image->colorspace == CMYKColorspace &&
1494 p_indexes[r] != q_indexes[x] ) )
1495 changed++; /* The pixel had some value changed! */
anthony602ab9b2010-01-05 08:06:50 +00001496 p++;
1497 q++;
anthony83ba99b2010-01-24 08:48:15 +00001498 } /* x */
anthony602ab9b2010-01-05 08:06:50 +00001499 sync=SyncCacheViewAuthenticPixels(q_view,exception);
1500 if (sync == MagickFalse)
1501 status=MagickFalse;
1502 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1503 {
1504 MagickBooleanType
1505 proceed;
1506
1507#if defined(MAGICKCORE_OPENMP_SUPPORT)
1508 #pragma omp critical (MagickCore_MorphologyImage)
1509#endif
1510 proceed=SetImageProgress(image,MorphologyTag,progress++,image->rows);
1511 if (proceed == MagickFalse)
1512 status=MagickFalse;
1513 }
anthony83ba99b2010-01-24 08:48:15 +00001514 } /* y */
anthony602ab9b2010-01-05 08:06:50 +00001515 result_image->type=image->type;
1516 q_view=DestroyCacheView(q_view);
1517 p_view=DestroyCacheView(p_view);
cristy150989e2010-02-01 14:59:39 +00001518 return(status ? (unsigned long) changed : 0);
anthony602ab9b2010-01-05 08:06:50 +00001519}
1520
anthony4fd27e22010-02-07 08:17:18 +00001521
1522MagickExport Image *MorphologyImage(const Image *image, const MorphologyMethod
anthony930be612010-02-08 04:26:15 +00001523 method, const long iterations,const KernelInfo *kernel, ExceptionInfo
1524 *exception)
cristy2be15382010-01-21 02:38:03 +00001525{
1526 Image
1527 *morphology_image;
1528
1529 morphology_image=MorphologyImageChannel(image,DefaultChannels,method,
1530 iterations,kernel,exception);
1531 return(morphology_image);
1532}
1533
anthony4fd27e22010-02-07 08:17:18 +00001534
cristyef656912010-03-05 19:54:59 +00001535MagickExport Image *MorphologyImageChannel(const Image *image,
1536 const ChannelType channel,const MorphologyMethod method,
1537 const long iterations,const KernelInfo *kernel,ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001538{
cristy150989e2010-02-01 14:59:39 +00001539 long
1540 count;
anthony602ab9b2010-01-05 08:06:50 +00001541
1542 Image
1543 *new_image,
anthony4fd27e22010-02-07 08:17:18 +00001544 *old_image,
1545 *grad_image;
anthony602ab9b2010-01-05 08:06:50 +00001546
anthonycc6c8362010-01-25 04:14:01 +00001547 const char
1548 *artifact;
1549
cristy150989e2010-02-01 14:59:39 +00001550 unsigned long
1551 changed,
1552 limit;
1553
anthony4fd27e22010-02-07 08:17:18 +00001554 KernelInfo
1555 *curr_kernel;
1556
1557 MorphologyMethod
1558 curr_method;
1559
anthony602ab9b2010-01-05 08:06:50 +00001560 assert(image != (Image *) NULL);
1561 assert(image->signature == MagickSignature);
anthony4fd27e22010-02-07 08:17:18 +00001562 assert(kernel != (KernelInfo *) NULL);
1563 assert(kernel->signature == MagickSignature);
anthony602ab9b2010-01-05 08:06:50 +00001564 assert(exception != (ExceptionInfo *) NULL);
1565 assert(exception->signature == MagickSignature);
1566
anthony602ab9b2010-01-05 08:06:50 +00001567 if ( iterations == 0 )
1568 return((Image *)NULL); /* null operation - nothing to do! */
1569
1570 /* kernel must be valid at this point
1571 * (except maybe for posible future morphology methods like "Prune"
1572 */
cristy2be15382010-01-21 02:38:03 +00001573 assert(kernel != (KernelInfo *)NULL);
anthony602ab9b2010-01-05 08:06:50 +00001574
anthony4fd27e22010-02-07 08:17:18 +00001575 count = 0; /* interation count */
1576 changed = 1; /* if compound method assume image was changed */
anthony930be612010-02-08 04:26:15 +00001577 curr_kernel = (KernelInfo *)kernel; /* allow kernel and method */
1578 curr_method = method; /* to be changed as nessary */
anthony4fd27e22010-02-07 08:17:18 +00001579
cristy150989e2010-02-01 14:59:39 +00001580 limit = (unsigned long) iterations;
anthony602ab9b2010-01-05 08:06:50 +00001581 if ( iterations < 0 )
1582 limit = image->columns > image->rows ? image->columns : image->rows;
1583
anthony4fd27e22010-02-07 08:17:18 +00001584 /* Third-level morphology methods */
cristy5ee247a2010-02-12 15:42:34 +00001585 grad_image=(Image *) NULL;
anthony4fd27e22010-02-07 08:17:18 +00001586 switch( curr_method ) {
1587 case EdgeMorphology:
1588 grad_image = MorphologyImageChannel(image, channel,
1589 DilateMorphology, iterations, curr_kernel, exception);
1590 /* FALL-THRU */
1591 case EdgeInMorphology:
1592 curr_method = ErodeMorphology;
anthony602ab9b2010-01-05 08:06:50 +00001593 break;
anthony4fd27e22010-02-07 08:17:18 +00001594 case EdgeOutMorphology:
1595 curr_method = DilateMorphology;
anthony602ab9b2010-01-05 08:06:50 +00001596 break;
anthony4fd27e22010-02-07 08:17:18 +00001597 case TopHatMorphology:
1598 curr_method = OpenMorphology;
1599 break;
1600 case BottomHatMorphology:
1601 curr_method = CloseMorphology;
1602 break;
1603 default:
anthony930be612010-02-08 04:26:15 +00001604 break; /* not a third-level method */
anthony4fd27e22010-02-07 08:17:18 +00001605 }
1606
1607 /* Second-level morphology methods */
1608 switch( curr_method ) {
anthony930be612010-02-08 04:26:15 +00001609 case OpenMorphology:
1610 /* Open is a Erode then a Dilate without reflection */
anthony4fd27e22010-02-07 08:17:18 +00001611 new_image = MorphologyImageChannel(image, channel,
1612 ErodeMorphology, iterations, curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001613 if (new_image == (Image *) NULL)
1614 return((Image *) NULL);
anthony4fd27e22010-02-07 08:17:18 +00001615 curr_method = DilateMorphology;
1616 break;
anthony602ab9b2010-01-05 08:06:50 +00001617 case OpenIntensityMorphology:
anthony4fd27e22010-02-07 08:17:18 +00001618 new_image = MorphologyImageChannel(image, channel,
1619 ErodeIntensityMorphology, iterations, curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001620 if (new_image == (Image *) NULL)
1621 return((Image *) NULL);
anthony4fd27e22010-02-07 08:17:18 +00001622 curr_method = DilateIntensityMorphology;
1623 break;
anthony930be612010-02-08 04:26:15 +00001624
1625 case CloseMorphology:
1626 /* Close is a Dilate then Erode using reflected kernel */
1627 /* A reflected kernel is needed for a Close */
1628 if ( curr_kernel == kernel )
1629 curr_kernel = CloneKernelInfo(kernel);
1630 RotateKernelInfo(curr_kernel,180);
1631 new_image = MorphologyImageChannel(image, channel,
1632 DilateMorphology, iterations, curr_kernel, exception);
1633 if (new_image == (Image *) NULL)
1634 return((Image *) NULL);
1635 curr_method = ErodeMorphology;
1636 break;
anthony4fd27e22010-02-07 08:17:18 +00001637 case CloseIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001638 /* A reflected kernel is needed for a Close */
1639 if ( curr_kernel == kernel )
1640 curr_kernel = CloneKernelInfo(kernel);
anthony4fd27e22010-02-07 08:17:18 +00001641 RotateKernelInfo(curr_kernel,180);
1642 new_image = MorphologyImageChannel(image, channel,
1643 DilateIntensityMorphology, iterations, curr_kernel, exception);
1644 if (new_image == (Image *) NULL)
1645 return((Image *) NULL);
1646 curr_method = ErodeIntensityMorphology;
anthony602ab9b2010-01-05 08:06:50 +00001647 break;
1648
anthony930be612010-02-08 04:26:15 +00001649 case CorrelateMorphology:
1650 /* A Correlation is actually a Convolution with a reflected kernel.
1651 ** However a Convolution is a weighted sum with a reflected kernel.
1652 ** It may seem stange to convert a Correlation into a Convolution
1653 ** as the Correleation is the simplier method, but Convolution is
1654 ** much more commonly used, and it makes sense to implement it directly
1655 ** so as to avoid the need to duplicate the kernel when it is not
1656 ** required (which is typically the default).
1657 */
1658 if ( curr_kernel == kernel )
1659 curr_kernel = CloneKernelInfo(kernel);
1660 RotateKernelInfo(curr_kernel,180);
1661 curr_method = ConvolveMorphology;
1662 /* FALL-THRU into Correlate (weigthed sum without reflection) */
1663
anthonyc94cdb02010-01-06 08:15:29 +00001664 case ConvolveMorphology:
anthony4fd27e22010-02-07 08:17:18 +00001665 /* Scale or Normalize kernel, according to user wishes
anthony930be612010-02-08 04:26:15 +00001666 ** before using it for the Convolve/Correlate method.
1667 **
1668 ** FUTURE: provide some way for internal functions to disable
1669 ** user bias and scaling effects.
anthonycc6c8362010-01-25 04:14:01 +00001670 */
1671 artifact = GetImageArtifact(image,"convolve:scale");
anthony4fd27e22010-02-07 08:17:18 +00001672 if ( artifact != (char *)NULL ) {
anthony999bb2c2010-02-18 12:38:01 +00001673 MagickStatusType
1674 flags;
1675 GeometryInfo
1676 args;
1677
anthony930be612010-02-08 04:26:15 +00001678 if ( curr_kernel == kernel )
1679 curr_kernel = CloneKernelInfo(kernel);
anthony999bb2c2010-02-18 12:38:01 +00001680
1681 args.rho = 1.0;
1682 flags = ParseGeometry(artifact, &args);
1683 ScaleKernelInfo(curr_kernel, args.rho, flags);
anthony4fd27e22010-02-07 08:17:18 +00001684 }
anthony930be612010-02-08 04:26:15 +00001685 /* FALL-THRU to do the first, and typically the only iteration */
anthony4fd27e22010-02-07 08:17:18 +00001686
anthony602ab9b2010-01-05 08:06:50 +00001687 default:
anthony930be612010-02-08 04:26:15 +00001688 /* Do a single iteration using the Low-Level Morphology method!
1689 ** This ensures a "new_image" has been generated, but allows us to skip
1690 ** the creation of 'old_image' if no more iterations are needed.
1691 **
1692 ** The "curr_method" should also be set to a low-level method that is
1693 ** understood by the MorphologyApply() internal function.
anthony602ab9b2010-01-05 08:06:50 +00001694 */
1695 new_image=CloneImage(image,0,0,MagickTrue,exception);
1696 if (new_image == (Image *) NULL)
1697 return((Image *) NULL);
1698 if (SetImageStorageClass(new_image,DirectClass) == MagickFalse)
1699 {
1700 InheritException(exception,&new_image->exception);
1701 new_image=DestroyImage(new_image);
1702 return((Image *) NULL);
1703 }
anthony4fd27e22010-02-07 08:17:18 +00001704 changed = MorphologyApply(image,new_image,curr_method,channel,curr_kernel,
anthony602ab9b2010-01-05 08:06:50 +00001705 exception);
1706 count++;
1707 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
cristy150989e2010-02-01 14:59:39 +00001708 fprintf(stderr, "Morphology %s:%ld => Changed %lu\n",
anthony4fd27e22010-02-07 08:17:18 +00001709 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
anthony602ab9b2010-01-05 08:06:50 +00001710 count, changed);
anthony930be612010-02-08 04:26:15 +00001711 break;
anthony602ab9b2010-01-05 08:06:50 +00001712 }
1713
anthony930be612010-02-08 04:26:15 +00001714 /* At this point the "curr_method" should not only be set to a low-level
1715 ** method that is understood by the MorphologyApply() internal function,
1716 ** but "new_image" should now be defined, as the image to apply the
1717 ** "curr_method" to.
1718 */
1719
1720 /* Repeat the low-level morphology until count or no change reached */
cristy150989e2010-02-01 14:59:39 +00001721 if ( count < (long) limit && changed > 0 ) {
anthony602ab9b2010-01-05 08:06:50 +00001722 old_image = CloneImage(new_image,0,0,MagickTrue,exception);
1723 if (old_image == (Image *) NULL)
1724 return(DestroyImage(new_image));
1725 if (SetImageStorageClass(old_image,DirectClass) == MagickFalse)
1726 {
1727 InheritException(exception,&old_image->exception);
1728 old_image=DestroyImage(old_image);
1729 return(DestroyImage(new_image));
1730 }
cristy150989e2010-02-01 14:59:39 +00001731 while( count < (long) limit && changed != 0 )
anthony602ab9b2010-01-05 08:06:50 +00001732 {
1733 Image *tmp = old_image;
1734 old_image = new_image;
1735 new_image = tmp;
anthony4fd27e22010-02-07 08:17:18 +00001736 changed = MorphologyApply(old_image,new_image,curr_method,channel,
1737 curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001738 count++;
1739 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
cristy150989e2010-02-01 14:59:39 +00001740 fprintf(stderr, "Morphology %s:%ld => Changed %lu\n",
anthony4fd27e22010-02-07 08:17:18 +00001741 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
anthony602ab9b2010-01-05 08:06:50 +00001742 count, changed);
1743 }
cristy150989e2010-02-01 14:59:39 +00001744 old_image=DestroyImage(old_image);
anthony602ab9b2010-01-05 08:06:50 +00001745 }
anthony930be612010-02-08 04:26:15 +00001746
1747 /* We are finished with kernel - destroy it if we made a clone */
anthony4fd27e22010-02-07 08:17:18 +00001748 if ( curr_kernel != kernel )
1749 curr_kernel=DestroyKernelInfo(curr_kernel);
1750
anthony930be612010-02-08 04:26:15 +00001751 /* Third-level Subtractive methods post-processing */
anthony4fd27e22010-02-07 08:17:18 +00001752 switch( method ) {
1753 case EdgeOutMorphology:
1754 case EdgeInMorphology:
1755 case TopHatMorphology:
1756 case BottomHatMorphology:
anthony930be612010-02-08 04:26:15 +00001757 /* Get Difference relative to the original image */
cristy04ffdba2010-02-18 14:34:47 +00001758 (void) CompositeImageChannel(new_image, channel, DifferenceCompositeOp,
anthony4fd27e22010-02-07 08:17:18 +00001759 image, 0, 0);
1760 break;
anthony930be612010-02-08 04:26:15 +00001761 case EdgeMorphology: /* subtract the Erode from a Dilate */
cristy04ffdba2010-02-18 14:34:47 +00001762 (void) CompositeImageChannel(new_image, channel, DifferenceCompositeOp,
anthony4fd27e22010-02-07 08:17:18 +00001763 grad_image, 0, 0);
1764 grad_image=DestroyImage(grad_image);
1765 break;
1766 default:
1767 break;
1768 }
anthony602ab9b2010-01-05 08:06:50 +00001769
1770 return(new_image);
1771}
anthony83ba99b2010-01-24 08:48:15 +00001772
1773/*
1774%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1775% %
1776% %
1777% %
anthony4fd27e22010-02-07 08:17:18 +00001778+ R o t a t e K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00001779% %
1780% %
1781% %
1782%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1783%
anthony4fd27e22010-02-07 08:17:18 +00001784% RotateKernelInfo() rotates the kernel by the angle given. Currently it is
anthony83ba99b2010-01-24 08:48:15 +00001785% restricted to 90 degree angles, but this may be improved in the future.
1786%
anthony4fd27e22010-02-07 08:17:18 +00001787% The format of the RotateKernelInfo method is:
anthony83ba99b2010-01-24 08:48:15 +00001788%
anthony4fd27e22010-02-07 08:17:18 +00001789% void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00001790%
1791% A description of each parameter follows:
1792%
1793% o kernel: the Morphology/Convolution kernel
1794%
1795% o angle: angle to rotate in degrees
1796%
anthonyc4c86e02010-01-27 09:30:32 +00001797% This function is only internel to this module, as it is not finalized,
1798% especially with regard to non-orthogonal angles, and rotation of larger
1799% 2D kernels.
anthony83ba99b2010-01-24 08:48:15 +00001800*/
anthony4fd27e22010-02-07 08:17:18 +00001801static void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00001802{
1803 /* WARNING: Currently assumes the kernel (rightly) is horizontally symetrical
1804 **
1805 ** TODO: expand beyond simple 90 degree rotates, flips and flops
1806 */
1807
1808 /* Modulus the angle */
1809 angle = fmod(angle, 360.0);
1810 if ( angle < 0 )
1811 angle += 360.0;
1812
1813 if ( 315.0 < angle || angle <= 45.0 )
1814 return; /* no change! - At least at this time */
1815
1816 switch (kernel->type) {
1817 /* These built-in kernels are cylindrical kernels, rotating is useless */
1818 case GaussianKernel:
1819 case LaplacianKernel:
1820 case LOGKernel:
1821 case DOGKernel:
1822 case DiskKernel:
1823 case ChebyshevKernel:
1824 case ManhattenKernel:
1825 case EuclideanKernel:
1826 return;
1827
1828 /* These may be rotatable at non-90 angles in the future */
1829 /* but simply rotating them in multiples of 90 degrees is useless */
1830 case SquareKernel:
1831 case DiamondKernel:
1832 case PlusKernel:
1833 return;
1834
1835 /* These only allows a +/-90 degree rotation (by transpose) */
1836 /* A 180 degree rotation is useless */
1837 case BlurKernel:
1838 case RectangleKernel:
1839 if ( 135.0 < angle && angle <= 225.0 )
1840 return;
1841 if ( 225.0 < angle && angle <= 315.0 )
1842 angle -= 180;
1843 break;
1844
1845 /* these are freely rotatable in 90 degree units */
1846 case CometKernel:
1847 case UndefinedKernel:
1848 case UserDefinedKernel:
1849 break;
1850 }
1851 if ( 135.0 < angle && angle <= 225.0 )
1852 {
1853 /* For a 180 degree rotation - also know as a reflection */
1854 /* This is actually a very very common operation! */
1855 /* Basically all that is needed is a reversal of the kernel data! */
1856 unsigned long
1857 i,j;
1858 register double
1859 *k,t;
1860
1861 k=kernel->values;
1862 for ( i=0, j=kernel->width*kernel->height-1; i<j; i++, j--)
1863 t=k[i], k[i]=k[j], k[j]=t;
1864
anthony930be612010-02-08 04:26:15 +00001865 kernel->x = (long) kernel->width - kernel->x - 1;
1866 kernel->y = (long) kernel->height - kernel->y - 1;
anthony83ba99b2010-01-24 08:48:15 +00001867 angle = fmod(angle+180.0, 360.0);
1868 }
1869 if ( 45.0 < angle && angle <= 135.0 )
1870 { /* Do a transpose and a flop, of the image, which results in a 90
1871 * degree rotation using two mirror operations.
1872 *
1873 * WARNING: this assumes the original image was a 1 dimentional image
1874 * but currently that is the only built-ins it is applied to.
1875 */
cristy150989e2010-02-01 14:59:39 +00001876 long
anthony83ba99b2010-01-24 08:48:15 +00001877 t;
cristy150989e2010-02-01 14:59:39 +00001878 t = (long) kernel->width;
anthony83ba99b2010-01-24 08:48:15 +00001879 kernel->width = kernel->height;
cristy150989e2010-02-01 14:59:39 +00001880 kernel->height = (unsigned long) t;
cristyc99304f2010-02-01 15:26:27 +00001881 t = kernel->x;
1882 kernel->x = kernel->y;
1883 kernel->y = t;
anthony83ba99b2010-01-24 08:48:15 +00001884 angle = fmod(450.0 - angle, 360.0);
1885 }
1886 /* At this point angle should be between -45 (315) and +45 degrees
1887 * In the future some form of non-orthogonal angled rotates could be
1888 * performed here, posibily with a linear kernel restriction.
1889 */
1890
1891#if 0
1892 Not currently in use!
1893 { /* Do a flop, this assumes kernel is horizontally symetrical.
1894 * Each row of the kernel needs to be reversed!
1895 */
1896 unsigned long
1897 y;
cristy150989e2010-02-01 14:59:39 +00001898 register long
anthony83ba99b2010-01-24 08:48:15 +00001899 x,r;
1900 register double
1901 *k,t;
1902
1903 for ( y=0, k=kernel->values; y < kernel->height; y++, k+=kernel->width)
1904 for ( x=0, r=kernel->width-1; x<kernel->width/2; x++, r--)
1905 t=k[x], k[x]=k[r], k[r]=t;
1906
cristyc99304f2010-02-01 15:26:27 +00001907 kernel->x = kernel->width - kernel->x - 1;
anthony83ba99b2010-01-24 08:48:15 +00001908 angle = fmod(angle+180.0, 360.0);
1909 }
1910#endif
1911 return;
1912}
1913
1914/*
1915%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1916% %
1917% %
1918% %
cristy6771f1e2010-03-05 19:43:39 +00001919% S c a l e K e r n e l I n f o %
anthonycc6c8362010-01-25 04:14:01 +00001920% %
1921% %
1922% %
1923%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1924%
anthony999bb2c2010-02-18 12:38:01 +00001925% ScaleKernelInfo() scales the kernel by the given amount, with or without
1926% normalization of the sum of the kernel values.
anthonycc6c8362010-01-25 04:14:01 +00001927%
anthony999bb2c2010-02-18 12:38:01 +00001928% By default (no flags given) the values within the kernel is scaled
1929% according the given scaling factor.
anthonycc6c8362010-01-25 04:14:01 +00001930%
anthony999bb2c2010-02-18 12:38:01 +00001931% If any 'normalize_flags' are given the kernel will be normalized and then
1932% further scaled by the scaleing factor value given. A 'PercentValue' flag
1933% will cause the given scaling factor to be divided by one hundred percent.
1934%
1935% Kernel normalization ('normalize_flags' given) is designed to ensure that
1936% any use of the kernel scaling factor with 'Convolve' or 'Correlate'
1937% morphology methods will fall into -1.0 to +1.0 range. Note however that
1938% for non-HDRI versions of IM this may cause images to have any negative
1939% results clipped, unless some 'clip' any negative output from 'Convolve'
1940% with the use of some kernels.
1941%
1942% More specifically. Kernels which only contain positive values (such as a
1943% 'Gaussian' kernel) will be scaled so that those values sum to +1.0,
1944% ensuring a 0.0 to +1.0 convolution output range for non-HDRI images.
1945%
1946% For Kernels that contain some negative values, (such as 'Sharpen' kernels)
1947% the kernel will be scaled by the absolute of the sum of kernel values, so
1948% that it will generally fall within the +/- 1.0 range.
1949%
1950% For kernels whose values sum to zero, (such as 'Laplician' kernels) kernel
1951% will be scaled by just the sum of the postive values, so that its output
1952% range will again fall into the +/- 1.0 range.
1953%
1954% For special kernels designed for locating shapes using 'Correlate', (often
1955% only containing +1 and -1 values, representing foreground/brackground
1956% matching) a special normalization method is provided to scale the positive
1957% values seperatally to those of the negative values, so the kernel will be
1958% forced to become a zero-sum kernel better suited to such searches.
1959%
1960% WARNING: Correct normalization of the kernal assumes that the '*_range'
1961% attributes within the kernel structure have been correctly set during the
1962% kernels creation.
1963%
1964% NOTE: The values used for 'normalize_flags' have been selected specifically
1965% to match the use of geometry options, so that '!' means NormalizeValue, '^'
1966% means CorrelateNormalizeValue, and '%' means PercentValue. All other
1967% GeometryFlags values are ignored.
anthonycc6c8362010-01-25 04:14:01 +00001968%
anthony4fd27e22010-02-07 08:17:18 +00001969% The format of the ScaleKernelInfo method is:
anthonycc6c8362010-01-25 04:14:01 +00001970%
anthony999bb2c2010-02-18 12:38:01 +00001971% void ScaleKernelInfo(KernelInfo *kernel, const double scaling_factor,
1972% const MagickStatusType normalize_flags )
anthonycc6c8362010-01-25 04:14:01 +00001973%
1974% A description of each parameter follows:
1975%
1976% o kernel: the Morphology/Convolution kernel
1977%
anthony999bb2c2010-02-18 12:38:01 +00001978% o scaling_factor:
1979% multiply all values (after normalization) by this factor if not
1980% zero. If the kernel is normalized regardless of any flags.
1981%
1982% o normalize_flags:
1983% GeometryFlags defining normalization method to use.
1984% specifically: NormalizeValue, CorrelateNormalizeValue,
1985% and/or PercentValue
anthonycc6c8362010-01-25 04:14:01 +00001986%
anthonyc4c86e02010-01-27 09:30:32 +00001987% This function is internal to this module only at this time, but can be
1988% exported to other modules if needed.
anthonycc6c8362010-01-25 04:14:01 +00001989*/
cristy6771f1e2010-03-05 19:43:39 +00001990MagickExport void ScaleKernelInfo(KernelInfo *kernel,
1991 const double scaling_factor,const GeometryFlags normalize_flags)
anthonycc6c8362010-01-25 04:14:01 +00001992{
cristy150989e2010-02-01 14:59:39 +00001993 register long
anthonycc6c8362010-01-25 04:14:01 +00001994 i;
1995
anthony999bb2c2010-02-18 12:38:01 +00001996 register double
1997 pos_scale,
1998 neg_scale;
1999
2000 pos_scale = 1.0;
2001 if ( (normalize_flags&NormalizeValue) != 0 ) {
2002 /* normalize kernel appropriately */
2003 if ( fabs(kernel->positive_range + kernel->negative_range) > MagickEpsilon )
2004 pos_scale = fabs(kernel->positive_range + kernel->negative_range);
anthonycc6c8362010-01-25 04:14:01 +00002005 else
anthony999bb2c2010-02-18 12:38:01 +00002006 pos_scale = kernel->positive_range; /* special zero-summing kernel */
2007 }
2008 /* force kernel into being a normalized zero-summing kernel */
2009 if ( (normalize_flags&CorrelateNormalizeValue) != 0 ) {
2010 pos_scale = ( fabs(kernel->positive_range) > MagickEpsilon )
2011 ? kernel->positive_range : 1.0;
2012 neg_scale = ( fabs(kernel->negative_range) > MagickEpsilon )
2013 ? -kernel->negative_range : 1.0;
2014 }
2015 else
2016 neg_scale = pos_scale;
2017
2018 /* finialize scaling_factor for positive and negative components */
2019 pos_scale = scaling_factor/pos_scale;
2020 neg_scale = scaling_factor/neg_scale;
2021 if ( (normalize_flags&PercentValue) != 0 ) {
2022 pos_scale /= 100.0;
2023 neg_scale /= 100.0;
anthonycc6c8362010-01-25 04:14:01 +00002024 }
2025
cristy150989e2010-02-01 14:59:39 +00002026 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00002027 if ( ! IsNan(kernel->values[i]) )
anthony999bb2c2010-02-18 12:38:01 +00002028 kernel->values[i] *= (kernel->values[i] >= 0) ? pos_scale : neg_scale;
anthonycc6c8362010-01-25 04:14:01 +00002029
anthony999bb2c2010-02-18 12:38:01 +00002030 /* convolution output range */
2031 kernel->positive_range *= pos_scale;
2032 kernel->negative_range *= neg_scale;
2033 /* maximum and minimum values in kernel */
2034 kernel->maximum *= (kernel->maximum >= 0.0) ? pos_scale : neg_scale;
2035 kernel->minimum *= (kernel->minimum >= 0.0) ? pos_scale : neg_scale;
2036
2037 /* swap kernel settings if user scaling factor is negative */
2038 if ( scaling_factor < MagickEpsilon ) {
2039 double t;
2040 t = kernel->positive_range;
2041 kernel->positive_range = kernel->negative_range;
2042 kernel->negative_range = t;
2043 t = kernel->maximum;
2044 kernel->maximum = kernel->minimum;
2045 kernel->minimum = 1;
2046 }
anthonycc6c8362010-01-25 04:14:01 +00002047
2048 return;
2049}
2050
2051/*
2052%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2053% %
2054% %
2055% %
anthony4fd27e22010-02-07 08:17:18 +00002056+ S h o w K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00002057% %
2058% %
2059% %
2060%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2061%
anthony4fd27e22010-02-07 08:17:18 +00002062% ShowKernelInfo() outputs the details of the given kernel defination to
2063% standard error, generally due to a users 'showkernel' option request.
anthony83ba99b2010-01-24 08:48:15 +00002064%
2065% The format of the ShowKernel method is:
2066%
anthony4fd27e22010-02-07 08:17:18 +00002067% void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00002068%
2069% A description of each parameter follows:
2070%
2071% o kernel: the Morphology/Convolution kernel
2072%
anthonyc4c86e02010-01-27 09:30:32 +00002073% This function is internal to this module only at this time. That may change
2074% in the future.
anthony83ba99b2010-01-24 08:48:15 +00002075*/
anthony4fd27e22010-02-07 08:17:18 +00002076MagickExport void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00002077{
cristy150989e2010-02-01 14:59:39 +00002078 long
anthony83ba99b2010-01-24 08:48:15 +00002079 i, u, v;
2080
2081 fprintf(stderr,
anthonycc6c8362010-01-25 04:14:01 +00002082 "Kernel \"%s\" of size %lux%lu%+ld%+ld with values from %.*lg to %.*lg\n",
anthony83ba99b2010-01-24 08:48:15 +00002083 MagickOptionToMnemonic(MagickKernelOptions, kernel->type),
2084 kernel->width, kernel->height,
cristyc99304f2010-02-01 15:26:27 +00002085 kernel->x, kernel->y,
2086 GetMagickPrecision(), kernel->minimum,
2087 GetMagickPrecision(), kernel->maximum);
anthonycc6c8362010-01-25 04:14:01 +00002088 fprintf(stderr, "Forming convolution output range from %.*lg to %.*lg%s\n",
cristyc99304f2010-02-01 15:26:27 +00002089 GetMagickPrecision(), kernel->negative_range,
2090 GetMagickPrecision(), kernel->positive_range,
anthonycc6c8362010-01-25 04:14:01 +00002091 /*kernel->normalized == MagickTrue ? " (normalized)" : */ "" );
cristy150989e2010-02-01 14:59:39 +00002092 for (i=v=0; v < (long) kernel->height; v++) {
anthony83ba99b2010-01-24 08:48:15 +00002093 fprintf(stderr,"%2ld:",v);
cristy150989e2010-02-01 14:59:39 +00002094 for (u=0; u < (long) kernel->width; u++, i++)
anthony83ba99b2010-01-24 08:48:15 +00002095 if ( IsNan(kernel->values[i]) )
2096 fprintf(stderr," %*s", GetMagickPrecision()+2, "nan");
2097 else
2098 fprintf(stderr," %*.*lg", GetMagickPrecision()+2,
2099 GetMagickPrecision(), kernel->values[i]);
2100 fprintf(stderr,"\n");
2101 }
2102}
anthonycc6c8362010-01-25 04:14:01 +00002103
2104/*
2105%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2106% %
2107% %
2108% %
anthony4fd27e22010-02-07 08:17:18 +00002109+ Z e r o K e r n e l N a n s %
anthonycc6c8362010-01-25 04:14:01 +00002110% %
2111% %
2112% %
2113%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2114%
2115% ZeroKernelNans() replaces any special 'nan' value that may be present in
2116% the kernel with a zero value. This is typically done when the kernel will
2117% be used in special hardware (GPU) convolution processors, to simply
2118% matters.
2119%
2120% The format of the ZeroKernelNans method is:
2121%
2122% voidZeroKernelNans (KernelInfo *kernel)
2123%
2124% A description of each parameter follows:
2125%
2126% o kernel: the Morphology/Convolution kernel
2127%
2128% FUTURE: return the information in a string for API usage.
2129*/
anthonyc4c86e02010-01-27 09:30:32 +00002130MagickExport void ZeroKernelNans(KernelInfo *kernel)
anthonycc6c8362010-01-25 04:14:01 +00002131{
cristy150989e2010-02-01 14:59:39 +00002132 register long
anthonycc6c8362010-01-25 04:14:01 +00002133 i;
2134
cristy150989e2010-02-01 14:59:39 +00002135 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00002136 if ( IsNan(kernel->values[i]) )
2137 kernel->values[i] = 0.0;
2138
2139 return;
2140}