blob: e694dbe60b85ba14f7b7d50ac713c914534a9766 [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
anthony83ba99b2010-01-24 08:48:15 +0000109/* Currently these are internal to this module
110 * Eventually these may become 'private' to library
111 * OR may become externally available to API's
112 */
113static MagickExport void
anthony83ba99b2010-01-24 08:48:15 +0000114 RotateKernel(KernelInfo *, double),
anthonycc6c8362010-01-25 04:14:01 +0000115 ScaleKernel(KernelInfo *, double),
116 ShowKernel(KernelInfo *),
117 ZeroKernelNans(KernelInfo *);
anthony83ba99b2010-01-24 08:48:15 +0000118
anthony602ab9b2010-01-05 08:06:50 +0000119
120/*
121%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
122% %
123% %
124% %
anthony83ba99b2010-01-24 08:48:15 +0000125% A c q u i r e K e r n e l I n f o %
anthony602ab9b2010-01-05 08:06:50 +0000126% %
127% %
128% %
129%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
130%
cristy2be15382010-01-21 02:38:03 +0000131% AcquireKernelInfo() takes the given string (generally supplied by the
anthony602ab9b2010-01-05 08:06:50 +0000132% user) and converts it into a Morphology/Convolution Kernel. This allows
133% users to specify a kernel from a number of pre-defined kernels, or to fully
134% specify their own kernel for a specific Convolution or Morphology
135% Operation.
136%
137% The kernel so generated can be any rectangular array of floating point
138% values (doubles) with the 'control point' or 'pixel being affected'
139% anywhere within that array of values.
140%
anthony83ba99b2010-01-24 08:48:15 +0000141% Previously IM was restricted to a square of odd size using the exact
142% center as origin, this is no longer the case, and any rectangular kernel
143% with any value being declared the origin. This in turn allows the use of
144% highly asymmetrical kernels.
anthony602ab9b2010-01-05 08:06:50 +0000145%
146% The floating point values in the kernel can also include a special value
anthony83ba99b2010-01-24 08:48:15 +0000147% known as 'nan' or 'not a number' to indicate that this value is not part
148% of the kernel array. This allows you to shaped the kernel within its
149% rectangular area. That is 'nan' values provide a 'mask' for the kernel
150% shape. However at least one non-nan value must be provided for correct
151% working of a kernel.
anthony602ab9b2010-01-05 08:06:50 +0000152%
anthony83ba99b2010-01-24 08:48:15 +0000153% The returned kernel should be free using the DestroyKernelInfo() when you
154% are finished with it.
anthony602ab9b2010-01-05 08:06:50 +0000155%
156% Input kernel defintion strings can consist of any of three types.
157%
anthony29188a82010-01-22 10:12:34 +0000158% "name:args"
159% Select from one of the built in kernels, using the name and
160% geometry arguments supplied. See AcquireKernelBuiltIn()
anthony602ab9b2010-01-05 08:06:50 +0000161%
162% "WxH[+X+Y]:num, num, num ..."
163% a kernal of size W by H, with W*H floating point numbers following.
164% the 'center' can be optionally be defined at +X+Y (such that +0+0
anthony29188a82010-01-22 10:12:34 +0000165% is top left corner). If not defined the pixel in the center, for
166% odd sizes, or to the immediate top or left of center for even sizes
167% is automatically selected.
anthony602ab9b2010-01-05 08:06:50 +0000168%
anthony29188a82010-01-22 10:12:34 +0000169% "num, num, num, num, ..."
170% list of floating point numbers defining an 'old style' odd sized
171% square kernel. At least 9 values should be provided for a 3x3
172% square kernel, 25 for a 5x5 square kernel, 49 for 7x7, etc.
173% Values can be space or comma separated. This is not recommended.
anthony602ab9b2010-01-05 08:06:50 +0000174%
anthony83ba99b2010-01-24 08:48:15 +0000175% Note that 'name' kernels will start with an alphabetic character while the
176% new kernel specification has a ':' character in its specification string.
177% If neither is the case, it is assumed an old style of a simple list of
178% numbers generating a odd-sized square kernel has been given.
anthony602ab9b2010-01-05 08:06:50 +0000179%
180% The format of the AcquireKernal method is:
181%
cristy2be15382010-01-21 02:38:03 +0000182% KernelInfo *AcquireKernelInfo(const char *kernel_string)
anthony602ab9b2010-01-05 08:06:50 +0000183%
184% A description of each parameter follows:
185%
186% o kernel_string: the Morphology/Convolution kernel wanted.
187%
188*/
189
cristy2be15382010-01-21 02:38:03 +0000190MagickExport KernelInfo *AcquireKernelInfo(const char *kernel_string)
anthony602ab9b2010-01-05 08:06:50 +0000191{
cristy2be15382010-01-21 02:38:03 +0000192 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000193 *kernel;
194
195 char
196 token[MaxTextExtent];
197
198 register unsigned long
199 i;
200
201 const char
202 *p;
203
204 MagickStatusType
205 flags;
206
207 GeometryInfo
208 args;
209
anthony29188a82010-01-22 10:12:34 +0000210 double
211 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
212
anthony602ab9b2010-01-05 08:06:50 +0000213 assert(kernel_string != (const char *) NULL);
214 SetGeometryInfo(&args);
215
216 /* does it start with an alpha - Return a builtin kernel */
217 GetMagickToken(kernel_string,&p,token);
218 if ( isalpha((int)token[0]) )
219 {
220 long
221 type;
222
anthony29188a82010-01-22 10:12:34 +0000223 type=ParseMagickOption(MagickKernelOptions,MagickFalse,token);
anthony602ab9b2010-01-05 08:06:50 +0000224 if ( type < 0 || type == UserDefinedKernel )
cristy2be15382010-01-21 02:38:03 +0000225 return((KernelInfo *)NULL);
anthony602ab9b2010-01-05 08:06:50 +0000226
227 while (((isspace((int) ((unsigned char) *p)) != 0) ||
228 (*p == ',') || (*p == ':' )) && (*p != '\0'))
229 p++;
230 flags = ParseGeometry(p, &args);
231
232 /* special handling of missing values in input string */
233 if ( type == RectangleKernel ) {
234 if ( (flags & WidthValue) == 0 ) /* if no width then */
235 args.rho = args.sigma; /* then width = height */
236 if ( args.rho < 1.0 ) /* if width too small */
237 args.rho = 3; /* then width = 3 */
238 if ( args.sigma < 1.0 ) /* if height too small */
239 args.sigma = args.rho; /* then height = width */
240 if ( (flags & XValue) == 0 ) /* center offset if not defined */
241 args.xi = (double)(((long)args.rho-1)/2);
242 if ( (flags & YValue) == 0 )
243 args.psi = (double)(((long)args.sigma-1)/2);
244 }
245
cristy2be15382010-01-21 02:38:03 +0000246 return(AcquireKernelBuiltIn((KernelInfoType)type, &args));
anthony602ab9b2010-01-05 08:06:50 +0000247 }
248
cristy2be15382010-01-21 02:38:03 +0000249 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
250 if (kernel == (KernelInfo *)NULL)
anthony602ab9b2010-01-05 08:06:50 +0000251 return(kernel);
252 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
253 kernel->type = UserDefinedKernel;
cristyd43a46b2010-01-21 02:13:41 +0000254 kernel->signature = MagickSignature;
anthony602ab9b2010-01-05 08:06:50 +0000255
256 /* Has a ':' in argument - New user kernel specification */
257 p = strchr(kernel_string, ':');
258 if ( p != (char *) NULL)
259 {
anthony602ab9b2010-01-05 08:06:50 +0000260 /* ParseGeometry() needs the geometry separated! -- Arrgghh */
261 memcpy(token, kernel_string, p-kernel_string);
262 token[p-kernel_string] = '\0';
263 flags = ParseGeometry(token, &args);
anthony602ab9b2010-01-05 08:06:50 +0000264
anthony29188a82010-01-22 10:12:34 +0000265 /* Size handling and checks of geometry settings */
anthony602ab9b2010-01-05 08:06:50 +0000266 if ( (flags & WidthValue) == 0 ) /* if no width then */
267 args.rho = args.sigma; /* then width = height */
268 if ( args.rho < 1.0 ) /* if width too small */
269 args.rho = 1.0; /* then width = 1 */
270 if ( args.sigma < 1.0 ) /* if height too small */
271 args.sigma = args.rho; /* then height = width */
272 kernel->width = (unsigned long)args.rho;
273 kernel->height = (unsigned long)args.sigma;
274
275 /* Offset Handling and Checks */
276 if ( args.xi < 0.0 || args.psi < 0.0 )
anthony83ba99b2010-01-24 08:48:15 +0000277 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000278 kernel->offset_x = ((flags & XValue)!=0) ? (unsigned long)args.xi
279 : (kernel->width-1)/2;
280 kernel->offset_y = ((flags & YValue)!=0) ? (unsigned long)args.psi
281 : (kernel->height-1)/2;
282 if ( kernel->offset_x >= kernel->width ||
283 kernel->offset_y >= kernel->height )
anthony83ba99b2010-01-24 08:48:15 +0000284 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000285
286 p++; /* advance beyond the ':' */
287 }
288 else
289 { /* ELSE - Old old kernel specification, forming odd-square kernel */
290 /* count up number of values given */
291 p=(const char *) kernel_string;
cristya699b172010-01-06 16:48:49 +0000292 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
anthony29188a82010-01-22 10:12:34 +0000293 p++; /* ignore "'" chars for convolve filter usage - Cristy */
anthony602ab9b2010-01-05 08:06:50 +0000294 for (i=0; *p != '\0'; i++)
295 {
296 GetMagickToken(p,&p,token);
297 if (*token == ',')
298 GetMagickToken(p,&p,token);
299 }
300 /* set the size of the kernel - old sized square */
301 kernel->width = kernel->height= (unsigned long) sqrt((double) i+1.0);
302 kernel->offset_x = kernel->offset_y = (kernel->width-1)/2;
303 p=(const char *) kernel_string;
anthony29188a82010-01-22 10:12:34 +0000304 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
305 p++; /* ignore "'" chars for convolve filter usage - Cristy */
anthony602ab9b2010-01-05 08:06:50 +0000306 }
307
308 /* Read in the kernel values from rest of input string argument */
309 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
310 kernel->height*sizeof(double));
311 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000312 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000313
anthony29188a82010-01-22 10:12:34 +0000314 kernel->value_min = +MagickHuge;
315 kernel->value_max = -MagickHuge;
anthony602ab9b2010-01-05 08:06:50 +0000316 kernel->range_neg = kernel->range_pos = 0.0;
317 for (i=0; (i < kernel->width*kernel->height) && (*p != '\0'); i++)
318 {
319 GetMagickToken(p,&p,token);
320 if (*token == ',')
321 GetMagickToken(p,&p,token);
anthony29188a82010-01-22 10:12:34 +0000322 if ( LocaleCompare("nan",token) == 0
323 || LocaleCompare("-",token) == 0 ) {
324 kernel->values[i] = nan; /* do not include this value in kernel */
325 }
326 else {
327 kernel->values[i] = StringToDouble(token);
328 ( kernel->values[i] < 0)
329 ? ( kernel->range_neg += kernel->values[i] )
330 : ( kernel->range_pos += kernel->values[i] );
331 Minimize(kernel->value_min, kernel->values[i]);
332 Maximize(kernel->value_max, kernel->values[i]);
333 }
anthony602ab9b2010-01-05 08:06:50 +0000334 }
anthonycc6c8362010-01-25 04:14:01 +0000335 /* check that we recieved at least one real (non-nan) value! */
336 if ( kernel->value_min == MagickHuge )
337 return(DestroyKernelInfo(kernel));
anthony29188a82010-01-22 10:12:34 +0000338
anthonycc6c8362010-01-25 04:14:01 +0000339 /* This should not be needed for a fully defined kernel
anthony29188a82010-01-22 10:12:34 +0000340 * Perhaps an error should be reported instead!
anthonycc6c8362010-01-25 04:14:01 +0000341 * Kept for backward compatibility.
anthony29188a82010-01-22 10:12:34 +0000342 */
343 if ( i < kernel->width*kernel->height ) {
344 Minimize(kernel->value_min, kernel->values[i]);
345 Maximize(kernel->value_max, kernel->values[i]);
346 for ( ; i < kernel->width*kernel->height; i++)
347 kernel->values[i]=0.0;
348 }
anthony602ab9b2010-01-05 08:06:50 +0000349
350 return(kernel);
351}
352
353/*
354%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
355% %
356% %
357% %
358% A c q u i r e K e r n e l B u i l t I n %
359% %
360% %
361% %
362%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
363%
364% AcquireKernelBuiltIn() returned one of the 'named' built-in types of
365% kernels used for special purposes such as gaussian blurring, skeleton
366% pruning, and edge distance determination.
367%
368% They take a KernelType, and a set of geometry style arguments, which were
369% typically decoded from a user supplied string, or from a more complex
370% Morphology Method that was requested.
371%
372% The format of the AcquireKernalBuiltIn method is:
373%
cristy2be15382010-01-21 02:38:03 +0000374% KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000375% const GeometryInfo args)
376%
377% A description of each parameter follows:
378%
379% o type: the pre-defined type of kernel wanted
380%
381% o args: arguments defining or modifying the kernel
382%
383% Convolution Kernels
384%
385% Gaussian "[{radius}]x{sigma}"
386% Generate a two-dimentional gaussian kernel, as used by -gaussian
387% A sigma is required, (with the 'x'), due to historical reasons.
388%
389% NOTE: that the 'radius' is optional, but if provided can limit (clip)
390% the final size of the resulting kernel to a square 2*radius+1 in size.
391% The radius should be at least 2 times that of the sigma value, or
392% sever clipping and aliasing may result. If not given or set to 0 the
393% radius will be determined so as to produce the best minimal error
394% result, which is usally much larger than is normally needed.
395%
396% Blur "[{radius}]x{sigma}[+angle]"
397% As per Gaussian, but generates a 1 dimensional or linear gaussian
398% blur, at the angle given (current restricted to orthogonal angles).
399% If a 'radius' is given the kernel is clipped to a width of 2*radius+1.
400%
401% NOTE that two such blurs perpendicular to each other is equivelent to
402% -blur and the previous gaussian, but is often 10 or more times faster.
403%
404% Comet "[{width}]x{sigma}[+angle]"
405% Blur in one direction only, mush like how a bright object leaves
406% a comet like trail. The Kernel is actually half a gaussian curve,
407% Adding two such blurs in oppiste directions produces a Linear Blur.
408%
409% NOTE: that the first argument is the width of the kernel and not the
410% radius of the kernel.
411%
412% # Still to be implemented...
413% #
414% # Laplacian "{radius}x{sigma}"
415% # Laplacian (a mexican hat like) Function
416% #
417% # LOG "{radius},{sigma1},{sigma2}
418% # Laplacian of Gaussian
419% #
420% # DOG "{radius},{sigma1},{sigma2}
421% # Difference of Gaussians
422%
423% Boolean Kernels
424%
425% Rectangle "{geometry}"
426% Simply generate a rectangle of 1's with the size given. You can also
427% specify the location of the 'control point', otherwise the closest
428% pixel to the center of the rectangle is selected.
429%
430% Properly centered and odd sized rectangles work the best.
431%
432% Diamond "[{radius}]"
433% Generate a diamond shaped kernal with given radius to the points.
434% Kernel size will again be radius*2+1 square and defaults to radius 1,
435% generating a 3x3 kernel that is slightly larger than a square.
436%
437% Square "[{radius}]"
438% Generate a square shaped kernel of size radius*2+1, and defaulting
439% to a 3x3 (radius 1).
440%
441% Note that using a larger radius for the "Square" or the "Diamond"
442% is also equivelent to iterating the basic morphological method
443% that many times. However However iterating with the smaller radius 1
444% default is actually faster than using a larger kernel radius.
445%
446% Disk "[{radius}]
447% Generate a binary disk of the radius given, radius may be a float.
448% Kernel size will be ceil(radius)*2+1 square.
449% NOTE: Here are some disk shapes of specific interest
450% "disk:1" => "diamond" or "cross:1"
451% "disk:1.5" => "square"
452% "disk:2" => "diamond:2"
anthony83ba99b2010-01-24 08:48:15 +0000453% "disk:2.5" => a general disk shape of radius 2
anthony602ab9b2010-01-05 08:06:50 +0000454% "disk:2.9" => "square:2"
anthony83ba99b2010-01-24 08:48:15 +0000455% "disk:3.5" => default - octagonal/disk shape of radius 3
anthony602ab9b2010-01-05 08:06:50 +0000456% "disk:4.2" => roughly octagonal shape of radius 4
anthony83ba99b2010-01-24 08:48:15 +0000457% "disk:4.3" => a general disk shape of radius 4
anthony602ab9b2010-01-05 08:06:50 +0000458% After this all the kernel shape becomes more and more circular.
459%
460% Because a "disk" is more circular when using a larger radius, using a
461% larger radius is preferred over iterating the morphological operation.
462%
463% Plus "[{radius}]"
464% Generate a kernel in the shape of a 'plus' sign. The length of each
465% arm is also the radius, which defaults to 2.
466%
467% This kernel is not a good general morphological kernel, but is used
468% more for highlighting and marking any single pixels in an image using,
469% a "Dilate" or "Erode" method as appropriate.
anthonyc94cdb02010-01-06 08:15:29 +0000470%
anthony602ab9b2010-01-05 08:06:50 +0000471% NOTE: "plus:1" is equivelent to a "Diamond" kernel.
472%
473% Note that unlike other kernels iterating a plus does not produce the
474% same result as using a larger radius for the cross.
475%
476% Distance Measuring Kernels
477%
478% Chebyshev "[{radius}][x{scale}]" largest x or y distance (default r=1)
479% Manhatten "[{radius}][x{scale}]" square grid distance (default r=1)
anthonyc94cdb02010-01-06 08:15:29 +0000480% Euclidean "[{radius}][x{scale}]" direct distance (default r=1)
anthony602ab9b2010-01-05 08:06:50 +0000481%
482% Different types of distance measuring methods, which are used with the
483% a 'Distance' morphology method for generating a gradient based on
484% distance from an edge of a binary shape, though there is a technique
485% for handling a anti-aliased shape.
486%
anthonyc94cdb02010-01-06 08:15:29 +0000487% Chebyshev Distance (also known as Tchebychev Distance) is a value of
488% one to any neighbour, orthogonal or diagonal. One why of thinking of
489% it is the number of squares a 'King' or 'Queen' in chess needs to
490% traverse reach any other position on a chess board. It results in a
491% 'square' like distance function, but one where diagonals are closer
492% than expected.
anthony602ab9b2010-01-05 08:06:50 +0000493%
anthonyc94cdb02010-01-06 08:15:29 +0000494% Manhatten Distance (also known as Rectilinear Distance, or the Taxi
495% Cab metric), is the distance needed when you can only travel in
496% orthogonal (horizontal or vertical) only. It is the distance a 'Rook'
497% in chess would travel. It results in a diamond like distances, where
498% diagonals are further than expected.
anthony602ab9b2010-01-05 08:06:50 +0000499%
anthonyc94cdb02010-01-06 08:15:29 +0000500% Euclidean Distance is the 'direct' or 'as the crow flys distance.
501% However by default the kernel size only has a radius of 1, which
502% limits the distance to 'Knight' like moves, with only orthogonal and
503% diagonal measurements being correct. As such for the default kernel
504% you will get octagonal like distance function, which is reasonally
505% accurate.
506%
507% However if you use a larger radius such as "Euclidean:4" you will
508% get a much smoother distance gradient from the edge of the shape.
509% Of course a larger kernel is slower to use, and generally not needed.
510%
511% To allow the use of fractional distances that you get with diagonals
512% the actual distance is scaled by a fixed value which the user can
513% provide. This is not actually nessary for either ""Chebyshev" or
514% "Manhatten" distance kernels, but is done for all three distance
515% kernels. If no scale is provided it is set to a value of 100,
516% allowing for a maximum distance measurement of 655 pixels using a Q16
517% version of IM, from any edge. However for small images this can
518% result in quite a dark gradient.
519%
520% See the 'Distance' Morphological Method, for information of how it is
521% applied.
anthony602ab9b2010-01-05 08:06:50 +0000522%
523*/
524
cristy2be15382010-01-21 02:38:03 +0000525MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000526 const GeometryInfo *args)
527{
cristy2be15382010-01-21 02:38:03 +0000528 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000529 *kernel;
530
531 register unsigned long
532 i;
533
534 register long
535 u,
536 v;
537
538 double
539 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
540
cristy2be15382010-01-21 02:38:03 +0000541 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
542 if (kernel == (KernelInfo *) NULL)
anthony602ab9b2010-01-05 08:06:50 +0000543 return(kernel);
544 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
anthonyc94cdb02010-01-06 08:15:29 +0000545 kernel->value_min = kernel->value_max = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000546 kernel->range_neg = kernel->range_pos = 0.0;
547 kernel->type = type;
cristyd43a46b2010-01-21 02:13:41 +0000548 kernel->signature = MagickSignature;
anthony602ab9b2010-01-05 08:06:50 +0000549
550 switch(type) {
551 /* Convolution Kernels */
552 case GaussianKernel:
553 { double
554 sigma = fabs(args->sigma);
555
556 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
557
558 kernel->width = kernel->height =
559 GetOptimalKernelWidth2D(args->rho,sigma);
560 kernel->offset_x = kernel->offset_y = (kernel->width-1)/2;
561 kernel->range_neg = kernel->range_pos = 0.0;
562 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
563 kernel->height*sizeof(double));
564 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000565 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000566
567 sigma = 2.0*sigma*sigma; /* simplify the expression */
568 for ( i=0, v=-kernel->offset_y; v <= (long)kernel->offset_y; v++)
569 for ( u=-kernel->offset_x; u <= (long)kernel->offset_x; u++, i++)
570 kernel->range_pos += (
571 kernel->values[i] =
572 exp(-((double)(u*u+v*v))/sigma)
573 /* / (MagickPI*sigma) */ );
anthonyc94cdb02010-01-06 08:15:29 +0000574 kernel->value_min = 0;
575 kernel->value_max = kernel->values[
576 kernel->offset_y*kernel->width+kernel->offset_x ];
anthony602ab9b2010-01-05 08:06:50 +0000577
anthonycc6c8362010-01-25 04:14:01 +0000578 ScaleKernel(kernel, 0.0); /* Normalize Kernel Values */
anthony602ab9b2010-01-05 08:06:50 +0000579
580 break;
581 }
582 case BlurKernel:
583 { double
584 sigma = fabs(args->sigma);
585
586 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
587
588 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
589 kernel->offset_x = (kernel->width-1)/2;
590 kernel->height = 1;
591 kernel->offset_y = 0;
592 kernel->range_neg = kernel->range_pos = 0.0;
593 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
594 kernel->height*sizeof(double));
595 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000596 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000597
598#if 1
599#define KernelRank 3
600 /* Formula derived from GetBlurKernel() in "effect.c" (plus bug fix).
601 ** It generates a gaussian 3 times the width, and compresses it into
602 ** the expected range. This produces a closer normalization of the
603 ** resulting kernel, especially for very low sigma values.
604 ** As such while wierd it is prefered.
605 **
606 ** I am told this method originally came from Photoshop.
607 */
608 sigma *= KernelRank; /* simplify expanded curve */
609 v = (kernel->width*KernelRank-1)/2; /* start/end points to fit range */
610 (void) ResetMagickMemory(kernel->values,0, (size_t)
611 kernel->width*sizeof(double));
612 for ( u=-v; u <= v; u++) {
613 kernel->values[(u+v)/KernelRank] +=
614 exp(-((double)(u*u))/(2.0*sigma*sigma))
615 /* / (MagickSQ2PI*sigma/KernelRank) */ ;
616 }
617 for (i=0; i < kernel->width; i++)
618 kernel->range_pos += kernel->values[i];
619#else
620 for ( i=0, u=-kernel->offset_x; i < kernel->width; i++, u++)
621 kernel->range_pos += (
622 kernel->values[i] =
623 exp(-((double)(u*u))/(2.0*sigma*sigma))
624 /* / (MagickSQ2PI*sigma) */ );
625#endif
anthonyc94cdb02010-01-06 08:15:29 +0000626 kernel->value_min = 0;
627 kernel->value_max = kernel->values[ kernel->offset_x ];
anthonycc6c8362010-01-25 04:14:01 +0000628 /* Note that neither methods above generate a normalized kernel,
629 ** though it gets close. The kernel may be 'clipped' by a user defined
630 ** radius, producing a smaller (darker) kernel. Also for very small
631 ** sigma's (> 0.1) the central value becomes larger than one, and thus
632 ** producing a very bright kernel.
anthony602ab9b2010-01-05 08:06:50 +0000633 */
anthonycc6c8362010-01-25 04:14:01 +0000634
anthony602ab9b2010-01-05 08:06:50 +0000635 /* Normalize the 1D Gaussian Kernel
636 **
637 ** Because of this the divisor in the above kernel generator is
anthonyc94cdb02010-01-06 08:15:29 +0000638 ** not needed, so is not done above.
anthony602ab9b2010-01-05 08:06:50 +0000639 */
anthonycc6c8362010-01-25 04:14:01 +0000640 ScaleKernel(kernel, 0.0); /* Normalize Kernel Values */
641
anthony602ab9b2010-01-05 08:06:50 +0000642 /* rotate the kernel by given angle */
anthony83ba99b2010-01-24 08:48:15 +0000643 RotateKernel(kernel, args->xi);
anthony602ab9b2010-01-05 08:06:50 +0000644 break;
645 }
646 case CometKernel:
647 { double
648 sigma = fabs(args->sigma);
649
650 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
651
652 if ( args->rho < 1.0 )
653 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
654 else
655 kernel->width = (unsigned long)args->rho;
656 kernel->offset_x = kernel->offset_y = 0;
657 kernel->height = 1;
658 kernel->range_neg = kernel->range_pos = 0.0;
659 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
660 kernel->height*sizeof(double));
661 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000662 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000663
664 /* A comet blur is half a gaussian curve, so that the object is
665 ** blurred in one direction only. This may not be quite the right
666 ** curve so may change in the future. The function must be normalised.
667 */
668#if 1
669#define KernelRank 3
670 sigma *= KernelRank; /* simplify expanded curve */
671 v = kernel->width*KernelRank; /* start/end points to fit range */
672 (void) ResetMagickMemory(kernel->values,0, (size_t)
673 kernel->width*sizeof(double));
674 for ( u=0; u < v; u++) {
675 kernel->values[u/KernelRank] +=
676 exp(-((double)(u*u))/(2.0*sigma*sigma))
677 /* / (MagickSQ2PI*sigma/KernelRank) */ ;
678 }
679 for (i=0; i < kernel->width; i++)
680 kernel->range_pos += kernel->values[i];
681#else
682 for ( i=0; i < kernel->width; i++)
683 kernel->range_pos += (
684 kernel->values[i] =
685 exp(-((double)(i*i))/(2.0*sigma*sigma))
686 /* / (MagickSQ2PI*sigma) */ );
687#endif
anthonyc94cdb02010-01-06 08:15:29 +0000688 kernel->value_min = 0;
689 kernel->value_max = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000690
anthonycc6c8362010-01-25 04:14:01 +0000691 ScaleKernel(kernel, 0.0); /* Normalize Kernel Values */
anthony83ba99b2010-01-24 08:48:15 +0000692 RotateKernel(kernel, args->xi);
anthony602ab9b2010-01-05 08:06:50 +0000693 break;
694 }
695 /* Boolean Kernels */
696 case RectangleKernel:
697 case SquareKernel:
698 {
699 if ( type == SquareKernel )
700 {
701 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000702 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000703 else
704 kernel->width = kernel->height = 2*(long)args->rho+1;
705 kernel->offset_x = kernel->offset_y = (kernel->width-1)/2;
706 }
707 else {
cristy2be15382010-01-21 02:38:03 +0000708 /* NOTE: user defaults set in "AcquireKernelInfo()" */
anthony602ab9b2010-01-05 08:06:50 +0000709 if ( args->rho < 1.0 || args->sigma < 1.0 )
anthony83ba99b2010-01-24 08:48:15 +0000710 return(DestroyKernelInfo(kernel)); /* invalid args given */
anthony602ab9b2010-01-05 08:06:50 +0000711 kernel->width = (unsigned long)args->rho;
712 kernel->height = (unsigned long)args->sigma;
713 if ( args->xi < 0.0 || args->xi > (double)kernel->width ||
714 args->psi < 0.0 || args->psi > (double)kernel->height )
anthony83ba99b2010-01-24 08:48:15 +0000715 return(DestroyKernelInfo(kernel)); /* invalid args given */
anthony602ab9b2010-01-05 08:06:50 +0000716 kernel->offset_x = (unsigned long)args->xi;
717 kernel->offset_y = (unsigned long)args->psi;
718 }
719 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
720 kernel->height*sizeof(double));
721 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000722 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000723
anthonycc6c8362010-01-25 04:14:01 +0000724 /* set all kernel values to 1.0 */
anthony602ab9b2010-01-05 08:06:50 +0000725 u=kernel->width*kernel->height;
726 for ( i=0; i < (unsigned long)u; i++)
727 kernel->values[i] = 1.0;
anthonycc6c8362010-01-25 04:14:01 +0000728 kernel->value_min = kernel->value_max = 1.0; /* a flat shape */
anthonyc94cdb02010-01-06 08:15:29 +0000729 kernel->range_pos = (double) u;
anthonycc6c8362010-01-25 04:14:01 +0000730 break;
anthony602ab9b2010-01-05 08:06:50 +0000731 }
732 case DiamondKernel:
733 {
734 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000735 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000736 else
737 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
738 kernel->offset_x = kernel->offset_y = (kernel->width-1)/2;
739
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 within diamond area to 1.0 */
anthony602ab9b2010-01-05 08:06:50 +0000746 for ( i=0, v=-kernel->offset_y; v <= (long)kernel->offset_y; v++)
747 for ( u=-kernel->offset_x; u <= (long)kernel->offset_x; u++, i++)
748 if ((labs(u)+labs(v)) <= (long)kernel->offset_x)
749 kernel->range_pos += kernel->values[i] = 1.0;
750 else
751 kernel->values[i] = nan;
anthonycc6c8362010-01-25 04:14:01 +0000752 kernel->value_min = kernel->value_max = 1.0; /* a flat shape */
anthony602ab9b2010-01-05 08:06:50 +0000753 break;
754 }
755 case DiskKernel:
756 {
757 long
758 limit;
759
760 limit = (long)(args->rho*args->rho);
anthony83ba99b2010-01-24 08:48:15 +0000761 if (args->rho < 0.1) /* default radius approx 3.5 */
762 kernel->width = kernel->height = 7L, limit = 10L;
anthony602ab9b2010-01-05 08:06:50 +0000763 else
764 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
765 kernel->offset_x = kernel->offset_y = (kernel->width-1)/2;
766
767 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
768 kernel->height*sizeof(double));
769 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000770 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000771
anthonycc6c8362010-01-25 04:14:01 +0000772 /* set all kernel values within disk area to 1.0 */
anthony602ab9b2010-01-05 08:06:50 +0000773 for ( i=0, v=-kernel->offset_y; v <= (long)kernel->offset_y; v++)
774 for ( u=-kernel->offset_x; u <= (long)kernel->offset_x; u++, i++)
775 if ((u*u+v*v) <= limit)
776 kernel->range_pos += kernel->values[i] = 1.0;
777 else
778 kernel->values[i] = nan;
anthonycc6c8362010-01-25 04:14:01 +0000779 kernel->value_min = kernel->value_max = 1.0; /* a flat shape */
anthony602ab9b2010-01-05 08:06:50 +0000780 break;
781 }
782 case PlusKernel:
783 {
784 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000785 kernel->width = kernel->height = 5; /* default radius 2 */
anthony602ab9b2010-01-05 08:06:50 +0000786 else
787 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
788 kernel->offset_x = kernel->offset_y = (kernel->width-1)/2;
789
790 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
791 kernel->height*sizeof(double));
792 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000793 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000794
anthonycc6c8362010-01-25 04:14:01 +0000795 /* set all kernel values along axises to 1.0 */
anthony602ab9b2010-01-05 08:06:50 +0000796 for ( i=0, v=-kernel->offset_y; v <= (long)kernel->offset_y; v++)
797 for ( u=-kernel->offset_x; u <= (long)kernel->offset_x; u++, i++)
798 kernel->values[i] = (u == 0 || v == 0) ? 1.0 : nan;
anthonycc6c8362010-01-25 04:14:01 +0000799 kernel->value_min = kernel->value_max = 1.0; /* a flat shape */
anthony602ab9b2010-01-05 08:06:50 +0000800 kernel->range_pos = kernel->width*2.0 - 1.0;
801 break;
802 }
803 /* Distance Measuring Kernels */
804 case ChebyshevKernel:
805 {
806 double
807 scale;
808
809 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000810 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000811 else
812 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
813 kernel->offset_x = kernel->offset_y = (kernel->width-1)/2;
814
815 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
816 kernel->height*sizeof(double));
817 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000818 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000819
820 scale = (args->sigma < 1.0) ? 100.0 : args->sigma;
821 for ( i=0, v=-kernel->offset_y; v <= (long)kernel->offset_y; v++)
822 for ( u=-kernel->offset_x; u <= (long)kernel->offset_x; u++, i++)
823 kernel->range_pos += ( kernel->values[i] =
824 scale*((labs(u)>labs(v)) ? labs(u) : labs(v)) );
anthonyc94cdb02010-01-06 08:15:29 +0000825 kernel->value_max = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000826 break;
827 }
828 case ManhattenKernel:
829 {
830 double
831 scale;
832
833 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000834 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000835 else
836 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
837 kernel->offset_x = kernel->offset_y = (kernel->width-1)/2;
838
839 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
840 kernel->height*sizeof(double));
841 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000842 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000843
844 scale = (args->sigma < 1.0) ? 100.0 : args->sigma;
845 for ( i=0, v=-kernel->offset_y; v <= (long)kernel->offset_y; v++)
846 for ( u=-kernel->offset_x; u <= (long)kernel->offset_x; u++, i++)
847 kernel->range_pos += ( kernel->values[i] =
848 scale*(labs(u)+labs(v)) );
anthonyc94cdb02010-01-06 08:15:29 +0000849 kernel->value_max = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000850 break;
851 }
852 case EuclideanKernel:
853 {
854 double
855 scale;
856
857 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000858 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000859 else
860 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
861 kernel->offset_x = kernel->offset_y = (kernel->width-1)/2;
862
863 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
864 kernel->height*sizeof(double));
865 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000866 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000867
868 scale = (args->sigma < 1.0) ? 100.0 : args->sigma;
869 for ( i=0, v=-kernel->offset_y; v <= (long)kernel->offset_y; v++)
870 for ( u=-kernel->offset_x; u <= (long)kernel->offset_x; u++, i++)
871 kernel->range_pos += ( kernel->values[i] =
872 scale*sqrt((double)(u*u+v*v)) );
anthonyc94cdb02010-01-06 08:15:29 +0000873 kernel->value_max = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000874 break;
875 }
876 /* Undefined Kernels */
877 case LaplacianKernel:
878 case LOGKernel:
879 case DOGKernel:
880 assert("Kernel Type has not been defined yet");
881 /* FALL THRU */
882 default:
883 /* Generate a No-Op minimal kernel - 1x1 pixel */
884 kernel->values=(double *)AcquireQuantumMemory((size_t)1,sizeof(double));
885 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000886 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000887 kernel->width = kernel->height = 1;
888 kernel->offset_x = kernel->offset_x = 0;
889 kernel->type = UndefinedKernel;
anthonyc94cdb02010-01-06 08:15:29 +0000890 kernel->value_max =
891 kernel->range_pos =
892 kernel->values[0] = 1.0; /* a flat single-point no-op kernel! */
anthony602ab9b2010-01-05 08:06:50 +0000893 break;
894 }
895
896 return(kernel);
897}
anthonyc94cdb02010-01-06 08:15:29 +0000898
anthony602ab9b2010-01-05 08:06:50 +0000899/*
900%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
901% %
902% %
903% %
anthony83ba99b2010-01-24 08:48:15 +0000904% D e s t r o y K e r n e l I n f o %
anthony602ab9b2010-01-05 08:06:50 +0000905% %
906% %
907% %
908%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
909%
anthony83ba99b2010-01-24 08:48:15 +0000910% DestroyKernelInfo() frees the memory used by a Convolution/Morphology
911% kernel.
anthony602ab9b2010-01-05 08:06:50 +0000912%
anthony83ba99b2010-01-24 08:48:15 +0000913% The format of the DestroyKernelInfo method is:
anthony602ab9b2010-01-05 08:06:50 +0000914%
anthony83ba99b2010-01-24 08:48:15 +0000915% KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +0000916%
917% A description of each parameter follows:
918%
919% o kernel: the Morphology/Convolution kernel to be destroyed
920%
921*/
922
anthony83ba99b2010-01-24 08:48:15 +0000923MagickExport KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +0000924{
cristy2be15382010-01-21 02:38:03 +0000925 assert(kernel != (KernelInfo *) NULL);
anthony602ab9b2010-01-05 08:06:50 +0000926 kernel->values=(double *)RelinquishMagickMemory(kernel->values);
cristy2be15382010-01-21 02:38:03 +0000927 kernel=(KernelInfo *) RelinquishMagickMemory(kernel);
anthony602ab9b2010-01-05 08:06:50 +0000928 return(kernel);
929}
anthonyc94cdb02010-01-06 08:15:29 +0000930
931/*
932%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
933% %
934% %
935% %
anthony29188a82010-01-22 10:12:34 +0000936% 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 +0000937% %
938% %
939% %
940%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
941%
anthony29188a82010-01-22 10:12:34 +0000942% MorphologyImageChannel() applies a user supplied kernel to the image
943% according to the given mophology method.
anthony602ab9b2010-01-05 08:06:50 +0000944%
945% The given kernel is assumed to have been pre-scaled appropriatally, usally
946% by the kernel generator.
947%
948% The format of the MorphologyImage method is:
949%
anthony29188a82010-01-22 10:12:34 +0000950% Image *MorphologyImage(const Image *image, MorphologyMethod method,
951% const long iterations, KernelInfo *kernel, ExceptionInfo *exception)
952% Image *MorphologyImageChannel(const Image *image, const ChannelType
953% channel, MorphologyMethod method, const long iterations, KernelInfo
954% *kernel, ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +0000955%
956% A description of each parameter follows:
957%
958% o image: the image.
959%
960% o method: the morphology method to be applied.
961%
962% o iterations: apply the operation this many times (or no change).
963% A value of -1 means loop until no change found.
964% How this is applied may depend on the morphology method.
965% Typically this is a value of 1.
966%
967% o channel: the channel type.
968%
969% o kernel: An array of double representing the morphology kernel.
anthony29188a82010-01-22 10:12:34 +0000970% Warning: kernel may be normalized for the Convolve method.
anthony602ab9b2010-01-05 08:06:50 +0000971%
972% o exception: return any errors or warnings in this structure.
973%
974%
975% TODO: bias and auto-scale handling of the kernel for convolution
976% The given kernel is assumed to have been pre-scaled appropriatally, usally
977% by the kernel generator.
978%
979*/
980
anthony602ab9b2010-01-05 08:06:50 +0000981/* Internal function
982 * Apply the Morphology method with the given Kernel
anthony83ba99b2010-01-24 08:48:15 +0000983 * And return the number of pixels that changed.
anthony602ab9b2010-01-05 08:06:50 +0000984 */
985static unsigned long MorphologyApply(const Image *image, Image
986 *result_image, const MorphologyMethod method, const ChannelType channel,
cristy2be15382010-01-21 02:38:03 +0000987 const KernelInfo *kernel, ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +0000988{
cristy2be15382010-01-21 02:38:03 +0000989#define MorphologyTag "Morphology/Image"
anthony602ab9b2010-01-05 08:06:50 +0000990
991 long
anthony29188a82010-01-22 10:12:34 +0000992 progress;
anthony602ab9b2010-01-05 08:06:50 +0000993
994 unsigned long
anthony29188a82010-01-22 10:12:34 +0000995 y, offx, offy,
anthony602ab9b2010-01-05 08:06:50 +0000996 changed;
997
998 MagickBooleanType
999 status;
1000
1001 MagickPixelPacket
1002 bias;
1003
1004 CacheView
1005 *p_view,
1006 *q_view;
1007
1008 /*
1009 Apply Morphology to Image.
1010 */
1011 status=MagickTrue;
1012 changed=0;
1013 progress=0;
1014
1015 GetMagickPixelPacket(image,&bias);
1016 SetMagickPixelPacketBias(image,&bias);
anthonycc6c8362010-01-25 04:14:01 +00001017 /* Future: handle auto-bias from user, based on kernel input */
anthony602ab9b2010-01-05 08:06:50 +00001018
1019 p_view=AcquireCacheView(image);
1020 q_view=AcquireCacheView(result_image);
anthony29188a82010-01-22 10:12:34 +00001021
anthonycc6c8362010-01-25 04:14:01 +00001022 /* Some methods (including convolve) needs use a reflected kernel.
1023 * Adjust 'origin' offsets for this reflected kernel.
anthony29188a82010-01-22 10:12:34 +00001024 */
1025 offx = kernel->offset_x;
1026 offy = kernel->offset_y;
1027 switch(method) {
1028 case ErodeMorphology:
1029 case ErodeIntensityMorphology:
1030 /* kernel is not reflected */
1031 break;
1032 default:
1033 /* kernel needs to be reflected */
1034 offx = kernel->width-offx-1;
1035 offy = kernel->height-offy-1;
1036 break;
1037 }
1038
anthony602ab9b2010-01-05 08:06:50 +00001039#if defined(MAGICKCORE_OPENMP_SUPPORT)
1040 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1041#endif
anthony29188a82010-01-22 10:12:34 +00001042 for (y=0; y < image->rows; y++)
anthony602ab9b2010-01-05 08:06:50 +00001043 {
1044 MagickBooleanType
1045 sync;
1046
1047 register const PixelPacket
1048 *restrict p;
1049
1050 register const IndexPacket
1051 *restrict p_indexes;
1052
1053 register PixelPacket
1054 *restrict q;
1055
1056 register IndexPacket
1057 *restrict q_indexes;
1058
anthony29188a82010-01-22 10:12:34 +00001059 register unsigned long
anthony602ab9b2010-01-05 08:06:50 +00001060 x;
1061
anthony29188a82010-01-22 10:12:34 +00001062 unsigned long
anthony602ab9b2010-01-05 08:06:50 +00001063 r;
1064
1065 if (status == MagickFalse)
1066 continue;
anthony29188a82010-01-22 10:12:34 +00001067 p=GetCacheViewVirtualPixels(p_view, -offx, y-offy,
1068 image->columns+kernel->width, kernel->height, exception);
anthony602ab9b2010-01-05 08:06:50 +00001069 q=GetCacheViewAuthenticPixels(q_view,0,y,result_image->columns,1,
1070 exception);
1071 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1072 {
1073 status=MagickFalse;
1074 continue;
1075 }
1076 p_indexes=GetCacheViewVirtualIndexQueue(p_view);
1077 q_indexes=GetCacheViewAuthenticIndexQueue(q_view);
anthony29188a82010-01-22 10:12:34 +00001078 r = (image->columns+kernel->width)*offy+offx; /* constant */
1079
1080 for (x=0; x < image->columns; x++)
anthony602ab9b2010-01-05 08:06:50 +00001081 {
anthony29188a82010-01-22 10:12:34 +00001082 unsigned long
anthony602ab9b2010-01-05 08:06:50 +00001083 v;
1084
anthony29188a82010-01-22 10:12:34 +00001085 register unsigned long
anthony602ab9b2010-01-05 08:06:50 +00001086 u;
1087
1088 register const double
1089 *restrict k;
1090
1091 register const PixelPacket
1092 *restrict k_pixels;
1093
1094 register const IndexPacket
1095 *restrict k_indexes;
1096
1097 MagickPixelPacket
1098 result;
1099
anthony29188a82010-01-22 10:12:34 +00001100 /* Copy input to ouput image for unused channels
anthony83ba99b2010-01-24 08:48:15 +00001101 * This removes need for 'cloning' a new image every iteration
anthony29188a82010-01-22 10:12:34 +00001102 */
anthony602ab9b2010-01-05 08:06:50 +00001103 *q = p[r];
1104 if (image->colorspace == CMYKColorspace)
1105 q_indexes[x] = p_indexes[r];
1106
anthony29188a82010-01-22 10:12:34 +00001107 result.index=0; /* stop compiler warnings */
anthony602ab9b2010-01-05 08:06:50 +00001108 switch (method) {
1109 case ConvolveMorphology:
1110 result=bias;
1111 break; /* default result is the convolution bias */
anthony83ba99b2010-01-24 08:48:15 +00001112#if 1
1113 case DilateMorphology:
anthony29188a82010-01-22 10:12:34 +00001114 result.red =
1115 result.green =
1116 result.blue =
1117 result.opacity =
1118 result.index = -MagickHuge;
1119 break;
1120 case ErodeMorphology:
1121 result.red =
1122 result.green =
1123 result.blue =
1124 result.opacity =
1125 result.index = +MagickHuge;
1126 break;
1127#endif
anthony602ab9b2010-01-05 08:06:50 +00001128 default:
anthony29188a82010-01-22 10:12:34 +00001129 /* Otherwise just start with the original pixel value */
anthony602ab9b2010-01-05 08:06:50 +00001130 result.red = p[r].red;
1131 result.green = p[r].green;
1132 result.blue = p[r].blue;
1133 result.opacity = QuantumRange - p[r].opacity;
1134 if ( image->colorspace == CMYKColorspace)
1135 result.index = p_indexes[r];
1136 break;
1137 }
1138
1139 switch ( method ) {
1140 case ConvolveMorphology:
anthony29188a82010-01-22 10:12:34 +00001141 /* Weighted Average of pixels
1142 *
1143 * NOTE for correct working of this operation for asymetrical
1144 * kernels, the kernel needs to be applied in its reflected form.
1145 * That is its values needs to be reversed.
1146 */
anthony602ab9b2010-01-05 08:06:50 +00001147 if (((channel & OpacityChannel) == 0) ||
1148 (image->matte == MagickFalse))
1149 {
anthony29188a82010-01-22 10:12:34 +00001150 /* Convolution (no transparency) */
1151 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001152 k_pixels = p;
1153 k_indexes = p_indexes;
anthony29188a82010-01-22 10:12:34 +00001154 for (v=0; v < kernel->height; v++) {
1155 for (u=0; u < kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001156 if ( IsNan(*k) ) continue;
1157 result.red += (*k)*k_pixels[u].red;
1158 result.green += (*k)*k_pixels[u].green;
1159 result.blue += (*k)*k_pixels[u].blue;
anthony83ba99b2010-01-24 08:48:15 +00001160 /* result.opacity += not involved here */
anthony602ab9b2010-01-05 08:06:50 +00001161 if ( image->colorspace == CMYKColorspace)
1162 result.index += (*k)*k_indexes[u];
1163 }
1164 k_pixels += image->columns+kernel->width;
1165 k_indexes += image->columns+kernel->width;
1166 }
anthony602ab9b2010-01-05 08:06:50 +00001167 }
1168 else
1169 { /* Kernel & Alpha weighted Convolution */
1170 MagickRealType
1171 alpha, /* alpha value * kernel weighting */
1172 gamma; /* weighting divisor */
1173
1174 gamma=0.0;
anthony29188a82010-01-22 10:12:34 +00001175 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001176 k_pixels = p;
1177 k_indexes = p_indexes;
anthony29188a82010-01-22 10:12:34 +00001178 for (v=0; v < kernel->height; v++) {
1179 for (u=0; u < kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001180 if ( IsNan(*k) ) continue;
1181 alpha=(*k)*(QuantumScale*(QuantumRange-
1182 k_pixels[u].opacity));
1183 gamma += alpha;
1184 result.red += alpha*k_pixels[u].red;
1185 result.green += alpha*k_pixels[u].green;
1186 result.blue += alpha*k_pixels[u].blue;
anthony83ba99b2010-01-24 08:48:15 +00001187 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00001188 if ( image->colorspace == CMYKColorspace)
1189 result.index += alpha*k_indexes[u];
1190 }
1191 k_pixels += image->columns+kernel->width;
1192 k_indexes += image->columns+kernel->width;
1193 }
1194 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
anthony83ba99b2010-01-24 08:48:15 +00001195 result.red *= gamma;
1196 result.green *= gamma;
1197 result.blue *= gamma;
1198 result.opacity *= gamma;
1199 result.index *= gamma;
anthony602ab9b2010-01-05 08:06:50 +00001200 }
1201 break;
1202
anthony83ba99b2010-01-24 08:48:15 +00001203 case DilateMorphology:
anthony29188a82010-01-22 10:12:34 +00001204 /* Maximize Value within kernel shape
1205 *
1206 * NOTE for correct working of this operation for asymetrical
1207 * kernels, the kernel needs to be applied in its reflected form.
1208 * That is its values needs to be reversed.
1209 *
1210 * NOTE: in normal Greyscale Morphology, the kernel value should
1211 * be added to the real value, this is currently not done, due to
1212 * the nature of the boolean kernels being used.
1213 *
1214 */
1215 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001216 k_pixels = p;
1217 k_indexes = p_indexes;
anthony29188a82010-01-22 10:12:34 +00001218 for (v=0; v < kernel->height; v++) {
1219 for (u=0; u < kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001220 if ( IsNan(*k) || (*k) < 0.5 ) continue;
1221 Maximize(result.red, k_pixels[u].red);
1222 Maximize(result.green, k_pixels[u].green);
1223 Maximize(result.blue, k_pixels[u].blue);
1224 Maximize(result.opacity, QuantumRange-k_pixels[u].opacity);
1225 if ( image->colorspace == CMYKColorspace)
1226 Maximize(result.index, k_indexes[u]);
1227 }
1228 k_pixels += image->columns+kernel->width;
1229 k_indexes += image->columns+kernel->width;
1230 }
anthony602ab9b2010-01-05 08:06:50 +00001231 break;
1232
1233 case ErodeMorphology:
anthony29188a82010-01-22 10:12:34 +00001234 /* Minimize Value within kernel shape
1235 *
1236 * NOTE that the kernel is not reflected for this operation!
1237 *
1238 * NOTE: in normal Greyscale Morphology, the kernel value should
1239 * be added to the real value, this is currently not done, due to
1240 * the nature of the boolean kernels being used.
1241 */
anthony602ab9b2010-01-05 08:06:50 +00001242 k = kernel->values;
1243 k_pixels = p;
1244 k_indexes = p_indexes;
anthony29188a82010-01-22 10:12:34 +00001245 for (v=0; v < kernel->height; v++) {
1246 for (u=0; u < kernel->width; u++, k++) {
anthony602ab9b2010-01-05 08:06:50 +00001247 if ( IsNan(*k) || (*k) < 0.5 ) continue;
1248 Minimize(result.red, k_pixels[u].red);
1249 Minimize(result.green, k_pixels[u].green);
1250 Minimize(result.blue, k_pixels[u].blue);
1251 Minimize(result.opacity, QuantumRange-k_pixels[u].opacity);
1252 if ( image->colorspace == CMYKColorspace)
1253 Minimize(result.index, k_indexes[u]);
1254 }
1255 k_pixels += image->columns+kernel->width;
1256 k_indexes += image->columns+kernel->width;
1257 }
anthony602ab9b2010-01-05 08:06:50 +00001258 break;
1259
anthony83ba99b2010-01-24 08:48:15 +00001260 case DilateIntensityMorphology:
anthony29188a82010-01-22 10:12:34 +00001261 /* Select pixel with maximum intensity within kernel shape
1262 *
1263 * WARNING: the intensity test fails for CMYK and does not
1264 * take into account the moderating effect of teh alpha channel
1265 * on the intensity.
1266 *
1267 * NOTE for correct working of this operation for asymetrical
1268 * kernels, the kernel needs to be applied in its reflected form.
1269 * That is its values needs to be reversed.
1270 */
1271 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001272 k_pixels = p;
1273 k_indexes = p_indexes;
anthony29188a82010-01-22 10:12:34 +00001274 for (v=0; v < kernel->height; v++) {
1275 for (u=0; u < kernel->width; u++, k--) {
1276 if ( IsNan(*k) || (*k) < 0.5 ) continue; /* boolean kernel */
1277 if ( result.red == 0.0 ||
1278 PixelIntensity(&(k_pixels[u])) > PixelIntensity(q) ) {
1279 /* copy the whole pixel - no channel selection */
1280 *q = k_pixels[u];
1281 if ( result.red > 0.0 ) changed++;
1282 result.red = 1.0;
1283 }
anthony602ab9b2010-01-05 08:06:50 +00001284 }
1285 k_pixels += image->columns+kernel->width;
1286 k_indexes += image->columns+kernel->width;
1287 }
anthony602ab9b2010-01-05 08:06:50 +00001288 break;
1289
1290 case ErodeIntensityMorphology:
anthony29188a82010-01-22 10:12:34 +00001291 /* Select pixel with mimimum intensity within kernel shape
1292 *
1293 * WARNING: the intensity test fails for CMYK and does not
1294 * take into account the moderating effect of teh alpha channel
1295 * on the intensity.
1296 *
1297 * NOTE that the kernel is not reflected for this operation!
1298 */
anthony602ab9b2010-01-05 08:06:50 +00001299 k = kernel->values;
1300 k_pixels = p;
1301 k_indexes = p_indexes;
anthony29188a82010-01-22 10:12:34 +00001302 for (v=0; v < kernel->height; v++) {
1303 for (u=0; u < kernel->width; u++, k++) {
anthony602ab9b2010-01-05 08:06:50 +00001304 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony29188a82010-01-22 10:12:34 +00001305 if ( result.red == 0.0 ||
1306 PixelIntensity(&(k_pixels[u])) < PixelIntensity(q) ) {
1307 /* copy the whole pixel - no channel selection */
1308 *q = k_pixels[u];
1309 if ( result.red > 0.0 ) changed++;
1310 result.red = 1.0;
1311 }
anthony602ab9b2010-01-05 08:06:50 +00001312 }
1313 k_pixels += image->columns+kernel->width;
1314 k_indexes += image->columns+kernel->width;
1315 }
anthony602ab9b2010-01-05 08:06:50 +00001316 break;
1317
1318 case DistanceMorphology:
anthony29188a82010-01-22 10:12:34 +00001319 /* Add kernel value and select the minimum value found.
1320 * The result is a iterative distance from edge function.
1321 *
1322 * All Distance Kernels are symetrical, but that may not always
1323 * be the case. For example how about a distance from left edges?
1324 * To make it work correctly for asymetrical kernels the reflected
1325 * kernel needs to be applied.
1326 */
anthony602ab9b2010-01-05 08:06:50 +00001327#if 0
anthony83ba99b2010-01-24 08:48:15 +00001328 /* No need to do distance morphology if original value is zero
1329 * Unfortunatally I have not been able to get this right
1330 * when channel selection also becomes involved. -- Arrgghhh
1331 */
anthony602ab9b2010-01-05 08:06:50 +00001332 if ( ((channel & RedChannel) == 0 && p[r].red == 0)
1333 || ((channel & GreenChannel) == 0 && p[r].green == 0)
1334 || ((channel & BlueChannel) == 0 && p[r].blue == 0)
1335 || ((channel & OpacityChannel) == 0 && p[r].opacity == 0)
1336 || (( (channel & IndexChannel) == 0
1337 || image->colorspace != CMYKColorspace
1338 ) && p_indexes[x] ==0 )
1339 )
1340 break;
1341#endif
anthony29188a82010-01-22 10:12:34 +00001342 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001343 k_pixels = p;
1344 k_indexes = p_indexes;
anthony29188a82010-01-22 10:12:34 +00001345 for (v=0; v < kernel->height; v++) {
1346 for (u=0; u < kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001347 if ( IsNan(*k) ) continue;
1348 Minimize(result.red, (*k)+k_pixels[u].red);
1349 Minimize(result.green, (*k)+k_pixels[u].green);
1350 Minimize(result.blue, (*k)+k_pixels[u].blue);
1351 Minimize(result.opacity, (*k)+QuantumRange-k_pixels[u].opacity);
1352 if ( image->colorspace == CMYKColorspace)
1353 Minimize(result.index, (*k)+k_indexes[u]);
1354 }
1355 k_pixels += image->columns+kernel->width;
1356 k_indexes += image->columns+kernel->width;
1357 }
anthony602ab9b2010-01-05 08:06:50 +00001358 break;
1359
1360 case UndefinedMorphology:
1361 default:
1362 break; /* Do nothing */
anthony83ba99b2010-01-24 08:48:15 +00001363 }
1364 switch ( method ) {
1365 case UndefinedMorphology:
1366 case DilateIntensityMorphology:
1367 case ErodeIntensityMorphology:
1368 break; /* full pixel was directly assigned */
1369 default:
1370 /* Assign the results */
1371 if ((channel & RedChannel) != 0)
1372 q->red = ClampToQuantum(result.red);
1373 if ((channel & GreenChannel) != 0)
1374 q->green = ClampToQuantum(result.green);
1375 if ((channel & BlueChannel) != 0)
1376 q->blue = ClampToQuantum(result.blue);
1377 if ((channel & OpacityChannel) != 0
1378 && image->matte == MagickTrue )
1379 q->opacity = ClampToQuantum(QuantumRange-result.opacity);
1380 if ((channel & IndexChannel) != 0
1381 && image->colorspace == CMYKColorspace)
1382 q_indexes[x] = ClampToQuantum(result.index);
1383 break;
1384 }
1385 if ( ( p[r].red != q->red )
1386 || ( p[r].green != q->green )
1387 || ( p[r].blue != q->blue )
1388 || ( p[r].opacity != q->opacity )
1389 || ( image->colorspace == CMYKColorspace &&
1390 p_indexes[r] != q_indexes[x] ) )
1391 changed++; /* The pixel had some value changed! */
anthony602ab9b2010-01-05 08:06:50 +00001392 p++;
1393 q++;
anthony83ba99b2010-01-24 08:48:15 +00001394 } /* x */
anthony602ab9b2010-01-05 08:06:50 +00001395 sync=SyncCacheViewAuthenticPixels(q_view,exception);
1396 if (sync == MagickFalse)
1397 status=MagickFalse;
1398 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1399 {
1400 MagickBooleanType
1401 proceed;
1402
1403#if defined(MAGICKCORE_OPENMP_SUPPORT)
1404 #pragma omp critical (MagickCore_MorphologyImage)
1405#endif
1406 proceed=SetImageProgress(image,MorphologyTag,progress++,image->rows);
1407 if (proceed == MagickFalse)
1408 status=MagickFalse;
1409 }
anthony83ba99b2010-01-24 08:48:15 +00001410 } /* y */
anthony602ab9b2010-01-05 08:06:50 +00001411 result_image->type=image->type;
1412 q_view=DestroyCacheView(q_view);
1413 p_view=DestroyCacheView(p_view);
1414 return(status ? changed : 0);
1415}
1416
cristy2be15382010-01-21 02:38:03 +00001417MagickExport Image *MorphologyImage(const Image *image,MorphologyMethod method,
1418 const long iterations,KernelInfo *kernel, ExceptionInfo *exception)
1419{
1420 Image
1421 *morphology_image;
1422
1423 morphology_image=MorphologyImageChannel(image,DefaultChannels,method,
1424 iterations,kernel,exception);
1425 return(morphology_image);
1426}
1427
1428MagickExport Image *MorphologyImageChannel(const Image *image,
cristy672da672010-01-10 15:43:07 +00001429 const ChannelType channel, MorphologyMethod method, const long iterations,
cristy2be15382010-01-21 02:38:03 +00001430 KernelInfo *kernel, ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001431{
1432 unsigned long
1433 count,
1434 limit,
1435 changed;
1436
1437 Image
1438 *new_image,
1439 *old_image;
1440
anthonycc6c8362010-01-25 04:14:01 +00001441 const char
1442 *artifact;
1443
anthony602ab9b2010-01-05 08:06:50 +00001444 assert(image != (Image *) NULL);
1445 assert(image->signature == MagickSignature);
1446 assert(exception != (ExceptionInfo *) NULL);
1447 assert(exception->signature == MagickSignature);
1448
1449 if ( GetImageArtifact(image,"showkernel") != (const char *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001450 ShowKernel(kernel); /* request to display the kernel to stderr */
anthony602ab9b2010-01-05 08:06:50 +00001451
1452 if ( iterations == 0 )
1453 return((Image *)NULL); /* null operation - nothing to do! */
1454
1455 /* kernel must be valid at this point
1456 * (except maybe for posible future morphology methods like "Prune"
1457 */
cristy2be15382010-01-21 02:38:03 +00001458 assert(kernel != (KernelInfo *)NULL);
anthony602ab9b2010-01-05 08:06:50 +00001459
1460 count = 0;
anthony29188a82010-01-22 10:12:34 +00001461 changed = 1;
anthony602ab9b2010-01-05 08:06:50 +00001462 limit = iterations;
1463 if ( iterations < 0 )
1464 limit = image->columns > image->rows ? image->columns : image->rows;
1465
1466 /* Special morphology cases */
1467 switch( method ) {
1468 case CloseMorphology:
anthony83ba99b2010-01-24 08:48:15 +00001469 new_image = MorphologyImageChannel(image, channel, DilateMorphology,
anthony29188a82010-01-22 10:12:34 +00001470 iterations, kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001471 if (new_image == (Image *) NULL)
1472 return((Image *) NULL);
1473 method = ErodeMorphology;
1474 break;
1475 case OpenMorphology:
anthony29188a82010-01-22 10:12:34 +00001476 new_image = MorphologyImageChannel(image, channel, ErodeMorphology,
1477 iterations, kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001478 if (new_image == (Image *) NULL)
1479 return((Image *) NULL);
anthony83ba99b2010-01-24 08:48:15 +00001480 method = DilateMorphology;
anthony602ab9b2010-01-05 08:06:50 +00001481 break;
1482 case CloseIntensityMorphology:
anthony83ba99b2010-01-24 08:48:15 +00001483 new_image = MorphologyImageChannel(image, channel, DilateIntensityMorphology,
anthony29188a82010-01-22 10:12:34 +00001484 iterations, kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001485 if (new_image == (Image *) NULL)
1486 return((Image *) NULL);
1487 method = ErodeIntensityMorphology;
1488 break;
1489 case OpenIntensityMorphology:
anthony29188a82010-01-22 10:12:34 +00001490 new_image = MorphologyImageChannel(image, channel, ErodeIntensityMorphology,
1491 iterations, kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001492 if (new_image == (Image *) NULL)
1493 return((Image *) NULL);
anthony83ba99b2010-01-24 08:48:15 +00001494 method = DilateIntensityMorphology;
anthony602ab9b2010-01-05 08:06:50 +00001495 break;
1496
anthonyc94cdb02010-01-06 08:15:29 +00001497 case ConvolveMorphology:
anthonycc6c8362010-01-25 04:14:01 +00001498 /* Scale or Normalize kernel according to user wishes
1499 ** WARNING: this directly modifies the kernel
1500 ** which probably should not be done.
1501 */
1502 artifact = GetImageArtifact(image,"convolve:scale");
1503 if ( artifact != (char *)NULL )
1504 ScaleKernel(kernel, StringToDouble(artifact) );
anthonyc94cdb02010-01-06 08:15:29 +00001505 /* FALL-THRU */
anthony602ab9b2010-01-05 08:06:50 +00001506 default:
anthonycc6c8362010-01-25 04:14:01 +00001507 /* Do a morphology iteration just once at this point!
anthony602ab9b2010-01-05 08:06:50 +00001508 This ensures a new_image has been generated, but allows us
anthonyc94cdb02010-01-06 08:15:29 +00001509 to skip the creation of 'old_image' if it isn't needed.
anthony602ab9b2010-01-05 08:06:50 +00001510 */
1511 new_image=CloneImage(image,0,0,MagickTrue,exception);
1512 if (new_image == (Image *) NULL)
1513 return((Image *) NULL);
1514 if (SetImageStorageClass(new_image,DirectClass) == MagickFalse)
1515 {
1516 InheritException(exception,&new_image->exception);
1517 new_image=DestroyImage(new_image);
1518 return((Image *) NULL);
1519 }
1520 changed = MorphologyApply(image,new_image,method,channel,kernel,
1521 exception);
1522 count++;
1523 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
1524 fprintf(stderr, "Morphology %s:%lu => Changed %lu\n",
1525 MagickOptionToMnemonic(MagickMorphologyOptions, method),
1526 count, changed);
1527 }
1528
anthonycc6c8362010-01-25 04:14:01 +00001529 /* Repeat an interative morphology until count or no change reached */
anthony602ab9b2010-01-05 08:06:50 +00001530 if ( count < limit && changed > 0 ) {
1531 old_image = CloneImage(new_image,0,0,MagickTrue,exception);
1532 if (old_image == (Image *) NULL)
1533 return(DestroyImage(new_image));
1534 if (SetImageStorageClass(old_image,DirectClass) == MagickFalse)
1535 {
1536 InheritException(exception,&old_image->exception);
1537 old_image=DestroyImage(old_image);
1538 return(DestroyImage(new_image));
1539 }
1540 while( count < limit && changed != 0 )
1541 {
1542 Image *tmp = old_image;
1543 old_image = new_image;
1544 new_image = tmp;
1545 changed = MorphologyApply(old_image,new_image,method,channel,kernel,
1546 exception);
1547 count++;
1548 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
1549 fprintf(stderr, "Morphology %s:%lu => Changed %lu\n",
1550 MagickOptionToMnemonic(MagickMorphologyOptions, method),
1551 count, changed);
1552 }
1553 DestroyImage(old_image);
1554 }
1555
1556 return(new_image);
1557}
anthony83ba99b2010-01-24 08:48:15 +00001558
1559/*
1560%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1561% %
1562% %
1563% %
anthony83ba99b2010-01-24 08:48:15 +00001564% R o t a t e K e r n e l %
1565% %
1566% %
1567% %
1568%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1569%
1570% RotateKernel() rotates the kernel by the angle given. Currently it is
1571% restricted to 90 degree angles, but this may be improved in the future.
1572%
1573% The format of the RotateKernel method is:
1574%
1575% void RotateKernel(KernelInfo *kernel, double angle)
1576%
1577% A description of each parameter follows:
1578%
1579% o kernel: the Morphology/Convolution kernel
1580%
1581% o angle: angle to rotate in degrees
1582%
1583*/
1584static void RotateKernel(KernelInfo *kernel, double angle)
1585{
1586 /* WARNING: Currently assumes the kernel (rightly) is horizontally symetrical
1587 **
1588 ** TODO: expand beyond simple 90 degree rotates, flips and flops
1589 */
1590
1591 /* Modulus the angle */
1592 angle = fmod(angle, 360.0);
1593 if ( angle < 0 )
1594 angle += 360.0;
1595
1596 if ( 315.0 < angle || angle <= 45.0 )
1597 return; /* no change! - At least at this time */
1598
1599 switch (kernel->type) {
1600 /* These built-in kernels are cylindrical kernels, rotating is useless */
1601 case GaussianKernel:
1602 case LaplacianKernel:
1603 case LOGKernel:
1604 case DOGKernel:
1605 case DiskKernel:
1606 case ChebyshevKernel:
1607 case ManhattenKernel:
1608 case EuclideanKernel:
1609 return;
1610
1611 /* These may be rotatable at non-90 angles in the future */
1612 /* but simply rotating them in multiples of 90 degrees is useless */
1613 case SquareKernel:
1614 case DiamondKernel:
1615 case PlusKernel:
1616 return;
1617
1618 /* These only allows a +/-90 degree rotation (by transpose) */
1619 /* A 180 degree rotation is useless */
1620 case BlurKernel:
1621 case RectangleKernel:
1622 if ( 135.0 < angle && angle <= 225.0 )
1623 return;
1624 if ( 225.0 < angle && angle <= 315.0 )
1625 angle -= 180;
1626 break;
1627
1628 /* these are freely rotatable in 90 degree units */
1629 case CometKernel:
1630 case UndefinedKernel:
1631 case UserDefinedKernel:
1632 break;
1633 }
1634 if ( 135.0 < angle && angle <= 225.0 )
1635 {
1636 /* For a 180 degree rotation - also know as a reflection */
1637 /* This is actually a very very common operation! */
1638 /* Basically all that is needed is a reversal of the kernel data! */
1639 unsigned long
1640 i,j;
1641 register double
1642 *k,t;
1643
1644 k=kernel->values;
1645 for ( i=0, j=kernel->width*kernel->height-1; i<j; i++, j--)
1646 t=k[i], k[i]=k[j], k[j]=t;
1647
1648 kernel->offset_x = kernel->width - kernel->offset_x - 1;
1649 kernel->offset_y = kernel->width - kernel->offset_y - 1;
1650 angle = fmod(angle+180.0, 360.0);
1651 }
1652 if ( 45.0 < angle && angle <= 135.0 )
1653 { /* Do a transpose and a flop, of the image, which results in a 90
1654 * degree rotation using two mirror operations.
1655 *
1656 * WARNING: this assumes the original image was a 1 dimentional image
1657 * but currently that is the only built-ins it is applied to.
1658 */
1659 unsigned long
1660 t;
1661 t = kernel->width;
1662 kernel->width = kernel->height;
1663 kernel->height = t;
1664 t = kernel->offset_x;
1665 kernel->offset_x = kernel->offset_y;
1666 kernel->offset_y = t;
1667 angle = fmod(450.0 - angle, 360.0);
1668 }
1669 /* At this point angle should be between -45 (315) and +45 degrees
1670 * In the future some form of non-orthogonal angled rotates could be
1671 * performed here, posibily with a linear kernel restriction.
1672 */
1673
1674#if 0
1675 Not currently in use!
1676 { /* Do a flop, this assumes kernel is horizontally symetrical.
1677 * Each row of the kernel needs to be reversed!
1678 */
1679 unsigned long
1680 y;
1681 register unsigned long
1682 x,r;
1683 register double
1684 *k,t;
1685
1686 for ( y=0, k=kernel->values; y < kernel->height; y++, k+=kernel->width)
1687 for ( x=0, r=kernel->width-1; x<kernel->width/2; x++, r--)
1688 t=k[x], k[x]=k[r], k[r]=t;
1689
1690 kernel->offset_x = kernel->width - kernel->offset_x - 1;
1691 angle = fmod(angle+180.0, 360.0);
1692 }
1693#endif
1694 return;
1695}
1696
1697/*
1698%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1699% %
1700% %
1701% %
anthonycc6c8362010-01-25 04:14:01 +00001702+ S c a l e K e r n e l %
1703% %
1704% %
1705% %
1706%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1707%
1708% ScaleKernel() scales the kernel by the given amount. Scaling by value of
1709% zero will result in a normalization of the kernel.
1710%
1711% For positive kernels normalization scales the kernel so the addition os all
1712% values is 1.0. While for kernels where values add to zero it is scaled
1713% so that the convolution output range covers 1.0. In such 'zero kernels'
1714% it is generally recomended that the user also provides a 50% bias to the
1715% output results.
1716%
1717% Correct normalization assumes the 'range_*' attributes of the kernel
1718% structure have been correctly set during the kernel creation.
1719%
1720% The format of the ScaleKernel method is:
1721%
1722% void ScaleKernel(KernelInfo *kernel)
1723%
1724% A description of each parameter follows:
1725%
1726% o kernel: the Morphology/Convolution kernel
1727%
1728% o scale: multiple all values by this, if zero normalize instead.
1729%
1730*/
1731static void ScaleKernel(KernelInfo *kernel, double scale)
1732{
1733 register unsigned long
1734 i;
1735
1736 if ( fabs(scale) < MagickEpsilon ) {
1737 if ( fabs(kernel->range_pos + kernel->range_neg) < MagickEpsilon )
1738 scale = 1/(kernel->range_pos - kernel->range_neg); /* zero kernels */
1739 else
1740 scale = 1/(kernel->range_pos + kernel->range_neg); /* non-zero kernel */
1741 }
1742
1743 for (i=0; i < kernel->width*kernel->height; i++)
1744 if ( ! IsNan(kernel->values[i]) )
1745 kernel->values[i] *= scale;
1746
1747 kernel->range_pos *= scale; /* convolution output range */
1748 kernel->range_neg *= scale;
1749 kernel->value_max *= scale; /* maximum and minimum values in kernel */
1750 kernel->value_min *= scale;
1751
1752 return;
1753}
1754
1755/*
1756%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1757% %
1758% %
1759% %
1760+ S h o w K e r n e l %
anthony83ba99b2010-01-24 08:48:15 +00001761% %
1762% %
1763% %
1764%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1765%
1766% ShowKernel() Output the details of the given kernel defination to
1767% standard error, as per a users 'showkernel' option request.
1768%
1769% The format of the ShowKernel method is:
1770%
anthonycc6c8362010-01-25 04:14:01 +00001771% void ShowKernel(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00001772%
1773% A description of each parameter follows:
1774%
1775% o kernel: the Morphology/Convolution kernel
1776%
1777% FUTURE: return the information in a string for API usage.
1778*/
1779static void ShowKernel(KernelInfo *kernel)
1780{
1781 unsigned long
1782 i, u, v;
1783
1784 fprintf(stderr,
anthonycc6c8362010-01-25 04:14:01 +00001785 "Kernel \"%s\" of size %lux%lu%+ld%+ld with values from %.*lg to %.*lg\n",
anthony83ba99b2010-01-24 08:48:15 +00001786 MagickOptionToMnemonic(MagickKernelOptions, kernel->type),
1787 kernel->width, kernel->height,
1788 kernel->offset_x, kernel->offset_y,
anthonycc6c8362010-01-25 04:14:01 +00001789 GetMagickPrecision(), kernel->value_min,
1790 GetMagickPrecision(), kernel->value_max);
1791 fprintf(stderr, "Forming convolution output range from %.*lg to %.*lg%s\n",
1792 GetMagickPrecision(), kernel->range_neg,
1793 GetMagickPrecision(), kernel->range_pos,
1794 /*kernel->normalized == MagickTrue ? " (normalized)" : */ "" );
anthony83ba99b2010-01-24 08:48:15 +00001795 for (i=v=0; v < kernel->height; v++) {
1796 fprintf(stderr,"%2ld:",v);
1797 for (u=0; u < kernel->width; u++, i++)
1798 if ( IsNan(kernel->values[i]) )
1799 fprintf(stderr," %*s", GetMagickPrecision()+2, "nan");
1800 else
1801 fprintf(stderr," %*.*lg", GetMagickPrecision()+2,
1802 GetMagickPrecision(), kernel->values[i]);
1803 fprintf(stderr,"\n");
1804 }
1805}
anthonycc6c8362010-01-25 04:14:01 +00001806
1807/*
1808%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1809% %
1810% %
1811% %
1812+ Z e r o K e r n e l N a n s %
1813% %
1814% %
1815% %
1816%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1817%
1818% ZeroKernelNans() replaces any special 'nan' value that may be present in
1819% the kernel with a zero value. This is typically done when the kernel will
1820% be used in special hardware (GPU) convolution processors, to simply
1821% matters.
1822%
1823% The format of the ZeroKernelNans method is:
1824%
1825% voidZeroKernelNans (KernelInfo *kernel)
1826%
1827% A description of each parameter follows:
1828%
1829% o kernel: the Morphology/Convolution kernel
1830%
1831% FUTURE: return the information in a string for API usage.
1832*/
1833static void ZeroKernelNans(KernelInfo *kernel)
1834{
1835 register unsigned long
1836 i;
1837
1838 for (i=0; i < kernel->width*kernel->height; i++)
1839 if ( IsNan(kernel->values[i]) )
1840 kernel->values[i] = 0.0;
1841
1842 return;
1843}