blob: a7ec68b073ce7cb7f8e18e7007392660916cf506 [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
anthony83ba99b2010-01-24 08:48:15 +0000111 RotateKernel(KernelInfo *, double),
anthonycc6c8362010-01-25 04:14:01 +0000112 ScaleKernel(KernelInfo *, double),
anthonyc4c86e02010-01-27 09:30:32 +0000113 ShowKernel(KernelInfo *);
anthony83ba99b2010-01-24 08:48:15 +0000114
anthony602ab9b2010-01-05 08:06:50 +0000115
116/*
117%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
118% %
119% %
120% %
anthony83ba99b2010-01-24 08:48:15 +0000121% A c q u i r e K e r n e l I n f o %
anthony602ab9b2010-01-05 08:06:50 +0000122% %
123% %
124% %
125%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
126%
cristy2be15382010-01-21 02:38:03 +0000127% AcquireKernelInfo() takes the given string (generally supplied by the
anthony602ab9b2010-01-05 08:06:50 +0000128% user) and converts it into a Morphology/Convolution Kernel. This allows
129% users to specify a kernel from a number of pre-defined kernels, or to fully
130% specify their own kernel for a specific Convolution or Morphology
131% Operation.
132%
133% The kernel so generated can be any rectangular array of floating point
134% values (doubles) with the 'control point' or 'pixel being affected'
135% anywhere within that array of values.
136%
anthony83ba99b2010-01-24 08:48:15 +0000137% Previously IM was restricted to a square of odd size using the exact
138% center as origin, this is no longer the case, and any rectangular kernel
139% with any value being declared the origin. This in turn allows the use of
140% highly asymmetrical kernels.
anthony602ab9b2010-01-05 08:06:50 +0000141%
142% The floating point values in the kernel can also include a special value
anthony83ba99b2010-01-24 08:48:15 +0000143% known as 'nan' or 'not a number' to indicate that this value is not part
144% of the kernel array. This allows you to shaped the kernel within its
145% rectangular area. That is 'nan' values provide a 'mask' for the kernel
146% shape. However at least one non-nan value must be provided for correct
147% working of a kernel.
anthony602ab9b2010-01-05 08:06:50 +0000148%
anthony83ba99b2010-01-24 08:48:15 +0000149% The returned kernel should be free using the DestroyKernelInfo() when you
150% are finished with it.
anthony602ab9b2010-01-05 08:06:50 +0000151%
152% Input kernel defintion strings can consist of any of three types.
153%
anthony29188a82010-01-22 10:12:34 +0000154% "name:args"
155% Select from one of the built in kernels, using the name and
156% geometry arguments supplied. See AcquireKernelBuiltIn()
anthony602ab9b2010-01-05 08:06:50 +0000157%
158% "WxH[+X+Y]:num, num, num ..."
159% a kernal of size W by H, with W*H floating point numbers following.
160% the 'center' can be optionally be defined at +X+Y (such that +0+0
anthony29188a82010-01-22 10:12:34 +0000161% is top left corner). If not defined the pixel in the center, for
162% odd sizes, or to the immediate top or left of center for even sizes
163% is automatically selected.
anthony602ab9b2010-01-05 08:06:50 +0000164%
anthony29188a82010-01-22 10:12:34 +0000165% "num, num, num, num, ..."
166% list of floating point numbers defining an 'old style' odd sized
167% square kernel. At least 9 values should be provided for a 3x3
168% square kernel, 25 for a 5x5 square kernel, 49 for 7x7, etc.
169% Values can be space or comma separated. This is not recommended.
anthony602ab9b2010-01-05 08:06:50 +0000170%
anthony83ba99b2010-01-24 08:48:15 +0000171% Note that 'name' kernels will start with an alphabetic character while the
172% new kernel specification has a ':' character in its specification string.
173% If neither is the case, it is assumed an old style of a simple list of
174% numbers generating a odd-sized square kernel has been given.
anthony602ab9b2010-01-05 08:06:50 +0000175%
176% The format of the AcquireKernal method is:
177%
cristy2be15382010-01-21 02:38:03 +0000178% KernelInfo *AcquireKernelInfo(const char *kernel_string)
anthony602ab9b2010-01-05 08:06:50 +0000179%
180% A description of each parameter follows:
181%
182% o kernel_string: the Morphology/Convolution kernel wanted.
183%
184*/
185
cristy2be15382010-01-21 02:38:03 +0000186MagickExport KernelInfo *AcquireKernelInfo(const char *kernel_string)
anthony602ab9b2010-01-05 08:06:50 +0000187{
cristy2be15382010-01-21 02:38:03 +0000188 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000189 *kernel;
190
191 char
192 token[MaxTextExtent];
193
cristy150989e2010-02-01 14:59:39 +0000194 register long
anthony602ab9b2010-01-05 08:06:50 +0000195 i;
196
197 const char
198 *p;
199
200 MagickStatusType
201 flags;
202
203 GeometryInfo
204 args;
205
anthony29188a82010-01-22 10:12:34 +0000206 double
207 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
208
anthony602ab9b2010-01-05 08:06:50 +0000209 assert(kernel_string != (const char *) NULL);
210 SetGeometryInfo(&args);
211
212 /* does it start with an alpha - Return a builtin kernel */
213 GetMagickToken(kernel_string,&p,token);
214 if ( isalpha((int)token[0]) )
215 {
216 long
217 type;
218
anthony29188a82010-01-22 10:12:34 +0000219 type=ParseMagickOption(MagickKernelOptions,MagickFalse,token);
anthony602ab9b2010-01-05 08:06:50 +0000220 if ( type < 0 || type == UserDefinedKernel )
cristy2be15382010-01-21 02:38:03 +0000221 return((KernelInfo *)NULL);
anthony602ab9b2010-01-05 08:06:50 +0000222
223 while (((isspace((int) ((unsigned char) *p)) != 0) ||
224 (*p == ',') || (*p == ':' )) && (*p != '\0'))
225 p++;
226 flags = ParseGeometry(p, &args);
227
228 /* special handling of missing values in input string */
229 if ( type == RectangleKernel ) {
230 if ( (flags & WidthValue) == 0 ) /* if no width then */
231 args.rho = args.sigma; /* then width = height */
232 if ( args.rho < 1.0 ) /* if width too small */
233 args.rho = 3; /* then width = 3 */
234 if ( args.sigma < 1.0 ) /* if height too small */
235 args.sigma = args.rho; /* then height = width */
236 if ( (flags & XValue) == 0 ) /* center offset if not defined */
237 args.xi = (double)(((long)args.rho-1)/2);
238 if ( (flags & YValue) == 0 )
239 args.psi = (double)(((long)args.sigma-1)/2);
240 }
241
cristy2be15382010-01-21 02:38:03 +0000242 return(AcquireKernelBuiltIn((KernelInfoType)type, &args));
anthony602ab9b2010-01-05 08:06:50 +0000243 }
244
cristy2be15382010-01-21 02:38:03 +0000245 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
246 if (kernel == (KernelInfo *)NULL)
anthony602ab9b2010-01-05 08:06:50 +0000247 return(kernel);
248 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
249 kernel->type = UserDefinedKernel;
cristyd43a46b2010-01-21 02:13:41 +0000250 kernel->signature = MagickSignature;
anthony602ab9b2010-01-05 08:06:50 +0000251
252 /* Has a ':' in argument - New user kernel specification */
253 p = strchr(kernel_string, ':');
254 if ( p != (char *) NULL)
255 {
anthony602ab9b2010-01-05 08:06:50 +0000256 /* ParseGeometry() needs the geometry separated! -- Arrgghh */
cristy150989e2010-02-01 14:59:39 +0000257 memcpy(token, kernel_string, (size_t) (p-kernel_string));
anthony602ab9b2010-01-05 08:06:50 +0000258 token[p-kernel_string] = '\0';
259 flags = ParseGeometry(token, &args);
anthony602ab9b2010-01-05 08:06:50 +0000260
anthony29188a82010-01-22 10:12:34 +0000261 /* Size handling and checks of geometry settings */
anthony602ab9b2010-01-05 08:06:50 +0000262 if ( (flags & WidthValue) == 0 ) /* if no width then */
263 args.rho = args.sigma; /* then width = height */
264 if ( args.rho < 1.0 ) /* if width too small */
265 args.rho = 1.0; /* then width = 1 */
266 if ( args.sigma < 1.0 ) /* if height too small */
267 args.sigma = args.rho; /* then height = width */
268 kernel->width = (unsigned long)args.rho;
269 kernel->height = (unsigned long)args.sigma;
270
271 /* Offset Handling and Checks */
272 if ( args.xi < 0.0 || args.psi < 0.0 )
anthony83ba99b2010-01-24 08:48:15 +0000273 return(DestroyKernelInfo(kernel));
cristy150989e2010-02-01 14:59:39 +0000274 kernel->offset_x = ((flags & XValue)!=0) ? (long)args.xi
275 : (long) (kernel->width-1)/2;
276 kernel->offset_y = ((flags & YValue)!=0) ? (long)args.psi
277 : (long) (kernel->height-1)/2;
278 if ( kernel->offset_x >= (long) kernel->width ||
279 kernel->offset_y >= (long) kernel->height )
anthony83ba99b2010-01-24 08:48:15 +0000280 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000281
282 p++; /* advance beyond the ':' */
283 }
284 else
285 { /* ELSE - Old old kernel specification, forming odd-square kernel */
286 /* count up number of values given */
287 p=(const char *) kernel_string;
cristya699b172010-01-06 16:48:49 +0000288 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
anthony29188a82010-01-22 10:12:34 +0000289 p++; /* ignore "'" chars for convolve filter usage - Cristy */
anthony602ab9b2010-01-05 08:06:50 +0000290 for (i=0; *p != '\0'; i++)
291 {
292 GetMagickToken(p,&p,token);
293 if (*token == ',')
294 GetMagickToken(p,&p,token);
295 }
296 /* set the size of the kernel - old sized square */
297 kernel->width = kernel->height= (unsigned long) sqrt((double) i+1.0);
cristy150989e2010-02-01 14:59:39 +0000298 kernel->offset_x = kernel->offset_y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000299 p=(const char *) kernel_string;
anthony29188a82010-01-22 10:12:34 +0000300 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
301 p++; /* ignore "'" chars for convolve filter usage - Cristy */
anthony602ab9b2010-01-05 08:06:50 +0000302 }
303
304 /* Read in the kernel values from rest of input string argument */
305 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
306 kernel->height*sizeof(double));
307 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000308 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000309
anthony29188a82010-01-22 10:12:34 +0000310 kernel->value_min = +MagickHuge;
311 kernel->value_max = -MagickHuge;
anthony602ab9b2010-01-05 08:06:50 +0000312 kernel->range_neg = kernel->range_pos = 0.0;
cristy150989e2010-02-01 14:59:39 +0000313 for (i=0; (i < (long) (kernel->width*kernel->height)) && (*p != '\0'); i++)
anthony602ab9b2010-01-05 08:06:50 +0000314 {
315 GetMagickToken(p,&p,token);
316 if (*token == ',')
317 GetMagickToken(p,&p,token);
anthony29188a82010-01-22 10:12:34 +0000318 if ( LocaleCompare("nan",token) == 0
319 || LocaleCompare("-",token) == 0 ) {
320 kernel->values[i] = nan; /* do not include this value in kernel */
321 }
322 else {
323 kernel->values[i] = StringToDouble(token);
324 ( kernel->values[i] < 0)
325 ? ( kernel->range_neg += kernel->values[i] )
326 : ( kernel->range_pos += kernel->values[i] );
327 Minimize(kernel->value_min, kernel->values[i]);
328 Maximize(kernel->value_max, kernel->values[i]);
329 }
anthony602ab9b2010-01-05 08:06:50 +0000330 }
anthonycc6c8362010-01-25 04:14:01 +0000331 /* check that we recieved at least one real (non-nan) value! */
332 if ( kernel->value_min == MagickHuge )
333 return(DestroyKernelInfo(kernel));
anthony29188a82010-01-22 10:12:34 +0000334
anthonycc6c8362010-01-25 04:14:01 +0000335 /* This should not be needed for a fully defined kernel
anthony29188a82010-01-22 10:12:34 +0000336 * Perhaps an error should be reported instead!
anthonycc6c8362010-01-25 04:14:01 +0000337 * Kept for backward compatibility.
anthony29188a82010-01-22 10:12:34 +0000338 */
cristy150989e2010-02-01 14:59:39 +0000339 if ( i < (long) (kernel->width*kernel->height) ) {
anthony29188a82010-01-22 10:12:34 +0000340 Minimize(kernel->value_min, kernel->values[i]);
341 Maximize(kernel->value_max, kernel->values[i]);
cristy150989e2010-02-01 14:59:39 +0000342 for ( ; i < (long) (kernel->width*kernel->height); i++)
anthony29188a82010-01-22 10:12:34 +0000343 kernel->values[i]=0.0;
344 }
anthony602ab9b2010-01-05 08:06:50 +0000345
346 return(kernel);
347}
348
349/*
350%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
351% %
352% %
353% %
354% A c q u i r e K e r n e l B u i l t I n %
355% %
356% %
357% %
358%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
359%
360% AcquireKernelBuiltIn() returned one of the 'named' built-in types of
361% kernels used for special purposes such as gaussian blurring, skeleton
362% pruning, and edge distance determination.
363%
364% They take a KernelType, and a set of geometry style arguments, which were
365% typically decoded from a user supplied string, or from a more complex
366% Morphology Method that was requested.
367%
368% The format of the AcquireKernalBuiltIn method is:
369%
cristy2be15382010-01-21 02:38:03 +0000370% KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000371% const GeometryInfo args)
372%
373% A description of each parameter follows:
374%
375% o type: the pre-defined type of kernel wanted
376%
377% o args: arguments defining or modifying the kernel
378%
379% Convolution Kernels
380%
381% Gaussian "[{radius}]x{sigma}"
382% Generate a two-dimentional gaussian kernel, as used by -gaussian
383% A sigma is required, (with the 'x'), due to historical reasons.
384%
385% NOTE: that the 'radius' is optional, but if provided can limit (clip)
386% the final size of the resulting kernel to a square 2*radius+1 in size.
387% The radius should be at least 2 times that of the sigma value, or
388% sever clipping and aliasing may result. If not given or set to 0 the
389% radius will be determined so as to produce the best minimal error
390% result, which is usally much larger than is normally needed.
391%
392% Blur "[{radius}]x{sigma}[+angle]"
393% As per Gaussian, but generates a 1 dimensional or linear gaussian
394% blur, at the angle given (current restricted to orthogonal angles).
395% If a 'radius' is given the kernel is clipped to a width of 2*radius+1.
396%
397% NOTE that two such blurs perpendicular to each other is equivelent to
398% -blur and the previous gaussian, but is often 10 or more times faster.
399%
400% Comet "[{width}]x{sigma}[+angle]"
401% Blur in one direction only, mush like how a bright object leaves
402% a comet like trail. The Kernel is actually half a gaussian curve,
403% Adding two such blurs in oppiste directions produces a Linear Blur.
404%
405% NOTE: that the first argument is the width of the kernel and not the
406% radius of the kernel.
407%
408% # Still to be implemented...
409% #
410% # Laplacian "{radius}x{sigma}"
411% # Laplacian (a mexican hat like) Function
412% #
413% # LOG "{radius},{sigma1},{sigma2}
414% # Laplacian of Gaussian
415% #
416% # DOG "{radius},{sigma1},{sigma2}
417% # Difference of Gaussians
418%
419% Boolean Kernels
420%
421% Rectangle "{geometry}"
422% Simply generate a rectangle of 1's with the size given. You can also
423% specify the location of the 'control point', otherwise the closest
424% pixel to the center of the rectangle is selected.
425%
426% Properly centered and odd sized rectangles work the best.
427%
428% Diamond "[{radius}]"
429% Generate a diamond shaped kernal with given radius to the points.
430% Kernel size will again be radius*2+1 square and defaults to radius 1,
431% generating a 3x3 kernel that is slightly larger than a square.
432%
433% Square "[{radius}]"
434% Generate a square shaped kernel of size radius*2+1, and defaulting
435% to a 3x3 (radius 1).
436%
437% Note that using a larger radius for the "Square" or the "Diamond"
438% is also equivelent to iterating the basic morphological method
439% that many times. However However iterating with the smaller radius 1
440% default is actually faster than using a larger kernel radius.
441%
442% Disk "[{radius}]
443% Generate a binary disk of the radius given, radius may be a float.
444% Kernel size will be ceil(radius)*2+1 square.
445% NOTE: Here are some disk shapes of specific interest
446% "disk:1" => "diamond" or "cross:1"
447% "disk:1.5" => "square"
448% "disk:2" => "diamond:2"
anthony83ba99b2010-01-24 08:48:15 +0000449% "disk:2.5" => a general disk shape of radius 2
anthony602ab9b2010-01-05 08:06:50 +0000450% "disk:2.9" => "square:2"
anthony83ba99b2010-01-24 08:48:15 +0000451% "disk:3.5" => default - octagonal/disk shape of radius 3
anthony602ab9b2010-01-05 08:06:50 +0000452% "disk:4.2" => roughly octagonal shape of radius 4
anthony83ba99b2010-01-24 08:48:15 +0000453% "disk:4.3" => a general disk shape of radius 4
anthony602ab9b2010-01-05 08:06:50 +0000454% After this all the kernel shape becomes more and more circular.
455%
456% Because a "disk" is more circular when using a larger radius, using a
457% larger radius is preferred over iterating the morphological operation.
458%
459% Plus "[{radius}]"
460% Generate a kernel in the shape of a 'plus' sign. The length of each
461% arm is also the radius, which defaults to 2.
462%
463% This kernel is not a good general morphological kernel, but is used
464% more for highlighting and marking any single pixels in an image using,
465% a "Dilate" or "Erode" method as appropriate.
anthonyc94cdb02010-01-06 08:15:29 +0000466%
anthony602ab9b2010-01-05 08:06:50 +0000467% NOTE: "plus:1" is equivelent to a "Diamond" kernel.
468%
469% Note that unlike other kernels iterating a plus does not produce the
470% same result as using a larger radius for the cross.
471%
472% Distance Measuring Kernels
473%
474% Chebyshev "[{radius}][x{scale}]" largest x or y distance (default r=1)
475% Manhatten "[{radius}][x{scale}]" square grid distance (default r=1)
anthonyc94cdb02010-01-06 08:15:29 +0000476% Euclidean "[{radius}][x{scale}]" direct distance (default r=1)
anthony602ab9b2010-01-05 08:06:50 +0000477%
478% Different types of distance measuring methods, which are used with the
479% a 'Distance' morphology method for generating a gradient based on
480% distance from an edge of a binary shape, though there is a technique
481% for handling a anti-aliased shape.
482%
anthonyc94cdb02010-01-06 08:15:29 +0000483% Chebyshev Distance (also known as Tchebychev Distance) is a value of
484% one to any neighbour, orthogonal or diagonal. One why of thinking of
485% it is the number of squares a 'King' or 'Queen' in chess needs to
486% traverse reach any other position on a chess board. It results in a
487% 'square' like distance function, but one where diagonals are closer
488% than expected.
anthony602ab9b2010-01-05 08:06:50 +0000489%
anthonyc94cdb02010-01-06 08:15:29 +0000490% Manhatten Distance (also known as Rectilinear Distance, or the Taxi
491% Cab metric), is the distance needed when you can only travel in
492% orthogonal (horizontal or vertical) only. It is the distance a 'Rook'
493% in chess would travel. It results in a diamond like distances, where
494% diagonals are further than expected.
anthony602ab9b2010-01-05 08:06:50 +0000495%
anthonyc94cdb02010-01-06 08:15:29 +0000496% Euclidean Distance is the 'direct' or 'as the crow flys distance.
497% However by default the kernel size only has a radius of 1, which
498% limits the distance to 'Knight' like moves, with only orthogonal and
499% diagonal measurements being correct. As such for the default kernel
500% you will get octagonal like distance function, which is reasonally
501% accurate.
502%
503% However if you use a larger radius such as "Euclidean:4" you will
504% get a much smoother distance gradient from the edge of the shape.
505% Of course a larger kernel is slower to use, and generally not needed.
506%
507% To allow the use of fractional distances that you get with diagonals
508% the actual distance is scaled by a fixed value which the user can
509% provide. This is not actually nessary for either ""Chebyshev" or
510% "Manhatten" distance kernels, but is done for all three distance
511% kernels. If no scale is provided it is set to a value of 100,
512% allowing for a maximum distance measurement of 655 pixels using a Q16
513% version of IM, from any edge. However for small images this can
514% result in quite a dark gradient.
515%
516% See the 'Distance' Morphological Method, for information of how it is
517% applied.
anthony602ab9b2010-01-05 08:06:50 +0000518%
519*/
520
cristy2be15382010-01-21 02:38:03 +0000521MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000522 const GeometryInfo *args)
523{
cristy2be15382010-01-21 02:38:03 +0000524 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000525 *kernel;
526
cristy150989e2010-02-01 14:59:39 +0000527 register long
anthony602ab9b2010-01-05 08:06:50 +0000528 i;
529
530 register long
531 u,
532 v;
533
534 double
535 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
536
cristy2be15382010-01-21 02:38:03 +0000537 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
538 if (kernel == (KernelInfo *) NULL)
anthony602ab9b2010-01-05 08:06:50 +0000539 return(kernel);
540 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
anthonyc94cdb02010-01-06 08:15:29 +0000541 kernel->value_min = kernel->value_max = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000542 kernel->range_neg = kernel->range_pos = 0.0;
543 kernel->type = type;
cristyd43a46b2010-01-21 02:13:41 +0000544 kernel->signature = MagickSignature;
anthony602ab9b2010-01-05 08:06:50 +0000545
546 switch(type) {
547 /* Convolution Kernels */
548 case GaussianKernel:
549 { double
550 sigma = fabs(args->sigma);
551
552 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
553
554 kernel->width = kernel->height =
555 GetOptimalKernelWidth2D(args->rho,sigma);
cristy150989e2010-02-01 14:59:39 +0000556 kernel->offset_x = kernel->offset_y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000557 kernel->range_neg = kernel->range_pos = 0.0;
558 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
559 kernel->height*sizeof(double));
560 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000561 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000562
563 sigma = 2.0*sigma*sigma; /* simplify the expression */
564 for ( i=0, v=-kernel->offset_y; v <= (long)kernel->offset_y; v++)
565 for ( u=-kernel->offset_x; u <= (long)kernel->offset_x; u++, i++)
566 kernel->range_pos += (
567 kernel->values[i] =
568 exp(-((double)(u*u+v*v))/sigma)
569 /* / (MagickPI*sigma) */ );
anthonyc94cdb02010-01-06 08:15:29 +0000570 kernel->value_min = 0;
571 kernel->value_max = kernel->values[
572 kernel->offset_y*kernel->width+kernel->offset_x ];
anthony602ab9b2010-01-05 08:06:50 +0000573
anthonycc6c8362010-01-25 04:14:01 +0000574 ScaleKernel(kernel, 0.0); /* Normalize Kernel Values */
anthony602ab9b2010-01-05 08:06:50 +0000575
576 break;
577 }
578 case BlurKernel:
579 { double
580 sigma = fabs(args->sigma);
581
582 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
583
584 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
cristy150989e2010-02-01 14:59:39 +0000585 kernel->offset_x = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000586 kernel->height = 1;
587 kernel->offset_y = 0;
588 kernel->range_neg = kernel->range_pos = 0.0;
589 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
590 kernel->height*sizeof(double));
591 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000592 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000593
594#if 1
595#define KernelRank 3
596 /* Formula derived from GetBlurKernel() in "effect.c" (plus bug fix).
597 ** It generates a gaussian 3 times the width, and compresses it into
598 ** the expected range. This produces a closer normalization of the
599 ** resulting kernel, especially for very low sigma values.
600 ** As such while wierd it is prefered.
601 **
602 ** I am told this method originally came from Photoshop.
603 */
604 sigma *= KernelRank; /* simplify expanded curve */
cristy150989e2010-02-01 14:59:39 +0000605 v = (long) (kernel->width*KernelRank-1)/2; /* start/end points to fit range */
anthony602ab9b2010-01-05 08:06:50 +0000606 (void) ResetMagickMemory(kernel->values,0, (size_t)
607 kernel->width*sizeof(double));
608 for ( u=-v; u <= v; u++) {
609 kernel->values[(u+v)/KernelRank] +=
610 exp(-((double)(u*u))/(2.0*sigma*sigma))
611 /* / (MagickSQ2PI*sigma/KernelRank) */ ;
612 }
cristy150989e2010-02-01 14:59:39 +0000613 for (i=0; i < (long) kernel->width; i++)
anthony602ab9b2010-01-05 08:06:50 +0000614 kernel->range_pos += kernel->values[i];
615#else
616 for ( i=0, u=-kernel->offset_x; i < kernel->width; i++, u++)
617 kernel->range_pos += (
618 kernel->values[i] =
619 exp(-((double)(u*u))/(2.0*sigma*sigma))
620 /* / (MagickSQ2PI*sigma) */ );
621#endif
anthonyc94cdb02010-01-06 08:15:29 +0000622 kernel->value_min = 0;
623 kernel->value_max = kernel->values[ kernel->offset_x ];
anthonycc6c8362010-01-25 04:14:01 +0000624 /* Note that neither methods above generate a normalized kernel,
625 ** though it gets close. The kernel may be 'clipped' by a user defined
626 ** radius, producing a smaller (darker) kernel. Also for very small
627 ** sigma's (> 0.1) the central value becomes larger than one, and thus
628 ** producing a very bright kernel.
anthony602ab9b2010-01-05 08:06:50 +0000629 */
anthonycc6c8362010-01-25 04:14:01 +0000630
anthony602ab9b2010-01-05 08:06:50 +0000631 /* Normalize the 1D Gaussian Kernel
632 **
633 ** Because of this the divisor in the above kernel generator is
anthonyc94cdb02010-01-06 08:15:29 +0000634 ** not needed, so is not done above.
anthony602ab9b2010-01-05 08:06:50 +0000635 */
anthonycc6c8362010-01-25 04:14:01 +0000636 ScaleKernel(kernel, 0.0); /* Normalize Kernel Values */
637
anthony602ab9b2010-01-05 08:06:50 +0000638 /* rotate the kernel by given angle */
anthony83ba99b2010-01-24 08:48:15 +0000639 RotateKernel(kernel, args->xi);
anthony602ab9b2010-01-05 08:06:50 +0000640 break;
641 }
642 case CometKernel:
643 { double
644 sigma = fabs(args->sigma);
645
646 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
647
648 if ( args->rho < 1.0 )
649 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
650 else
651 kernel->width = (unsigned long)args->rho;
652 kernel->offset_x = kernel->offset_y = 0;
653 kernel->height = 1;
654 kernel->range_neg = kernel->range_pos = 0.0;
655 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
656 kernel->height*sizeof(double));
657 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000658 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000659
660 /* A comet blur is half a gaussian curve, so that the object is
661 ** blurred in one direction only. This may not be quite the right
662 ** curve so may change in the future. The function must be normalised.
663 */
664#if 1
665#define KernelRank 3
666 sigma *= KernelRank; /* simplify expanded curve */
cristy150989e2010-02-01 14:59:39 +0000667 v = (long) kernel->width*KernelRank; /* start/end points to fit range */
anthony602ab9b2010-01-05 08:06:50 +0000668 (void) ResetMagickMemory(kernel->values,0, (size_t)
669 kernel->width*sizeof(double));
670 for ( u=0; u < v; u++) {
671 kernel->values[u/KernelRank] +=
672 exp(-((double)(u*u))/(2.0*sigma*sigma))
673 /* / (MagickSQ2PI*sigma/KernelRank) */ ;
674 }
cristy150989e2010-02-01 14:59:39 +0000675 for (i=0; i < (long) kernel->width; i++)
anthony602ab9b2010-01-05 08:06:50 +0000676 kernel->range_pos += kernel->values[i];
677#else
cristy150989e2010-02-01 14:59:39 +0000678 for ( i=0; i < (long) kernel->width; i++)
anthony602ab9b2010-01-05 08:06:50 +0000679 kernel->range_pos += (
680 kernel->values[i] =
681 exp(-((double)(i*i))/(2.0*sigma*sigma))
682 /* / (MagickSQ2PI*sigma) */ );
683#endif
anthonyc94cdb02010-01-06 08:15:29 +0000684 kernel->value_min = 0;
685 kernel->value_max = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000686
anthonycc6c8362010-01-25 04:14:01 +0000687 ScaleKernel(kernel, 0.0); /* Normalize Kernel Values */
anthony83ba99b2010-01-24 08:48:15 +0000688 RotateKernel(kernel, args->xi);
anthony602ab9b2010-01-05 08:06:50 +0000689 break;
690 }
691 /* Boolean Kernels */
692 case RectangleKernel:
693 case SquareKernel:
694 {
695 if ( type == SquareKernel )
696 {
697 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000698 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000699 else
cristy150989e2010-02-01 14:59:39 +0000700 kernel->width = kernel->height = (unsigned long) (2*args->rho+1);
701 kernel->offset_x = kernel->offset_y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000702 }
703 else {
cristy2be15382010-01-21 02:38:03 +0000704 /* NOTE: user defaults set in "AcquireKernelInfo()" */
anthony602ab9b2010-01-05 08:06:50 +0000705 if ( args->rho < 1.0 || args->sigma < 1.0 )
anthony83ba99b2010-01-24 08:48:15 +0000706 return(DestroyKernelInfo(kernel)); /* invalid args given */
anthony602ab9b2010-01-05 08:06:50 +0000707 kernel->width = (unsigned long)args->rho;
708 kernel->height = (unsigned long)args->sigma;
709 if ( args->xi < 0.0 || args->xi > (double)kernel->width ||
710 args->psi < 0.0 || args->psi > (double)kernel->height )
anthony83ba99b2010-01-24 08:48:15 +0000711 return(DestroyKernelInfo(kernel)); /* invalid args given */
cristy150989e2010-02-01 14:59:39 +0000712 kernel->offset_x = (long) args->xi;
713 kernel->offset_y = (long) args->psi;
anthony602ab9b2010-01-05 08:06:50 +0000714 }
715 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
716 kernel->height*sizeof(double));
717 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000718 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000719
anthonycc6c8362010-01-25 04:14:01 +0000720 /* set all kernel values to 1.0 */
cristy150989e2010-02-01 14:59:39 +0000721 u=(long) kernel->width*kernel->height;
722 for ( i=0; i < u; i++)
anthony602ab9b2010-01-05 08:06:50 +0000723 kernel->values[i] = 1.0;
anthonycc6c8362010-01-25 04:14:01 +0000724 kernel->value_min = kernel->value_max = 1.0; /* a flat shape */
anthonyc94cdb02010-01-06 08:15:29 +0000725 kernel->range_pos = (double) u;
anthonycc6c8362010-01-25 04:14:01 +0000726 break;
anthony602ab9b2010-01-05 08:06:50 +0000727 }
728 case DiamondKernel:
729 {
730 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000731 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000732 else
733 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristy150989e2010-02-01 14:59:39 +0000734 kernel->offset_x = kernel->offset_y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000735
736 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
737 kernel->height*sizeof(double));
738 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000739 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000740
anthonycc6c8362010-01-25 04:14:01 +0000741 /* set all kernel values within diamond area to 1.0 */
anthony602ab9b2010-01-05 08:06:50 +0000742 for ( i=0, v=-kernel->offset_y; v <= (long)kernel->offset_y; v++)
743 for ( u=-kernel->offset_x; u <= (long)kernel->offset_x; u++, i++)
744 if ((labs(u)+labs(v)) <= (long)kernel->offset_x)
745 kernel->range_pos += kernel->values[i] = 1.0;
746 else
747 kernel->values[i] = nan;
anthonycc6c8362010-01-25 04:14:01 +0000748 kernel->value_min = kernel->value_max = 1.0; /* a flat shape */
anthony602ab9b2010-01-05 08:06:50 +0000749 break;
750 }
751 case DiskKernel:
752 {
753 long
754 limit;
755
756 limit = (long)(args->rho*args->rho);
anthony83ba99b2010-01-24 08:48:15 +0000757 if (args->rho < 0.1) /* default radius approx 3.5 */
758 kernel->width = kernel->height = 7L, limit = 10L;
anthony602ab9b2010-01-05 08:06:50 +0000759 else
760 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristy150989e2010-02-01 14:59:39 +0000761 kernel->offset_x = kernel->offset_y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000762
763 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
764 kernel->height*sizeof(double));
765 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000766 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000767
anthonycc6c8362010-01-25 04:14:01 +0000768 /* set all kernel values within disk area to 1.0 */
cristy150989e2010-02-01 14:59:39 +0000769 for ( i=0, v= -kernel->offset_y; v <= (long)kernel->offset_y; v++)
anthony602ab9b2010-01-05 08:06:50 +0000770 for ( u=-kernel->offset_x; u <= (long)kernel->offset_x; u++, i++)
771 if ((u*u+v*v) <= limit)
772 kernel->range_pos += kernel->values[i] = 1.0;
773 else
774 kernel->values[i] = nan;
anthonycc6c8362010-01-25 04:14:01 +0000775 kernel->value_min = kernel->value_max = 1.0; /* a flat shape */
anthony602ab9b2010-01-05 08:06:50 +0000776 break;
777 }
778 case PlusKernel:
779 {
780 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000781 kernel->width = kernel->height = 5; /* default radius 2 */
anthony602ab9b2010-01-05 08:06:50 +0000782 else
783 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristy150989e2010-02-01 14:59:39 +0000784 kernel->offset_x = kernel->offset_y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000785
786 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
787 kernel->height*sizeof(double));
788 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000789 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000790
anthonycc6c8362010-01-25 04:14:01 +0000791 /* set all kernel values along axises to 1.0 */
anthony602ab9b2010-01-05 08:06:50 +0000792 for ( i=0, v=-kernel->offset_y; v <= (long)kernel->offset_y; v++)
793 for ( u=-kernel->offset_x; u <= (long)kernel->offset_x; u++, i++)
794 kernel->values[i] = (u == 0 || v == 0) ? 1.0 : nan;
anthonycc6c8362010-01-25 04:14:01 +0000795 kernel->value_min = kernel->value_max = 1.0; /* a flat shape */
cristy150989e2010-02-01 14:59:39 +0000796 kernel->range_pos = (double) kernel->width*2.0 - 1.0;
anthony602ab9b2010-01-05 08:06:50 +0000797 break;
798 }
799 /* Distance Measuring Kernels */
800 case ChebyshevKernel:
801 {
802 double
803 scale;
804
805 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000806 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000807 else
808 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristy150989e2010-02-01 14:59:39 +0000809 kernel->offset_x = kernel->offset_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
816 scale = (args->sigma < 1.0) ? 100.0 : args->sigma;
817 for ( i=0, v=-kernel->offset_y; v <= (long)kernel->offset_y; v++)
818 for ( u=-kernel->offset_x; u <= (long)kernel->offset_x; u++, i++)
819 kernel->range_pos += ( kernel->values[i] =
820 scale*((labs(u)>labs(v)) ? labs(u) : labs(v)) );
anthonyc94cdb02010-01-06 08:15:29 +0000821 kernel->value_max = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000822 break;
823 }
824 case ManhattenKernel:
825 {
826 double
827 scale;
828
829 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000830 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000831 else
832 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristy150989e2010-02-01 14:59:39 +0000833 kernel->offset_x = kernel->offset_y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000834
835 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
836 kernel->height*sizeof(double));
837 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000838 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000839
840 scale = (args->sigma < 1.0) ? 100.0 : args->sigma;
841 for ( i=0, v=-kernel->offset_y; v <= (long)kernel->offset_y; v++)
842 for ( u=-kernel->offset_x; u <= (long)kernel->offset_x; u++, i++)
843 kernel->range_pos += ( kernel->values[i] =
844 scale*(labs(u)+labs(v)) );
anthonyc94cdb02010-01-06 08:15:29 +0000845 kernel->value_max = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000846 break;
847 }
848 case EuclideanKernel:
849 {
850 double
851 scale;
852
853 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000854 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000855 else
856 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristy150989e2010-02-01 14:59:39 +0000857 kernel->offset_x = kernel->offset_y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000858
859 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
860 kernel->height*sizeof(double));
861 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000862 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000863
864 scale = (args->sigma < 1.0) ? 100.0 : args->sigma;
865 for ( i=0, v=-kernel->offset_y; v <= (long)kernel->offset_y; v++)
866 for ( u=-kernel->offset_x; u <= (long)kernel->offset_x; u++, i++)
867 kernel->range_pos += ( kernel->values[i] =
868 scale*sqrt((double)(u*u+v*v)) );
anthonyc94cdb02010-01-06 08:15:29 +0000869 kernel->value_max = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000870 break;
871 }
872 /* Undefined Kernels */
873 case LaplacianKernel:
874 case LOGKernel:
875 case DOGKernel:
cristy150989e2010-02-01 14:59:39 +0000876 perror("Kernel Type has not been defined yet");
anthony602ab9b2010-01-05 08:06:50 +0000877 /* FALL THRU */
878 default:
879 /* Generate a No-Op minimal kernel - 1x1 pixel */
880 kernel->values=(double *)AcquireQuantumMemory((size_t)1,sizeof(double));
881 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000882 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000883 kernel->width = kernel->height = 1;
884 kernel->offset_x = kernel->offset_x = 0;
885 kernel->type = UndefinedKernel;
anthonyc94cdb02010-01-06 08:15:29 +0000886 kernel->value_max =
887 kernel->range_pos =
888 kernel->values[0] = 1.0; /* a flat single-point no-op kernel! */
anthony602ab9b2010-01-05 08:06:50 +0000889 break;
890 }
891
892 return(kernel);
893}
anthonyc94cdb02010-01-06 08:15:29 +0000894
anthony602ab9b2010-01-05 08:06:50 +0000895/*
896%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
897% %
898% %
899% %
anthony83ba99b2010-01-24 08:48:15 +0000900% D e s t r o y K e r n e l I n f o %
anthony602ab9b2010-01-05 08:06:50 +0000901% %
902% %
903% %
904%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
905%
anthony83ba99b2010-01-24 08:48:15 +0000906% DestroyKernelInfo() frees the memory used by a Convolution/Morphology
907% kernel.
anthony602ab9b2010-01-05 08:06:50 +0000908%
anthony83ba99b2010-01-24 08:48:15 +0000909% The format of the DestroyKernelInfo method is:
anthony602ab9b2010-01-05 08:06:50 +0000910%
anthony83ba99b2010-01-24 08:48:15 +0000911% KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +0000912%
913% A description of each parameter follows:
914%
915% o kernel: the Morphology/Convolution kernel to be destroyed
916%
917*/
918
anthony83ba99b2010-01-24 08:48:15 +0000919MagickExport KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +0000920{
cristy2be15382010-01-21 02:38:03 +0000921 assert(kernel != (KernelInfo *) NULL);
anthony602ab9b2010-01-05 08:06:50 +0000922 kernel->values=(double *)RelinquishMagickMemory(kernel->values);
cristy2be15382010-01-21 02:38:03 +0000923 kernel=(KernelInfo *) RelinquishMagickMemory(kernel);
anthony602ab9b2010-01-05 08:06:50 +0000924 return(kernel);
925}
anthonyc94cdb02010-01-06 08:15:29 +0000926
927/*
928%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
929% %
930% %
931% %
anthony29188a82010-01-22 10:12:34 +0000932% 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 +0000933% %
934% %
935% %
936%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
937%
anthony29188a82010-01-22 10:12:34 +0000938% MorphologyImageChannel() applies a user supplied kernel to the image
939% according to the given mophology method.
anthony602ab9b2010-01-05 08:06:50 +0000940%
941% The given kernel is assumed to have been pre-scaled appropriatally, usally
942% by the kernel generator.
943%
944% The format of the MorphologyImage method is:
945%
anthony29188a82010-01-22 10:12:34 +0000946% Image *MorphologyImage(const Image *image, MorphologyMethod method,
947% const long iterations, KernelInfo *kernel, ExceptionInfo *exception)
948% Image *MorphologyImageChannel(const Image *image, const ChannelType
949% channel, MorphologyMethod method, const long iterations, KernelInfo
950% *kernel, ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +0000951%
952% A description of each parameter follows:
953%
954% o image: the image.
955%
956% o method: the morphology method to be applied.
957%
958% o iterations: apply the operation this many times (or no change).
959% A value of -1 means loop until no change found.
960% How this is applied may depend on the morphology method.
961% Typically this is a value of 1.
962%
963% o channel: the channel type.
964%
965% o kernel: An array of double representing the morphology kernel.
anthony29188a82010-01-22 10:12:34 +0000966% Warning: kernel may be normalized for the Convolve method.
anthony602ab9b2010-01-05 08:06:50 +0000967%
968% o exception: return any errors or warnings in this structure.
969%
970%
971% TODO: bias and auto-scale handling of the kernel for convolution
972% The given kernel is assumed to have been pre-scaled appropriatally, usally
973% by the kernel generator.
974%
975*/
976
anthony602ab9b2010-01-05 08:06:50 +0000977/* Internal function
978 * Apply the Morphology method with the given Kernel
anthony83ba99b2010-01-24 08:48:15 +0000979 * And return the number of pixels that changed.
anthony602ab9b2010-01-05 08:06:50 +0000980 */
981static unsigned long MorphologyApply(const Image *image, Image
982 *result_image, const MorphologyMethod method, const ChannelType channel,
cristy2be15382010-01-21 02:38:03 +0000983 const KernelInfo *kernel, ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +0000984{
cristy2be15382010-01-21 02:38:03 +0000985#define MorphologyTag "Morphology/Image"
anthony602ab9b2010-01-05 08:06:50 +0000986
987 long
cristy150989e2010-02-01 14:59:39 +0000988 progress,
anthony29188a82010-01-22 10:12:34 +0000989 y, offx, offy,
anthony602ab9b2010-01-05 08:06:50 +0000990 changed;
991
992 MagickBooleanType
993 status;
994
995 MagickPixelPacket
996 bias;
997
998 CacheView
999 *p_view,
1000 *q_view;
1001
1002 /*
1003 Apply Morphology to Image.
1004 */
1005 status=MagickTrue;
1006 changed=0;
1007 progress=0;
1008
1009 GetMagickPixelPacket(image,&bias);
1010 SetMagickPixelPacketBias(image,&bias);
anthonycc6c8362010-01-25 04:14:01 +00001011 /* Future: handle auto-bias from user, based on kernel input */
anthony602ab9b2010-01-05 08:06:50 +00001012
1013 p_view=AcquireCacheView(image);
1014 q_view=AcquireCacheView(result_image);
anthony29188a82010-01-22 10:12:34 +00001015
anthonycc6c8362010-01-25 04:14:01 +00001016 /* Some methods (including convolve) needs use a reflected kernel.
1017 * Adjust 'origin' offsets for this reflected kernel.
anthony29188a82010-01-22 10:12:34 +00001018 */
1019 offx = kernel->offset_x;
1020 offy = kernel->offset_y;
1021 switch(method) {
1022 case ErodeMorphology:
1023 case ErodeIntensityMorphology:
1024 /* kernel is not reflected */
1025 break;
1026 default:
1027 /* kernel needs to be reflected */
cristy150989e2010-02-01 14:59:39 +00001028 offx = (long) kernel->width-offx-1;
1029 offy = (long) kernel->height-offy-1;
anthony29188a82010-01-22 10:12:34 +00001030 break;
1031 }
1032
anthony602ab9b2010-01-05 08:06:50 +00001033#if defined(MAGICKCORE_OPENMP_SUPPORT)
1034 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1035#endif
cristy150989e2010-02-01 14:59:39 +00001036 for (y=0; y < (long) image->rows; y++)
anthony602ab9b2010-01-05 08:06:50 +00001037 {
1038 MagickBooleanType
1039 sync;
1040
1041 register const PixelPacket
1042 *restrict p;
1043
1044 register const IndexPacket
1045 *restrict p_indexes;
1046
1047 register PixelPacket
1048 *restrict q;
1049
1050 register IndexPacket
1051 *restrict q_indexes;
1052
cristy150989e2010-02-01 14:59:39 +00001053 register long
anthony602ab9b2010-01-05 08:06:50 +00001054 x;
1055
anthony29188a82010-01-22 10:12:34 +00001056 unsigned long
anthony602ab9b2010-01-05 08:06:50 +00001057 r;
1058
1059 if (status == MagickFalse)
1060 continue;
anthony29188a82010-01-22 10:12:34 +00001061 p=GetCacheViewVirtualPixels(p_view, -offx, y-offy,
1062 image->columns+kernel->width, kernel->height, exception);
anthony602ab9b2010-01-05 08:06:50 +00001063 q=GetCacheViewAuthenticPixels(q_view,0,y,result_image->columns,1,
1064 exception);
1065 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1066 {
1067 status=MagickFalse;
1068 continue;
1069 }
1070 p_indexes=GetCacheViewVirtualIndexQueue(p_view);
1071 q_indexes=GetCacheViewAuthenticIndexQueue(q_view);
anthony29188a82010-01-22 10:12:34 +00001072 r = (image->columns+kernel->width)*offy+offx; /* constant */
1073
cristy150989e2010-02-01 14:59:39 +00001074 for (x=0; x < (long) image->columns; x++)
anthony602ab9b2010-01-05 08:06:50 +00001075 {
cristy150989e2010-02-01 14:59:39 +00001076 long
anthony602ab9b2010-01-05 08:06:50 +00001077 v;
1078
cristy150989e2010-02-01 14:59:39 +00001079 register long
anthony602ab9b2010-01-05 08:06:50 +00001080 u;
1081
1082 register const double
1083 *restrict k;
1084
1085 register const PixelPacket
1086 *restrict k_pixels;
1087
1088 register const IndexPacket
1089 *restrict k_indexes;
1090
1091 MagickPixelPacket
1092 result;
1093
anthony29188a82010-01-22 10:12:34 +00001094 /* Copy input to ouput image for unused channels
anthony83ba99b2010-01-24 08:48:15 +00001095 * This removes need for 'cloning' a new image every iteration
anthony29188a82010-01-22 10:12:34 +00001096 */
anthony602ab9b2010-01-05 08:06:50 +00001097 *q = p[r];
1098 if (image->colorspace == CMYKColorspace)
1099 q_indexes[x] = p_indexes[r];
1100
cristy150989e2010-02-01 14:59:39 +00001101 result.index=(MagickRealType) 0; /* stop compiler warnings */
anthony602ab9b2010-01-05 08:06:50 +00001102 switch (method) {
1103 case ConvolveMorphology:
1104 result=bias;
1105 break; /* default result is the convolution bias */
anthony83ba99b2010-01-24 08:48:15 +00001106#if 1
1107 case DilateMorphology:
anthony29188a82010-01-22 10:12:34 +00001108 result.red =
1109 result.green =
1110 result.blue =
1111 result.opacity =
1112 result.index = -MagickHuge;
1113 break;
1114 case ErodeMorphology:
1115 result.red =
1116 result.green =
1117 result.blue =
1118 result.opacity =
1119 result.index = +MagickHuge;
1120 break;
1121#endif
anthony602ab9b2010-01-05 08:06:50 +00001122 default:
anthony29188a82010-01-22 10:12:34 +00001123 /* Otherwise just start with the original pixel value */
cristy150989e2010-02-01 14:59:39 +00001124 result.red = (MagickRealType) p[r].red;
1125 result.green = (MagickRealType) p[r].green;
1126 result.blue = (MagickRealType) p[r].blue;
1127 result.opacity = QuantumRange - (MagickRealType) p[r].opacity;
anthony602ab9b2010-01-05 08:06:50 +00001128 if ( image->colorspace == CMYKColorspace)
cristy150989e2010-02-01 14:59:39 +00001129 result.index = (MagickRealType) p_indexes[r];
anthony602ab9b2010-01-05 08:06:50 +00001130 break;
1131 }
1132
1133 switch ( method ) {
1134 case ConvolveMorphology:
anthony29188a82010-01-22 10:12:34 +00001135 /* Weighted Average of pixels
1136 *
1137 * NOTE for correct working of this operation for asymetrical
1138 * kernels, the kernel needs to be applied in its reflected form.
1139 * That is its values needs to be reversed.
1140 */
anthony602ab9b2010-01-05 08:06:50 +00001141 if (((channel & OpacityChannel) == 0) ||
1142 (image->matte == MagickFalse))
1143 {
anthony29188a82010-01-22 10:12:34 +00001144 /* Convolution (no transparency) */
1145 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001146 k_pixels = p;
1147 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001148 for (v=0; v < (long) kernel->height; v++) {
1149 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001150 if ( IsNan(*k) ) continue;
1151 result.red += (*k)*k_pixels[u].red;
1152 result.green += (*k)*k_pixels[u].green;
1153 result.blue += (*k)*k_pixels[u].blue;
anthony83ba99b2010-01-24 08:48:15 +00001154 /* result.opacity += not involved here */
anthony602ab9b2010-01-05 08:06:50 +00001155 if ( image->colorspace == CMYKColorspace)
1156 result.index += (*k)*k_indexes[u];
1157 }
1158 k_pixels += image->columns+kernel->width;
1159 k_indexes += image->columns+kernel->width;
1160 }
anthony602ab9b2010-01-05 08:06:50 +00001161 }
1162 else
1163 { /* Kernel & Alpha weighted Convolution */
1164 MagickRealType
1165 alpha, /* alpha value * kernel weighting */
1166 gamma; /* weighting divisor */
1167
1168 gamma=0.0;
anthony29188a82010-01-22 10:12:34 +00001169 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001170 k_pixels = p;
1171 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001172 for (v=0; v < (long) kernel->height; v++) {
1173 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001174 if ( IsNan(*k) ) continue;
1175 alpha=(*k)*(QuantumScale*(QuantumRange-
1176 k_pixels[u].opacity));
1177 gamma += alpha;
1178 result.red += alpha*k_pixels[u].red;
1179 result.green += alpha*k_pixels[u].green;
1180 result.blue += alpha*k_pixels[u].blue;
anthony83ba99b2010-01-24 08:48:15 +00001181 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00001182 if ( image->colorspace == CMYKColorspace)
1183 result.index += alpha*k_indexes[u];
1184 }
1185 k_pixels += image->columns+kernel->width;
1186 k_indexes += image->columns+kernel->width;
1187 }
1188 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
anthony83ba99b2010-01-24 08:48:15 +00001189 result.red *= gamma;
1190 result.green *= gamma;
1191 result.blue *= gamma;
1192 result.opacity *= gamma;
1193 result.index *= gamma;
anthony602ab9b2010-01-05 08:06:50 +00001194 }
1195 break;
1196
anthony83ba99b2010-01-24 08:48:15 +00001197 case DilateMorphology:
anthony29188a82010-01-22 10:12:34 +00001198 /* Maximize Value within kernel shape
1199 *
1200 * NOTE for correct working of this operation for asymetrical
1201 * kernels, the kernel needs to be applied in its reflected form.
1202 * That is its values needs to be reversed.
1203 *
1204 * NOTE: in normal Greyscale Morphology, the kernel value should
1205 * be added to the real value, this is currently not done, due to
1206 * the nature of the boolean kernels being used.
1207 *
1208 */
1209 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001210 k_pixels = p;
1211 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001212 for (v=0; v < (long) kernel->height; v++) {
1213 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001214 if ( IsNan(*k) || (*k) < 0.5 ) continue;
cristy150989e2010-02-01 14:59:39 +00001215 Maximize(result.red, (double) k_pixels[u].red);
1216 Maximize(result.green, (double) k_pixels[u].green);
1217 Maximize(result.blue, (double) k_pixels[u].blue);
1218 Maximize(result.opacity, QuantumRange-(double) k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00001219 if ( image->colorspace == CMYKColorspace)
cristy150989e2010-02-01 14:59:39 +00001220 Maximize(result.index, (double) k_indexes[u]);
anthony602ab9b2010-01-05 08:06:50 +00001221 }
1222 k_pixels += image->columns+kernel->width;
1223 k_indexes += image->columns+kernel->width;
1224 }
anthony602ab9b2010-01-05 08:06:50 +00001225 break;
1226
1227 case ErodeMorphology:
anthony29188a82010-01-22 10:12:34 +00001228 /* Minimize Value within kernel shape
1229 *
1230 * NOTE that the kernel is not reflected for this operation!
1231 *
1232 * NOTE: in normal Greyscale Morphology, the kernel value should
1233 * be added to the real value, this is currently not done, due to
1234 * the nature of the boolean kernels being used.
1235 */
anthony602ab9b2010-01-05 08:06:50 +00001236 k = kernel->values;
1237 k_pixels = p;
1238 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001239 for (v=0; v < (long) kernel->height; v++) {
1240 for (u=0; u < (long) kernel->width; u++, k++) {
anthony602ab9b2010-01-05 08:06:50 +00001241 if ( IsNan(*k) || (*k) < 0.5 ) continue;
cristy150989e2010-02-01 14:59:39 +00001242 Minimize(result.red, (double) k_pixels[u].red);
1243 Minimize(result.green, (double) k_pixels[u].green);
1244 Minimize(result.blue, (double) k_pixels[u].blue);
1245 Minimize(result.opacity, QuantumRange-(double) k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00001246 if ( image->colorspace == CMYKColorspace)
cristy150989e2010-02-01 14:59:39 +00001247 Minimize(result.index, (double) k_indexes[u]);
anthony602ab9b2010-01-05 08:06:50 +00001248 }
1249 k_pixels += image->columns+kernel->width;
1250 k_indexes += image->columns+kernel->width;
1251 }
anthony602ab9b2010-01-05 08:06:50 +00001252 break;
1253
anthony83ba99b2010-01-24 08:48:15 +00001254 case DilateIntensityMorphology:
anthony29188a82010-01-22 10:12:34 +00001255 /* Select pixel with maximum intensity within kernel shape
1256 *
1257 * WARNING: the intensity test fails for CMYK and does not
1258 * take into account the moderating effect of teh alpha channel
1259 * on the intensity.
1260 *
1261 * NOTE for correct working of this operation for asymetrical
1262 * kernels, the kernel needs to be applied in its reflected form.
1263 * That is its values needs to be reversed.
1264 */
1265 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001266 k_pixels = p;
1267 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001268 for (v=0; v < (long) kernel->height; v++) {
1269 for (u=0; u < (long) kernel->width; u++, k--) {
anthony29188a82010-01-22 10:12:34 +00001270 if ( IsNan(*k) || (*k) < 0.5 ) continue; /* boolean kernel */
1271 if ( result.red == 0.0 ||
1272 PixelIntensity(&(k_pixels[u])) > PixelIntensity(q) ) {
1273 /* copy the whole pixel - no channel selection */
1274 *q = k_pixels[u];
1275 if ( result.red > 0.0 ) changed++;
1276 result.red = 1.0;
1277 }
anthony602ab9b2010-01-05 08:06:50 +00001278 }
1279 k_pixels += image->columns+kernel->width;
1280 k_indexes += image->columns+kernel->width;
1281 }
anthony602ab9b2010-01-05 08:06:50 +00001282 break;
1283
1284 case ErodeIntensityMorphology:
anthony29188a82010-01-22 10:12:34 +00001285 /* Select pixel with mimimum intensity within kernel shape
1286 *
1287 * WARNING: the intensity test fails for CMYK and does not
1288 * take into account the moderating effect of teh alpha channel
1289 * on the intensity.
1290 *
1291 * NOTE that the kernel is not reflected for this operation!
1292 */
anthony602ab9b2010-01-05 08:06:50 +00001293 k = kernel->values;
1294 k_pixels = p;
1295 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001296 for (v=0; v < (long) kernel->height; v++) {
1297 for (u=0; u < (long) kernel->width; u++, k++) {
anthony602ab9b2010-01-05 08:06:50 +00001298 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony29188a82010-01-22 10:12:34 +00001299 if ( result.red == 0.0 ||
1300 PixelIntensity(&(k_pixels[u])) < PixelIntensity(q) ) {
1301 /* copy the whole pixel - no channel selection */
1302 *q = k_pixels[u];
1303 if ( result.red > 0.0 ) changed++;
1304 result.red = 1.0;
1305 }
anthony602ab9b2010-01-05 08:06:50 +00001306 }
1307 k_pixels += image->columns+kernel->width;
1308 k_indexes += image->columns+kernel->width;
1309 }
anthony602ab9b2010-01-05 08:06:50 +00001310 break;
1311
1312 case DistanceMorphology:
anthony29188a82010-01-22 10:12:34 +00001313 /* Add kernel value and select the minimum value found.
1314 * The result is a iterative distance from edge function.
1315 *
1316 * All Distance Kernels are symetrical, but that may not always
1317 * be the case. For example how about a distance from left edges?
1318 * To make it work correctly for asymetrical kernels the reflected
1319 * kernel needs to be applied.
1320 */
anthony602ab9b2010-01-05 08:06:50 +00001321#if 0
anthony83ba99b2010-01-24 08:48:15 +00001322 /* No need to do distance morphology if original value is zero
1323 * Unfortunatally I have not been able to get this right
1324 * when channel selection also becomes involved. -- Arrgghhh
1325 */
anthony602ab9b2010-01-05 08:06:50 +00001326 if ( ((channel & RedChannel) == 0 && p[r].red == 0)
1327 || ((channel & GreenChannel) == 0 && p[r].green == 0)
1328 || ((channel & BlueChannel) == 0 && p[r].blue == 0)
1329 || ((channel & OpacityChannel) == 0 && p[r].opacity == 0)
1330 || (( (channel & IndexChannel) == 0
1331 || image->colorspace != CMYKColorspace
1332 ) && p_indexes[x] ==0 )
1333 )
1334 break;
1335#endif
anthony29188a82010-01-22 10:12:34 +00001336 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001337 k_pixels = p;
1338 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001339 for (v=0; v < (long) kernel->height; v++) {
1340 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001341 if ( IsNan(*k) ) continue;
1342 Minimize(result.red, (*k)+k_pixels[u].red);
1343 Minimize(result.green, (*k)+k_pixels[u].green);
1344 Minimize(result.blue, (*k)+k_pixels[u].blue);
1345 Minimize(result.opacity, (*k)+QuantumRange-k_pixels[u].opacity);
1346 if ( image->colorspace == CMYKColorspace)
1347 Minimize(result.index, (*k)+k_indexes[u]);
1348 }
1349 k_pixels += image->columns+kernel->width;
1350 k_indexes += image->columns+kernel->width;
1351 }
anthony602ab9b2010-01-05 08:06:50 +00001352 break;
1353
1354 case UndefinedMorphology:
1355 default:
1356 break; /* Do nothing */
anthony83ba99b2010-01-24 08:48:15 +00001357 }
1358 switch ( method ) {
1359 case UndefinedMorphology:
1360 case DilateIntensityMorphology:
1361 case ErodeIntensityMorphology:
1362 break; /* full pixel was directly assigned */
1363 default:
1364 /* Assign the results */
1365 if ((channel & RedChannel) != 0)
1366 q->red = ClampToQuantum(result.red);
1367 if ((channel & GreenChannel) != 0)
1368 q->green = ClampToQuantum(result.green);
1369 if ((channel & BlueChannel) != 0)
1370 q->blue = ClampToQuantum(result.blue);
1371 if ((channel & OpacityChannel) != 0
1372 && image->matte == MagickTrue )
1373 q->opacity = ClampToQuantum(QuantumRange-result.opacity);
1374 if ((channel & IndexChannel) != 0
1375 && image->colorspace == CMYKColorspace)
1376 q_indexes[x] = ClampToQuantum(result.index);
1377 break;
1378 }
1379 if ( ( p[r].red != q->red )
1380 || ( p[r].green != q->green )
1381 || ( p[r].blue != q->blue )
1382 || ( p[r].opacity != q->opacity )
1383 || ( image->colorspace == CMYKColorspace &&
1384 p_indexes[r] != q_indexes[x] ) )
1385 changed++; /* The pixel had some value changed! */
anthony602ab9b2010-01-05 08:06:50 +00001386 p++;
1387 q++;
anthony83ba99b2010-01-24 08:48:15 +00001388 } /* x */
anthony602ab9b2010-01-05 08:06:50 +00001389 sync=SyncCacheViewAuthenticPixels(q_view,exception);
1390 if (sync == MagickFalse)
1391 status=MagickFalse;
1392 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1393 {
1394 MagickBooleanType
1395 proceed;
1396
1397#if defined(MAGICKCORE_OPENMP_SUPPORT)
1398 #pragma omp critical (MagickCore_MorphologyImage)
1399#endif
1400 proceed=SetImageProgress(image,MorphologyTag,progress++,image->rows);
1401 if (proceed == MagickFalse)
1402 status=MagickFalse;
1403 }
anthony83ba99b2010-01-24 08:48:15 +00001404 } /* y */
anthony602ab9b2010-01-05 08:06:50 +00001405 result_image->type=image->type;
1406 q_view=DestroyCacheView(q_view);
1407 p_view=DestroyCacheView(p_view);
cristy150989e2010-02-01 14:59:39 +00001408 return(status ? (unsigned long) changed : 0);
anthony602ab9b2010-01-05 08:06:50 +00001409}
1410
cristy2be15382010-01-21 02:38:03 +00001411MagickExport Image *MorphologyImage(const Image *image,MorphologyMethod method,
1412 const long iterations,KernelInfo *kernel, ExceptionInfo *exception)
1413{
1414 Image
1415 *morphology_image;
1416
1417 morphology_image=MorphologyImageChannel(image,DefaultChannels,method,
1418 iterations,kernel,exception);
1419 return(morphology_image);
1420}
1421
1422MagickExport Image *MorphologyImageChannel(const Image *image,
cristy672da672010-01-10 15:43:07 +00001423 const ChannelType channel, MorphologyMethod method, const long iterations,
cristy2be15382010-01-21 02:38:03 +00001424 KernelInfo *kernel, ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001425{
cristy150989e2010-02-01 14:59:39 +00001426 long
1427 count;
anthony602ab9b2010-01-05 08:06:50 +00001428
1429 Image
1430 *new_image,
1431 *old_image;
1432
anthonycc6c8362010-01-25 04:14:01 +00001433 const char
1434 *artifact;
1435
cristy150989e2010-02-01 14:59:39 +00001436 unsigned long
1437 changed,
1438 limit;
1439
anthony602ab9b2010-01-05 08:06:50 +00001440 assert(image != (Image *) NULL);
1441 assert(image->signature == MagickSignature);
1442 assert(exception != (ExceptionInfo *) NULL);
1443 assert(exception->signature == MagickSignature);
1444
1445 if ( GetImageArtifact(image,"showkernel") != (const char *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001446 ShowKernel(kernel); /* request to display the kernel to stderr */
anthony602ab9b2010-01-05 08:06:50 +00001447
1448 if ( iterations == 0 )
1449 return((Image *)NULL); /* null operation - nothing to do! */
1450
1451 /* kernel must be valid at this point
1452 * (except maybe for posible future morphology methods like "Prune"
1453 */
cristy2be15382010-01-21 02:38:03 +00001454 assert(kernel != (KernelInfo *)NULL);
anthony602ab9b2010-01-05 08:06:50 +00001455
1456 count = 0;
anthony29188a82010-01-22 10:12:34 +00001457 changed = 1;
cristy150989e2010-02-01 14:59:39 +00001458 limit = (unsigned long) iterations;
anthony602ab9b2010-01-05 08:06:50 +00001459 if ( iterations < 0 )
1460 limit = image->columns > image->rows ? image->columns : image->rows;
1461
1462 /* Special morphology cases */
1463 switch( method ) {
1464 case CloseMorphology:
anthony83ba99b2010-01-24 08:48:15 +00001465 new_image = MorphologyImageChannel(image, channel, DilateMorphology,
anthony29188a82010-01-22 10:12:34 +00001466 iterations, kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001467 if (new_image == (Image *) NULL)
1468 return((Image *) NULL);
1469 method = ErodeMorphology;
1470 break;
1471 case OpenMorphology:
anthony29188a82010-01-22 10:12:34 +00001472 new_image = MorphologyImageChannel(image, channel, ErodeMorphology,
1473 iterations, kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001474 if (new_image == (Image *) NULL)
1475 return((Image *) NULL);
anthony83ba99b2010-01-24 08:48:15 +00001476 method = DilateMorphology;
anthony602ab9b2010-01-05 08:06:50 +00001477 break;
1478 case CloseIntensityMorphology:
anthony83ba99b2010-01-24 08:48:15 +00001479 new_image = MorphologyImageChannel(image, channel, DilateIntensityMorphology,
anthony29188a82010-01-22 10:12:34 +00001480 iterations, kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001481 if (new_image == (Image *) NULL)
1482 return((Image *) NULL);
1483 method = ErodeIntensityMorphology;
1484 break;
1485 case OpenIntensityMorphology:
anthony29188a82010-01-22 10:12:34 +00001486 new_image = MorphologyImageChannel(image, channel, ErodeIntensityMorphology,
1487 iterations, kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001488 if (new_image == (Image *) NULL)
1489 return((Image *) NULL);
anthony83ba99b2010-01-24 08:48:15 +00001490 method = DilateIntensityMorphology;
anthony602ab9b2010-01-05 08:06:50 +00001491 break;
1492
anthonyc94cdb02010-01-06 08:15:29 +00001493 case ConvolveMorphology:
anthonycc6c8362010-01-25 04:14:01 +00001494 /* Scale or Normalize kernel according to user wishes
1495 ** WARNING: this directly modifies the kernel
1496 ** which probably should not be done.
1497 */
1498 artifact = GetImageArtifact(image,"convolve:scale");
1499 if ( artifact != (char *)NULL )
1500 ScaleKernel(kernel, StringToDouble(artifact) );
anthonyc94cdb02010-01-06 08:15:29 +00001501 /* FALL-THRU */
anthony602ab9b2010-01-05 08:06:50 +00001502 default:
anthonycc6c8362010-01-25 04:14:01 +00001503 /* Do a morphology iteration just once at this point!
anthony602ab9b2010-01-05 08:06:50 +00001504 This ensures a new_image has been generated, but allows us
anthonyc94cdb02010-01-06 08:15:29 +00001505 to skip the creation of 'old_image' if it isn't needed.
anthony602ab9b2010-01-05 08:06:50 +00001506 */
1507 new_image=CloneImage(image,0,0,MagickTrue,exception);
1508 if (new_image == (Image *) NULL)
1509 return((Image *) NULL);
1510 if (SetImageStorageClass(new_image,DirectClass) == MagickFalse)
1511 {
1512 InheritException(exception,&new_image->exception);
1513 new_image=DestroyImage(new_image);
1514 return((Image *) NULL);
1515 }
1516 changed = MorphologyApply(image,new_image,method,channel,kernel,
1517 exception);
1518 count++;
1519 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
cristy150989e2010-02-01 14:59:39 +00001520 fprintf(stderr, "Morphology %s:%ld => Changed %lu\n",
anthony602ab9b2010-01-05 08:06:50 +00001521 MagickOptionToMnemonic(MagickMorphologyOptions, method),
1522 count, changed);
1523 }
1524
anthonycc6c8362010-01-25 04:14:01 +00001525 /* Repeat an interative morphology until count or no change reached */
cristy150989e2010-02-01 14:59:39 +00001526 if ( count < (long) limit && changed > 0 ) {
anthony602ab9b2010-01-05 08:06:50 +00001527 old_image = CloneImage(new_image,0,0,MagickTrue,exception);
1528 if (old_image == (Image *) NULL)
1529 return(DestroyImage(new_image));
1530 if (SetImageStorageClass(old_image,DirectClass) == MagickFalse)
1531 {
1532 InheritException(exception,&old_image->exception);
1533 old_image=DestroyImage(old_image);
1534 return(DestroyImage(new_image));
1535 }
cristy150989e2010-02-01 14:59:39 +00001536 while( count < (long) limit && changed != 0 )
anthony602ab9b2010-01-05 08:06:50 +00001537 {
1538 Image *tmp = old_image;
1539 old_image = new_image;
1540 new_image = tmp;
1541 changed = MorphologyApply(old_image,new_image,method,channel,kernel,
1542 exception);
1543 count++;
1544 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
cristy150989e2010-02-01 14:59:39 +00001545 fprintf(stderr, "Morphology %s:%ld => Changed %lu\n",
anthony602ab9b2010-01-05 08:06:50 +00001546 MagickOptionToMnemonic(MagickMorphologyOptions, method),
1547 count, changed);
1548 }
cristy150989e2010-02-01 14:59:39 +00001549 old_image=DestroyImage(old_image);
anthony602ab9b2010-01-05 08:06:50 +00001550 }
1551
1552 return(new_image);
1553}
anthony83ba99b2010-01-24 08:48:15 +00001554
1555/*
1556%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1557% %
1558% %
1559% %
anthonyc4c86e02010-01-27 09:30:32 +00001560+ R o t a t e K e r n e l %
anthony83ba99b2010-01-24 08:48:15 +00001561% %
1562% %
1563% %
1564%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1565%
1566% RotateKernel() rotates the kernel by the angle given. Currently it is
1567% restricted to 90 degree angles, but this may be improved in the future.
1568%
1569% The format of the RotateKernel method is:
1570%
1571% void RotateKernel(KernelInfo *kernel, double angle)
1572%
1573% A description of each parameter follows:
1574%
1575% o kernel: the Morphology/Convolution kernel
1576%
1577% o angle: angle to rotate in degrees
1578%
anthonyc4c86e02010-01-27 09:30:32 +00001579% This function is only internel to this module, as it is not finalized,
1580% especially with regard to non-orthogonal angles, and rotation of larger
1581% 2D kernels.
anthony83ba99b2010-01-24 08:48:15 +00001582*/
cristy150989e2010-02-01 14:59:39 +00001583static void RotateKernel(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00001584{
1585 /* WARNING: Currently assumes the kernel (rightly) is horizontally symetrical
1586 **
1587 ** TODO: expand beyond simple 90 degree rotates, flips and flops
1588 */
1589
1590 /* Modulus the angle */
1591 angle = fmod(angle, 360.0);
1592 if ( angle < 0 )
1593 angle += 360.0;
1594
1595 if ( 315.0 < angle || angle <= 45.0 )
1596 return; /* no change! - At least at this time */
1597
1598 switch (kernel->type) {
1599 /* These built-in kernels are cylindrical kernels, rotating is useless */
1600 case GaussianKernel:
1601 case LaplacianKernel:
1602 case LOGKernel:
1603 case DOGKernel:
1604 case DiskKernel:
1605 case ChebyshevKernel:
1606 case ManhattenKernel:
1607 case EuclideanKernel:
1608 return;
1609
1610 /* These may be rotatable at non-90 angles in the future */
1611 /* but simply rotating them in multiples of 90 degrees is useless */
1612 case SquareKernel:
1613 case DiamondKernel:
1614 case PlusKernel:
1615 return;
1616
1617 /* These only allows a +/-90 degree rotation (by transpose) */
1618 /* A 180 degree rotation is useless */
1619 case BlurKernel:
1620 case RectangleKernel:
1621 if ( 135.0 < angle && angle <= 225.0 )
1622 return;
1623 if ( 225.0 < angle && angle <= 315.0 )
1624 angle -= 180;
1625 break;
1626
1627 /* these are freely rotatable in 90 degree units */
1628 case CometKernel:
1629 case UndefinedKernel:
1630 case UserDefinedKernel:
1631 break;
1632 }
1633 if ( 135.0 < angle && angle <= 225.0 )
1634 {
1635 /* For a 180 degree rotation - also know as a reflection */
1636 /* This is actually a very very common operation! */
1637 /* Basically all that is needed is a reversal of the kernel data! */
1638 unsigned long
1639 i,j;
1640 register double
1641 *k,t;
1642
1643 k=kernel->values;
1644 for ( i=0, j=kernel->width*kernel->height-1; i<j; i++, j--)
1645 t=k[i], k[i]=k[j], k[j]=t;
1646
cristy150989e2010-02-01 14:59:39 +00001647 kernel->offset_x = (long) kernel->width - kernel->offset_x - 1;
1648 kernel->offset_y = (long) kernel->width - kernel->offset_y - 1;
anthony83ba99b2010-01-24 08:48:15 +00001649 angle = fmod(angle+180.0, 360.0);
1650 }
1651 if ( 45.0 < angle && angle <= 135.0 )
1652 { /* Do a transpose and a flop, of the image, which results in a 90
1653 * degree rotation using two mirror operations.
1654 *
1655 * WARNING: this assumes the original image was a 1 dimentional image
1656 * but currently that is the only built-ins it is applied to.
1657 */
cristy150989e2010-02-01 14:59:39 +00001658 long
anthony83ba99b2010-01-24 08:48:15 +00001659 t;
cristy150989e2010-02-01 14:59:39 +00001660 t = (long) kernel->width;
anthony83ba99b2010-01-24 08:48:15 +00001661 kernel->width = kernel->height;
cristy150989e2010-02-01 14:59:39 +00001662 kernel->height = (unsigned long) t;
anthony83ba99b2010-01-24 08:48:15 +00001663 t = kernel->offset_x;
1664 kernel->offset_x = kernel->offset_y;
1665 kernel->offset_y = t;
1666 angle = fmod(450.0 - angle, 360.0);
1667 }
1668 /* At this point angle should be between -45 (315) and +45 degrees
1669 * In the future some form of non-orthogonal angled rotates could be
1670 * performed here, posibily with a linear kernel restriction.
1671 */
1672
1673#if 0
1674 Not currently in use!
1675 { /* Do a flop, this assumes kernel is horizontally symetrical.
1676 * Each row of the kernel needs to be reversed!
1677 */
1678 unsigned long
1679 y;
cristy150989e2010-02-01 14:59:39 +00001680 register long
anthony83ba99b2010-01-24 08:48:15 +00001681 x,r;
1682 register double
1683 *k,t;
1684
1685 for ( y=0, k=kernel->values; y < kernel->height; y++, k+=kernel->width)
1686 for ( x=0, r=kernel->width-1; x<kernel->width/2; x++, r--)
1687 t=k[x], k[x]=k[r], k[r]=t;
1688
1689 kernel->offset_x = kernel->width - kernel->offset_x - 1;
1690 angle = fmod(angle+180.0, 360.0);
1691 }
1692#endif
1693 return;
1694}
1695
1696/*
1697%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1698% %
1699% %
1700% %
anthonycc6c8362010-01-25 04:14:01 +00001701+ S c a l e K e r n e l %
1702% %
1703% %
1704% %
1705%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1706%
1707% ScaleKernel() scales the kernel by the given amount. Scaling by value of
1708% zero will result in a normalization of the kernel.
1709%
1710% For positive kernels normalization scales the kernel so the addition os all
1711% values is 1.0. While for kernels where values add to zero it is scaled
1712% so that the convolution output range covers 1.0. In such 'zero kernels'
1713% it is generally recomended that the user also provides a 50% bias to the
1714% output results.
1715%
1716% Correct normalization assumes the 'range_*' attributes of the kernel
1717% structure have been correctly set during the kernel creation.
1718%
1719% The format of the ScaleKernel method is:
1720%
1721% void ScaleKernel(KernelInfo *kernel)
1722%
1723% A description of each parameter follows:
1724%
1725% o kernel: the Morphology/Convolution kernel
1726%
1727% o scale: multiple all values by this, if zero normalize instead.
1728%
anthonyc4c86e02010-01-27 09:30:32 +00001729% This function is internal to this module only at this time, but can be
1730% exported to other modules if needed.
anthonycc6c8362010-01-25 04:14:01 +00001731*/
cristy150989e2010-02-01 14:59:39 +00001732static void ScaleKernel(KernelInfo *kernel, double scale)
anthonycc6c8362010-01-25 04:14:01 +00001733{
cristy150989e2010-02-01 14:59:39 +00001734 register long
anthonycc6c8362010-01-25 04:14:01 +00001735 i;
1736
1737 if ( fabs(scale) < MagickEpsilon ) {
1738 if ( fabs(kernel->range_pos + kernel->range_neg) < MagickEpsilon )
1739 scale = 1/(kernel->range_pos - kernel->range_neg); /* zero kernels */
1740 else
1741 scale = 1/(kernel->range_pos + kernel->range_neg); /* non-zero kernel */
1742 }
1743
cristy150989e2010-02-01 14:59:39 +00001744 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00001745 if ( ! IsNan(kernel->values[i]) )
1746 kernel->values[i] *= scale;
1747
1748 kernel->range_pos *= scale; /* convolution output range */
1749 kernel->range_neg *= scale;
1750 kernel->value_max *= scale; /* maximum and minimum values in kernel */
1751 kernel->value_min *= scale;
1752
1753 return;
1754}
1755
1756/*
1757%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1758% %
1759% %
1760% %
1761+ S h o w K e r n e l %
anthony83ba99b2010-01-24 08:48:15 +00001762% %
1763% %
1764% %
1765%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1766%
1767% ShowKernel() Output the details of the given kernel defination to
1768% standard error, as per a users 'showkernel' option request.
1769%
1770% The format of the ShowKernel method is:
1771%
anthonycc6c8362010-01-25 04:14:01 +00001772% void ShowKernel(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00001773%
1774% A description of each parameter follows:
1775%
1776% o kernel: the Morphology/Convolution kernel
1777%
anthonyc4c86e02010-01-27 09:30:32 +00001778% This function is internal to this module only at this time. That may change
1779% in the future.
anthony83ba99b2010-01-24 08:48:15 +00001780*/
1781static void ShowKernel(KernelInfo *kernel)
1782{
cristy150989e2010-02-01 14:59:39 +00001783 long
anthony83ba99b2010-01-24 08:48:15 +00001784 i, u, v;
1785
1786 fprintf(stderr,
anthonycc6c8362010-01-25 04:14:01 +00001787 "Kernel \"%s\" of size %lux%lu%+ld%+ld with values from %.*lg to %.*lg\n",
anthony83ba99b2010-01-24 08:48:15 +00001788 MagickOptionToMnemonic(MagickKernelOptions, kernel->type),
1789 kernel->width, kernel->height,
1790 kernel->offset_x, kernel->offset_y,
anthonycc6c8362010-01-25 04:14:01 +00001791 GetMagickPrecision(), kernel->value_min,
1792 GetMagickPrecision(), kernel->value_max);
1793 fprintf(stderr, "Forming convolution output range from %.*lg to %.*lg%s\n",
1794 GetMagickPrecision(), kernel->range_neg,
1795 GetMagickPrecision(), kernel->range_pos,
1796 /*kernel->normalized == MagickTrue ? " (normalized)" : */ "" );
cristy150989e2010-02-01 14:59:39 +00001797 for (i=v=0; v < (long) kernel->height; v++) {
anthony83ba99b2010-01-24 08:48:15 +00001798 fprintf(stderr,"%2ld:",v);
cristy150989e2010-02-01 14:59:39 +00001799 for (u=0; u < (long) kernel->width; u++, i++)
anthony83ba99b2010-01-24 08:48:15 +00001800 if ( IsNan(kernel->values[i]) )
1801 fprintf(stderr," %*s", GetMagickPrecision()+2, "nan");
1802 else
1803 fprintf(stderr," %*.*lg", GetMagickPrecision()+2,
1804 GetMagickPrecision(), kernel->values[i]);
1805 fprintf(stderr,"\n");
1806 }
1807}
anthonycc6c8362010-01-25 04:14:01 +00001808
1809/*
1810%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1811% %
1812% %
1813% %
1814+ Z e r o K e r n e l N a n s %
1815% %
1816% %
1817% %
1818%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1819%
1820% ZeroKernelNans() replaces any special 'nan' value that may be present in
1821% the kernel with a zero value. This is typically done when the kernel will
1822% be used in special hardware (GPU) convolution processors, to simply
1823% matters.
1824%
1825% The format of the ZeroKernelNans method is:
1826%
1827% voidZeroKernelNans (KernelInfo *kernel)
1828%
1829% A description of each parameter follows:
1830%
1831% o kernel: the Morphology/Convolution kernel
1832%
1833% FUTURE: return the information in a string for API usage.
1834*/
anthonyc4c86e02010-01-27 09:30:32 +00001835MagickExport void ZeroKernelNans(KernelInfo *kernel)
anthonycc6c8362010-01-25 04:14:01 +00001836{
cristy150989e2010-02-01 14:59:39 +00001837 register long
anthonycc6c8362010-01-25 04:14:01 +00001838 i;
1839
cristy150989e2010-02-01 14:59:39 +00001840 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00001841 if ( IsNan(kernel->values[i]) )
1842 kernel->values[i] = 0.0;
1843
1844 return;
1845}