blob: 02118f7b674a8af5f964478412c512c3493ed694 [file] [log] [blame]
cristy701db312009-11-20 03:14:08 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% M M OOO RRRR PPPP H H OOO L OOO GGGG Y Y %
7% MM MM O O R R P P H H O O L O O G Y Y %
8% M M M O O RRRR PPPP HHHHH O O L O O G GGG Y %
9% M M O O R R P H H O O L O O G G Y %
10% M M OOO R R P H H OOO LLLLL OOO GGG Y %
11% %
12% %
13% MagickCore Morphology Methods %
14% %
15% Software Design %
16% Anthony Thyssen %
anthonyc94cdb02010-01-06 08:15:29 +000017% January 2010 %
cristy701db312009-11-20 03:14:08 +000018% %
19% %
cristy16af1cb2009-12-11 21:38:29 +000020% Copyright 1999-2010 ImageMagick Studio LLC, a non-profit organization %
cristy701db312009-11-20 03:14:08 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
anthony602ab9b2010-01-05 08:06:50 +000036% Morpology is the the application of various kernals, of any size and even
37% shape, to a image in various ways (typically binary, but not always).
cristy701db312009-11-20 03:14:08 +000038%
anthony602ab9b2010-01-05 08:06:50 +000039% Convolution (weighted sum or average) is just one specific type of
40% morphology. Just one that is very common for image bluring and sharpening
41% effects. Not only 2D Gaussian blurring, but also 2-pass 1D Blurring.
42%
43% This module provides not only a general morphology function, and the ability
44% to apply more advanced or iterative morphologies, but also functions for the
45% generation of many different types of kernel arrays from user supplied
46% arguments. Prehaps even the generation of a kernel from a small image.
cristy701db312009-11-20 03:14:08 +000047*/
48
49/*
50 Include declarations.
51*/
52#include "magick/studio.h"
anthony602ab9b2010-01-05 08:06:50 +000053#include "magick/artifact.h"
cristy701db312009-11-20 03:14:08 +000054#include "magick/cache-view.h"
55#include "magick/color-private.h"
56#include "magick/enhance.h"
57#include "magick/exception.h"
58#include "magick/exception-private.h"
anthony602ab9b2010-01-05 08:06:50 +000059#include "magick/gem.h"
cristy701db312009-11-20 03:14:08 +000060#include "magick/hashmap.h"
61#include "magick/image.h"
cristybba804b2010-01-05 15:39:59 +000062#include "magick/image-private.h"
cristy701db312009-11-20 03:14:08 +000063#include "magick/list.h"
anthony29188a82010-01-22 10:12:34 +000064#include "magick/magick.h"
cristy701db312009-11-20 03:14:08 +000065#include "magick/memory_.h"
66#include "magick/monitor-private.h"
67#include "magick/morphology.h"
anthony602ab9b2010-01-05 08:06:50 +000068#include "magick/option.h"
cristy701db312009-11-20 03:14:08 +000069#include "magick/pixel-private.h"
70#include "magick/prepress.h"
71#include "magick/quantize.h"
72#include "magick/registry.h"
73#include "magick/semaphore.h"
74#include "magick/splay-tree.h"
75#include "magick/statistic.h"
76#include "magick/string_.h"
anthony602ab9b2010-01-05 08:06:50 +000077#include "magick/string-private.h"
78#include "magick/token.h"
cristya29d45f2010-03-05 21:14:54 +000079
anthony602ab9b2010-01-05 08:06:50 +000080/*
cristya29d45f2010-03-05 21:14:54 +000081 The following test is for special floating point numbers of value NaN (not
82 a number), that may be used within a Kernel Definition. NaN's are defined
83 as part of the IEEE standard for floating point number representation.
84
85 These are used a Kernel value of NaN means that that kernal position is not
86 part of the normal convolution or morphology process, and thus allowing the
87 use of 'shaped' kernels.
88
89 Special properities two NaN's are never equal, even if they are from the
90 same variable That is the IsNaN() macro is only true if the value is NaN.
91*/
anthony602ab9b2010-01-05 08:06:50 +000092#define IsNan(a) ((a)!=(a))
93
anthony29188a82010-01-22 10:12:34 +000094/*
cristya29d45f2010-03-05 21:14:54 +000095 Other global definitions used by module.
96*/
anthony29188a82010-01-22 10:12:34 +000097static inline double MagickMin(const double x,const double y)
98{
99 return( x < y ? x : y);
100}
101static inline double MagickMax(const double x,const double y)
102{
103 return( x > y ? x : y);
104}
105#define Minimize(assign,value) assign=MagickMin(assign,value)
106#define Maximize(assign,value) assign=MagickMax(assign,value)
107
anthonyc4c86e02010-01-27 09:30:32 +0000108/* Currently these are only internal to this module */
109static void
cristyef656912010-03-05 19:54:59 +0000110 RotateKernelInfo(KernelInfo *, double);
anthony602ab9b2010-01-05 08:06:50 +0000111
112/*
113%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
114% %
115% %
116% %
anthony83ba99b2010-01-24 08:48:15 +0000117% A c q u i r e K e r n e l I n f o %
anthony602ab9b2010-01-05 08:06:50 +0000118% %
119% %
120% %
121%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
122%
cristy2be15382010-01-21 02:38:03 +0000123% AcquireKernelInfo() takes the given string (generally supplied by the
anthony602ab9b2010-01-05 08:06:50 +0000124% user) and converts it into a Morphology/Convolution Kernel. This allows
125% users to specify a kernel from a number of pre-defined kernels, or to fully
126% specify their own kernel for a specific Convolution or Morphology
127% Operation.
128%
129% The kernel so generated can be any rectangular array of floating point
130% values (doubles) with the 'control point' or 'pixel being affected'
131% anywhere within that array of values.
132%
anthony83ba99b2010-01-24 08:48:15 +0000133% Previously IM was restricted to a square of odd size using the exact
134% center as origin, this is no longer the case, and any rectangular kernel
135% with any value being declared the origin. This in turn allows the use of
136% highly asymmetrical kernels.
anthony602ab9b2010-01-05 08:06:50 +0000137%
138% The floating point values in the kernel can also include a special value
anthony83ba99b2010-01-24 08:48:15 +0000139% known as 'nan' or 'not a number' to indicate that this value is not part
140% of the kernel array. This allows you to shaped the kernel within its
141% rectangular area. That is 'nan' values provide a 'mask' for the kernel
142% shape. However at least one non-nan value must be provided for correct
143% working of a kernel.
anthony602ab9b2010-01-05 08:06:50 +0000144%
anthony83ba99b2010-01-24 08:48:15 +0000145% The returned kernel should be free using the DestroyKernelInfo() when you
146% are finished with it.
anthony602ab9b2010-01-05 08:06:50 +0000147%
148% Input kernel defintion strings can consist of any of three types.
149%
anthony29188a82010-01-22 10:12:34 +0000150% "name:args"
151% Select from one of the built in kernels, using the name and
152% geometry arguments supplied. See AcquireKernelBuiltIn()
anthony602ab9b2010-01-05 08:06:50 +0000153%
154% "WxH[+X+Y]:num, num, num ..."
155% a kernal of size W by H, with W*H floating point numbers following.
156% the 'center' can be optionally be defined at +X+Y (such that +0+0
anthony29188a82010-01-22 10:12:34 +0000157% is top left corner). If not defined the pixel in the center, for
158% odd sizes, or to the immediate top or left of center for even sizes
159% is automatically selected.
anthony602ab9b2010-01-05 08:06:50 +0000160%
anthony29188a82010-01-22 10:12:34 +0000161% "num, num, num, num, ..."
162% list of floating point numbers defining an 'old style' odd sized
163% square kernel. At least 9 values should be provided for a 3x3
164% square kernel, 25 for a 5x5 square kernel, 49 for 7x7, etc.
165% Values can be space or comma separated. This is not recommended.
anthony602ab9b2010-01-05 08:06:50 +0000166%
anthony83ba99b2010-01-24 08:48:15 +0000167% Note that 'name' kernels will start with an alphabetic character while the
168% new kernel specification has a ':' character in its specification string.
169% If neither is the case, it is assumed an old style of a simple list of
170% numbers generating a odd-sized square kernel has been given.
anthony602ab9b2010-01-05 08:06:50 +0000171%
172% The format of the AcquireKernal method is:
173%
cristy2be15382010-01-21 02:38:03 +0000174% KernelInfo *AcquireKernelInfo(const char *kernel_string)
anthony602ab9b2010-01-05 08:06:50 +0000175%
176% A description of each parameter follows:
177%
178% o kernel_string: the Morphology/Convolution kernel wanted.
179%
180*/
181
cristy2be15382010-01-21 02:38:03 +0000182MagickExport KernelInfo *AcquireKernelInfo(const char *kernel_string)
anthony602ab9b2010-01-05 08:06:50 +0000183{
cristy2be15382010-01-21 02:38:03 +0000184 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000185 *kernel;
186
187 char
188 token[MaxTextExtent];
189
cristy150989e2010-02-01 14:59:39 +0000190 register long
anthony602ab9b2010-01-05 08:06:50 +0000191 i;
192
193 const char
194 *p;
195
196 MagickStatusType
197 flags;
198
199 GeometryInfo
200 args;
201
anthony29188a82010-01-22 10:12:34 +0000202 double
203 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
204
anthony602ab9b2010-01-05 08:06:50 +0000205 assert(kernel_string != (const char *) NULL);
206 SetGeometryInfo(&args);
207
208 /* does it start with an alpha - Return a builtin kernel */
209 GetMagickToken(kernel_string,&p,token);
210 if ( isalpha((int)token[0]) )
211 {
212 long
213 type;
214
anthony29188a82010-01-22 10:12:34 +0000215 type=ParseMagickOption(MagickKernelOptions,MagickFalse,token);
anthony602ab9b2010-01-05 08:06:50 +0000216 if ( type < 0 || type == UserDefinedKernel )
cristy2be15382010-01-21 02:38:03 +0000217 return((KernelInfo *)NULL);
anthony602ab9b2010-01-05 08:06:50 +0000218
219 while (((isspace((int) ((unsigned char) *p)) != 0) ||
220 (*p == ',') || (*p == ':' )) && (*p != '\0'))
221 p++;
222 flags = ParseGeometry(p, &args);
223
224 /* special handling of missing values in input string */
anthony4fd27e22010-02-07 08:17:18 +0000225 switch( type ) {
226 case RectangleKernel:
anthony602ab9b2010-01-05 08:06:50 +0000227 if ( (flags & WidthValue) == 0 ) /* if no width then */
228 args.rho = args.sigma; /* then width = height */
229 if ( args.rho < 1.0 ) /* if width too small */
230 args.rho = 3; /* then width = 3 */
231 if ( args.sigma < 1.0 ) /* if height too small */
232 args.sigma = args.rho; /* then height = width */
233 if ( (flags & XValue) == 0 ) /* center offset if not defined */
234 args.xi = (double)(((long)args.rho-1)/2);
235 if ( (flags & YValue) == 0 )
236 args.psi = (double)(((long)args.sigma-1)/2);
anthony4fd27e22010-02-07 08:17:18 +0000237 break;
238 case SquareKernel:
239 case DiamondKernel:
240 case DiskKernel:
241 case PlusKernel:
242 if ( (flags & HeightValue) == 0 ) /* if no scale */
243 args.sigma = 1.0; /* then scale = 1.0 */
244 break;
245 default:
246 break;
anthony602ab9b2010-01-05 08:06:50 +0000247 }
248
cristy2be15382010-01-21 02:38:03 +0000249 return(AcquireKernelBuiltIn((KernelInfoType)type, &args));
anthony602ab9b2010-01-05 08:06:50 +0000250 }
251
cristy2be15382010-01-21 02:38:03 +0000252 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
253 if (kernel == (KernelInfo *)NULL)
anthony602ab9b2010-01-05 08:06:50 +0000254 return(kernel);
255 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
256 kernel->type = UserDefinedKernel;
cristyd43a46b2010-01-21 02:13:41 +0000257 kernel->signature = MagickSignature;
anthony602ab9b2010-01-05 08:06:50 +0000258
259 /* Has a ':' in argument - New user kernel specification */
260 p = strchr(kernel_string, ':');
261 if ( p != (char *) NULL)
262 {
anthony602ab9b2010-01-05 08:06:50 +0000263 /* ParseGeometry() needs the geometry separated! -- Arrgghh */
cristy150989e2010-02-01 14:59:39 +0000264 memcpy(token, kernel_string, (size_t) (p-kernel_string));
anthony602ab9b2010-01-05 08:06:50 +0000265 token[p-kernel_string] = '\0';
266 flags = ParseGeometry(token, &args);
anthony602ab9b2010-01-05 08:06:50 +0000267
anthony29188a82010-01-22 10:12:34 +0000268 /* Size handling and checks of geometry settings */
anthony602ab9b2010-01-05 08:06:50 +0000269 if ( (flags & WidthValue) == 0 ) /* if no width then */
270 args.rho = args.sigma; /* then width = height */
271 if ( args.rho < 1.0 ) /* if width too small */
272 args.rho = 1.0; /* then width = 1 */
273 if ( args.sigma < 1.0 ) /* if height too small */
274 args.sigma = args.rho; /* then height = width */
275 kernel->width = (unsigned long)args.rho;
276 kernel->height = (unsigned long)args.sigma;
277
278 /* Offset Handling and Checks */
279 if ( args.xi < 0.0 || args.psi < 0.0 )
anthony83ba99b2010-01-24 08:48:15 +0000280 return(DestroyKernelInfo(kernel));
cristyc99304f2010-02-01 15:26:27 +0000281 kernel->x = ((flags & XValue)!=0) ? (long)args.xi
cristy150989e2010-02-01 14:59:39 +0000282 : (long) (kernel->width-1)/2;
cristyc99304f2010-02-01 15:26:27 +0000283 kernel->y = ((flags & YValue)!=0) ? (long)args.psi
cristy150989e2010-02-01 14:59:39 +0000284 : (long) (kernel->height-1)/2;
cristyc99304f2010-02-01 15:26:27 +0000285 if ( kernel->x >= (long) kernel->width ||
286 kernel->y >= (long) kernel->height )
anthony83ba99b2010-01-24 08:48:15 +0000287 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000288
289 p++; /* advance beyond the ':' */
290 }
291 else
292 { /* ELSE - Old old kernel specification, forming odd-square kernel */
293 /* count up number of values given */
294 p=(const char *) kernel_string;
cristya699b172010-01-06 16:48:49 +0000295 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
anthony29188a82010-01-22 10:12:34 +0000296 p++; /* ignore "'" chars for convolve filter usage - Cristy */
anthony602ab9b2010-01-05 08:06:50 +0000297 for (i=0; *p != '\0'; i++)
298 {
299 GetMagickToken(p,&p,token);
300 if (*token == ',')
301 GetMagickToken(p,&p,token);
302 }
303 /* set the size of the kernel - old sized square */
304 kernel->width = kernel->height= (unsigned long) sqrt((double) i+1.0);
cristyc99304f2010-02-01 15:26:27 +0000305 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000306 p=(const char *) kernel_string;
anthony29188a82010-01-22 10:12:34 +0000307 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
308 p++; /* ignore "'" chars for convolve filter usage - Cristy */
anthony602ab9b2010-01-05 08:06:50 +0000309 }
310
311 /* Read in the kernel values from rest of input string argument */
312 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
313 kernel->height*sizeof(double));
314 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000315 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000316
cristyc99304f2010-02-01 15:26:27 +0000317 kernel->minimum = +MagickHuge;
318 kernel->maximum = -MagickHuge;
319 kernel->negative_range = kernel->positive_range = 0.0;
cristy150989e2010-02-01 14:59:39 +0000320 for (i=0; (i < (long) (kernel->width*kernel->height)) && (*p != '\0'); i++)
anthony602ab9b2010-01-05 08:06:50 +0000321 {
322 GetMagickToken(p,&p,token);
323 if (*token == ',')
324 GetMagickToken(p,&p,token);
anthony29188a82010-01-22 10:12:34 +0000325 if ( LocaleCompare("nan",token) == 0
326 || LocaleCompare("-",token) == 0 ) {
327 kernel->values[i] = nan; /* do not include this value in kernel */
328 }
329 else {
330 kernel->values[i] = StringToDouble(token);
331 ( kernel->values[i] < 0)
cristyc99304f2010-02-01 15:26:27 +0000332 ? ( kernel->negative_range += kernel->values[i] )
333 : ( kernel->positive_range += kernel->values[i] );
334 Minimize(kernel->minimum, kernel->values[i]);
335 Maximize(kernel->maximum, kernel->values[i]);
anthony29188a82010-01-22 10:12:34 +0000336 }
anthony602ab9b2010-01-05 08:06:50 +0000337 }
anthonycc6c8362010-01-25 04:14:01 +0000338 /* check that we recieved at least one real (non-nan) value! */
cristyc99304f2010-02-01 15:26:27 +0000339 if ( kernel->minimum == MagickHuge )
anthonycc6c8362010-01-25 04:14:01 +0000340 return(DestroyKernelInfo(kernel));
anthony29188a82010-01-22 10:12:34 +0000341
anthonycc6c8362010-01-25 04:14:01 +0000342 /* This should not be needed for a fully defined kernel
anthony29188a82010-01-22 10:12:34 +0000343 * Perhaps an error should be reported instead!
anthonycc6c8362010-01-25 04:14:01 +0000344 * Kept for backward compatibility.
anthony29188a82010-01-22 10:12:34 +0000345 */
cristy150989e2010-02-01 14:59:39 +0000346 if ( i < (long) (kernel->width*kernel->height) ) {
cristyc99304f2010-02-01 15:26:27 +0000347 Minimize(kernel->minimum, kernel->values[i]);
348 Maximize(kernel->maximum, kernel->values[i]);
cristy150989e2010-02-01 14:59:39 +0000349 for ( ; i < (long) (kernel->width*kernel->height); i++)
anthony29188a82010-01-22 10:12:34 +0000350 kernel->values[i]=0.0;
351 }
anthony602ab9b2010-01-05 08:06:50 +0000352
353 return(kernel);
354}
355
356/*
357%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
358% %
359% %
360% %
361% A c q u i r e K e r n e l B u i l t I n %
362% %
363% %
364% %
365%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
366%
367% AcquireKernelBuiltIn() returned one of the 'named' built-in types of
368% kernels used for special purposes such as gaussian blurring, skeleton
369% pruning, and edge distance determination.
370%
371% They take a KernelType, and a set of geometry style arguments, which were
372% typically decoded from a user supplied string, or from a more complex
373% Morphology Method that was requested.
374%
375% The format of the AcquireKernalBuiltIn method is:
376%
cristy2be15382010-01-21 02:38:03 +0000377% KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000378% const GeometryInfo args)
379%
380% A description of each parameter follows:
381%
382% o type: the pre-defined type of kernel wanted
383%
384% o args: arguments defining or modifying the kernel
385%
386% Convolution Kernels
387%
anthony4fd27e22010-02-07 08:17:18 +0000388% Gaussian "{radius},{sigma}"
anthony602ab9b2010-01-05 08:06:50 +0000389% Generate a two-dimentional gaussian kernel, as used by -gaussian
390% A sigma is required, (with the 'x'), due to historical reasons.
391%
392% NOTE: that the 'radius' is optional, but if provided can limit (clip)
393% the final size of the resulting kernel to a square 2*radius+1 in size.
394% The radius should be at least 2 times that of the sigma value, or
395% sever clipping and aliasing may result. If not given or set to 0 the
396% radius will be determined so as to produce the best minimal error
397% result, which is usally much larger than is normally needed.
398%
anthony4fd27e22010-02-07 08:17:18 +0000399% Blur "{radius},{sigma},{angle}"
anthony602ab9b2010-01-05 08:06:50 +0000400% As per Gaussian, but generates a 1 dimensional or linear gaussian
401% blur, at the angle given (current restricted to orthogonal angles).
402% If a 'radius' is given the kernel is clipped to a width of 2*radius+1.
403%
404% NOTE that two such blurs perpendicular to each other is equivelent to
405% -blur and the previous gaussian, but is often 10 or more times faster.
406%
anthony4fd27e22010-02-07 08:17:18 +0000407% Comet "{width},{sigma},{angle}"
anthony602ab9b2010-01-05 08:06:50 +0000408% Blur in one direction only, mush like how a bright object leaves
409% a comet like trail. The Kernel is actually half a gaussian curve,
410% Adding two such blurs in oppiste directions produces a Linear Blur.
411%
412% NOTE: that the first argument is the width of the kernel and not the
413% radius of the kernel.
414%
415% # Still to be implemented...
416% #
anthony4fd27e22010-02-07 08:17:18 +0000417% # Sharpen "{radius},{sigma}
418% # Negated Gaussian (center zeroed and re-normalized),
419% # with a 2 unit positive peak. -- Check On line documentation
420% #
421% # Laplacian "{radius},{sigma}"
anthony602ab9b2010-01-05 08:06:50 +0000422% # Laplacian (a mexican hat like) Function
423% #
424% # LOG "{radius},{sigma1},{sigma2}
425% # Laplacian of Gaussian
426% #
427% # DOG "{radius},{sigma1},{sigma2}
anthony4fd27e22010-02-07 08:17:18 +0000428% # Difference of two Gaussians
429% #
430% # Filter2D
431% # Filter1D
432% # Set kernel values using a resize filter, and given scale (sigma)
433% # Cylindrical or Linear. Is this posible with an image?
434% #
anthony602ab9b2010-01-05 08:06:50 +0000435%
436% Boolean Kernels
437%
438% Rectangle "{geometry}"
439% Simply generate a rectangle of 1's with the size given. You can also
440% specify the location of the 'control point', otherwise the closest
441% pixel to the center of the rectangle is selected.
442%
443% Properly centered and odd sized rectangles work the best.
444%
anthony4fd27e22010-02-07 08:17:18 +0000445% Diamond "[{radius}[,{scale}]]"
anthony602ab9b2010-01-05 08:06:50 +0000446% Generate a diamond shaped kernal with given radius to the points.
447% Kernel size will again be radius*2+1 square and defaults to radius 1,
448% generating a 3x3 kernel that is slightly larger than a square.
449%
anthony4fd27e22010-02-07 08:17:18 +0000450% Square "[{radius}[,{scale}]]"
anthony602ab9b2010-01-05 08:06:50 +0000451% Generate a square shaped kernel of size radius*2+1, and defaulting
452% to a 3x3 (radius 1).
453%
454% Note that using a larger radius for the "Square" or the "Diamond"
455% is also equivelent to iterating the basic morphological method
456% that many times. However However iterating with the smaller radius 1
457% default is actually faster than using a larger kernel radius.
458%
anthony4fd27e22010-02-07 08:17:18 +0000459% Disk "[{radius}[,{scale}]]
anthony602ab9b2010-01-05 08:06:50 +0000460% Generate a binary disk of the radius given, radius may be a float.
461% Kernel size will be ceil(radius)*2+1 square.
462% NOTE: Here are some disk shapes of specific interest
463% "disk:1" => "diamond" or "cross:1"
464% "disk:1.5" => "square"
465% "disk:2" => "diamond:2"
anthony83ba99b2010-01-24 08:48:15 +0000466% "disk:2.5" => a general disk shape of radius 2
anthony602ab9b2010-01-05 08:06:50 +0000467% "disk:2.9" => "square:2"
anthony83ba99b2010-01-24 08:48:15 +0000468% "disk:3.5" => default - octagonal/disk shape of radius 3
anthony602ab9b2010-01-05 08:06:50 +0000469% "disk:4.2" => roughly octagonal shape of radius 4
anthony83ba99b2010-01-24 08:48:15 +0000470% "disk:4.3" => a general disk shape of radius 4
anthony602ab9b2010-01-05 08:06:50 +0000471% After this all the kernel shape becomes more and more circular.
472%
473% Because a "disk" is more circular when using a larger radius, using a
474% larger radius is preferred over iterating the morphological operation.
475%
anthony4fd27e22010-02-07 08:17:18 +0000476% Plus "[{radius}[,{scale}]]"
anthony602ab9b2010-01-05 08:06:50 +0000477% Generate a kernel in the shape of a 'plus' sign. The length of each
478% arm is also the radius, which defaults to 2.
479%
480% This kernel is not a good general morphological kernel, but is used
481% more for highlighting and marking any single pixels in an image using,
482% a "Dilate" or "Erode" method as appropriate.
anthonyc94cdb02010-01-06 08:15:29 +0000483%
anthony602ab9b2010-01-05 08:06:50 +0000484% NOTE: "plus:1" is equivelent to a "Diamond" kernel.
485%
486% Note that unlike other kernels iterating a plus does not produce the
487% same result as using a larger radius for the cross.
488%
489% Distance Measuring Kernels
490%
491% Chebyshev "[{radius}][x{scale}]" largest x or y distance (default r=1)
492% Manhatten "[{radius}][x{scale}]" square grid distance (default r=1)
anthonyc94cdb02010-01-06 08:15:29 +0000493% Euclidean "[{radius}][x{scale}]" direct distance (default r=1)
anthony602ab9b2010-01-05 08:06:50 +0000494%
495% Different types of distance measuring methods, which are used with the
496% a 'Distance' morphology method for generating a gradient based on
497% distance from an edge of a binary shape, though there is a technique
498% for handling a anti-aliased shape.
499%
anthonyc94cdb02010-01-06 08:15:29 +0000500% Chebyshev Distance (also known as Tchebychev Distance) is a value of
501% one to any neighbour, orthogonal or diagonal. One why of thinking of
502% it is the number of squares a 'King' or 'Queen' in chess needs to
503% traverse reach any other position on a chess board. It results in a
504% 'square' like distance function, but one where diagonals are closer
505% than expected.
anthony602ab9b2010-01-05 08:06:50 +0000506%
anthonyc94cdb02010-01-06 08:15:29 +0000507% Manhatten Distance (also known as Rectilinear Distance, or the Taxi
508% Cab metric), is the distance needed when you can only travel in
509% orthogonal (horizontal or vertical) only. It is the distance a 'Rook'
510% in chess would travel. It results in a diamond like distances, where
511% diagonals are further than expected.
anthony602ab9b2010-01-05 08:06:50 +0000512%
anthonyc94cdb02010-01-06 08:15:29 +0000513% Euclidean Distance is the 'direct' or 'as the crow flys distance.
514% However by default the kernel size only has a radius of 1, which
515% limits the distance to 'Knight' like moves, with only orthogonal and
516% diagonal measurements being correct. As such for the default kernel
517% you will get octagonal like distance function, which is reasonally
518% accurate.
519%
520% However if you use a larger radius such as "Euclidean:4" you will
521% get a much smoother distance gradient from the edge of the shape.
522% Of course a larger kernel is slower to use, and generally not needed.
523%
524% To allow the use of fractional distances that you get with diagonals
525% the actual distance is scaled by a fixed value which the user can
526% provide. This is not actually nessary for either ""Chebyshev" or
527% "Manhatten" distance kernels, but is done for all three distance
528% kernels. If no scale is provided it is set to a value of 100,
529% allowing for a maximum distance measurement of 655 pixels using a Q16
530% version of IM, from any edge. However for small images this can
531% result in quite a dark gradient.
532%
533% See the 'Distance' Morphological Method, for information of how it is
534% applied.
anthony602ab9b2010-01-05 08:06:50 +0000535%
anthony4fd27e22010-02-07 08:17:18 +0000536% # Hit-n-Miss Kernel-Lists -- Still to be implemented
537% #
538% # specifically for Pruning, Thinning, Thickening
539% #
anthony602ab9b2010-01-05 08:06:50 +0000540*/
541
cristy2be15382010-01-21 02:38:03 +0000542MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000543 const GeometryInfo *args)
544{
cristy2be15382010-01-21 02:38:03 +0000545 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000546 *kernel;
547
cristy150989e2010-02-01 14:59:39 +0000548 register long
anthony602ab9b2010-01-05 08:06:50 +0000549 i;
550
551 register long
552 u,
553 v;
554
555 double
556 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
557
cristy2be15382010-01-21 02:38:03 +0000558 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
559 if (kernel == (KernelInfo *) NULL)
anthony602ab9b2010-01-05 08:06:50 +0000560 return(kernel);
561 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
cristyc99304f2010-02-01 15:26:27 +0000562 kernel->minimum = kernel->maximum = 0.0;
563 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000564 kernel->type = type;
cristyd43a46b2010-01-21 02:13:41 +0000565 kernel->signature = MagickSignature;
anthony602ab9b2010-01-05 08:06:50 +0000566
567 switch(type) {
568 /* Convolution Kernels */
569 case GaussianKernel:
570 { double
571 sigma = fabs(args->sigma);
572
573 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
574
575 kernel->width = kernel->height =
576 GetOptimalKernelWidth2D(args->rho,sigma);
cristyc99304f2010-02-01 15:26:27 +0000577 kernel->x = kernel->y = (long) (kernel->width-1)/2;
578 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000579 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
580 kernel->height*sizeof(double));
581 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000582 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000583
584 sigma = 2.0*sigma*sigma; /* simplify the expression */
cristyc99304f2010-02-01 15:26:27 +0000585 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
586 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
587 kernel->positive_range += (
anthony602ab9b2010-01-05 08:06:50 +0000588 kernel->values[i] =
589 exp(-((double)(u*u+v*v))/sigma)
590 /* / (MagickPI*sigma) */ );
cristyc99304f2010-02-01 15:26:27 +0000591 kernel->minimum = 0;
592 kernel->maximum = kernel->values[
593 kernel->y*kernel->width+kernel->x ];
anthony602ab9b2010-01-05 08:06:50 +0000594
anthony999bb2c2010-02-18 12:38:01 +0000595 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
anthony602ab9b2010-01-05 08:06:50 +0000596
597 break;
598 }
599 case BlurKernel:
600 { double
601 sigma = fabs(args->sigma);
602
603 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
604
605 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
cristyc99304f2010-02-01 15:26:27 +0000606 kernel->x = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000607 kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +0000608 kernel->y = 0;
609 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000610 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
611 kernel->height*sizeof(double));
612 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000613 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000614
615#if 1
616#define KernelRank 3
617 /* Formula derived from GetBlurKernel() in "effect.c" (plus bug fix).
618 ** It generates a gaussian 3 times the width, and compresses it into
619 ** the expected range. This produces a closer normalization of the
620 ** resulting kernel, especially for very low sigma values.
621 ** As such while wierd it is prefered.
622 **
623 ** I am told this method originally came from Photoshop.
624 */
625 sigma *= KernelRank; /* simplify expanded curve */
cristy150989e2010-02-01 14:59:39 +0000626 v = (long) (kernel->width*KernelRank-1)/2; /* start/end points to fit range */
anthony602ab9b2010-01-05 08:06:50 +0000627 (void) ResetMagickMemory(kernel->values,0, (size_t)
628 kernel->width*sizeof(double));
629 for ( u=-v; u <= v; u++) {
630 kernel->values[(u+v)/KernelRank] +=
631 exp(-((double)(u*u))/(2.0*sigma*sigma))
632 /* / (MagickSQ2PI*sigma/KernelRank) */ ;
633 }
cristy150989e2010-02-01 14:59:39 +0000634 for (i=0; i < (long) kernel->width; i++)
cristyc99304f2010-02-01 15:26:27 +0000635 kernel->positive_range += kernel->values[i];
anthony602ab9b2010-01-05 08:06:50 +0000636#else
cristyc99304f2010-02-01 15:26:27 +0000637 for ( i=0, u=-kernel->x; i < kernel->width; i++, u++)
638 kernel->positive_range += (
anthony602ab9b2010-01-05 08:06:50 +0000639 kernel->values[i] =
640 exp(-((double)(u*u))/(2.0*sigma*sigma))
641 /* / (MagickSQ2PI*sigma) */ );
642#endif
cristyc99304f2010-02-01 15:26:27 +0000643 kernel->minimum = 0;
644 kernel->maximum = kernel->values[ kernel->x ];
anthonycc6c8362010-01-25 04:14:01 +0000645 /* Note that neither methods above generate a normalized kernel,
646 ** though it gets close. The kernel may be 'clipped' by a user defined
647 ** radius, producing a smaller (darker) kernel. Also for very small
648 ** sigma's (> 0.1) the central value becomes larger than one, and thus
649 ** producing a very bright kernel.
anthony602ab9b2010-01-05 08:06:50 +0000650 */
anthonycc6c8362010-01-25 04:14:01 +0000651
anthony602ab9b2010-01-05 08:06:50 +0000652 /* Normalize the 1D Gaussian Kernel
653 **
654 ** Because of this the divisor in the above kernel generator is
anthonyc94cdb02010-01-06 08:15:29 +0000655 ** not needed, so is not done above.
anthony602ab9b2010-01-05 08:06:50 +0000656 */
anthony999bb2c2010-02-18 12:38:01 +0000657 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
anthonycc6c8362010-01-25 04:14:01 +0000658
anthony602ab9b2010-01-05 08:06:50 +0000659 /* rotate the kernel by given angle */
anthony4fd27e22010-02-07 08:17:18 +0000660 RotateKernelInfo(kernel, args->xi);
anthony602ab9b2010-01-05 08:06:50 +0000661 break;
662 }
663 case CometKernel:
664 { double
665 sigma = fabs(args->sigma);
666
667 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
668
669 if ( args->rho < 1.0 )
670 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
671 else
672 kernel->width = (unsigned long)args->rho;
cristyc99304f2010-02-01 15:26:27 +0000673 kernel->x = kernel->y = 0;
anthony602ab9b2010-01-05 08:06:50 +0000674 kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +0000675 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000676 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
677 kernel->height*sizeof(double));
678 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000679 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000680
681 /* A comet blur is half a gaussian curve, so that the object is
682 ** blurred in one direction only. This may not be quite the right
683 ** curve so may change in the future. The function must be normalised.
684 */
685#if 1
686#define KernelRank 3
687 sigma *= KernelRank; /* simplify expanded curve */
cristy150989e2010-02-01 14:59:39 +0000688 v = (long) kernel->width*KernelRank; /* start/end points to fit range */
anthony602ab9b2010-01-05 08:06:50 +0000689 (void) ResetMagickMemory(kernel->values,0, (size_t)
690 kernel->width*sizeof(double));
691 for ( u=0; u < v; u++) {
692 kernel->values[u/KernelRank] +=
693 exp(-((double)(u*u))/(2.0*sigma*sigma))
694 /* / (MagickSQ2PI*sigma/KernelRank) */ ;
695 }
cristy150989e2010-02-01 14:59:39 +0000696 for (i=0; i < (long) kernel->width; i++)
cristyc99304f2010-02-01 15:26:27 +0000697 kernel->positive_range += kernel->values[i];
anthony602ab9b2010-01-05 08:06:50 +0000698#else
cristy150989e2010-02-01 14:59:39 +0000699 for ( i=0; i < (long) kernel->width; i++)
cristyc99304f2010-02-01 15:26:27 +0000700 kernel->positive_range += (
anthony602ab9b2010-01-05 08:06:50 +0000701 kernel->values[i] =
702 exp(-((double)(i*i))/(2.0*sigma*sigma))
703 /* / (MagickSQ2PI*sigma) */ );
704#endif
cristyc99304f2010-02-01 15:26:27 +0000705 kernel->minimum = 0;
706 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000707
anthony999bb2c2010-02-18 12:38:01 +0000708 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
709 RotateKernelInfo(kernel, args->xi); /* Rotate by angle */
anthony602ab9b2010-01-05 08:06:50 +0000710 break;
711 }
712 /* Boolean Kernels */
713 case RectangleKernel:
714 case SquareKernel:
715 {
anthony4fd27e22010-02-07 08:17:18 +0000716 double scale;
anthony602ab9b2010-01-05 08:06:50 +0000717 if ( type == SquareKernel )
718 {
719 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000720 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000721 else
cristy150989e2010-02-01 14:59:39 +0000722 kernel->width = kernel->height = (unsigned long) (2*args->rho+1);
cristyc99304f2010-02-01 15:26:27 +0000723 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony4fd27e22010-02-07 08:17:18 +0000724 scale = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +0000725 }
726 else {
cristy2be15382010-01-21 02:38:03 +0000727 /* NOTE: user defaults set in "AcquireKernelInfo()" */
anthony602ab9b2010-01-05 08:06:50 +0000728 if ( args->rho < 1.0 || args->sigma < 1.0 )
anthony83ba99b2010-01-24 08:48:15 +0000729 return(DestroyKernelInfo(kernel)); /* invalid args given */
anthony602ab9b2010-01-05 08:06:50 +0000730 kernel->width = (unsigned long)args->rho;
731 kernel->height = (unsigned long)args->sigma;
732 if ( args->xi < 0.0 || args->xi > (double)kernel->width ||
733 args->psi < 0.0 || args->psi > (double)kernel->height )
anthony83ba99b2010-01-24 08:48:15 +0000734 return(DestroyKernelInfo(kernel)); /* invalid args given */
cristyc99304f2010-02-01 15:26:27 +0000735 kernel->x = (long) args->xi;
736 kernel->y = (long) args->psi;
anthony4fd27e22010-02-07 08:17:18 +0000737 scale = 1.0;
anthony602ab9b2010-01-05 08:06:50 +0000738 }
739 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
740 kernel->height*sizeof(double));
741 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000742 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000743
anthonycc6c8362010-01-25 04:14:01 +0000744 /* set all kernel values to 1.0 */
cristy150989e2010-02-01 14:59:39 +0000745 u=(long) kernel->width*kernel->height;
746 for ( i=0; i < u; i++)
anthony4fd27e22010-02-07 08:17:18 +0000747 kernel->values[i] = scale;
748 kernel->minimum = kernel->maximum = scale; /* a flat shape */
749 kernel->positive_range = scale*u;
anthonycc6c8362010-01-25 04:14:01 +0000750 break;
anthony602ab9b2010-01-05 08:06:50 +0000751 }
752 case DiamondKernel:
753 {
754 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000755 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000756 else
757 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000758 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000759
760 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
761 kernel->height*sizeof(double));
762 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000763 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000764
anthony4fd27e22010-02-07 08:17:18 +0000765 /* set all kernel values within diamond area to scale given */
cristyc99304f2010-02-01 15:26:27 +0000766 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
767 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
768 if ((labs(u)+labs(v)) <= (long)kernel->x)
anthony4fd27e22010-02-07 08:17:18 +0000769 kernel->positive_range += kernel->values[i] = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +0000770 else
771 kernel->values[i] = nan;
anthony4fd27e22010-02-07 08:17:18 +0000772 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
anthony602ab9b2010-01-05 08:06:50 +0000773 break;
774 }
775 case DiskKernel:
776 {
777 long
778 limit;
779
780 limit = (long)(args->rho*args->rho);
anthony83ba99b2010-01-24 08:48:15 +0000781 if (args->rho < 0.1) /* default radius approx 3.5 */
782 kernel->width = kernel->height = 7L, limit = 10L;
anthony602ab9b2010-01-05 08:06:50 +0000783 else
784 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000785 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000786
787 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
788 kernel->height*sizeof(double));
789 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000790 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000791
anthonycc6c8362010-01-25 04:14:01 +0000792 /* set all kernel values within disk area to 1.0 */
cristyc99304f2010-02-01 15:26:27 +0000793 for ( i=0, v= -kernel->y; v <= (long)kernel->y; v++)
794 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony602ab9b2010-01-05 08:06:50 +0000795 if ((u*u+v*v) <= limit)
anthony4fd27e22010-02-07 08:17:18 +0000796 kernel->positive_range += kernel->values[i] = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +0000797 else
798 kernel->values[i] = nan;
anthony4fd27e22010-02-07 08:17:18 +0000799 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
anthony602ab9b2010-01-05 08:06:50 +0000800 break;
801 }
802 case PlusKernel:
803 {
804 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000805 kernel->width = kernel->height = 5; /* default radius 2 */
anthony602ab9b2010-01-05 08:06:50 +0000806 else
807 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000808 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000809
810 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
811 kernel->height*sizeof(double));
812 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000813 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000814
anthonycc6c8362010-01-25 04:14:01 +0000815 /* set all kernel values along axises to 1.0 */
cristyc99304f2010-02-01 15:26:27 +0000816 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
817 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony4fd27e22010-02-07 08:17:18 +0000818 kernel->values[i] = (u == 0 || v == 0) ? args->sigma : nan;
819 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
820 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
anthony602ab9b2010-01-05 08:06:50 +0000821 break;
822 }
823 /* Distance Measuring Kernels */
824 case ChebyshevKernel:
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;
cristyc99304f2010-02-01 15:26:27 +0000833 kernel->x = kernel->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;
cristyc99304f2010-02-01 15:26:27 +0000841 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
842 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
843 kernel->positive_range += ( kernel->values[i] =
anthony602ab9b2010-01-05 08:06:50 +0000844 scale*((labs(u)>labs(v)) ? labs(u) : labs(v)) );
cristyc99304f2010-02-01 15:26:27 +0000845 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000846 break;
847 }
848 case ManhattenKernel:
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;
cristyc99304f2010-02-01 15:26:27 +0000857 kernel->x = kernel->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;
cristyc99304f2010-02-01 15:26:27 +0000865 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
866 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
867 kernel->positive_range += ( kernel->values[i] =
anthony602ab9b2010-01-05 08:06:50 +0000868 scale*(labs(u)+labs(v)) );
cristyc99304f2010-02-01 15:26:27 +0000869 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000870 break;
871 }
872 case EuclideanKernel:
873 {
874 double
875 scale;
876
877 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000878 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000879 else
880 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000881 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000882
883 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
884 kernel->height*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
888 scale = (args->sigma < 1.0) ? 100.0 : args->sigma;
cristyc99304f2010-02-01 15:26:27 +0000889 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
890 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
891 kernel->positive_range += ( kernel->values[i] =
anthony602ab9b2010-01-05 08:06:50 +0000892 scale*sqrt((double)(u*u+v*v)) );
cristyc99304f2010-02-01 15:26:27 +0000893 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000894 break;
895 }
896 /* Undefined Kernels */
897 case LaplacianKernel:
898 case LOGKernel:
899 case DOGKernel:
cristy150989e2010-02-01 14:59:39 +0000900 perror("Kernel Type has not been defined yet");
anthony602ab9b2010-01-05 08:06:50 +0000901 /* FALL THRU */
902 default:
903 /* Generate a No-Op minimal kernel - 1x1 pixel */
904 kernel->values=(double *)AcquireQuantumMemory((size_t)1,sizeof(double));
905 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000906 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000907 kernel->width = kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +0000908 kernel->x = kernel->x = 0;
anthony602ab9b2010-01-05 08:06:50 +0000909 kernel->type = UndefinedKernel;
cristyc99304f2010-02-01 15:26:27 +0000910 kernel->maximum =
911 kernel->positive_range =
anthonyc94cdb02010-01-06 08:15:29 +0000912 kernel->values[0] = 1.0; /* a flat single-point no-op kernel! */
anthony602ab9b2010-01-05 08:06:50 +0000913 break;
914 }
915
916 return(kernel);
917}
anthonyc94cdb02010-01-06 08:15:29 +0000918
anthony602ab9b2010-01-05 08:06:50 +0000919/*
920%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
921% %
922% %
923% %
cristy6771f1e2010-03-05 19:43:39 +0000924% C l o n e K e r n e l I n f o %
anthony4fd27e22010-02-07 08:17:18 +0000925% %
926% %
927% %
928%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
929%
930% CloneKernelInfo() creates a new clone of the given Kernel so that its can
931% be modified without effecting the original. The cloned kernel should be
932% destroyed using DestoryKernelInfo() when no longer needed.
933%
cristye6365592010-04-02 17:31:23 +0000934% The format of the CloneKernelInfo method is:
anthony4fd27e22010-02-07 08:17:18 +0000935%
anthony930be612010-02-08 04:26:15 +0000936% KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +0000937%
938% A description of each parameter follows:
939%
940% o kernel: the Morphology/Convolution kernel to be cloned
941%
942*/
cristyef656912010-03-05 19:54:59 +0000943MagickExport KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +0000944{
945 register long
946 i;
947
948 KernelInfo *
949 new;
950
951 assert(kernel != (KernelInfo *) NULL);
952
953 new=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
954 if (new == (KernelInfo *) NULL)
955 return(new);
956 *new = *kernel; /* copy values in structure */
957
958 new->values=(double *) AcquireQuantumMemory(kernel->width,
959 kernel->height*sizeof(double));
960 if (new->values == (double *) NULL)
961 return(DestroyKernelInfo(new));
962
963 for (i=0; i < (long) (kernel->width*kernel->height); i++)
964 new->values[i] = kernel->values[i];
965
966 return(new);
967}
968
969/*
970%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
971% %
972% %
973% %
anthony83ba99b2010-01-24 08:48:15 +0000974% D e s t r o y K e r n e l I n f o %
anthony602ab9b2010-01-05 08:06:50 +0000975% %
976% %
977% %
978%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
979%
anthony83ba99b2010-01-24 08:48:15 +0000980% DestroyKernelInfo() frees the memory used by a Convolution/Morphology
981% kernel.
anthony602ab9b2010-01-05 08:06:50 +0000982%
anthony83ba99b2010-01-24 08:48:15 +0000983% The format of the DestroyKernelInfo method is:
anthony602ab9b2010-01-05 08:06:50 +0000984%
anthony83ba99b2010-01-24 08:48:15 +0000985% KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +0000986%
987% A description of each parameter follows:
988%
989% o kernel: the Morphology/Convolution kernel to be destroyed
990%
991*/
992
anthony83ba99b2010-01-24 08:48:15 +0000993MagickExport KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +0000994{
cristy2be15382010-01-21 02:38:03 +0000995 assert(kernel != (KernelInfo *) NULL);
anthony4fd27e22010-02-07 08:17:18 +0000996
997 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
998 kernel->height*sizeof(double));
anthony602ab9b2010-01-05 08:06:50 +0000999 kernel->values=(double *)RelinquishMagickMemory(kernel->values);
cristy2be15382010-01-21 02:38:03 +00001000 kernel=(KernelInfo *) RelinquishMagickMemory(kernel);
anthony602ab9b2010-01-05 08:06:50 +00001001 return(kernel);
1002}
anthonyc94cdb02010-01-06 08:15:29 +00001003
1004/*
1005%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1006% %
1007% %
1008% %
anthony29188a82010-01-22 10:12:34 +00001009% 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 +00001010% %
1011% %
1012% %
1013%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1014%
anthony29188a82010-01-22 10:12:34 +00001015% MorphologyImageChannel() applies a user supplied kernel to the image
1016% according to the given mophology method.
anthony602ab9b2010-01-05 08:06:50 +00001017%
1018% The given kernel is assumed to have been pre-scaled appropriatally, usally
1019% by the kernel generator.
1020%
1021% The format of the MorphologyImage method is:
1022%
cristyef656912010-03-05 19:54:59 +00001023% Image *MorphologyImage(const Image *image,MorphologyMethod method,
1024% const long iterations,KernelInfo *kernel,ExceptionInfo *exception)
anthony29188a82010-01-22 10:12:34 +00001025% Image *MorphologyImageChannel(const Image *image, const ChannelType
cristyef656912010-03-05 19:54:59 +00001026% channel,MorphologyMethod method,const long iterations,
1027% KernelInfo *kernel,ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001028%
1029% A description of each parameter follows:
1030%
1031% o image: the image.
1032%
1033% o method: the morphology method to be applied.
1034%
1035% o iterations: apply the operation this many times (or no change).
1036% A value of -1 means loop until no change found.
1037% How this is applied may depend on the morphology method.
1038% Typically this is a value of 1.
1039%
1040% o channel: the channel type.
1041%
1042% o kernel: An array of double representing the morphology kernel.
anthony29188a82010-01-22 10:12:34 +00001043% Warning: kernel may be normalized for the Convolve method.
anthony602ab9b2010-01-05 08:06:50 +00001044%
1045% o exception: return any errors or warnings in this structure.
1046%
1047%
1048% TODO: bias and auto-scale handling of the kernel for convolution
1049% The given kernel is assumed to have been pre-scaled appropriatally, usally
1050% by the kernel generator.
1051%
1052*/
1053
anthony930be612010-02-08 04:26:15 +00001054
anthony602ab9b2010-01-05 08:06:50 +00001055/* Internal function
anthony930be612010-02-08 04:26:15 +00001056 * Apply the Low-Level Morphology Method using the given Kernel
1057 * Returning the number of pixels that changed.
1058 * Two pre-created images must be provided, no image is created.
anthony602ab9b2010-01-05 08:06:50 +00001059 */
1060static unsigned long MorphologyApply(const Image *image, Image
1061 *result_image, const MorphologyMethod method, const ChannelType channel,
cristy2be15382010-01-21 02:38:03 +00001062 const KernelInfo *kernel, ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001063{
cristy2be15382010-01-21 02:38:03 +00001064#define MorphologyTag "Morphology/Image"
anthony602ab9b2010-01-05 08:06:50 +00001065
1066 long
cristy150989e2010-02-01 14:59:39 +00001067 progress,
anthony29188a82010-01-22 10:12:34 +00001068 y, offx, offy,
anthony602ab9b2010-01-05 08:06:50 +00001069 changed;
1070
1071 MagickBooleanType
1072 status;
1073
1074 MagickPixelPacket
1075 bias;
1076
1077 CacheView
1078 *p_view,
1079 *q_view;
1080
anthony4fd27e22010-02-07 08:17:18 +00001081 /* Only the most basic morphology is actually performed by this routine */
anthony4fd27e22010-02-07 08:17:18 +00001082
anthony602ab9b2010-01-05 08:06:50 +00001083 /*
anthony4fd27e22010-02-07 08:17:18 +00001084 Apply Basic Morphology to Image.
anthony602ab9b2010-01-05 08:06:50 +00001085 */
1086 status=MagickTrue;
1087 changed=0;
1088 progress=0;
1089
1090 GetMagickPixelPacket(image,&bias);
1091 SetMagickPixelPacketBias(image,&bias);
anthonycc6c8362010-01-25 04:14:01 +00001092 /* Future: handle auto-bias from user, based on kernel input */
anthony602ab9b2010-01-05 08:06:50 +00001093
1094 p_view=AcquireCacheView(image);
1095 q_view=AcquireCacheView(result_image);
anthony29188a82010-01-22 10:12:34 +00001096
anthonycc6c8362010-01-25 04:14:01 +00001097 /* Some methods (including convolve) needs use a reflected kernel.
1098 * Adjust 'origin' offsets for this reflected kernel.
anthony29188a82010-01-22 10:12:34 +00001099 */
cristyc99304f2010-02-01 15:26:27 +00001100 offx = kernel->x;
1101 offy = kernel->y;
anthony29188a82010-01-22 10:12:34 +00001102 switch(method) {
1103 case ErodeMorphology:
1104 case ErodeIntensityMorphology:
anthony999bb2c2010-02-18 12:38:01 +00001105 /* kernel is user as is, without reflection */
anthony29188a82010-01-22 10:12:34 +00001106 break;
anthony930be612010-02-08 04:26:15 +00001107 case ConvolveMorphology:
1108 case DilateMorphology:
1109 case DilateIntensityMorphology:
1110 case DistanceMorphology:
anthony999bb2c2010-02-18 12:38:01 +00001111 /* kernel needs to used with reflection */
cristy150989e2010-02-01 14:59:39 +00001112 offx = (long) kernel->width-offx-1;
1113 offy = (long) kernel->height-offy-1;
anthony29188a82010-01-22 10:12:34 +00001114 break;
anthony930be612010-02-08 04:26:15 +00001115 default:
1116 perror("Not a low level Morpholgy Method");
1117 break;
anthony29188a82010-01-22 10:12:34 +00001118 }
1119
anthony602ab9b2010-01-05 08:06:50 +00001120#if defined(MAGICKCORE_OPENMP_SUPPORT)
1121 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1122#endif
cristy150989e2010-02-01 14:59:39 +00001123 for (y=0; y < (long) image->rows; y++)
anthony602ab9b2010-01-05 08:06:50 +00001124 {
1125 MagickBooleanType
1126 sync;
1127
1128 register const PixelPacket
1129 *restrict p;
1130
1131 register const IndexPacket
1132 *restrict p_indexes;
1133
1134 register PixelPacket
1135 *restrict q;
1136
1137 register IndexPacket
1138 *restrict q_indexes;
1139
cristy150989e2010-02-01 14:59:39 +00001140 register long
anthony602ab9b2010-01-05 08:06:50 +00001141 x;
1142
anthony29188a82010-01-22 10:12:34 +00001143 unsigned long
anthony602ab9b2010-01-05 08:06:50 +00001144 r;
1145
1146 if (status == MagickFalse)
1147 continue;
anthony29188a82010-01-22 10:12:34 +00001148 p=GetCacheViewVirtualPixels(p_view, -offx, y-offy,
1149 image->columns+kernel->width, kernel->height, exception);
anthony602ab9b2010-01-05 08:06:50 +00001150 q=GetCacheViewAuthenticPixels(q_view,0,y,result_image->columns,1,
1151 exception);
1152 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1153 {
1154 status=MagickFalse;
1155 continue;
1156 }
1157 p_indexes=GetCacheViewVirtualIndexQueue(p_view);
1158 q_indexes=GetCacheViewAuthenticIndexQueue(q_view);
anthony29188a82010-01-22 10:12:34 +00001159 r = (image->columns+kernel->width)*offy+offx; /* constant */
1160
cristy150989e2010-02-01 14:59:39 +00001161 for (x=0; x < (long) image->columns; x++)
anthony602ab9b2010-01-05 08:06:50 +00001162 {
cristy150989e2010-02-01 14:59:39 +00001163 long
anthony602ab9b2010-01-05 08:06:50 +00001164 v;
1165
cristy150989e2010-02-01 14:59:39 +00001166 register long
anthony602ab9b2010-01-05 08:06:50 +00001167 u;
1168
1169 register const double
1170 *restrict k;
1171
1172 register const PixelPacket
1173 *restrict k_pixels;
1174
1175 register const IndexPacket
1176 *restrict k_indexes;
1177
1178 MagickPixelPacket
1179 result;
1180
anthony29188a82010-01-22 10:12:34 +00001181 /* Copy input to ouput image for unused channels
anthony83ba99b2010-01-24 08:48:15 +00001182 * This removes need for 'cloning' a new image every iteration
anthony29188a82010-01-22 10:12:34 +00001183 */
anthony602ab9b2010-01-05 08:06:50 +00001184 *q = p[r];
1185 if (image->colorspace == CMYKColorspace)
1186 q_indexes[x] = p_indexes[r];
1187
cristy5ee247a2010-02-12 15:42:34 +00001188 result.green=(MagickRealType) 0;
1189 result.blue=(MagickRealType) 0;
1190 result.opacity=(MagickRealType) 0;
1191 result.index=(MagickRealType) 0;
anthony602ab9b2010-01-05 08:06:50 +00001192 switch (method) {
1193 case ConvolveMorphology:
anthony930be612010-02-08 04:26:15 +00001194 /* Set the user defined bias of the weighted average output
1195 **
1196 ** FUTURE: provide some way for internal functions to disable
1197 ** user defined bias and scaling effects.
1198 */
anthony602ab9b2010-01-05 08:06:50 +00001199 result=bias;
anthony930be612010-02-08 04:26:15 +00001200 break;
anthony83ba99b2010-01-24 08:48:15 +00001201 case DilateMorphology:
anthony29188a82010-01-22 10:12:34 +00001202 result.red =
1203 result.green =
1204 result.blue =
1205 result.opacity =
1206 result.index = -MagickHuge;
1207 break;
1208 case ErodeMorphology:
1209 result.red =
1210 result.green =
1211 result.blue =
1212 result.opacity =
1213 result.index = +MagickHuge;
1214 break;
anthony4fd27e22010-02-07 08:17:18 +00001215 case DilateIntensityMorphology:
1216 case ErodeIntensityMorphology:
1217 result.red = 0.0; /* flag indicating first match found */
1218 break;
anthony602ab9b2010-01-05 08:06:50 +00001219 default:
anthony29188a82010-01-22 10:12:34 +00001220 /* Otherwise just start with the original pixel value */
cristy150989e2010-02-01 14:59:39 +00001221 result.red = (MagickRealType) p[r].red;
1222 result.green = (MagickRealType) p[r].green;
1223 result.blue = (MagickRealType) p[r].blue;
1224 result.opacity = QuantumRange - (MagickRealType) p[r].opacity;
anthony602ab9b2010-01-05 08:06:50 +00001225 if ( image->colorspace == CMYKColorspace)
cristy150989e2010-02-01 14:59:39 +00001226 result.index = (MagickRealType) p_indexes[r];
anthony602ab9b2010-01-05 08:06:50 +00001227 break;
1228 }
1229
1230 switch ( method ) {
1231 case ConvolveMorphology:
anthony930be612010-02-08 04:26:15 +00001232 /* Weighted Average of pixels using reflected kernel
1233 **
1234 ** NOTE for correct working of this operation for asymetrical
1235 ** kernels, the kernel needs to be applied in its reflected form.
1236 ** That is its values needs to be reversed.
1237 **
1238 ** Correlation is actually the same as this but without reflecting
1239 ** the kernel, and thus 'lower-level' that Convolution. However
1240 ** as Convolution is the more common method used, and it does not
1241 ** really cost us much in terms of processing to use a reflected
1242 ** kernel it is Convolution that is implemented.
1243 **
1244 ** Correlation will have its kernel reflected before calling
1245 ** this function to do a Convolve.
1246 **
1247 ** For more details of Correlation vs Convolution see
1248 ** http://www.cs.umd.edu/~djacobs/CMSC426/Convolution.pdf
1249 */
anthony602ab9b2010-01-05 08:06:50 +00001250 if (((channel & OpacityChannel) == 0) ||
1251 (image->matte == MagickFalse))
1252 {
anthony930be612010-02-08 04:26:15 +00001253 /* Convolution without transparency effects */
anthony29188a82010-01-22 10:12:34 +00001254 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001255 k_pixels = p;
1256 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001257 for (v=0; v < (long) kernel->height; v++) {
1258 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001259 if ( IsNan(*k) ) continue;
1260 result.red += (*k)*k_pixels[u].red;
1261 result.green += (*k)*k_pixels[u].green;
1262 result.blue += (*k)*k_pixels[u].blue;
anthony83ba99b2010-01-24 08:48:15 +00001263 /* result.opacity += not involved here */
anthony602ab9b2010-01-05 08:06:50 +00001264 if ( image->colorspace == CMYKColorspace)
1265 result.index += (*k)*k_indexes[u];
1266 }
1267 k_pixels += image->columns+kernel->width;
1268 k_indexes += image->columns+kernel->width;
1269 }
anthony602ab9b2010-01-05 08:06:50 +00001270 }
1271 else
1272 { /* Kernel & Alpha weighted Convolution */
1273 MagickRealType
1274 alpha, /* alpha value * kernel weighting */
1275 gamma; /* weighting divisor */
1276
1277 gamma=0.0;
anthony29188a82010-01-22 10:12:34 +00001278 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001279 k_pixels = p;
1280 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001281 for (v=0; v < (long) kernel->height; v++) {
1282 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001283 if ( IsNan(*k) ) continue;
1284 alpha=(*k)*(QuantumScale*(QuantumRange-
1285 k_pixels[u].opacity));
1286 gamma += alpha;
1287 result.red += alpha*k_pixels[u].red;
1288 result.green += alpha*k_pixels[u].green;
1289 result.blue += alpha*k_pixels[u].blue;
anthony83ba99b2010-01-24 08:48:15 +00001290 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00001291 if ( image->colorspace == CMYKColorspace)
1292 result.index += alpha*k_indexes[u];
1293 }
1294 k_pixels += image->columns+kernel->width;
1295 k_indexes += image->columns+kernel->width;
1296 }
1297 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
anthony83ba99b2010-01-24 08:48:15 +00001298 result.red *= gamma;
1299 result.green *= gamma;
1300 result.blue *= gamma;
1301 result.opacity *= gamma;
1302 result.index *= gamma;
anthony602ab9b2010-01-05 08:06:50 +00001303 }
1304 break;
1305
anthony4fd27e22010-02-07 08:17:18 +00001306 case ErodeMorphology:
anthony930be612010-02-08 04:26:15 +00001307 /* Minimize Value within kernel neighbourhood
1308 **
1309 ** NOTE that the kernel is not reflected for this operation!
1310 **
1311 ** NOTE: in normal Greyscale Morphology, the kernel value should
1312 ** be added to the real value, this is currently not done, due to
1313 ** the nature of the boolean kernels being used.
1314 */
anthony4fd27e22010-02-07 08:17:18 +00001315 k = kernel->values;
1316 k_pixels = p;
1317 k_indexes = p_indexes;
1318 for (v=0; v < (long) kernel->height; v++) {
1319 for (u=0; u < (long) kernel->width; u++, k++) {
1320 if ( IsNan(*k) || (*k) < 0.5 ) continue;
1321 Minimize(result.red, (double) k_pixels[u].red);
1322 Minimize(result.green, (double) k_pixels[u].green);
1323 Minimize(result.blue, (double) k_pixels[u].blue);
1324 Minimize(result.opacity, QuantumRange-(double) k_pixels[u].opacity);
1325 if ( image->colorspace == CMYKColorspace)
1326 Minimize(result.index, (double) k_indexes[u]);
1327 }
1328 k_pixels += image->columns+kernel->width;
1329 k_indexes += image->columns+kernel->width;
1330 }
1331 break;
1332
anthony83ba99b2010-01-24 08:48:15 +00001333 case DilateMorphology:
anthony930be612010-02-08 04:26:15 +00001334 /* Maximize Value within kernel neighbourhood
1335 **
1336 ** NOTE for correct working of this operation for asymetrical
1337 ** kernels, the kernel needs to be applied in its reflected form.
1338 ** That is its values needs to be reversed.
1339 **
1340 ** NOTE: in normal Greyscale Morphology, the kernel value should
1341 ** be added to the real value, this is currently not done, due to
1342 ** the nature of the boolean kernels being used.
1343 **
1344 */
anthony29188a82010-01-22 10:12:34 +00001345 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001346 k_pixels = p;
1347 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001348 for (v=0; v < (long) kernel->height; v++) {
1349 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001350 if ( IsNan(*k) || (*k) < 0.5 ) continue;
cristy150989e2010-02-01 14:59:39 +00001351 Maximize(result.red, (double) k_pixels[u].red);
1352 Maximize(result.green, (double) k_pixels[u].green);
1353 Maximize(result.blue, (double) k_pixels[u].blue);
1354 Maximize(result.opacity, QuantumRange-(double) k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00001355 if ( image->colorspace == CMYKColorspace)
cristy150989e2010-02-01 14:59:39 +00001356 Maximize(result.index, (double) k_indexes[u]);
anthony602ab9b2010-01-05 08:06:50 +00001357 }
1358 k_pixels += image->columns+kernel->width;
1359 k_indexes += image->columns+kernel->width;
1360 }
anthony602ab9b2010-01-05 08:06:50 +00001361 break;
1362
anthony4fd27e22010-02-07 08:17:18 +00001363 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001364 /* Select Pixel with Minimum Intensity within kernel neighbourhood
1365 **
1366 ** WARNING: the intensity test fails for CMYK and does not
1367 ** take into account the moderating effect of teh alpha channel
1368 ** on the intensity.
1369 **
1370 ** NOTE that the kernel is not reflected for this operation!
1371 */
anthony602ab9b2010-01-05 08:06:50 +00001372 k = kernel->values;
1373 k_pixels = p;
1374 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001375 for (v=0; v < (long) kernel->height; v++) {
1376 for (u=0; u < (long) kernel->width; u++, k++) {
anthony602ab9b2010-01-05 08:06:50 +00001377 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony4fd27e22010-02-07 08:17:18 +00001378 if ( result.red == 0.0 ||
1379 PixelIntensity(&(k_pixels[u])) < PixelIntensity(q) ) {
1380 /* copy the whole pixel - no channel selection */
1381 *q = k_pixels[u];
1382 if ( result.red > 0.0 ) changed++;
1383 result.red = 1.0;
1384 }
anthony602ab9b2010-01-05 08:06:50 +00001385 }
1386 k_pixels += image->columns+kernel->width;
1387 k_indexes += image->columns+kernel->width;
1388 }
anthony602ab9b2010-01-05 08:06:50 +00001389 break;
1390
anthony83ba99b2010-01-24 08:48:15 +00001391 case DilateIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001392 /* Select Pixel with Maximum Intensity within kernel neighbourhood
1393 **
1394 ** WARNING: the intensity test fails for CMYK and does not
1395 ** take into account the moderating effect of teh alpha channel
1396 ** on the intensity.
1397 **
1398 ** NOTE for correct working of this operation for asymetrical
1399 ** kernels, the kernel needs to be applied in its reflected form.
1400 ** That is its values needs to be reversed.
1401 */
anthony29188a82010-01-22 10:12:34 +00001402 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001403 k_pixels = p;
1404 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001405 for (v=0; v < (long) kernel->height; v++) {
1406 for (u=0; u < (long) kernel->width; u++, k--) {
anthony29188a82010-01-22 10:12:34 +00001407 if ( IsNan(*k) || (*k) < 0.5 ) continue; /* boolean kernel */
1408 if ( result.red == 0.0 ||
1409 PixelIntensity(&(k_pixels[u])) > PixelIntensity(q) ) {
1410 /* copy the whole pixel - no channel selection */
1411 *q = k_pixels[u];
1412 if ( result.red > 0.0 ) changed++;
1413 result.red = 1.0;
1414 }
anthony602ab9b2010-01-05 08:06:50 +00001415 }
1416 k_pixels += image->columns+kernel->width;
1417 k_indexes += image->columns+kernel->width;
1418 }
anthony602ab9b2010-01-05 08:06:50 +00001419 break;
1420
anthony602ab9b2010-01-05 08:06:50 +00001421 case DistanceMorphology:
anthony930be612010-02-08 04:26:15 +00001422 /* Add kernel Value and select the minimum value found.
1423 ** The result is a iterative distance from edge of image shape.
1424 **
1425 ** All Distance Kernels are symetrical, but that may not always
1426 ** be the case. For example how about a distance from left edges?
1427 ** To work correctly with asymetrical kernels the reflected kernel
1428 ** needs to be applied.
1429 */
anthony602ab9b2010-01-05 08:06:50 +00001430#if 0
anthony930be612010-02-08 04:26:15 +00001431 /* No need to do distance morphology if original value is zero
1432 ** Unfortunatally I have not been able to get this right
1433 ** when channel selection also becomes involved. -- Arrgghhh
1434 */
1435 if ( ((channel & RedChannel) == 0 && p[r].red == 0)
1436 || ((channel & GreenChannel) == 0 && p[r].green == 0)
1437 || ((channel & BlueChannel) == 0 && p[r].blue == 0)
1438 || ((channel & OpacityChannel) == 0 && p[r].opacity == 0)
1439 || (( (channel & IndexChannel) == 0
1440 || image->colorspace != CMYKColorspace
1441 ) && p_indexes[x] ==0 )
1442 )
1443 break;
anthony602ab9b2010-01-05 08:06:50 +00001444#endif
anthony29188a82010-01-22 10:12:34 +00001445 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001446 k_pixels = p;
1447 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001448 for (v=0; v < (long) kernel->height; v++) {
1449 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001450 if ( IsNan(*k) ) continue;
1451 Minimize(result.red, (*k)+k_pixels[u].red);
1452 Minimize(result.green, (*k)+k_pixels[u].green);
1453 Minimize(result.blue, (*k)+k_pixels[u].blue);
1454 Minimize(result.opacity, (*k)+QuantumRange-k_pixels[u].opacity);
1455 if ( image->colorspace == CMYKColorspace)
1456 Minimize(result.index, (*k)+k_indexes[u]);
1457 }
1458 k_pixels += image->columns+kernel->width;
1459 k_indexes += image->columns+kernel->width;
1460 }
anthony602ab9b2010-01-05 08:06:50 +00001461 break;
1462
1463 case UndefinedMorphology:
1464 default:
1465 break; /* Do nothing */
anthony83ba99b2010-01-24 08:48:15 +00001466 }
1467 switch ( method ) {
1468 case UndefinedMorphology:
1469 case DilateIntensityMorphology:
1470 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001471 break; /* full pixel was directly assigned - not a channel method */
anthony83ba99b2010-01-24 08:48:15 +00001472 default:
1473 /* Assign the results */
1474 if ((channel & RedChannel) != 0)
1475 q->red = ClampToQuantum(result.red);
1476 if ((channel & GreenChannel) != 0)
1477 q->green = ClampToQuantum(result.green);
1478 if ((channel & BlueChannel) != 0)
1479 q->blue = ClampToQuantum(result.blue);
1480 if ((channel & OpacityChannel) != 0
1481 && image->matte == MagickTrue )
1482 q->opacity = ClampToQuantum(QuantumRange-result.opacity);
1483 if ((channel & IndexChannel) != 0
1484 && image->colorspace == CMYKColorspace)
1485 q_indexes[x] = ClampToQuantum(result.index);
1486 break;
1487 }
1488 if ( ( p[r].red != q->red )
1489 || ( p[r].green != q->green )
1490 || ( p[r].blue != q->blue )
1491 || ( p[r].opacity != q->opacity )
1492 || ( image->colorspace == CMYKColorspace &&
1493 p_indexes[r] != q_indexes[x] ) )
1494 changed++; /* The pixel had some value changed! */
anthony602ab9b2010-01-05 08:06:50 +00001495 p++;
1496 q++;
anthony83ba99b2010-01-24 08:48:15 +00001497 } /* x */
anthony602ab9b2010-01-05 08:06:50 +00001498 sync=SyncCacheViewAuthenticPixels(q_view,exception);
1499 if (sync == MagickFalse)
1500 status=MagickFalse;
1501 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1502 {
1503 MagickBooleanType
1504 proceed;
1505
1506#if defined(MAGICKCORE_OPENMP_SUPPORT)
1507 #pragma omp critical (MagickCore_MorphologyImage)
1508#endif
1509 proceed=SetImageProgress(image,MorphologyTag,progress++,image->rows);
1510 if (proceed == MagickFalse)
1511 status=MagickFalse;
1512 }
anthony83ba99b2010-01-24 08:48:15 +00001513 } /* y */
anthony602ab9b2010-01-05 08:06:50 +00001514 result_image->type=image->type;
1515 q_view=DestroyCacheView(q_view);
1516 p_view=DestroyCacheView(p_view);
cristy150989e2010-02-01 14:59:39 +00001517 return(status ? (unsigned long) changed : 0);
anthony602ab9b2010-01-05 08:06:50 +00001518}
1519
anthony4fd27e22010-02-07 08:17:18 +00001520
1521MagickExport Image *MorphologyImage(const Image *image, const MorphologyMethod
anthony930be612010-02-08 04:26:15 +00001522 method, const long iterations,const KernelInfo *kernel, ExceptionInfo
1523 *exception)
cristy2be15382010-01-21 02:38:03 +00001524{
1525 Image
1526 *morphology_image;
1527
1528 morphology_image=MorphologyImageChannel(image,DefaultChannels,method,
1529 iterations,kernel,exception);
1530 return(morphology_image);
1531}
1532
anthony4fd27e22010-02-07 08:17:18 +00001533
cristyef656912010-03-05 19:54:59 +00001534MagickExport Image *MorphologyImageChannel(const Image *image,
1535 const ChannelType channel,const MorphologyMethod method,
1536 const long iterations,const KernelInfo *kernel,ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001537{
cristy150989e2010-02-01 14:59:39 +00001538 long
1539 count;
anthony602ab9b2010-01-05 08:06:50 +00001540
1541 Image
1542 *new_image,
anthony4fd27e22010-02-07 08:17:18 +00001543 *old_image,
1544 *grad_image;
anthony602ab9b2010-01-05 08:06:50 +00001545
anthonycc6c8362010-01-25 04:14:01 +00001546 const char
1547 *artifact;
1548
cristy150989e2010-02-01 14:59:39 +00001549 unsigned long
1550 changed,
1551 limit;
1552
anthony4fd27e22010-02-07 08:17:18 +00001553 KernelInfo
1554 *curr_kernel;
1555
1556 MorphologyMethod
1557 curr_method;
1558
anthony602ab9b2010-01-05 08:06:50 +00001559 assert(image != (Image *) NULL);
1560 assert(image->signature == MagickSignature);
anthony4fd27e22010-02-07 08:17:18 +00001561 assert(kernel != (KernelInfo *) NULL);
1562 assert(kernel->signature == MagickSignature);
anthony602ab9b2010-01-05 08:06:50 +00001563 assert(exception != (ExceptionInfo *) NULL);
1564 assert(exception->signature == MagickSignature);
1565
anthony602ab9b2010-01-05 08:06:50 +00001566 if ( iterations == 0 )
1567 return((Image *)NULL); /* null operation - nothing to do! */
1568
1569 /* kernel must be valid at this point
1570 * (except maybe for posible future morphology methods like "Prune"
1571 */
cristy2be15382010-01-21 02:38:03 +00001572 assert(kernel != (KernelInfo *)NULL);
anthony602ab9b2010-01-05 08:06:50 +00001573
anthony4fd27e22010-02-07 08:17:18 +00001574 count = 0; /* interation count */
1575 changed = 1; /* if compound method assume image was changed */
anthony930be612010-02-08 04:26:15 +00001576 curr_kernel = (KernelInfo *)kernel; /* allow kernel and method */
1577 curr_method = method; /* to be changed as nessary */
anthony4fd27e22010-02-07 08:17:18 +00001578
cristy150989e2010-02-01 14:59:39 +00001579 limit = (unsigned long) iterations;
anthony602ab9b2010-01-05 08:06:50 +00001580 if ( iterations < 0 )
1581 limit = image->columns > image->rows ? image->columns : image->rows;
1582
anthony4fd27e22010-02-07 08:17:18 +00001583 /* Third-level morphology methods */
cristy5ee247a2010-02-12 15:42:34 +00001584 grad_image=(Image *) NULL;
anthony4fd27e22010-02-07 08:17:18 +00001585 switch( curr_method ) {
1586 case EdgeMorphology:
1587 grad_image = MorphologyImageChannel(image, channel,
1588 DilateMorphology, iterations, curr_kernel, exception);
1589 /* FALL-THRU */
1590 case EdgeInMorphology:
1591 curr_method = ErodeMorphology;
anthony602ab9b2010-01-05 08:06:50 +00001592 break;
anthony4fd27e22010-02-07 08:17:18 +00001593 case EdgeOutMorphology:
1594 curr_method = DilateMorphology;
anthony602ab9b2010-01-05 08:06:50 +00001595 break;
anthony4fd27e22010-02-07 08:17:18 +00001596 case TopHatMorphology:
1597 curr_method = OpenMorphology;
1598 break;
1599 case BottomHatMorphology:
1600 curr_method = CloseMorphology;
1601 break;
1602 default:
anthony930be612010-02-08 04:26:15 +00001603 break; /* not a third-level method */
anthony4fd27e22010-02-07 08:17:18 +00001604 }
1605
1606 /* Second-level morphology methods */
1607 switch( curr_method ) {
anthony930be612010-02-08 04:26:15 +00001608 case OpenMorphology:
1609 /* Open is a Erode then a Dilate without reflection */
anthony4fd27e22010-02-07 08:17:18 +00001610 new_image = MorphologyImageChannel(image, channel,
1611 ErodeMorphology, iterations, curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001612 if (new_image == (Image *) NULL)
1613 return((Image *) NULL);
anthony4fd27e22010-02-07 08:17:18 +00001614 curr_method = DilateMorphology;
1615 break;
anthony602ab9b2010-01-05 08:06:50 +00001616 case OpenIntensityMorphology:
anthony4fd27e22010-02-07 08:17:18 +00001617 new_image = MorphologyImageChannel(image, channel,
1618 ErodeIntensityMorphology, iterations, curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001619 if (new_image == (Image *) NULL)
1620 return((Image *) NULL);
anthony4fd27e22010-02-07 08:17:18 +00001621 curr_method = DilateIntensityMorphology;
1622 break;
anthony930be612010-02-08 04:26:15 +00001623
1624 case CloseMorphology:
1625 /* Close is a Dilate then Erode using reflected kernel */
1626 /* A reflected kernel is needed for a Close */
1627 if ( curr_kernel == kernel )
1628 curr_kernel = CloneKernelInfo(kernel);
1629 RotateKernelInfo(curr_kernel,180);
1630 new_image = MorphologyImageChannel(image, channel,
1631 DilateMorphology, iterations, curr_kernel, exception);
1632 if (new_image == (Image *) NULL)
1633 return((Image *) NULL);
1634 curr_method = ErodeMorphology;
1635 break;
anthony4fd27e22010-02-07 08:17:18 +00001636 case CloseIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001637 /* A reflected kernel is needed for a Close */
1638 if ( curr_kernel == kernel )
1639 curr_kernel = CloneKernelInfo(kernel);
anthony4fd27e22010-02-07 08:17:18 +00001640 RotateKernelInfo(curr_kernel,180);
1641 new_image = MorphologyImageChannel(image, channel,
1642 DilateIntensityMorphology, iterations, curr_kernel, exception);
1643 if (new_image == (Image *) NULL)
1644 return((Image *) NULL);
1645 curr_method = ErodeIntensityMorphology;
anthony602ab9b2010-01-05 08:06:50 +00001646 break;
1647
anthony930be612010-02-08 04:26:15 +00001648 case CorrelateMorphology:
1649 /* A Correlation is actually a Convolution with a reflected kernel.
1650 ** However a Convolution is a weighted sum with a reflected kernel.
1651 ** It may seem stange to convert a Correlation into a Convolution
1652 ** as the Correleation is the simplier method, but Convolution is
1653 ** much more commonly used, and it makes sense to implement it directly
1654 ** so as to avoid the need to duplicate the kernel when it is not
1655 ** required (which is typically the default).
1656 */
1657 if ( curr_kernel == kernel )
1658 curr_kernel = CloneKernelInfo(kernel);
1659 RotateKernelInfo(curr_kernel,180);
1660 curr_method = ConvolveMorphology;
1661 /* FALL-THRU into Correlate (weigthed sum without reflection) */
1662
anthonyc94cdb02010-01-06 08:15:29 +00001663 case ConvolveMorphology:
anthony4fd27e22010-02-07 08:17:18 +00001664 /* Scale or Normalize kernel, according to user wishes
anthony930be612010-02-08 04:26:15 +00001665 ** before using it for the Convolve/Correlate method.
1666 **
1667 ** FUTURE: provide some way for internal functions to disable
1668 ** user bias and scaling effects.
anthonycc6c8362010-01-25 04:14:01 +00001669 */
1670 artifact = GetImageArtifact(image,"convolve:scale");
anthony4fd27e22010-02-07 08:17:18 +00001671 if ( artifact != (char *)NULL ) {
anthony999bb2c2010-02-18 12:38:01 +00001672 MagickStatusType
1673 flags;
1674 GeometryInfo
1675 args;
1676
anthony930be612010-02-08 04:26:15 +00001677 if ( curr_kernel == kernel )
1678 curr_kernel = CloneKernelInfo(kernel);
anthony999bb2c2010-02-18 12:38:01 +00001679
1680 args.rho = 1.0;
1681 flags = ParseGeometry(artifact, &args);
1682 ScaleKernelInfo(curr_kernel, args.rho, flags);
anthony4fd27e22010-02-07 08:17:18 +00001683 }
anthony930be612010-02-08 04:26:15 +00001684 /* FALL-THRU to do the first, and typically the only iteration */
anthony4fd27e22010-02-07 08:17:18 +00001685
anthony602ab9b2010-01-05 08:06:50 +00001686 default:
anthony930be612010-02-08 04:26:15 +00001687 /* Do a single iteration using the Low-Level Morphology method!
1688 ** This ensures a "new_image" has been generated, but allows us to skip
1689 ** the creation of 'old_image' if no more iterations are needed.
1690 **
1691 ** The "curr_method" should also be set to a low-level method that is
1692 ** understood by the MorphologyApply() internal function.
anthony602ab9b2010-01-05 08:06:50 +00001693 */
1694 new_image=CloneImage(image,0,0,MagickTrue,exception);
1695 if (new_image == (Image *) NULL)
1696 return((Image *) NULL);
1697 if (SetImageStorageClass(new_image,DirectClass) == MagickFalse)
1698 {
1699 InheritException(exception,&new_image->exception);
1700 new_image=DestroyImage(new_image);
1701 return((Image *) NULL);
1702 }
anthony4fd27e22010-02-07 08:17:18 +00001703 changed = MorphologyApply(image,new_image,curr_method,channel,curr_kernel,
anthony602ab9b2010-01-05 08:06:50 +00001704 exception);
1705 count++;
1706 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
cristy150989e2010-02-01 14:59:39 +00001707 fprintf(stderr, "Morphology %s:%ld => Changed %lu\n",
anthony4fd27e22010-02-07 08:17:18 +00001708 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
anthony602ab9b2010-01-05 08:06:50 +00001709 count, changed);
anthony930be612010-02-08 04:26:15 +00001710 break;
anthony602ab9b2010-01-05 08:06:50 +00001711 }
1712
anthony930be612010-02-08 04:26:15 +00001713 /* At this point the "curr_method" should not only be set to a low-level
1714 ** method that is understood by the MorphologyApply() internal function,
1715 ** but "new_image" should now be defined, as the image to apply the
1716 ** "curr_method" to.
1717 */
1718
1719 /* Repeat the low-level morphology until count or no change reached */
cristy150989e2010-02-01 14:59:39 +00001720 if ( count < (long) limit && changed > 0 ) {
anthony602ab9b2010-01-05 08:06:50 +00001721 old_image = CloneImage(new_image,0,0,MagickTrue,exception);
1722 if (old_image == (Image *) NULL)
1723 return(DestroyImage(new_image));
1724 if (SetImageStorageClass(old_image,DirectClass) == MagickFalse)
1725 {
1726 InheritException(exception,&old_image->exception);
1727 old_image=DestroyImage(old_image);
1728 return(DestroyImage(new_image));
1729 }
cristy150989e2010-02-01 14:59:39 +00001730 while( count < (long) limit && changed != 0 )
anthony602ab9b2010-01-05 08:06:50 +00001731 {
1732 Image *tmp = old_image;
1733 old_image = new_image;
1734 new_image = tmp;
anthony4fd27e22010-02-07 08:17:18 +00001735 changed = MorphologyApply(old_image,new_image,curr_method,channel,
1736 curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001737 count++;
1738 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
cristy150989e2010-02-01 14:59:39 +00001739 fprintf(stderr, "Morphology %s:%ld => Changed %lu\n",
anthony4fd27e22010-02-07 08:17:18 +00001740 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
anthony602ab9b2010-01-05 08:06:50 +00001741 count, changed);
1742 }
cristy150989e2010-02-01 14:59:39 +00001743 old_image=DestroyImage(old_image);
anthony602ab9b2010-01-05 08:06:50 +00001744 }
anthony930be612010-02-08 04:26:15 +00001745
1746 /* We are finished with kernel - destroy it if we made a clone */
anthony4fd27e22010-02-07 08:17:18 +00001747 if ( curr_kernel != kernel )
1748 curr_kernel=DestroyKernelInfo(curr_kernel);
1749
anthony930be612010-02-08 04:26:15 +00001750 /* Third-level Subtractive methods post-processing */
anthony4fd27e22010-02-07 08:17:18 +00001751 switch( method ) {
1752 case EdgeOutMorphology:
1753 case EdgeInMorphology:
1754 case TopHatMorphology:
1755 case BottomHatMorphology:
anthony930be612010-02-08 04:26:15 +00001756 /* Get Difference relative to the original image */
cristy04ffdba2010-02-18 14:34:47 +00001757 (void) CompositeImageChannel(new_image, channel, DifferenceCompositeOp,
anthony4fd27e22010-02-07 08:17:18 +00001758 image, 0, 0);
1759 break;
anthony930be612010-02-08 04:26:15 +00001760 case EdgeMorphology: /* subtract the Erode from a Dilate */
cristy04ffdba2010-02-18 14:34:47 +00001761 (void) CompositeImageChannel(new_image, channel, DifferenceCompositeOp,
anthony4fd27e22010-02-07 08:17:18 +00001762 grad_image, 0, 0);
1763 grad_image=DestroyImage(grad_image);
1764 break;
1765 default:
1766 break;
1767 }
anthony602ab9b2010-01-05 08:06:50 +00001768
1769 return(new_image);
1770}
anthony83ba99b2010-01-24 08:48:15 +00001771
1772/*
1773%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1774% %
1775% %
1776% %
anthony4fd27e22010-02-07 08:17:18 +00001777+ R o t a t e K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00001778% %
1779% %
1780% %
1781%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1782%
anthony4fd27e22010-02-07 08:17:18 +00001783% RotateKernelInfo() rotates the kernel by the angle given. Currently it is
anthony83ba99b2010-01-24 08:48:15 +00001784% restricted to 90 degree angles, but this may be improved in the future.
1785%
anthony4fd27e22010-02-07 08:17:18 +00001786% The format of the RotateKernelInfo method is:
anthony83ba99b2010-01-24 08:48:15 +00001787%
anthony4fd27e22010-02-07 08:17:18 +00001788% void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00001789%
1790% A description of each parameter follows:
1791%
1792% o kernel: the Morphology/Convolution kernel
1793%
1794% o angle: angle to rotate in degrees
1795%
anthonyc4c86e02010-01-27 09:30:32 +00001796% This function is only internel to this module, as it is not finalized,
1797% especially with regard to non-orthogonal angles, and rotation of larger
1798% 2D kernels.
anthony83ba99b2010-01-24 08:48:15 +00001799*/
anthony4fd27e22010-02-07 08:17:18 +00001800static void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00001801{
1802 /* WARNING: Currently assumes the kernel (rightly) is horizontally symetrical
1803 **
1804 ** TODO: expand beyond simple 90 degree rotates, flips and flops
1805 */
1806
1807 /* Modulus the angle */
1808 angle = fmod(angle, 360.0);
1809 if ( angle < 0 )
1810 angle += 360.0;
1811
1812 if ( 315.0 < angle || angle <= 45.0 )
1813 return; /* no change! - At least at this time */
1814
1815 switch (kernel->type) {
1816 /* These built-in kernels are cylindrical kernels, rotating is useless */
1817 case GaussianKernel:
1818 case LaplacianKernel:
1819 case LOGKernel:
1820 case DOGKernel:
1821 case DiskKernel:
1822 case ChebyshevKernel:
1823 case ManhattenKernel:
1824 case EuclideanKernel:
1825 return;
1826
1827 /* These may be rotatable at non-90 angles in the future */
1828 /* but simply rotating them in multiples of 90 degrees is useless */
1829 case SquareKernel:
1830 case DiamondKernel:
1831 case PlusKernel:
1832 return;
1833
1834 /* These only allows a +/-90 degree rotation (by transpose) */
1835 /* A 180 degree rotation is useless */
1836 case BlurKernel:
1837 case RectangleKernel:
1838 if ( 135.0 < angle && angle <= 225.0 )
1839 return;
1840 if ( 225.0 < angle && angle <= 315.0 )
1841 angle -= 180;
1842 break;
1843
1844 /* these are freely rotatable in 90 degree units */
1845 case CometKernel:
1846 case UndefinedKernel:
1847 case UserDefinedKernel:
1848 break;
1849 }
1850 if ( 135.0 < angle && angle <= 225.0 )
1851 {
1852 /* For a 180 degree rotation - also know as a reflection */
1853 /* This is actually a very very common operation! */
1854 /* Basically all that is needed is a reversal of the kernel data! */
1855 unsigned long
1856 i,j;
1857 register double
1858 *k,t;
1859
1860 k=kernel->values;
1861 for ( i=0, j=kernel->width*kernel->height-1; i<j; i++, j--)
1862 t=k[i], k[i]=k[j], k[j]=t;
1863
anthony930be612010-02-08 04:26:15 +00001864 kernel->x = (long) kernel->width - kernel->x - 1;
1865 kernel->y = (long) kernel->height - kernel->y - 1;
anthony83ba99b2010-01-24 08:48:15 +00001866 angle = fmod(angle+180.0, 360.0);
1867 }
1868 if ( 45.0 < angle && angle <= 135.0 )
1869 { /* Do a transpose and a flop, of the image, which results in a 90
1870 * degree rotation using two mirror operations.
1871 *
1872 * WARNING: this assumes the original image was a 1 dimentional image
1873 * but currently that is the only built-ins it is applied to.
1874 */
cristy150989e2010-02-01 14:59:39 +00001875 long
anthony83ba99b2010-01-24 08:48:15 +00001876 t;
cristy150989e2010-02-01 14:59:39 +00001877 t = (long) kernel->width;
anthony83ba99b2010-01-24 08:48:15 +00001878 kernel->width = kernel->height;
cristy150989e2010-02-01 14:59:39 +00001879 kernel->height = (unsigned long) t;
cristyc99304f2010-02-01 15:26:27 +00001880 t = kernel->x;
1881 kernel->x = kernel->y;
1882 kernel->y = t;
anthony83ba99b2010-01-24 08:48:15 +00001883 angle = fmod(450.0 - angle, 360.0);
1884 }
1885 /* At this point angle should be between -45 (315) and +45 degrees
1886 * In the future some form of non-orthogonal angled rotates could be
1887 * performed here, posibily with a linear kernel restriction.
1888 */
1889
1890#if 0
1891 Not currently in use!
1892 { /* Do a flop, this assumes kernel is horizontally symetrical.
1893 * Each row of the kernel needs to be reversed!
1894 */
1895 unsigned long
1896 y;
cristy150989e2010-02-01 14:59:39 +00001897 register long
anthony83ba99b2010-01-24 08:48:15 +00001898 x,r;
1899 register double
1900 *k,t;
1901
1902 for ( y=0, k=kernel->values; y < kernel->height; y++, k+=kernel->width)
1903 for ( x=0, r=kernel->width-1; x<kernel->width/2; x++, r--)
1904 t=k[x], k[x]=k[r], k[r]=t;
1905
cristyc99304f2010-02-01 15:26:27 +00001906 kernel->x = kernel->width - kernel->x - 1;
anthony83ba99b2010-01-24 08:48:15 +00001907 angle = fmod(angle+180.0, 360.0);
1908 }
1909#endif
1910 return;
1911}
1912
1913/*
1914%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1915% %
1916% %
1917% %
cristy6771f1e2010-03-05 19:43:39 +00001918% S c a l e K e r n e l I n f o %
anthonycc6c8362010-01-25 04:14:01 +00001919% %
1920% %
1921% %
1922%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1923%
anthony999bb2c2010-02-18 12:38:01 +00001924% ScaleKernelInfo() scales the kernel by the given amount, with or without
1925% normalization of the sum of the kernel values.
anthonycc6c8362010-01-25 04:14:01 +00001926%
anthony999bb2c2010-02-18 12:38:01 +00001927% By default (no flags given) the values within the kernel is scaled
1928% according the given scaling factor.
anthonycc6c8362010-01-25 04:14:01 +00001929%
anthony999bb2c2010-02-18 12:38:01 +00001930% If any 'normalize_flags' are given the kernel will be normalized and then
1931% further scaled by the scaleing factor value given. A 'PercentValue' flag
1932% will cause the given scaling factor to be divided by one hundred percent.
1933%
1934% Kernel normalization ('normalize_flags' given) is designed to ensure that
1935% any use of the kernel scaling factor with 'Convolve' or 'Correlate'
1936% morphology methods will fall into -1.0 to +1.0 range. Note however that
1937% for non-HDRI versions of IM this may cause images to have any negative
1938% results clipped, unless some 'clip' any negative output from 'Convolve'
1939% with the use of some kernels.
1940%
1941% More specifically. Kernels which only contain positive values (such as a
1942% 'Gaussian' kernel) will be scaled so that those values sum to +1.0,
1943% ensuring a 0.0 to +1.0 convolution output range for non-HDRI images.
1944%
1945% For Kernels that contain some negative values, (such as 'Sharpen' kernels)
1946% the kernel will be scaled by the absolute of the sum of kernel values, so
1947% that it will generally fall within the +/- 1.0 range.
1948%
1949% For kernels whose values sum to zero, (such as 'Laplician' kernels) kernel
1950% will be scaled by just the sum of the postive values, so that its output
1951% range will again fall into the +/- 1.0 range.
1952%
1953% For special kernels designed for locating shapes using 'Correlate', (often
1954% only containing +1 and -1 values, representing foreground/brackground
1955% matching) a special normalization method is provided to scale the positive
1956% values seperatally to those of the negative values, so the kernel will be
1957% forced to become a zero-sum kernel better suited to such searches.
1958%
1959% WARNING: Correct normalization of the kernal assumes that the '*_range'
1960% attributes within the kernel structure have been correctly set during the
1961% kernels creation.
1962%
1963% NOTE: The values used for 'normalize_flags' have been selected specifically
1964% to match the use of geometry options, so that '!' means NormalizeValue, '^'
1965% means CorrelateNormalizeValue, and '%' means PercentValue. All other
1966% GeometryFlags values are ignored.
anthonycc6c8362010-01-25 04:14:01 +00001967%
anthony4fd27e22010-02-07 08:17:18 +00001968% The format of the ScaleKernelInfo method is:
anthonycc6c8362010-01-25 04:14:01 +00001969%
anthony999bb2c2010-02-18 12:38:01 +00001970% void ScaleKernelInfo(KernelInfo *kernel, const double scaling_factor,
1971% const MagickStatusType normalize_flags )
anthonycc6c8362010-01-25 04:14:01 +00001972%
1973% A description of each parameter follows:
1974%
1975% o kernel: the Morphology/Convolution kernel
1976%
anthony999bb2c2010-02-18 12:38:01 +00001977% o scaling_factor:
1978% multiply all values (after normalization) by this factor if not
1979% zero. If the kernel is normalized regardless of any flags.
1980%
1981% o normalize_flags:
1982% GeometryFlags defining normalization method to use.
1983% specifically: NormalizeValue, CorrelateNormalizeValue,
1984% and/or PercentValue
anthonycc6c8362010-01-25 04:14:01 +00001985%
anthonyc4c86e02010-01-27 09:30:32 +00001986% This function is internal to this module only at this time, but can be
1987% exported to other modules if needed.
anthonycc6c8362010-01-25 04:14:01 +00001988*/
cristy6771f1e2010-03-05 19:43:39 +00001989MagickExport void ScaleKernelInfo(KernelInfo *kernel,
1990 const double scaling_factor,const GeometryFlags normalize_flags)
anthonycc6c8362010-01-25 04:14:01 +00001991{
cristy150989e2010-02-01 14:59:39 +00001992 register long
anthonycc6c8362010-01-25 04:14:01 +00001993 i;
1994
anthony999bb2c2010-02-18 12:38:01 +00001995 register double
1996 pos_scale,
1997 neg_scale;
1998
1999 pos_scale = 1.0;
2000 if ( (normalize_flags&NormalizeValue) != 0 ) {
2001 /* normalize kernel appropriately */
2002 if ( fabs(kernel->positive_range + kernel->negative_range) > MagickEpsilon )
2003 pos_scale = fabs(kernel->positive_range + kernel->negative_range);
anthonycc6c8362010-01-25 04:14:01 +00002004 else
anthony999bb2c2010-02-18 12:38:01 +00002005 pos_scale = kernel->positive_range; /* special zero-summing kernel */
2006 }
2007 /* force kernel into being a normalized zero-summing kernel */
2008 if ( (normalize_flags&CorrelateNormalizeValue) != 0 ) {
2009 pos_scale = ( fabs(kernel->positive_range) > MagickEpsilon )
2010 ? kernel->positive_range : 1.0;
2011 neg_scale = ( fabs(kernel->negative_range) > MagickEpsilon )
2012 ? -kernel->negative_range : 1.0;
2013 }
2014 else
2015 neg_scale = pos_scale;
2016
2017 /* finialize scaling_factor for positive and negative components */
2018 pos_scale = scaling_factor/pos_scale;
2019 neg_scale = scaling_factor/neg_scale;
2020 if ( (normalize_flags&PercentValue) != 0 ) {
2021 pos_scale /= 100.0;
2022 neg_scale /= 100.0;
anthonycc6c8362010-01-25 04:14:01 +00002023 }
2024
cristy150989e2010-02-01 14:59:39 +00002025 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00002026 if ( ! IsNan(kernel->values[i]) )
anthony999bb2c2010-02-18 12:38:01 +00002027 kernel->values[i] *= (kernel->values[i] >= 0) ? pos_scale : neg_scale;
anthonycc6c8362010-01-25 04:14:01 +00002028
anthony999bb2c2010-02-18 12:38:01 +00002029 /* convolution output range */
2030 kernel->positive_range *= pos_scale;
2031 kernel->negative_range *= neg_scale;
2032 /* maximum and minimum values in kernel */
2033 kernel->maximum *= (kernel->maximum >= 0.0) ? pos_scale : neg_scale;
2034 kernel->minimum *= (kernel->minimum >= 0.0) ? pos_scale : neg_scale;
2035
2036 /* swap kernel settings if user scaling factor is negative */
2037 if ( scaling_factor < MagickEpsilon ) {
2038 double t;
2039 t = kernel->positive_range;
2040 kernel->positive_range = kernel->negative_range;
2041 kernel->negative_range = t;
2042 t = kernel->maximum;
2043 kernel->maximum = kernel->minimum;
2044 kernel->minimum = 1;
2045 }
anthonycc6c8362010-01-25 04:14:01 +00002046
2047 return;
2048}
2049
2050/*
2051%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2052% %
2053% %
2054% %
anthony4fd27e22010-02-07 08:17:18 +00002055+ S h o w K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00002056% %
2057% %
2058% %
2059%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2060%
anthony4fd27e22010-02-07 08:17:18 +00002061% ShowKernelInfo() outputs the details of the given kernel defination to
2062% standard error, generally due to a users 'showkernel' option request.
anthony83ba99b2010-01-24 08:48:15 +00002063%
2064% The format of the ShowKernel method is:
2065%
anthony4fd27e22010-02-07 08:17:18 +00002066% void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00002067%
2068% A description of each parameter follows:
2069%
2070% o kernel: the Morphology/Convolution kernel
2071%
anthonyc4c86e02010-01-27 09:30:32 +00002072% This function is internal to this module only at this time. That may change
2073% in the future.
anthony83ba99b2010-01-24 08:48:15 +00002074*/
anthony4fd27e22010-02-07 08:17:18 +00002075MagickExport void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00002076{
cristy150989e2010-02-01 14:59:39 +00002077 long
anthony83ba99b2010-01-24 08:48:15 +00002078 i, u, v;
2079
2080 fprintf(stderr,
anthonycc6c8362010-01-25 04:14:01 +00002081 "Kernel \"%s\" of size %lux%lu%+ld%+ld with values from %.*lg to %.*lg\n",
anthony83ba99b2010-01-24 08:48:15 +00002082 MagickOptionToMnemonic(MagickKernelOptions, kernel->type),
2083 kernel->width, kernel->height,
cristyc99304f2010-02-01 15:26:27 +00002084 kernel->x, kernel->y,
2085 GetMagickPrecision(), kernel->minimum,
2086 GetMagickPrecision(), kernel->maximum);
anthonycc6c8362010-01-25 04:14:01 +00002087 fprintf(stderr, "Forming convolution output range from %.*lg to %.*lg%s\n",
cristyc99304f2010-02-01 15:26:27 +00002088 GetMagickPrecision(), kernel->negative_range,
2089 GetMagickPrecision(), kernel->positive_range,
anthonycc6c8362010-01-25 04:14:01 +00002090 /*kernel->normalized == MagickTrue ? " (normalized)" : */ "" );
cristy150989e2010-02-01 14:59:39 +00002091 for (i=v=0; v < (long) kernel->height; v++) {
anthony83ba99b2010-01-24 08:48:15 +00002092 fprintf(stderr,"%2ld:",v);
cristy150989e2010-02-01 14:59:39 +00002093 for (u=0; u < (long) kernel->width; u++, i++)
anthony83ba99b2010-01-24 08:48:15 +00002094 if ( IsNan(kernel->values[i]) )
2095 fprintf(stderr," %*s", GetMagickPrecision()+2, "nan");
2096 else
2097 fprintf(stderr," %*.*lg", GetMagickPrecision()+2,
2098 GetMagickPrecision(), kernel->values[i]);
2099 fprintf(stderr,"\n");
2100 }
2101}
anthonycc6c8362010-01-25 04:14:01 +00002102
2103/*
2104%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2105% %
2106% %
2107% %
anthony4fd27e22010-02-07 08:17:18 +00002108+ Z e r o K e r n e l N a n s %
anthonycc6c8362010-01-25 04:14:01 +00002109% %
2110% %
2111% %
2112%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2113%
2114% ZeroKernelNans() replaces any special 'nan' value that may be present in
2115% the kernel with a zero value. This is typically done when the kernel will
2116% be used in special hardware (GPU) convolution processors, to simply
2117% matters.
2118%
2119% The format of the ZeroKernelNans method is:
2120%
2121% voidZeroKernelNans (KernelInfo *kernel)
2122%
2123% A description of each parameter follows:
2124%
2125% o kernel: the Morphology/Convolution kernel
2126%
2127% FUTURE: return the information in a string for API usage.
2128*/
anthonyc4c86e02010-01-27 09:30:32 +00002129MagickExport void ZeroKernelNans(KernelInfo *kernel)
anthonycc6c8362010-01-25 04:14:01 +00002130{
cristy150989e2010-02-01 14:59:39 +00002131 register long
anthonycc6c8362010-01-25 04:14:01 +00002132 i;
2133
cristy150989e2010-02-01 14:59:39 +00002134 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00002135 if ( IsNan(kernel->values[i]) )
2136 kernel->values[i] = 0.0;
2137
2138 return;
2139}