blob: 61d10419ccbf87f077e5133330628f75d1a6ca43 [file] [log] [blame]
cristy701db312009-11-20 03:14:08 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% M M OOO RRRR PPPP H H OOO L OOO GGGG Y Y %
7% MM MM O O R R P P H H O O L O O G Y Y %
8% M M M O O RRRR PPPP HHHHH O O L O O G GGG Y %
9% M M O O R R P H H O O L O O G G Y %
10% M M OOO R R P H H OOO LLLLL OOO GGG Y %
11% %
12% %
13% MagickCore Morphology Methods %
14% %
15% Software Design %
16% Anthony Thyssen %
anthonyc94cdb02010-01-06 08:15:29 +000017% January 2010 %
cristy701db312009-11-20 03:14:08 +000018% %
19% %
cristy16af1cb2009-12-11 21:38:29 +000020% Copyright 1999-2010 ImageMagick Studio LLC, a non-profit organization %
cristy701db312009-11-20 03:14:08 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
anthony602ab9b2010-01-05 08:06:50 +000036% Morpology is the the application of various kernals, of any size and even
37% shape, to a image in various ways (typically binary, but not always).
cristy701db312009-11-20 03:14:08 +000038%
anthony602ab9b2010-01-05 08:06:50 +000039% Convolution (weighted sum or average) is just one specific type of
40% morphology. Just one that is very common for image bluring and sharpening
41% effects. Not only 2D Gaussian blurring, but also 2-pass 1D Blurring.
42%
43% This module provides not only a general morphology function, and the ability
44% to apply more advanced or iterative morphologies, but also functions for the
45% generation of many different types of kernel arrays from user supplied
46% arguments. Prehaps even the generation of a kernel from a small image.
cristy701db312009-11-20 03:14:08 +000047*/
48
49/*
50 Include declarations.
51*/
52#include "magick/studio.h"
anthony602ab9b2010-01-05 08:06:50 +000053#include "magick/artifact.h"
cristy701db312009-11-20 03:14:08 +000054#include "magick/cache-view.h"
55#include "magick/color-private.h"
56#include "magick/enhance.h"
57#include "magick/exception.h"
58#include "magick/exception-private.h"
anthony602ab9b2010-01-05 08:06:50 +000059#include "magick/gem.h"
cristy701db312009-11-20 03:14:08 +000060#include "magick/hashmap.h"
61#include "magick/image.h"
cristybba804b2010-01-05 15:39:59 +000062#include "magick/image-private.h"
cristy701db312009-11-20 03:14:08 +000063#include "magick/list.h"
anthony29188a82010-01-22 10:12:34 +000064#include "magick/magick.h"
cristy701db312009-11-20 03:14:08 +000065#include "magick/memory_.h"
66#include "magick/monitor-private.h"
67#include "magick/morphology.h"
anthony602ab9b2010-01-05 08:06:50 +000068#include "magick/option.h"
cristy701db312009-11-20 03:14:08 +000069#include "magick/pixel-private.h"
70#include "magick/prepress.h"
71#include "magick/quantize.h"
72#include "magick/registry.h"
73#include "magick/semaphore.h"
74#include "magick/splay-tree.h"
75#include "magick/statistic.h"
76#include "magick/string_.h"
anthony602ab9b2010-01-05 08:06:50 +000077#include "magick/string-private.h"
78#include "magick/token.h"
79
anthonyc94cdb02010-01-06 08:15:29 +000080
anthony602ab9b2010-01-05 08:06:50 +000081/*
anthonyc94cdb02010-01-06 08:15:29 +000082 * The following test is for special floating point numbers of value NaN (not
83 * a number), that may be used within a Kernel Definition. NaN's are defined
84 * as part of the IEEE standard for floating point number representation.
anthony602ab9b2010-01-05 08:06:50 +000085 *
anthonyc94cdb02010-01-06 08:15:29 +000086 * These are used a Kernel value of NaN means that that kernal position is not
87 * part of the normal convolution or morphology process, and thus allowing the
88 * use of 'shaped' kernels.
anthony602ab9b2010-01-05 08:06:50 +000089 *
anthonyc94cdb02010-01-06 08:15:29 +000090 * Special Properities Two NaN's are never equal, even if they are from the
91 * same variable That is the IsNaN() macro is only true if the value is NaN.
anthony602ab9b2010-01-05 08:06:50 +000092 */
93#define IsNan(a) ((a)!=(a))
94
anthony29188a82010-01-22 10:12:34 +000095/*
96 * Other global definitions used by module
97 */
98static inline double MagickMin(const double x,const double y)
99{
100 return( x < y ? x : y);
101}
102static inline double MagickMax(const double x,const double y)
103{
104 return( x > y ? x : y);
105}
106#define Minimize(assign,value) assign=MagickMin(assign,value)
107#define Maximize(assign,value) assign=MagickMax(assign,value)
108
anthonyc4c86e02010-01-27 09:30:32 +0000109/* Currently these are only internal to this module */
110static void
anthony202f89d2010-03-03 05:01:47 +0000111 RotateKernelInfo(KernelInfo *, double),
112 ScaleKernelInfo(KernelInfo *, const double, const MagickStatusType);
anthony4fd27e22010-02-07 08:17:18 +0000113
114static KernelInfo
anthony930be612010-02-08 04:26:15 +0000115 *CloneKernelInfo(const KernelInfo *);
anthony83ba99b2010-01-24 08:48:15 +0000116
anthony602ab9b2010-01-05 08:06:50 +0000117
118/*
119%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
120% %
121% %
122% %
anthony83ba99b2010-01-24 08:48:15 +0000123% A c q u i r e K e r n e l I n f o %
anthony602ab9b2010-01-05 08:06:50 +0000124% %
125% %
126% %
127%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
128%
cristy2be15382010-01-21 02:38:03 +0000129% AcquireKernelInfo() takes the given string (generally supplied by the
anthony602ab9b2010-01-05 08:06:50 +0000130% user) and converts it into a Morphology/Convolution Kernel. This allows
131% users to specify a kernel from a number of pre-defined kernels, or to fully
132% specify their own kernel for a specific Convolution or Morphology
133% Operation.
134%
135% The kernel so generated can be any rectangular array of floating point
136% values (doubles) with the 'control point' or 'pixel being affected'
137% anywhere within that array of values.
138%
anthony83ba99b2010-01-24 08:48:15 +0000139% Previously IM was restricted to a square of odd size using the exact
140% center as origin, this is no longer the case, and any rectangular kernel
141% with any value being declared the origin. This in turn allows the use of
142% highly asymmetrical kernels.
anthony602ab9b2010-01-05 08:06:50 +0000143%
144% The floating point values in the kernel can also include a special value
anthony83ba99b2010-01-24 08:48:15 +0000145% known as 'nan' or 'not a number' to indicate that this value is not part
146% of the kernel array. This allows you to shaped the kernel within its
147% rectangular area. That is 'nan' values provide a 'mask' for the kernel
148% shape. However at least one non-nan value must be provided for correct
149% working of a kernel.
anthony602ab9b2010-01-05 08:06:50 +0000150%
anthony83ba99b2010-01-24 08:48:15 +0000151% The returned kernel should be free using the DestroyKernelInfo() when you
152% are finished with it.
anthony602ab9b2010-01-05 08:06:50 +0000153%
154% Input kernel defintion strings can consist of any of three types.
155%
anthony29188a82010-01-22 10:12:34 +0000156% "name:args"
157% Select from one of the built in kernels, using the name and
158% geometry arguments supplied. See AcquireKernelBuiltIn()
anthony602ab9b2010-01-05 08:06:50 +0000159%
160% "WxH[+X+Y]:num, num, num ..."
161% a kernal of size W by H, with W*H floating point numbers following.
162% the 'center' can be optionally be defined at +X+Y (such that +0+0
anthony29188a82010-01-22 10:12:34 +0000163% is top left corner). If not defined the pixel in the center, for
164% odd sizes, or to the immediate top or left of center for even sizes
165% is automatically selected.
anthony602ab9b2010-01-05 08:06:50 +0000166%
anthony29188a82010-01-22 10:12:34 +0000167% "num, num, num, num, ..."
168% list of floating point numbers defining an 'old style' odd sized
169% square kernel. At least 9 values should be provided for a 3x3
170% square kernel, 25 for a 5x5 square kernel, 49 for 7x7, etc.
171% Values can be space or comma separated. This is not recommended.
anthony602ab9b2010-01-05 08:06:50 +0000172%
anthony83ba99b2010-01-24 08:48:15 +0000173% Note that 'name' kernels will start with an alphabetic character while the
174% new kernel specification has a ':' character in its specification string.
175% If neither is the case, it is assumed an old style of a simple list of
176% numbers generating a odd-sized square kernel has been given.
anthony602ab9b2010-01-05 08:06:50 +0000177%
178% The format of the AcquireKernal method is:
179%
cristy2be15382010-01-21 02:38:03 +0000180% KernelInfo *AcquireKernelInfo(const char *kernel_string)
anthony602ab9b2010-01-05 08:06:50 +0000181%
182% A description of each parameter follows:
183%
184% o kernel_string: the Morphology/Convolution kernel wanted.
185%
186*/
187
cristy2be15382010-01-21 02:38:03 +0000188MagickExport KernelInfo *AcquireKernelInfo(const char *kernel_string)
anthony602ab9b2010-01-05 08:06:50 +0000189{
cristy2be15382010-01-21 02:38:03 +0000190 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000191 *kernel;
192
193 char
194 token[MaxTextExtent];
195
cristy150989e2010-02-01 14:59:39 +0000196 register long
anthony602ab9b2010-01-05 08:06:50 +0000197 i;
198
199 const char
200 *p;
201
202 MagickStatusType
203 flags;
204
205 GeometryInfo
206 args;
207
anthony29188a82010-01-22 10:12:34 +0000208 double
209 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
210
anthony602ab9b2010-01-05 08:06:50 +0000211 assert(kernel_string != (const char *) NULL);
212 SetGeometryInfo(&args);
213
214 /* does it start with an alpha - Return a builtin kernel */
215 GetMagickToken(kernel_string,&p,token);
216 if ( isalpha((int)token[0]) )
217 {
218 long
219 type;
220
anthony29188a82010-01-22 10:12:34 +0000221 type=ParseMagickOption(MagickKernelOptions,MagickFalse,token);
anthony602ab9b2010-01-05 08:06:50 +0000222 if ( type < 0 || type == UserDefinedKernel )
cristy2be15382010-01-21 02:38:03 +0000223 return((KernelInfo *)NULL);
anthony602ab9b2010-01-05 08:06:50 +0000224
225 while (((isspace((int) ((unsigned char) *p)) != 0) ||
226 (*p == ',') || (*p == ':' )) && (*p != '\0'))
227 p++;
228 flags = ParseGeometry(p, &args);
229
230 /* special handling of missing values in input string */
anthony4fd27e22010-02-07 08:17:18 +0000231 switch( type ) {
232 case RectangleKernel:
anthony602ab9b2010-01-05 08:06:50 +0000233 if ( (flags & WidthValue) == 0 ) /* if no width then */
234 args.rho = args.sigma; /* then width = height */
235 if ( args.rho < 1.0 ) /* if width too small */
236 args.rho = 3; /* then width = 3 */
237 if ( args.sigma < 1.0 ) /* if height too small */
238 args.sigma = args.rho; /* then height = width */
239 if ( (flags & XValue) == 0 ) /* center offset if not defined */
240 args.xi = (double)(((long)args.rho-1)/2);
241 if ( (flags & YValue) == 0 )
242 args.psi = (double)(((long)args.sigma-1)/2);
anthony4fd27e22010-02-07 08:17:18 +0000243 break;
244 case SquareKernel:
245 case DiamondKernel:
246 case DiskKernel:
247 case PlusKernel:
248 if ( (flags & HeightValue) == 0 ) /* if no scale */
249 args.sigma = 1.0; /* then scale = 1.0 */
250 break;
251 default:
252 break;
anthony602ab9b2010-01-05 08:06:50 +0000253 }
254
cristy2be15382010-01-21 02:38:03 +0000255 return(AcquireKernelBuiltIn((KernelInfoType)type, &args));
anthony602ab9b2010-01-05 08:06:50 +0000256 }
257
cristy2be15382010-01-21 02:38:03 +0000258 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
259 if (kernel == (KernelInfo *)NULL)
anthony602ab9b2010-01-05 08:06:50 +0000260 return(kernel);
261 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
262 kernel->type = UserDefinedKernel;
cristyd43a46b2010-01-21 02:13:41 +0000263 kernel->signature = MagickSignature;
anthony602ab9b2010-01-05 08:06:50 +0000264
265 /* Has a ':' in argument - New user kernel specification */
266 p = strchr(kernel_string, ':');
267 if ( p != (char *) NULL)
268 {
anthony602ab9b2010-01-05 08:06:50 +0000269 /* ParseGeometry() needs the geometry separated! -- Arrgghh */
cristy150989e2010-02-01 14:59:39 +0000270 memcpy(token, kernel_string, (size_t) (p-kernel_string));
anthony602ab9b2010-01-05 08:06:50 +0000271 token[p-kernel_string] = '\0';
272 flags = ParseGeometry(token, &args);
anthony602ab9b2010-01-05 08:06:50 +0000273
anthony29188a82010-01-22 10:12:34 +0000274 /* Size handling and checks of geometry settings */
anthony602ab9b2010-01-05 08:06:50 +0000275 if ( (flags & WidthValue) == 0 ) /* if no width then */
276 args.rho = args.sigma; /* then width = height */
277 if ( args.rho < 1.0 ) /* if width too small */
278 args.rho = 1.0; /* then width = 1 */
279 if ( args.sigma < 1.0 ) /* if height too small */
280 args.sigma = args.rho; /* then height = width */
281 kernel->width = (unsigned long)args.rho;
282 kernel->height = (unsigned long)args.sigma;
283
284 /* Offset Handling and Checks */
285 if ( args.xi < 0.0 || args.psi < 0.0 )
anthony83ba99b2010-01-24 08:48:15 +0000286 return(DestroyKernelInfo(kernel));
cristyc99304f2010-02-01 15:26:27 +0000287 kernel->x = ((flags & XValue)!=0) ? (long)args.xi
cristy150989e2010-02-01 14:59:39 +0000288 : (long) (kernel->width-1)/2;
cristyc99304f2010-02-01 15:26:27 +0000289 kernel->y = ((flags & YValue)!=0) ? (long)args.psi
cristy150989e2010-02-01 14:59:39 +0000290 : (long) (kernel->height-1)/2;
cristyc99304f2010-02-01 15:26:27 +0000291 if ( kernel->x >= (long) kernel->width ||
292 kernel->y >= (long) kernel->height )
anthony83ba99b2010-01-24 08:48:15 +0000293 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000294
295 p++; /* advance beyond the ':' */
296 }
297 else
298 { /* ELSE - Old old kernel specification, forming odd-square kernel */
299 /* count up number of values given */
300 p=(const char *) kernel_string;
cristya699b172010-01-06 16:48:49 +0000301 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
anthony29188a82010-01-22 10:12:34 +0000302 p++; /* ignore "'" chars for convolve filter usage - Cristy */
anthony602ab9b2010-01-05 08:06:50 +0000303 for (i=0; *p != '\0'; i++)
304 {
305 GetMagickToken(p,&p,token);
306 if (*token == ',')
307 GetMagickToken(p,&p,token);
308 }
309 /* set the size of the kernel - old sized square */
310 kernel->width = kernel->height= (unsigned long) sqrt((double) i+1.0);
cristyc99304f2010-02-01 15:26:27 +0000311 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000312 p=(const char *) kernel_string;
anthony29188a82010-01-22 10:12:34 +0000313 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
314 p++; /* ignore "'" chars for convolve filter usage - Cristy */
anthony602ab9b2010-01-05 08:06:50 +0000315 }
316
317 /* Read in the kernel values from rest of input string argument */
318 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
319 kernel->height*sizeof(double));
320 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000321 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000322
cristyc99304f2010-02-01 15:26:27 +0000323 kernel->minimum = +MagickHuge;
324 kernel->maximum = -MagickHuge;
325 kernel->negative_range = kernel->positive_range = 0.0;
cristy150989e2010-02-01 14:59:39 +0000326 for (i=0; (i < (long) (kernel->width*kernel->height)) && (*p != '\0'); i++)
anthony602ab9b2010-01-05 08:06:50 +0000327 {
328 GetMagickToken(p,&p,token);
329 if (*token == ',')
330 GetMagickToken(p,&p,token);
anthony29188a82010-01-22 10:12:34 +0000331 if ( LocaleCompare("nan",token) == 0
332 || LocaleCompare("-",token) == 0 ) {
333 kernel->values[i] = nan; /* do not include this value in kernel */
334 }
335 else {
336 kernel->values[i] = StringToDouble(token);
337 ( kernel->values[i] < 0)
cristyc99304f2010-02-01 15:26:27 +0000338 ? ( kernel->negative_range += kernel->values[i] )
339 : ( kernel->positive_range += kernel->values[i] );
340 Minimize(kernel->minimum, kernel->values[i]);
341 Maximize(kernel->maximum, kernel->values[i]);
anthony29188a82010-01-22 10:12:34 +0000342 }
anthony602ab9b2010-01-05 08:06:50 +0000343 }
anthonycc6c8362010-01-25 04:14:01 +0000344 /* check that we recieved at least one real (non-nan) value! */
cristyc99304f2010-02-01 15:26:27 +0000345 if ( kernel->minimum == MagickHuge )
anthonycc6c8362010-01-25 04:14:01 +0000346 return(DestroyKernelInfo(kernel));
anthony29188a82010-01-22 10:12:34 +0000347
anthonycc6c8362010-01-25 04:14:01 +0000348 /* This should not be needed for a fully defined kernel
anthony29188a82010-01-22 10:12:34 +0000349 * Perhaps an error should be reported instead!
anthonycc6c8362010-01-25 04:14:01 +0000350 * Kept for backward compatibility.
anthony29188a82010-01-22 10:12:34 +0000351 */
cristy150989e2010-02-01 14:59:39 +0000352 if ( i < (long) (kernel->width*kernel->height) ) {
cristyc99304f2010-02-01 15:26:27 +0000353 Minimize(kernel->minimum, kernel->values[i]);
354 Maximize(kernel->maximum, kernel->values[i]);
cristy150989e2010-02-01 14:59:39 +0000355 for ( ; i < (long) (kernel->width*kernel->height); i++)
anthony29188a82010-01-22 10:12:34 +0000356 kernel->values[i]=0.0;
357 }
anthony602ab9b2010-01-05 08:06:50 +0000358
359 return(kernel);
360}
361
362/*
363%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
364% %
365% %
366% %
367% A c q u i r e K e r n e l B u i l t I n %
368% %
369% %
370% %
371%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
372%
373% AcquireKernelBuiltIn() returned one of the 'named' built-in types of
374% kernels used for special purposes such as gaussian blurring, skeleton
375% pruning, and edge distance determination.
376%
377% They take a KernelType, and a set of geometry style arguments, which were
378% typically decoded from a user supplied string, or from a more complex
379% Morphology Method that was requested.
380%
381% The format of the AcquireKernalBuiltIn method is:
382%
cristy2be15382010-01-21 02:38:03 +0000383% KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000384% const GeometryInfo args)
385%
386% A description of each parameter follows:
387%
388% o type: the pre-defined type of kernel wanted
389%
390% o args: arguments defining or modifying the kernel
391%
392% Convolution Kernels
393%
anthony4fd27e22010-02-07 08:17:18 +0000394% Gaussian "{radius},{sigma}"
anthony602ab9b2010-01-05 08:06:50 +0000395% Generate a two-dimentional gaussian kernel, as used by -gaussian
396% A sigma is required, (with the 'x'), due to historical reasons.
397%
398% NOTE: that the 'radius' is optional, but if provided can limit (clip)
399% the final size of the resulting kernel to a square 2*radius+1 in size.
400% The radius should be at least 2 times that of the sigma value, or
401% sever clipping and aliasing may result. If not given or set to 0 the
402% radius will be determined so as to produce the best minimal error
403% result, which is usally much larger than is normally needed.
404%
anthony4fd27e22010-02-07 08:17:18 +0000405% Blur "{radius},{sigma},{angle}"
anthony602ab9b2010-01-05 08:06:50 +0000406% As per Gaussian, but generates a 1 dimensional or linear gaussian
407% blur, at the angle given (current restricted to orthogonal angles).
408% If a 'radius' is given the kernel is clipped to a width of 2*radius+1.
409%
410% NOTE that two such blurs perpendicular to each other is equivelent to
411% -blur and the previous gaussian, but is often 10 or more times faster.
412%
anthony4fd27e22010-02-07 08:17:18 +0000413% Comet "{width},{sigma},{angle}"
anthony602ab9b2010-01-05 08:06:50 +0000414% Blur in one direction only, mush like how a bright object leaves
415% a comet like trail. The Kernel is actually half a gaussian curve,
416% Adding two such blurs in oppiste directions produces a Linear Blur.
417%
418% NOTE: that the first argument is the width of the kernel and not the
419% radius of the kernel.
420%
421% # Still to be implemented...
422% #
anthony4fd27e22010-02-07 08:17:18 +0000423% # Sharpen "{radius},{sigma}
424% # Negated Gaussian (center zeroed and re-normalized),
425% # with a 2 unit positive peak. -- Check On line documentation
426% #
427% # Laplacian "{radius},{sigma}"
anthony602ab9b2010-01-05 08:06:50 +0000428% # Laplacian (a mexican hat like) Function
429% #
430% # LOG "{radius},{sigma1},{sigma2}
431% # Laplacian of Gaussian
432% #
433% # DOG "{radius},{sigma1},{sigma2}
anthony4fd27e22010-02-07 08:17:18 +0000434% # Difference of two Gaussians
435% #
436% # Filter2D
437% # Filter1D
438% # Set kernel values using a resize filter, and given scale (sigma)
439% # Cylindrical or Linear. Is this posible with an image?
440% #
anthony602ab9b2010-01-05 08:06:50 +0000441%
442% Boolean Kernels
443%
444% Rectangle "{geometry}"
445% Simply generate a rectangle of 1's with the size given. You can also
446% specify the location of the 'control point', otherwise the closest
447% pixel to the center of the rectangle is selected.
448%
449% Properly centered and odd sized rectangles work the best.
450%
anthony4fd27e22010-02-07 08:17:18 +0000451% Diamond "[{radius}[,{scale}]]"
anthony602ab9b2010-01-05 08:06:50 +0000452% Generate a diamond shaped kernal with given radius to the points.
453% Kernel size will again be radius*2+1 square and defaults to radius 1,
454% generating a 3x3 kernel that is slightly larger than a square.
455%
anthony4fd27e22010-02-07 08:17:18 +0000456% Square "[{radius}[,{scale}]]"
anthony602ab9b2010-01-05 08:06:50 +0000457% Generate a square shaped kernel of size radius*2+1, and defaulting
458% to a 3x3 (radius 1).
459%
460% Note that using a larger radius for the "Square" or the "Diamond"
461% is also equivelent to iterating the basic morphological method
462% that many times. However However iterating with the smaller radius 1
463% default is actually faster than using a larger kernel radius.
464%
anthony4fd27e22010-02-07 08:17:18 +0000465% Disk "[{radius}[,{scale}]]
anthony602ab9b2010-01-05 08:06:50 +0000466% Generate a binary disk of the radius given, radius may be a float.
467% Kernel size will be ceil(radius)*2+1 square.
468% NOTE: Here are some disk shapes of specific interest
469% "disk:1" => "diamond" or "cross:1"
470% "disk:1.5" => "square"
471% "disk:2" => "diamond:2"
anthony83ba99b2010-01-24 08:48:15 +0000472% "disk:2.5" => a general disk shape of radius 2
anthony602ab9b2010-01-05 08:06:50 +0000473% "disk:2.9" => "square:2"
anthony83ba99b2010-01-24 08:48:15 +0000474% "disk:3.5" => default - octagonal/disk shape of radius 3
anthony602ab9b2010-01-05 08:06:50 +0000475% "disk:4.2" => roughly octagonal shape of radius 4
anthony83ba99b2010-01-24 08:48:15 +0000476% "disk:4.3" => a general disk shape of radius 4
anthony602ab9b2010-01-05 08:06:50 +0000477% After this all the kernel shape becomes more and more circular.
478%
479% Because a "disk" is more circular when using a larger radius, using a
480% larger radius is preferred over iterating the morphological operation.
481%
anthony4fd27e22010-02-07 08:17:18 +0000482% Plus "[{radius}[,{scale}]]"
anthony602ab9b2010-01-05 08:06:50 +0000483% Generate a kernel in the shape of a 'plus' sign. The length of each
484% arm is also the radius, which defaults to 2.
485%
486% This kernel is not a good general morphological kernel, but is used
487% more for highlighting and marking any single pixels in an image using,
488% a "Dilate" or "Erode" method as appropriate.
anthonyc94cdb02010-01-06 08:15:29 +0000489%
anthony602ab9b2010-01-05 08:06:50 +0000490% NOTE: "plus:1" is equivelent to a "Diamond" kernel.
491%
492% Note that unlike other kernels iterating a plus does not produce the
493% same result as using a larger radius for the cross.
494%
495% Distance Measuring Kernels
496%
497% Chebyshev "[{radius}][x{scale}]" largest x or y distance (default r=1)
498% Manhatten "[{radius}][x{scale}]" square grid distance (default r=1)
anthonyc94cdb02010-01-06 08:15:29 +0000499% Euclidean "[{radius}][x{scale}]" direct distance (default r=1)
anthony602ab9b2010-01-05 08:06:50 +0000500%
501% Different types of distance measuring methods, which are used with the
502% a 'Distance' morphology method for generating a gradient based on
503% distance from an edge of a binary shape, though there is a technique
504% for handling a anti-aliased shape.
505%
anthonyc94cdb02010-01-06 08:15:29 +0000506% Chebyshev Distance (also known as Tchebychev Distance) is a value of
507% one to any neighbour, orthogonal or diagonal. One why of thinking of
508% it is the number of squares a 'King' or 'Queen' in chess needs to
509% traverse reach any other position on a chess board. It results in a
510% 'square' like distance function, but one where diagonals are closer
511% than expected.
anthony602ab9b2010-01-05 08:06:50 +0000512%
anthonyc94cdb02010-01-06 08:15:29 +0000513% Manhatten Distance (also known as Rectilinear Distance, or the Taxi
514% Cab metric), is the distance needed when you can only travel in
515% orthogonal (horizontal or vertical) only. It is the distance a 'Rook'
516% in chess would travel. It results in a diamond like distances, where
517% diagonals are further than expected.
anthony602ab9b2010-01-05 08:06:50 +0000518%
anthonyc94cdb02010-01-06 08:15:29 +0000519% Euclidean Distance is the 'direct' or 'as the crow flys distance.
520% However by default the kernel size only has a radius of 1, which
521% limits the distance to 'Knight' like moves, with only orthogonal and
522% diagonal measurements being correct. As such for the default kernel
523% you will get octagonal like distance function, which is reasonally
524% accurate.
525%
526% However if you use a larger radius such as "Euclidean:4" you will
527% get a much smoother distance gradient from the edge of the shape.
528% Of course a larger kernel is slower to use, and generally not needed.
529%
530% To allow the use of fractional distances that you get with diagonals
531% the actual distance is scaled by a fixed value which the user can
532% provide. This is not actually nessary for either ""Chebyshev" or
533% "Manhatten" distance kernels, but is done for all three distance
534% kernels. If no scale is provided it is set to a value of 100,
535% allowing for a maximum distance measurement of 655 pixels using a Q16
536% version of IM, from any edge. However for small images this can
537% result in quite a dark gradient.
538%
539% See the 'Distance' Morphological Method, for information of how it is
540% applied.
anthony602ab9b2010-01-05 08:06:50 +0000541%
anthony4fd27e22010-02-07 08:17:18 +0000542% # Hit-n-Miss Kernel-Lists -- Still to be implemented
543% #
544% # specifically for Pruning, Thinning, Thickening
545% #
anthony602ab9b2010-01-05 08:06:50 +0000546*/
547
cristy2be15382010-01-21 02:38:03 +0000548MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000549 const GeometryInfo *args)
550{
cristy2be15382010-01-21 02:38:03 +0000551 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000552 *kernel;
553
cristy150989e2010-02-01 14:59:39 +0000554 register long
anthony602ab9b2010-01-05 08:06:50 +0000555 i;
556
557 register long
558 u,
559 v;
560
561 double
562 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
563
cristy2be15382010-01-21 02:38:03 +0000564 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
565 if (kernel == (KernelInfo *) NULL)
anthony602ab9b2010-01-05 08:06:50 +0000566 return(kernel);
567 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
cristyc99304f2010-02-01 15:26:27 +0000568 kernel->minimum = kernel->maximum = 0.0;
569 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000570 kernel->type = type;
cristyd43a46b2010-01-21 02:13:41 +0000571 kernel->signature = MagickSignature;
anthony602ab9b2010-01-05 08:06:50 +0000572
573 switch(type) {
574 /* Convolution Kernels */
575 case GaussianKernel:
576 { double
577 sigma = fabs(args->sigma);
578
579 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
580
581 kernel->width = kernel->height =
582 GetOptimalKernelWidth2D(args->rho,sigma);
cristyc99304f2010-02-01 15:26:27 +0000583 kernel->x = kernel->y = (long) (kernel->width-1)/2;
584 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000585 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
586 kernel->height*sizeof(double));
587 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000588 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000589
590 sigma = 2.0*sigma*sigma; /* simplify the expression */
cristyc99304f2010-02-01 15:26:27 +0000591 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
592 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
593 kernel->positive_range += (
anthony602ab9b2010-01-05 08:06:50 +0000594 kernel->values[i] =
595 exp(-((double)(u*u+v*v))/sigma)
596 /* / (MagickPI*sigma) */ );
cristyc99304f2010-02-01 15:26:27 +0000597 kernel->minimum = 0;
598 kernel->maximum = kernel->values[
599 kernel->y*kernel->width+kernel->x ];
anthony602ab9b2010-01-05 08:06:50 +0000600
anthony999bb2c2010-02-18 12:38:01 +0000601 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
anthony602ab9b2010-01-05 08:06:50 +0000602
603 break;
604 }
605 case BlurKernel:
606 { double
607 sigma = fabs(args->sigma);
608
609 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
610
611 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
cristyc99304f2010-02-01 15:26:27 +0000612 kernel->x = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000613 kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +0000614 kernel->y = 0;
615 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000616 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
617 kernel->height*sizeof(double));
618 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000619 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000620
621#if 1
622#define KernelRank 3
623 /* Formula derived from GetBlurKernel() in "effect.c" (plus bug fix).
624 ** It generates a gaussian 3 times the width, and compresses it into
625 ** the expected range. This produces a closer normalization of the
626 ** resulting kernel, especially for very low sigma values.
627 ** As such while wierd it is prefered.
628 **
629 ** I am told this method originally came from Photoshop.
630 */
631 sigma *= KernelRank; /* simplify expanded curve */
cristy150989e2010-02-01 14:59:39 +0000632 v = (long) (kernel->width*KernelRank-1)/2; /* start/end points to fit range */
anthony602ab9b2010-01-05 08:06:50 +0000633 (void) ResetMagickMemory(kernel->values,0, (size_t)
634 kernel->width*sizeof(double));
635 for ( u=-v; u <= v; u++) {
636 kernel->values[(u+v)/KernelRank] +=
637 exp(-((double)(u*u))/(2.0*sigma*sigma))
638 /* / (MagickSQ2PI*sigma/KernelRank) */ ;
639 }
cristy150989e2010-02-01 14:59:39 +0000640 for (i=0; i < (long) kernel->width; i++)
cristyc99304f2010-02-01 15:26:27 +0000641 kernel->positive_range += kernel->values[i];
anthony602ab9b2010-01-05 08:06:50 +0000642#else
cristyc99304f2010-02-01 15:26:27 +0000643 for ( i=0, u=-kernel->x; i < kernel->width; i++, u++)
644 kernel->positive_range += (
anthony602ab9b2010-01-05 08:06:50 +0000645 kernel->values[i] =
646 exp(-((double)(u*u))/(2.0*sigma*sigma))
647 /* / (MagickSQ2PI*sigma) */ );
648#endif
cristyc99304f2010-02-01 15:26:27 +0000649 kernel->minimum = 0;
650 kernel->maximum = kernel->values[ kernel->x ];
anthonycc6c8362010-01-25 04:14:01 +0000651 /* Note that neither methods above generate a normalized kernel,
652 ** though it gets close. The kernel may be 'clipped' by a user defined
653 ** radius, producing a smaller (darker) kernel. Also for very small
654 ** sigma's (> 0.1) the central value becomes larger than one, and thus
655 ** producing a very bright kernel.
anthony602ab9b2010-01-05 08:06:50 +0000656 */
anthonycc6c8362010-01-25 04:14:01 +0000657
anthony602ab9b2010-01-05 08:06:50 +0000658 /* Normalize the 1D Gaussian Kernel
659 **
660 ** Because of this the divisor in the above kernel generator is
anthonyc94cdb02010-01-06 08:15:29 +0000661 ** not needed, so is not done above.
anthony602ab9b2010-01-05 08:06:50 +0000662 */
anthony999bb2c2010-02-18 12:38:01 +0000663 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
anthonycc6c8362010-01-25 04:14:01 +0000664
anthony602ab9b2010-01-05 08:06:50 +0000665 /* rotate the kernel by given angle */
anthony4fd27e22010-02-07 08:17:18 +0000666 RotateKernelInfo(kernel, args->xi);
anthony602ab9b2010-01-05 08:06:50 +0000667 break;
668 }
669 case CometKernel:
670 { double
671 sigma = fabs(args->sigma);
672
673 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
674
675 if ( args->rho < 1.0 )
676 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
677 else
678 kernel->width = (unsigned long)args->rho;
cristyc99304f2010-02-01 15:26:27 +0000679 kernel->x = kernel->y = 0;
anthony602ab9b2010-01-05 08:06:50 +0000680 kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +0000681 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000682 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
683 kernel->height*sizeof(double));
684 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000685 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000686
687 /* A comet blur is half a gaussian curve, so that the object is
688 ** blurred in one direction only. This may not be quite the right
689 ** curve so may change in the future. The function must be normalised.
690 */
691#if 1
692#define KernelRank 3
693 sigma *= KernelRank; /* simplify expanded curve */
cristy150989e2010-02-01 14:59:39 +0000694 v = (long) kernel->width*KernelRank; /* start/end points to fit range */
anthony602ab9b2010-01-05 08:06:50 +0000695 (void) ResetMagickMemory(kernel->values,0, (size_t)
696 kernel->width*sizeof(double));
697 for ( u=0; u < v; u++) {
698 kernel->values[u/KernelRank] +=
699 exp(-((double)(u*u))/(2.0*sigma*sigma))
700 /* / (MagickSQ2PI*sigma/KernelRank) */ ;
701 }
cristy150989e2010-02-01 14:59:39 +0000702 for (i=0; i < (long) kernel->width; i++)
cristyc99304f2010-02-01 15:26:27 +0000703 kernel->positive_range += kernel->values[i];
anthony602ab9b2010-01-05 08:06:50 +0000704#else
cristy150989e2010-02-01 14:59:39 +0000705 for ( i=0; i < (long) kernel->width; i++)
cristyc99304f2010-02-01 15:26:27 +0000706 kernel->positive_range += (
anthony602ab9b2010-01-05 08:06:50 +0000707 kernel->values[i] =
708 exp(-((double)(i*i))/(2.0*sigma*sigma))
709 /* / (MagickSQ2PI*sigma) */ );
710#endif
cristyc99304f2010-02-01 15:26:27 +0000711 kernel->minimum = 0;
712 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000713
anthony999bb2c2010-02-18 12:38:01 +0000714 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
715 RotateKernelInfo(kernel, args->xi); /* Rotate by angle */
anthony602ab9b2010-01-05 08:06:50 +0000716 break;
717 }
718 /* Boolean Kernels */
719 case RectangleKernel:
720 case SquareKernel:
721 {
anthony4fd27e22010-02-07 08:17:18 +0000722 double scale;
anthony602ab9b2010-01-05 08:06:50 +0000723 if ( type == SquareKernel )
724 {
725 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000726 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000727 else
cristy150989e2010-02-01 14:59:39 +0000728 kernel->width = kernel->height = (unsigned long) (2*args->rho+1);
cristyc99304f2010-02-01 15:26:27 +0000729 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony4fd27e22010-02-07 08:17:18 +0000730 scale = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +0000731 }
732 else {
cristy2be15382010-01-21 02:38:03 +0000733 /* NOTE: user defaults set in "AcquireKernelInfo()" */
anthony602ab9b2010-01-05 08:06:50 +0000734 if ( args->rho < 1.0 || args->sigma < 1.0 )
anthony83ba99b2010-01-24 08:48:15 +0000735 return(DestroyKernelInfo(kernel)); /* invalid args given */
anthony602ab9b2010-01-05 08:06:50 +0000736 kernel->width = (unsigned long)args->rho;
737 kernel->height = (unsigned long)args->sigma;
738 if ( args->xi < 0.0 || args->xi > (double)kernel->width ||
739 args->psi < 0.0 || args->psi > (double)kernel->height )
anthony83ba99b2010-01-24 08:48:15 +0000740 return(DestroyKernelInfo(kernel)); /* invalid args given */
cristyc99304f2010-02-01 15:26:27 +0000741 kernel->x = (long) args->xi;
742 kernel->y = (long) args->psi;
anthony4fd27e22010-02-07 08:17:18 +0000743 scale = 1.0;
anthony602ab9b2010-01-05 08:06:50 +0000744 }
745 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
746 kernel->height*sizeof(double));
747 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000748 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000749
anthonycc6c8362010-01-25 04:14:01 +0000750 /* set all kernel values to 1.0 */
cristy150989e2010-02-01 14:59:39 +0000751 u=(long) kernel->width*kernel->height;
752 for ( i=0; i < u; i++)
anthony4fd27e22010-02-07 08:17:18 +0000753 kernel->values[i] = scale;
754 kernel->minimum = kernel->maximum = scale; /* a flat shape */
755 kernel->positive_range = scale*u;
anthonycc6c8362010-01-25 04:14:01 +0000756 break;
anthony602ab9b2010-01-05 08:06:50 +0000757 }
758 case DiamondKernel:
759 {
760 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000761 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000762 else
763 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000764 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000765
766 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
767 kernel->height*sizeof(double));
768 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000769 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000770
anthony4fd27e22010-02-07 08:17:18 +0000771 /* set all kernel values within diamond area to scale given */
cristyc99304f2010-02-01 15:26:27 +0000772 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
773 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
774 if ((labs(u)+labs(v)) <= (long)kernel->x)
anthony4fd27e22010-02-07 08:17:18 +0000775 kernel->positive_range += kernel->values[i] = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +0000776 else
777 kernel->values[i] = nan;
anthony4fd27e22010-02-07 08:17:18 +0000778 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
anthony602ab9b2010-01-05 08:06:50 +0000779 break;
780 }
781 case DiskKernel:
782 {
783 long
784 limit;
785
786 limit = (long)(args->rho*args->rho);
anthony83ba99b2010-01-24 08:48:15 +0000787 if (args->rho < 0.1) /* default radius approx 3.5 */
788 kernel->width = kernel->height = 7L, limit = 10L;
anthony602ab9b2010-01-05 08:06:50 +0000789 else
790 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000791 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000792
793 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
794 kernel->height*sizeof(double));
795 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000796 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000797
anthonycc6c8362010-01-25 04:14:01 +0000798 /* set all kernel values within disk area to 1.0 */
cristyc99304f2010-02-01 15:26:27 +0000799 for ( i=0, v= -kernel->y; v <= (long)kernel->y; v++)
800 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony602ab9b2010-01-05 08:06:50 +0000801 if ((u*u+v*v) <= limit)
anthony4fd27e22010-02-07 08:17:18 +0000802 kernel->positive_range += kernel->values[i] = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +0000803 else
804 kernel->values[i] = nan;
anthony4fd27e22010-02-07 08:17:18 +0000805 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
anthony602ab9b2010-01-05 08:06:50 +0000806 break;
807 }
808 case PlusKernel:
809 {
810 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000811 kernel->width = kernel->height = 5; /* default radius 2 */
anthony602ab9b2010-01-05 08:06:50 +0000812 else
813 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000814 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000815
816 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
817 kernel->height*sizeof(double));
818 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000819 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000820
anthonycc6c8362010-01-25 04:14:01 +0000821 /* set all kernel values along axises to 1.0 */
cristyc99304f2010-02-01 15:26:27 +0000822 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
823 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony4fd27e22010-02-07 08:17:18 +0000824 kernel->values[i] = (u == 0 || v == 0) ? args->sigma : nan;
825 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
826 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
anthony602ab9b2010-01-05 08:06:50 +0000827 break;
828 }
829 /* Distance Measuring Kernels */
830 case ChebyshevKernel:
831 {
832 double
833 scale;
834
835 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000836 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000837 else
838 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000839 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000840
841 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
842 kernel->height*sizeof(double));
843 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000844 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000845
846 scale = (args->sigma < 1.0) ? 100.0 : args->sigma;
cristyc99304f2010-02-01 15:26:27 +0000847 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
848 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
849 kernel->positive_range += ( kernel->values[i] =
anthony602ab9b2010-01-05 08:06:50 +0000850 scale*((labs(u)>labs(v)) ? labs(u) : labs(v)) );
cristyc99304f2010-02-01 15:26:27 +0000851 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000852 break;
853 }
854 case ManhattenKernel:
855 {
856 double
857 scale;
858
859 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000860 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000861 else
862 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000863 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000864
865 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
866 kernel->height*sizeof(double));
867 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000868 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000869
870 scale = (args->sigma < 1.0) ? 100.0 : args->sigma;
cristyc99304f2010-02-01 15:26:27 +0000871 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
872 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
873 kernel->positive_range += ( kernel->values[i] =
anthony602ab9b2010-01-05 08:06:50 +0000874 scale*(labs(u)+labs(v)) );
cristyc99304f2010-02-01 15:26:27 +0000875 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000876 break;
877 }
878 case EuclideanKernel:
879 {
880 double
881 scale;
882
883 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000884 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000885 else
886 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000887 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000888
889 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
890 kernel->height*sizeof(double));
891 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000892 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000893
894 scale = (args->sigma < 1.0) ? 100.0 : args->sigma;
cristyc99304f2010-02-01 15:26:27 +0000895 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
896 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
897 kernel->positive_range += ( kernel->values[i] =
anthony602ab9b2010-01-05 08:06:50 +0000898 scale*sqrt((double)(u*u+v*v)) );
cristyc99304f2010-02-01 15:26:27 +0000899 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000900 break;
901 }
902 /* Undefined Kernels */
903 case LaplacianKernel:
904 case LOGKernel:
905 case DOGKernel:
cristy150989e2010-02-01 14:59:39 +0000906 perror("Kernel Type has not been defined yet");
anthony602ab9b2010-01-05 08:06:50 +0000907 /* FALL THRU */
908 default:
909 /* Generate a No-Op minimal kernel - 1x1 pixel */
910 kernel->values=(double *)AcquireQuantumMemory((size_t)1,sizeof(double));
911 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000912 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000913 kernel->width = kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +0000914 kernel->x = kernel->x = 0;
anthony602ab9b2010-01-05 08:06:50 +0000915 kernel->type = UndefinedKernel;
cristyc99304f2010-02-01 15:26:27 +0000916 kernel->maximum =
917 kernel->positive_range =
anthonyc94cdb02010-01-06 08:15:29 +0000918 kernel->values[0] = 1.0; /* a flat single-point no-op kernel! */
anthony602ab9b2010-01-05 08:06:50 +0000919 break;
920 }
921
922 return(kernel);
923}
anthonyc94cdb02010-01-06 08:15:29 +0000924
anthony602ab9b2010-01-05 08:06:50 +0000925/*
926%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
927% %
928% %
929% %
cristy6771f1e2010-03-05 19:43:39 +0000930% C l o n e K e r n e l I n f o %
anthony4fd27e22010-02-07 08:17:18 +0000931% %
932% %
933% %
934%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
935%
936% CloneKernelInfo() creates a new clone of the given Kernel so that its can
937% be modified without effecting the original. The cloned kernel should be
938% destroyed using DestoryKernelInfo() when no longer needed.
939%
940% The format of the DestroyKernelInfo method is:
941%
anthony930be612010-02-08 04:26:15 +0000942% KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +0000943%
944% A description of each parameter follows:
945%
946% o kernel: the Morphology/Convolution kernel to be cloned
947%
948*/
cristy6771f1e2010-03-05 19:43:39 +0000949MagickBooleanType KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +0000950{
951 register long
952 i;
953
954 KernelInfo *
955 new;
956
957 assert(kernel != (KernelInfo *) NULL);
958
959 new=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
960 if (new == (KernelInfo *) NULL)
961 return(new);
962 *new = *kernel; /* copy values in structure */
963
964 new->values=(double *) AcquireQuantumMemory(kernel->width,
965 kernel->height*sizeof(double));
966 if (new->values == (double *) NULL)
967 return(DestroyKernelInfo(new));
968
969 for (i=0; i < (long) (kernel->width*kernel->height); i++)
970 new->values[i] = kernel->values[i];
971
972 return(new);
973}
974
975/*
976%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
977% %
978% %
979% %
anthony83ba99b2010-01-24 08:48:15 +0000980% D e s t r o y K e r n e l I n f o %
anthony602ab9b2010-01-05 08:06:50 +0000981% %
982% %
983% %
984%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
985%
anthony83ba99b2010-01-24 08:48:15 +0000986% DestroyKernelInfo() frees the memory used by a Convolution/Morphology
987% kernel.
anthony602ab9b2010-01-05 08:06:50 +0000988%
anthony83ba99b2010-01-24 08:48:15 +0000989% The format of the DestroyKernelInfo method is:
anthony602ab9b2010-01-05 08:06:50 +0000990%
anthony83ba99b2010-01-24 08:48:15 +0000991% KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +0000992%
993% A description of each parameter follows:
994%
995% o kernel: the Morphology/Convolution kernel to be destroyed
996%
997*/
998
anthony83ba99b2010-01-24 08:48:15 +0000999MagickExport KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +00001000{
cristy2be15382010-01-21 02:38:03 +00001001 assert(kernel != (KernelInfo *) NULL);
anthony4fd27e22010-02-07 08:17:18 +00001002
1003 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1004 kernel->height*sizeof(double));
anthony602ab9b2010-01-05 08:06:50 +00001005 kernel->values=(double *)RelinquishMagickMemory(kernel->values);
cristy2be15382010-01-21 02:38:03 +00001006 kernel=(KernelInfo *) RelinquishMagickMemory(kernel);
anthony602ab9b2010-01-05 08:06:50 +00001007 return(kernel);
1008}
anthonyc94cdb02010-01-06 08:15:29 +00001009
1010/*
1011%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1012% %
1013% %
1014% %
anthony29188a82010-01-22 10:12:34 +00001015% 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 +00001016% %
1017% %
1018% %
1019%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1020%
anthony29188a82010-01-22 10:12:34 +00001021% MorphologyImageChannel() applies a user supplied kernel to the image
1022% according to the given mophology method.
anthony602ab9b2010-01-05 08:06:50 +00001023%
1024% The given kernel is assumed to have been pre-scaled appropriatally, usally
1025% by the kernel generator.
1026%
1027% The format of the MorphologyImage method is:
1028%
anthony29188a82010-01-22 10:12:34 +00001029% Image *MorphologyImage(const Image *image, MorphologyMethod method,
1030% const long iterations, KernelInfo *kernel, ExceptionInfo *exception)
1031% Image *MorphologyImageChannel(const Image *image, const ChannelType
1032% channel, MorphologyMethod method, const long iterations, KernelInfo
1033% *kernel, ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001034%
1035% A description of each parameter follows:
1036%
1037% o image: the image.
1038%
1039% o method: the morphology method to be applied.
1040%
1041% o iterations: apply the operation this many times (or no change).
1042% A value of -1 means loop until no change found.
1043% How this is applied may depend on the morphology method.
1044% Typically this is a value of 1.
1045%
1046% o channel: the channel type.
1047%
1048% o kernel: An array of double representing the morphology kernel.
anthony29188a82010-01-22 10:12:34 +00001049% Warning: kernel may be normalized for the Convolve method.
anthony602ab9b2010-01-05 08:06:50 +00001050%
1051% o exception: return any errors or warnings in this structure.
1052%
1053%
1054% TODO: bias and auto-scale handling of the kernel for convolution
1055% The given kernel is assumed to have been pre-scaled appropriatally, usally
1056% by the kernel generator.
1057%
1058*/
1059
anthony930be612010-02-08 04:26:15 +00001060
anthony602ab9b2010-01-05 08:06:50 +00001061/* Internal function
anthony930be612010-02-08 04:26:15 +00001062 * Apply the Low-Level Morphology Method using the given Kernel
1063 * Returning the number of pixels that changed.
1064 * Two pre-created images must be provided, no image is created.
anthony602ab9b2010-01-05 08:06:50 +00001065 */
1066static unsigned long MorphologyApply(const Image *image, Image
1067 *result_image, const MorphologyMethod method, const ChannelType channel,
cristy2be15382010-01-21 02:38:03 +00001068 const KernelInfo *kernel, ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001069{
cristy2be15382010-01-21 02:38:03 +00001070#define MorphologyTag "Morphology/Image"
anthony602ab9b2010-01-05 08:06:50 +00001071
1072 long
cristy150989e2010-02-01 14:59:39 +00001073 progress,
anthony29188a82010-01-22 10:12:34 +00001074 y, offx, offy,
anthony602ab9b2010-01-05 08:06:50 +00001075 changed;
1076
1077 MagickBooleanType
1078 status;
1079
1080 MagickPixelPacket
1081 bias;
1082
1083 CacheView
1084 *p_view,
1085 *q_view;
1086
anthony4fd27e22010-02-07 08:17:18 +00001087 /* Only the most basic morphology is actually performed by this routine */
anthony4fd27e22010-02-07 08:17:18 +00001088
anthony602ab9b2010-01-05 08:06:50 +00001089 /*
anthony4fd27e22010-02-07 08:17:18 +00001090 Apply Basic Morphology to Image.
anthony602ab9b2010-01-05 08:06:50 +00001091 */
1092 status=MagickTrue;
1093 changed=0;
1094 progress=0;
1095
1096 GetMagickPixelPacket(image,&bias);
1097 SetMagickPixelPacketBias(image,&bias);
anthonycc6c8362010-01-25 04:14:01 +00001098 /* Future: handle auto-bias from user, based on kernel input */
anthony602ab9b2010-01-05 08:06:50 +00001099
1100 p_view=AcquireCacheView(image);
1101 q_view=AcquireCacheView(result_image);
anthony29188a82010-01-22 10:12:34 +00001102
anthonycc6c8362010-01-25 04:14:01 +00001103 /* Some methods (including convolve) needs use a reflected kernel.
1104 * Adjust 'origin' offsets for this reflected kernel.
anthony29188a82010-01-22 10:12:34 +00001105 */
cristyc99304f2010-02-01 15:26:27 +00001106 offx = kernel->x;
1107 offy = kernel->y;
anthony29188a82010-01-22 10:12:34 +00001108 switch(method) {
1109 case ErodeMorphology:
1110 case ErodeIntensityMorphology:
anthony999bb2c2010-02-18 12:38:01 +00001111 /* kernel is user as is, without reflection */
anthony29188a82010-01-22 10:12:34 +00001112 break;
anthony930be612010-02-08 04:26:15 +00001113 case ConvolveMorphology:
1114 case DilateMorphology:
1115 case DilateIntensityMorphology:
1116 case DistanceMorphology:
anthony999bb2c2010-02-18 12:38:01 +00001117 /* kernel needs to used with reflection */
cristy150989e2010-02-01 14:59:39 +00001118 offx = (long) kernel->width-offx-1;
1119 offy = (long) kernel->height-offy-1;
anthony29188a82010-01-22 10:12:34 +00001120 break;
anthony930be612010-02-08 04:26:15 +00001121 default:
1122 perror("Not a low level Morpholgy Method");
1123 break;
anthony29188a82010-01-22 10:12:34 +00001124 }
1125
anthony602ab9b2010-01-05 08:06:50 +00001126#if defined(MAGICKCORE_OPENMP_SUPPORT)
1127 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1128#endif
cristy150989e2010-02-01 14:59:39 +00001129 for (y=0; y < (long) image->rows; y++)
anthony602ab9b2010-01-05 08:06:50 +00001130 {
1131 MagickBooleanType
1132 sync;
1133
1134 register const PixelPacket
1135 *restrict p;
1136
1137 register const IndexPacket
1138 *restrict p_indexes;
1139
1140 register PixelPacket
1141 *restrict q;
1142
1143 register IndexPacket
1144 *restrict q_indexes;
1145
cristy150989e2010-02-01 14:59:39 +00001146 register long
anthony602ab9b2010-01-05 08:06:50 +00001147 x;
1148
anthony29188a82010-01-22 10:12:34 +00001149 unsigned long
anthony602ab9b2010-01-05 08:06:50 +00001150 r;
1151
1152 if (status == MagickFalse)
1153 continue;
anthony29188a82010-01-22 10:12:34 +00001154 p=GetCacheViewVirtualPixels(p_view, -offx, y-offy,
1155 image->columns+kernel->width, kernel->height, exception);
anthony602ab9b2010-01-05 08:06:50 +00001156 q=GetCacheViewAuthenticPixels(q_view,0,y,result_image->columns,1,
1157 exception);
1158 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1159 {
1160 status=MagickFalse;
1161 continue;
1162 }
1163 p_indexes=GetCacheViewVirtualIndexQueue(p_view);
1164 q_indexes=GetCacheViewAuthenticIndexQueue(q_view);
anthony29188a82010-01-22 10:12:34 +00001165 r = (image->columns+kernel->width)*offy+offx; /* constant */
1166
cristy150989e2010-02-01 14:59:39 +00001167 for (x=0; x < (long) image->columns; x++)
anthony602ab9b2010-01-05 08:06:50 +00001168 {
cristy150989e2010-02-01 14:59:39 +00001169 long
anthony602ab9b2010-01-05 08:06:50 +00001170 v;
1171
cristy150989e2010-02-01 14:59:39 +00001172 register long
anthony602ab9b2010-01-05 08:06:50 +00001173 u;
1174
1175 register const double
1176 *restrict k;
1177
1178 register const PixelPacket
1179 *restrict k_pixels;
1180
1181 register const IndexPacket
1182 *restrict k_indexes;
1183
1184 MagickPixelPacket
1185 result;
1186
anthony29188a82010-01-22 10:12:34 +00001187 /* Copy input to ouput image for unused channels
anthony83ba99b2010-01-24 08:48:15 +00001188 * This removes need for 'cloning' a new image every iteration
anthony29188a82010-01-22 10:12:34 +00001189 */
anthony602ab9b2010-01-05 08:06:50 +00001190 *q = p[r];
1191 if (image->colorspace == CMYKColorspace)
1192 q_indexes[x] = p_indexes[r];
1193
cristy5ee247a2010-02-12 15:42:34 +00001194 result.green=(MagickRealType) 0;
1195 result.blue=(MagickRealType) 0;
1196 result.opacity=(MagickRealType) 0;
1197 result.index=(MagickRealType) 0;
anthony602ab9b2010-01-05 08:06:50 +00001198 switch (method) {
1199 case ConvolveMorphology:
anthony930be612010-02-08 04:26:15 +00001200 /* Set the user defined bias of the weighted average output
1201 **
1202 ** FUTURE: provide some way for internal functions to disable
1203 ** user defined bias and scaling effects.
1204 */
anthony602ab9b2010-01-05 08:06:50 +00001205 result=bias;
anthony930be612010-02-08 04:26:15 +00001206 break;
anthony83ba99b2010-01-24 08:48:15 +00001207 case DilateMorphology:
anthony29188a82010-01-22 10:12:34 +00001208 result.red =
1209 result.green =
1210 result.blue =
1211 result.opacity =
1212 result.index = -MagickHuge;
1213 break;
1214 case ErodeMorphology:
1215 result.red =
1216 result.green =
1217 result.blue =
1218 result.opacity =
1219 result.index = +MagickHuge;
1220 break;
anthony4fd27e22010-02-07 08:17:18 +00001221 case DilateIntensityMorphology:
1222 case ErodeIntensityMorphology:
1223 result.red = 0.0; /* flag indicating first match found */
1224 break;
anthony602ab9b2010-01-05 08:06:50 +00001225 default:
anthony29188a82010-01-22 10:12:34 +00001226 /* Otherwise just start with the original pixel value */
cristy150989e2010-02-01 14:59:39 +00001227 result.red = (MagickRealType) p[r].red;
1228 result.green = (MagickRealType) p[r].green;
1229 result.blue = (MagickRealType) p[r].blue;
1230 result.opacity = QuantumRange - (MagickRealType) p[r].opacity;
anthony602ab9b2010-01-05 08:06:50 +00001231 if ( image->colorspace == CMYKColorspace)
cristy150989e2010-02-01 14:59:39 +00001232 result.index = (MagickRealType) p_indexes[r];
anthony602ab9b2010-01-05 08:06:50 +00001233 break;
1234 }
1235
1236 switch ( method ) {
1237 case ConvolveMorphology:
anthony930be612010-02-08 04:26:15 +00001238 /* Weighted Average of pixels using reflected kernel
1239 **
1240 ** NOTE for correct working of this operation for asymetrical
1241 ** kernels, the kernel needs to be applied in its reflected form.
1242 ** That is its values needs to be reversed.
1243 **
1244 ** Correlation is actually the same as this but without reflecting
1245 ** the kernel, and thus 'lower-level' that Convolution. However
1246 ** as Convolution is the more common method used, and it does not
1247 ** really cost us much in terms of processing to use a reflected
1248 ** kernel it is Convolution that is implemented.
1249 **
1250 ** Correlation will have its kernel reflected before calling
1251 ** this function to do a Convolve.
1252 **
1253 ** For more details of Correlation vs Convolution see
1254 ** http://www.cs.umd.edu/~djacobs/CMSC426/Convolution.pdf
1255 */
anthony602ab9b2010-01-05 08:06:50 +00001256 if (((channel & OpacityChannel) == 0) ||
1257 (image->matte == MagickFalse))
1258 {
anthony930be612010-02-08 04:26:15 +00001259 /* Convolution without transparency effects */
anthony29188a82010-01-22 10:12:34 +00001260 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001261 k_pixels = p;
1262 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001263 for (v=0; v < (long) kernel->height; v++) {
1264 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001265 if ( IsNan(*k) ) continue;
1266 result.red += (*k)*k_pixels[u].red;
1267 result.green += (*k)*k_pixels[u].green;
1268 result.blue += (*k)*k_pixels[u].blue;
anthony83ba99b2010-01-24 08:48:15 +00001269 /* result.opacity += not involved here */
anthony602ab9b2010-01-05 08:06:50 +00001270 if ( image->colorspace == CMYKColorspace)
1271 result.index += (*k)*k_indexes[u];
1272 }
1273 k_pixels += image->columns+kernel->width;
1274 k_indexes += image->columns+kernel->width;
1275 }
anthony602ab9b2010-01-05 08:06:50 +00001276 }
1277 else
1278 { /* Kernel & Alpha weighted Convolution */
1279 MagickRealType
1280 alpha, /* alpha value * kernel weighting */
1281 gamma; /* weighting divisor */
1282
1283 gamma=0.0;
anthony29188a82010-01-22 10:12:34 +00001284 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001285 k_pixels = p;
1286 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001287 for (v=0; v < (long) kernel->height; v++) {
1288 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001289 if ( IsNan(*k) ) continue;
1290 alpha=(*k)*(QuantumScale*(QuantumRange-
1291 k_pixels[u].opacity));
1292 gamma += alpha;
1293 result.red += alpha*k_pixels[u].red;
1294 result.green += alpha*k_pixels[u].green;
1295 result.blue += alpha*k_pixels[u].blue;
anthony83ba99b2010-01-24 08:48:15 +00001296 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00001297 if ( image->colorspace == CMYKColorspace)
1298 result.index += alpha*k_indexes[u];
1299 }
1300 k_pixels += image->columns+kernel->width;
1301 k_indexes += image->columns+kernel->width;
1302 }
1303 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
anthony83ba99b2010-01-24 08:48:15 +00001304 result.red *= gamma;
1305 result.green *= gamma;
1306 result.blue *= gamma;
1307 result.opacity *= gamma;
1308 result.index *= gamma;
anthony602ab9b2010-01-05 08:06:50 +00001309 }
1310 break;
1311
anthony4fd27e22010-02-07 08:17:18 +00001312 case ErodeMorphology:
anthony930be612010-02-08 04:26:15 +00001313 /* Minimize Value within kernel neighbourhood
1314 **
1315 ** NOTE that the kernel is not reflected for this operation!
1316 **
1317 ** NOTE: in normal Greyscale Morphology, the kernel value should
1318 ** be added to the real value, this is currently not done, due to
1319 ** the nature of the boolean kernels being used.
1320 */
anthony4fd27e22010-02-07 08:17:18 +00001321 k = kernel->values;
1322 k_pixels = p;
1323 k_indexes = p_indexes;
1324 for (v=0; v < (long) kernel->height; v++) {
1325 for (u=0; u < (long) kernel->width; u++, k++) {
1326 if ( IsNan(*k) || (*k) < 0.5 ) continue;
1327 Minimize(result.red, (double) k_pixels[u].red);
1328 Minimize(result.green, (double) k_pixels[u].green);
1329 Minimize(result.blue, (double) k_pixels[u].blue);
1330 Minimize(result.opacity, QuantumRange-(double) k_pixels[u].opacity);
1331 if ( image->colorspace == CMYKColorspace)
1332 Minimize(result.index, (double) k_indexes[u]);
1333 }
1334 k_pixels += image->columns+kernel->width;
1335 k_indexes += image->columns+kernel->width;
1336 }
1337 break;
1338
anthony83ba99b2010-01-24 08:48:15 +00001339 case DilateMorphology:
anthony930be612010-02-08 04:26:15 +00001340 /* Maximize Value within kernel neighbourhood
1341 **
1342 ** NOTE for correct working of this operation for asymetrical
1343 ** kernels, the kernel needs to be applied in its reflected form.
1344 ** That is its values needs to be reversed.
1345 **
1346 ** NOTE: in normal Greyscale Morphology, the kernel value should
1347 ** be added to the real value, this is currently not done, due to
1348 ** the nature of the boolean kernels being used.
1349 **
1350 */
anthony29188a82010-01-22 10:12:34 +00001351 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001352 k_pixels = p;
1353 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001354 for (v=0; v < (long) kernel->height; v++) {
1355 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001356 if ( IsNan(*k) || (*k) < 0.5 ) continue;
cristy150989e2010-02-01 14:59:39 +00001357 Maximize(result.red, (double) k_pixels[u].red);
1358 Maximize(result.green, (double) k_pixels[u].green);
1359 Maximize(result.blue, (double) k_pixels[u].blue);
1360 Maximize(result.opacity, QuantumRange-(double) k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00001361 if ( image->colorspace == CMYKColorspace)
cristy150989e2010-02-01 14:59:39 +00001362 Maximize(result.index, (double) k_indexes[u]);
anthony602ab9b2010-01-05 08:06:50 +00001363 }
1364 k_pixels += image->columns+kernel->width;
1365 k_indexes += image->columns+kernel->width;
1366 }
anthony602ab9b2010-01-05 08:06:50 +00001367 break;
1368
anthony4fd27e22010-02-07 08:17:18 +00001369 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001370 /* Select Pixel with Minimum Intensity within kernel neighbourhood
1371 **
1372 ** WARNING: the intensity test fails for CMYK and does not
1373 ** take into account the moderating effect of teh alpha channel
1374 ** on the intensity.
1375 **
1376 ** NOTE that the kernel is not reflected for this operation!
1377 */
anthony602ab9b2010-01-05 08:06:50 +00001378 k = kernel->values;
1379 k_pixels = p;
1380 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001381 for (v=0; v < (long) kernel->height; v++) {
1382 for (u=0; u < (long) kernel->width; u++, k++) {
anthony602ab9b2010-01-05 08:06:50 +00001383 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony4fd27e22010-02-07 08:17:18 +00001384 if ( result.red == 0.0 ||
1385 PixelIntensity(&(k_pixels[u])) < PixelIntensity(q) ) {
1386 /* copy the whole pixel - no channel selection */
1387 *q = k_pixels[u];
1388 if ( result.red > 0.0 ) changed++;
1389 result.red = 1.0;
1390 }
anthony602ab9b2010-01-05 08:06:50 +00001391 }
1392 k_pixels += image->columns+kernel->width;
1393 k_indexes += image->columns+kernel->width;
1394 }
anthony602ab9b2010-01-05 08:06:50 +00001395 break;
1396
anthony83ba99b2010-01-24 08:48:15 +00001397 case DilateIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001398 /* Select Pixel with Maximum Intensity within kernel neighbourhood
1399 **
1400 ** WARNING: the intensity test fails for CMYK and does not
1401 ** take into account the moderating effect of teh alpha channel
1402 ** on the intensity.
1403 **
1404 ** NOTE for correct working of this operation for asymetrical
1405 ** kernels, the kernel needs to be applied in its reflected form.
1406 ** That is its values needs to be reversed.
1407 */
anthony29188a82010-01-22 10:12:34 +00001408 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001409 k_pixels = p;
1410 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001411 for (v=0; v < (long) kernel->height; v++) {
1412 for (u=0; u < (long) kernel->width; u++, k--) {
anthony29188a82010-01-22 10:12:34 +00001413 if ( IsNan(*k) || (*k) < 0.5 ) continue; /* boolean kernel */
1414 if ( result.red == 0.0 ||
1415 PixelIntensity(&(k_pixels[u])) > PixelIntensity(q) ) {
1416 /* copy the whole pixel - no channel selection */
1417 *q = k_pixels[u];
1418 if ( result.red > 0.0 ) changed++;
1419 result.red = 1.0;
1420 }
anthony602ab9b2010-01-05 08:06:50 +00001421 }
1422 k_pixels += image->columns+kernel->width;
1423 k_indexes += image->columns+kernel->width;
1424 }
anthony602ab9b2010-01-05 08:06:50 +00001425 break;
1426
anthony602ab9b2010-01-05 08:06:50 +00001427 case DistanceMorphology:
anthony930be612010-02-08 04:26:15 +00001428 /* Add kernel Value and select the minimum value found.
1429 ** The result is a iterative distance from edge of image shape.
1430 **
1431 ** All Distance Kernels are symetrical, but that may not always
1432 ** be the case. For example how about a distance from left edges?
1433 ** To work correctly with asymetrical kernels the reflected kernel
1434 ** needs to be applied.
1435 */
anthony602ab9b2010-01-05 08:06:50 +00001436#if 0
anthony930be612010-02-08 04:26:15 +00001437 /* No need to do distance morphology if original value is zero
1438 ** Unfortunatally I have not been able to get this right
1439 ** when channel selection also becomes involved. -- Arrgghhh
1440 */
1441 if ( ((channel & RedChannel) == 0 && p[r].red == 0)
1442 || ((channel & GreenChannel) == 0 && p[r].green == 0)
1443 || ((channel & BlueChannel) == 0 && p[r].blue == 0)
1444 || ((channel & OpacityChannel) == 0 && p[r].opacity == 0)
1445 || (( (channel & IndexChannel) == 0
1446 || image->colorspace != CMYKColorspace
1447 ) && p_indexes[x] ==0 )
1448 )
1449 break;
anthony602ab9b2010-01-05 08:06:50 +00001450#endif
anthony29188a82010-01-22 10:12:34 +00001451 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001452 k_pixels = p;
1453 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001454 for (v=0; v < (long) kernel->height; v++) {
1455 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001456 if ( IsNan(*k) ) continue;
1457 Minimize(result.red, (*k)+k_pixels[u].red);
1458 Minimize(result.green, (*k)+k_pixels[u].green);
1459 Minimize(result.blue, (*k)+k_pixels[u].blue);
1460 Minimize(result.opacity, (*k)+QuantumRange-k_pixels[u].opacity);
1461 if ( image->colorspace == CMYKColorspace)
1462 Minimize(result.index, (*k)+k_indexes[u]);
1463 }
1464 k_pixels += image->columns+kernel->width;
1465 k_indexes += image->columns+kernel->width;
1466 }
anthony602ab9b2010-01-05 08:06:50 +00001467 break;
1468
1469 case UndefinedMorphology:
1470 default:
1471 break; /* Do nothing */
anthony83ba99b2010-01-24 08:48:15 +00001472 }
1473 switch ( method ) {
1474 case UndefinedMorphology:
1475 case DilateIntensityMorphology:
1476 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001477 break; /* full pixel was directly assigned - not a channel method */
anthony83ba99b2010-01-24 08:48:15 +00001478 default:
1479 /* Assign the results */
1480 if ((channel & RedChannel) != 0)
1481 q->red = ClampToQuantum(result.red);
1482 if ((channel & GreenChannel) != 0)
1483 q->green = ClampToQuantum(result.green);
1484 if ((channel & BlueChannel) != 0)
1485 q->blue = ClampToQuantum(result.blue);
1486 if ((channel & OpacityChannel) != 0
1487 && image->matte == MagickTrue )
1488 q->opacity = ClampToQuantum(QuantumRange-result.opacity);
1489 if ((channel & IndexChannel) != 0
1490 && image->colorspace == CMYKColorspace)
1491 q_indexes[x] = ClampToQuantum(result.index);
1492 break;
1493 }
1494 if ( ( p[r].red != q->red )
1495 || ( p[r].green != q->green )
1496 || ( p[r].blue != q->blue )
1497 || ( p[r].opacity != q->opacity )
1498 || ( image->colorspace == CMYKColorspace &&
1499 p_indexes[r] != q_indexes[x] ) )
1500 changed++; /* The pixel had some value changed! */
anthony602ab9b2010-01-05 08:06:50 +00001501 p++;
1502 q++;
anthony83ba99b2010-01-24 08:48:15 +00001503 } /* x */
anthony602ab9b2010-01-05 08:06:50 +00001504 sync=SyncCacheViewAuthenticPixels(q_view,exception);
1505 if (sync == MagickFalse)
1506 status=MagickFalse;
1507 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1508 {
1509 MagickBooleanType
1510 proceed;
1511
1512#if defined(MAGICKCORE_OPENMP_SUPPORT)
1513 #pragma omp critical (MagickCore_MorphologyImage)
1514#endif
1515 proceed=SetImageProgress(image,MorphologyTag,progress++,image->rows);
1516 if (proceed == MagickFalse)
1517 status=MagickFalse;
1518 }
anthony83ba99b2010-01-24 08:48:15 +00001519 } /* y */
anthony602ab9b2010-01-05 08:06:50 +00001520 result_image->type=image->type;
1521 q_view=DestroyCacheView(q_view);
1522 p_view=DestroyCacheView(p_view);
cristy150989e2010-02-01 14:59:39 +00001523 return(status ? (unsigned long) changed : 0);
anthony602ab9b2010-01-05 08:06:50 +00001524}
1525
anthony4fd27e22010-02-07 08:17:18 +00001526
1527MagickExport Image *MorphologyImage(const Image *image, const MorphologyMethod
anthony930be612010-02-08 04:26:15 +00001528 method, const long iterations,const KernelInfo *kernel, ExceptionInfo
1529 *exception)
cristy2be15382010-01-21 02:38:03 +00001530{
1531 Image
1532 *morphology_image;
1533
1534 morphology_image=MorphologyImageChannel(image,DefaultChannels,method,
1535 iterations,kernel,exception);
1536 return(morphology_image);
1537}
1538
anthony4fd27e22010-02-07 08:17:18 +00001539
anthony930be612010-02-08 04:26:15 +00001540MagickExport Image *MorphologyImageChannel(const Image *image, const
1541 ChannelType channel, const MorphologyMethod method, const long
1542 iterations, const KernelInfo *kernel, ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001543{
cristy150989e2010-02-01 14:59:39 +00001544 long
1545 count;
anthony602ab9b2010-01-05 08:06:50 +00001546
1547 Image
1548 *new_image,
anthony4fd27e22010-02-07 08:17:18 +00001549 *old_image,
1550 *grad_image;
anthony602ab9b2010-01-05 08:06:50 +00001551
anthonycc6c8362010-01-25 04:14:01 +00001552 const char
1553 *artifact;
1554
cristy150989e2010-02-01 14:59:39 +00001555 unsigned long
1556 changed,
1557 limit;
1558
anthony4fd27e22010-02-07 08:17:18 +00001559 KernelInfo
1560 *curr_kernel;
1561
1562 MorphologyMethod
1563 curr_method;
1564
anthony602ab9b2010-01-05 08:06:50 +00001565 assert(image != (Image *) NULL);
1566 assert(image->signature == MagickSignature);
anthony4fd27e22010-02-07 08:17:18 +00001567 assert(kernel != (KernelInfo *) NULL);
1568 assert(kernel->signature == MagickSignature);
anthony602ab9b2010-01-05 08:06:50 +00001569 assert(exception != (ExceptionInfo *) NULL);
1570 assert(exception->signature == MagickSignature);
1571
anthony602ab9b2010-01-05 08:06:50 +00001572 if ( iterations == 0 )
1573 return((Image *)NULL); /* null operation - nothing to do! */
1574
1575 /* kernel must be valid at this point
1576 * (except maybe for posible future morphology methods like "Prune"
1577 */
cristy2be15382010-01-21 02:38:03 +00001578 assert(kernel != (KernelInfo *)NULL);
anthony602ab9b2010-01-05 08:06:50 +00001579
anthony4fd27e22010-02-07 08:17:18 +00001580 count = 0; /* interation count */
1581 changed = 1; /* if compound method assume image was changed */
anthony930be612010-02-08 04:26:15 +00001582 curr_kernel = (KernelInfo *)kernel; /* allow kernel and method */
1583 curr_method = method; /* to be changed as nessary */
anthony4fd27e22010-02-07 08:17:18 +00001584
cristy150989e2010-02-01 14:59:39 +00001585 limit = (unsigned long) iterations;
anthony602ab9b2010-01-05 08:06:50 +00001586 if ( iterations < 0 )
1587 limit = image->columns > image->rows ? image->columns : image->rows;
1588
anthony4fd27e22010-02-07 08:17:18 +00001589 /* Third-level morphology methods */
cristy5ee247a2010-02-12 15:42:34 +00001590 grad_image=(Image *) NULL;
anthony4fd27e22010-02-07 08:17:18 +00001591 switch( curr_method ) {
1592 case EdgeMorphology:
1593 grad_image = MorphologyImageChannel(image, channel,
1594 DilateMorphology, iterations, curr_kernel, exception);
1595 /* FALL-THRU */
1596 case EdgeInMorphology:
1597 curr_method = ErodeMorphology;
anthony602ab9b2010-01-05 08:06:50 +00001598 break;
anthony4fd27e22010-02-07 08:17:18 +00001599 case EdgeOutMorphology:
1600 curr_method = DilateMorphology;
anthony602ab9b2010-01-05 08:06:50 +00001601 break;
anthony4fd27e22010-02-07 08:17:18 +00001602 case TopHatMorphology:
1603 curr_method = OpenMorphology;
1604 break;
1605 case BottomHatMorphology:
1606 curr_method = CloseMorphology;
1607 break;
1608 default:
anthony930be612010-02-08 04:26:15 +00001609 break; /* not a third-level method */
anthony4fd27e22010-02-07 08:17:18 +00001610 }
1611
1612 /* Second-level morphology methods */
1613 switch( curr_method ) {
anthony930be612010-02-08 04:26:15 +00001614 case OpenMorphology:
1615 /* Open is a Erode then a Dilate without reflection */
anthony4fd27e22010-02-07 08:17:18 +00001616 new_image = MorphologyImageChannel(image, channel,
1617 ErodeMorphology, iterations, curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001618 if (new_image == (Image *) NULL)
1619 return((Image *) NULL);
anthony4fd27e22010-02-07 08:17:18 +00001620 curr_method = DilateMorphology;
1621 break;
anthony602ab9b2010-01-05 08:06:50 +00001622 case OpenIntensityMorphology:
anthony4fd27e22010-02-07 08:17:18 +00001623 new_image = MorphologyImageChannel(image, channel,
1624 ErodeIntensityMorphology, iterations, curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001625 if (new_image == (Image *) NULL)
1626 return((Image *) NULL);
anthony4fd27e22010-02-07 08:17:18 +00001627 curr_method = DilateIntensityMorphology;
1628 break;
anthony930be612010-02-08 04:26:15 +00001629
1630 case CloseMorphology:
1631 /* Close is a Dilate then Erode using reflected kernel */
1632 /* A reflected kernel is needed for a Close */
1633 if ( curr_kernel == kernel )
1634 curr_kernel = CloneKernelInfo(kernel);
1635 RotateKernelInfo(curr_kernel,180);
1636 new_image = MorphologyImageChannel(image, channel,
1637 DilateMorphology, iterations, curr_kernel, exception);
1638 if (new_image == (Image *) NULL)
1639 return((Image *) NULL);
1640 curr_method = ErodeMorphology;
1641 break;
anthony4fd27e22010-02-07 08:17:18 +00001642 case CloseIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001643 /* A reflected kernel is needed for a Close */
1644 if ( curr_kernel == kernel )
1645 curr_kernel = CloneKernelInfo(kernel);
anthony4fd27e22010-02-07 08:17:18 +00001646 RotateKernelInfo(curr_kernel,180);
1647 new_image = MorphologyImageChannel(image, channel,
1648 DilateIntensityMorphology, iterations, curr_kernel, exception);
1649 if (new_image == (Image *) NULL)
1650 return((Image *) NULL);
1651 curr_method = ErodeIntensityMorphology;
anthony602ab9b2010-01-05 08:06:50 +00001652 break;
1653
anthony930be612010-02-08 04:26:15 +00001654 case CorrelateMorphology:
1655 /* A Correlation is actually a Convolution with a reflected kernel.
1656 ** However a Convolution is a weighted sum with a reflected kernel.
1657 ** It may seem stange to convert a Correlation into a Convolution
1658 ** as the Correleation is the simplier method, but Convolution is
1659 ** much more commonly used, and it makes sense to implement it directly
1660 ** so as to avoid the need to duplicate the kernel when it is not
1661 ** required (which is typically the default).
1662 */
1663 if ( curr_kernel == kernel )
1664 curr_kernel = CloneKernelInfo(kernel);
1665 RotateKernelInfo(curr_kernel,180);
1666 curr_method = ConvolveMorphology;
1667 /* FALL-THRU into Correlate (weigthed sum without reflection) */
1668
anthonyc94cdb02010-01-06 08:15:29 +00001669 case ConvolveMorphology:
anthony4fd27e22010-02-07 08:17:18 +00001670 /* Scale or Normalize kernel, according to user wishes
anthony930be612010-02-08 04:26:15 +00001671 ** before using it for the Convolve/Correlate method.
1672 **
1673 ** FUTURE: provide some way for internal functions to disable
1674 ** user bias and scaling effects.
anthonycc6c8362010-01-25 04:14:01 +00001675 */
1676 artifact = GetImageArtifact(image,"convolve:scale");
anthony4fd27e22010-02-07 08:17:18 +00001677 if ( artifact != (char *)NULL ) {
anthony999bb2c2010-02-18 12:38:01 +00001678 MagickStatusType
1679 flags;
1680 GeometryInfo
1681 args;
1682
anthony930be612010-02-08 04:26:15 +00001683 if ( curr_kernel == kernel )
1684 curr_kernel = CloneKernelInfo(kernel);
anthony999bb2c2010-02-18 12:38:01 +00001685
1686 args.rho = 1.0;
1687 flags = ParseGeometry(artifact, &args);
1688 ScaleKernelInfo(curr_kernel, args.rho, flags);
anthony4fd27e22010-02-07 08:17:18 +00001689 }
anthony930be612010-02-08 04:26:15 +00001690 /* FALL-THRU to do the first, and typically the only iteration */
anthony4fd27e22010-02-07 08:17:18 +00001691
anthony602ab9b2010-01-05 08:06:50 +00001692 default:
anthony930be612010-02-08 04:26:15 +00001693 /* Do a single iteration using the Low-Level Morphology method!
1694 ** This ensures a "new_image" has been generated, but allows us to skip
1695 ** the creation of 'old_image' if no more iterations are needed.
1696 **
1697 ** The "curr_method" should also be set to a low-level method that is
1698 ** understood by the MorphologyApply() internal function.
anthony602ab9b2010-01-05 08:06:50 +00001699 */
1700 new_image=CloneImage(image,0,0,MagickTrue,exception);
1701 if (new_image == (Image *) NULL)
1702 return((Image *) NULL);
1703 if (SetImageStorageClass(new_image,DirectClass) == MagickFalse)
1704 {
1705 InheritException(exception,&new_image->exception);
1706 new_image=DestroyImage(new_image);
1707 return((Image *) NULL);
1708 }
anthony4fd27e22010-02-07 08:17:18 +00001709 changed = MorphologyApply(image,new_image,curr_method,channel,curr_kernel,
anthony602ab9b2010-01-05 08:06:50 +00001710 exception);
1711 count++;
1712 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
cristy150989e2010-02-01 14:59:39 +00001713 fprintf(stderr, "Morphology %s:%ld => Changed %lu\n",
anthony4fd27e22010-02-07 08:17:18 +00001714 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
anthony602ab9b2010-01-05 08:06:50 +00001715 count, changed);
anthony930be612010-02-08 04:26:15 +00001716 break;
anthony602ab9b2010-01-05 08:06:50 +00001717 }
1718
anthony930be612010-02-08 04:26:15 +00001719 /* At this point the "curr_method" should not only be set to a low-level
1720 ** method that is understood by the MorphologyApply() internal function,
1721 ** but "new_image" should now be defined, as the image to apply the
1722 ** "curr_method" to.
1723 */
1724
1725 /* Repeat the low-level morphology until count or no change reached */
cristy150989e2010-02-01 14:59:39 +00001726 if ( count < (long) limit && changed > 0 ) {
anthony602ab9b2010-01-05 08:06:50 +00001727 old_image = CloneImage(new_image,0,0,MagickTrue,exception);
1728 if (old_image == (Image *) NULL)
1729 return(DestroyImage(new_image));
1730 if (SetImageStorageClass(old_image,DirectClass) == MagickFalse)
1731 {
1732 InheritException(exception,&old_image->exception);
1733 old_image=DestroyImage(old_image);
1734 return(DestroyImage(new_image));
1735 }
cristy150989e2010-02-01 14:59:39 +00001736 while( count < (long) limit && changed != 0 )
anthony602ab9b2010-01-05 08:06:50 +00001737 {
1738 Image *tmp = old_image;
1739 old_image = new_image;
1740 new_image = tmp;
anthony4fd27e22010-02-07 08:17:18 +00001741 changed = MorphologyApply(old_image,new_image,curr_method,channel,
1742 curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001743 count++;
1744 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
cristy150989e2010-02-01 14:59:39 +00001745 fprintf(stderr, "Morphology %s:%ld => Changed %lu\n",
anthony4fd27e22010-02-07 08:17:18 +00001746 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
anthony602ab9b2010-01-05 08:06:50 +00001747 count, changed);
1748 }
cristy150989e2010-02-01 14:59:39 +00001749 old_image=DestroyImage(old_image);
anthony602ab9b2010-01-05 08:06:50 +00001750 }
anthony930be612010-02-08 04:26:15 +00001751
1752 /* We are finished with kernel - destroy it if we made a clone */
anthony4fd27e22010-02-07 08:17:18 +00001753 if ( curr_kernel != kernel )
1754 curr_kernel=DestroyKernelInfo(curr_kernel);
1755
anthony930be612010-02-08 04:26:15 +00001756 /* Third-level Subtractive methods post-processing */
anthony4fd27e22010-02-07 08:17:18 +00001757 switch( method ) {
1758 case EdgeOutMorphology:
1759 case EdgeInMorphology:
1760 case TopHatMorphology:
1761 case BottomHatMorphology:
anthony930be612010-02-08 04:26:15 +00001762 /* Get Difference relative to the original image */
cristy04ffdba2010-02-18 14:34:47 +00001763 (void) CompositeImageChannel(new_image, channel, DifferenceCompositeOp,
anthony4fd27e22010-02-07 08:17:18 +00001764 image, 0, 0);
1765 break;
anthony930be612010-02-08 04:26:15 +00001766 case EdgeMorphology: /* subtract the Erode from a Dilate */
cristy04ffdba2010-02-18 14:34:47 +00001767 (void) CompositeImageChannel(new_image, channel, DifferenceCompositeOp,
anthony4fd27e22010-02-07 08:17:18 +00001768 grad_image, 0, 0);
1769 grad_image=DestroyImage(grad_image);
1770 break;
1771 default:
1772 break;
1773 }
anthony602ab9b2010-01-05 08:06:50 +00001774
1775 return(new_image);
1776}
anthony83ba99b2010-01-24 08:48:15 +00001777
1778/*
1779%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1780% %
1781% %
1782% %
anthony4fd27e22010-02-07 08:17:18 +00001783+ R o t a t e K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00001784% %
1785% %
1786% %
1787%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1788%
anthony4fd27e22010-02-07 08:17:18 +00001789% RotateKernelInfo() rotates the kernel by the angle given. Currently it is
anthony83ba99b2010-01-24 08:48:15 +00001790% restricted to 90 degree angles, but this may be improved in the future.
1791%
anthony4fd27e22010-02-07 08:17:18 +00001792% The format of the RotateKernelInfo method is:
anthony83ba99b2010-01-24 08:48:15 +00001793%
anthony4fd27e22010-02-07 08:17:18 +00001794% void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00001795%
1796% A description of each parameter follows:
1797%
1798% o kernel: the Morphology/Convolution kernel
1799%
1800% o angle: angle to rotate in degrees
1801%
anthonyc4c86e02010-01-27 09:30:32 +00001802% This function is only internel to this module, as it is not finalized,
1803% especially with regard to non-orthogonal angles, and rotation of larger
1804% 2D kernels.
anthony83ba99b2010-01-24 08:48:15 +00001805*/
anthony4fd27e22010-02-07 08:17:18 +00001806static void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00001807{
1808 /* WARNING: Currently assumes the kernel (rightly) is horizontally symetrical
1809 **
1810 ** TODO: expand beyond simple 90 degree rotates, flips and flops
1811 */
1812
1813 /* Modulus the angle */
1814 angle = fmod(angle, 360.0);
1815 if ( angle < 0 )
1816 angle += 360.0;
1817
1818 if ( 315.0 < angle || angle <= 45.0 )
1819 return; /* no change! - At least at this time */
1820
1821 switch (kernel->type) {
1822 /* These built-in kernels are cylindrical kernels, rotating is useless */
1823 case GaussianKernel:
1824 case LaplacianKernel:
1825 case LOGKernel:
1826 case DOGKernel:
1827 case DiskKernel:
1828 case ChebyshevKernel:
1829 case ManhattenKernel:
1830 case EuclideanKernel:
1831 return;
1832
1833 /* These may be rotatable at non-90 angles in the future */
1834 /* but simply rotating them in multiples of 90 degrees is useless */
1835 case SquareKernel:
1836 case DiamondKernel:
1837 case PlusKernel:
1838 return;
1839
1840 /* These only allows a +/-90 degree rotation (by transpose) */
1841 /* A 180 degree rotation is useless */
1842 case BlurKernel:
1843 case RectangleKernel:
1844 if ( 135.0 < angle && angle <= 225.0 )
1845 return;
1846 if ( 225.0 < angle && angle <= 315.0 )
1847 angle -= 180;
1848 break;
1849
1850 /* these are freely rotatable in 90 degree units */
1851 case CometKernel:
1852 case UndefinedKernel:
1853 case UserDefinedKernel:
1854 break;
1855 }
1856 if ( 135.0 < angle && angle <= 225.0 )
1857 {
1858 /* For a 180 degree rotation - also know as a reflection */
1859 /* This is actually a very very common operation! */
1860 /* Basically all that is needed is a reversal of the kernel data! */
1861 unsigned long
1862 i,j;
1863 register double
1864 *k,t;
1865
1866 k=kernel->values;
1867 for ( i=0, j=kernel->width*kernel->height-1; i<j; i++, j--)
1868 t=k[i], k[i]=k[j], k[j]=t;
1869
anthony930be612010-02-08 04:26:15 +00001870 kernel->x = (long) kernel->width - kernel->x - 1;
1871 kernel->y = (long) kernel->height - kernel->y - 1;
anthony83ba99b2010-01-24 08:48:15 +00001872 angle = fmod(angle+180.0, 360.0);
1873 }
1874 if ( 45.0 < angle && angle <= 135.0 )
1875 { /* Do a transpose and a flop, of the image, which results in a 90
1876 * degree rotation using two mirror operations.
1877 *
1878 * WARNING: this assumes the original image was a 1 dimentional image
1879 * but currently that is the only built-ins it is applied to.
1880 */
cristy150989e2010-02-01 14:59:39 +00001881 long
anthony83ba99b2010-01-24 08:48:15 +00001882 t;
cristy150989e2010-02-01 14:59:39 +00001883 t = (long) kernel->width;
anthony83ba99b2010-01-24 08:48:15 +00001884 kernel->width = kernel->height;
cristy150989e2010-02-01 14:59:39 +00001885 kernel->height = (unsigned long) t;
cristyc99304f2010-02-01 15:26:27 +00001886 t = kernel->x;
1887 kernel->x = kernel->y;
1888 kernel->y = t;
anthony83ba99b2010-01-24 08:48:15 +00001889 angle = fmod(450.0 - angle, 360.0);
1890 }
1891 /* At this point angle should be between -45 (315) and +45 degrees
1892 * In the future some form of non-orthogonal angled rotates could be
1893 * performed here, posibily with a linear kernel restriction.
1894 */
1895
1896#if 0
1897 Not currently in use!
1898 { /* Do a flop, this assumes kernel is horizontally symetrical.
1899 * Each row of the kernel needs to be reversed!
1900 */
1901 unsigned long
1902 y;
cristy150989e2010-02-01 14:59:39 +00001903 register long
anthony83ba99b2010-01-24 08:48:15 +00001904 x,r;
1905 register double
1906 *k,t;
1907
1908 for ( y=0, k=kernel->values; y < kernel->height; y++, k+=kernel->width)
1909 for ( x=0, r=kernel->width-1; x<kernel->width/2; x++, r--)
1910 t=k[x], k[x]=k[r], k[r]=t;
1911
cristyc99304f2010-02-01 15:26:27 +00001912 kernel->x = kernel->width - kernel->x - 1;
anthony83ba99b2010-01-24 08:48:15 +00001913 angle = fmod(angle+180.0, 360.0);
1914 }
1915#endif
1916 return;
1917}
1918
1919/*
1920%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1921% %
1922% %
1923% %
cristy6771f1e2010-03-05 19:43:39 +00001924% S c a l e K e r n e l I n f o %
anthonycc6c8362010-01-25 04:14:01 +00001925% %
1926% %
1927% %
1928%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1929%
anthony999bb2c2010-02-18 12:38:01 +00001930% ScaleKernelInfo() scales the kernel by the given amount, with or without
1931% normalization of the sum of the kernel values.
anthonycc6c8362010-01-25 04:14:01 +00001932%
anthony999bb2c2010-02-18 12:38:01 +00001933% By default (no flags given) the values within the kernel is scaled
1934% according the given scaling factor.
anthonycc6c8362010-01-25 04:14:01 +00001935%
anthony999bb2c2010-02-18 12:38:01 +00001936% If any 'normalize_flags' are given the kernel will be normalized and then
1937% further scaled by the scaleing factor value given. A 'PercentValue' flag
1938% will cause the given scaling factor to be divided by one hundred percent.
1939%
1940% Kernel normalization ('normalize_flags' given) is designed to ensure that
1941% any use of the kernel scaling factor with 'Convolve' or 'Correlate'
1942% morphology methods will fall into -1.0 to +1.0 range. Note however that
1943% for non-HDRI versions of IM this may cause images to have any negative
1944% results clipped, unless some 'clip' any negative output from 'Convolve'
1945% with the use of some kernels.
1946%
1947% More specifically. Kernels which only contain positive values (such as a
1948% 'Gaussian' kernel) will be scaled so that those values sum to +1.0,
1949% ensuring a 0.0 to +1.0 convolution output range for non-HDRI images.
1950%
1951% For Kernels that contain some negative values, (such as 'Sharpen' kernels)
1952% the kernel will be scaled by the absolute of the sum of kernel values, so
1953% that it will generally fall within the +/- 1.0 range.
1954%
1955% For kernels whose values sum to zero, (such as 'Laplician' kernels) kernel
1956% will be scaled by just the sum of the postive values, so that its output
1957% range will again fall into the +/- 1.0 range.
1958%
1959% For special kernels designed for locating shapes using 'Correlate', (often
1960% only containing +1 and -1 values, representing foreground/brackground
1961% matching) a special normalization method is provided to scale the positive
1962% values seperatally to those of the negative values, so the kernel will be
1963% forced to become a zero-sum kernel better suited to such searches.
1964%
1965% WARNING: Correct normalization of the kernal assumes that the '*_range'
1966% attributes within the kernel structure have been correctly set during the
1967% kernels creation.
1968%
1969% NOTE: The values used for 'normalize_flags' have been selected specifically
1970% to match the use of geometry options, so that '!' means NormalizeValue, '^'
1971% means CorrelateNormalizeValue, and '%' means PercentValue. All other
1972% GeometryFlags values are ignored.
anthonycc6c8362010-01-25 04:14:01 +00001973%
anthony4fd27e22010-02-07 08:17:18 +00001974% The format of the ScaleKernelInfo method is:
anthonycc6c8362010-01-25 04:14:01 +00001975%
anthony999bb2c2010-02-18 12:38:01 +00001976% void ScaleKernelInfo(KernelInfo *kernel, const double scaling_factor,
1977% const MagickStatusType normalize_flags )
anthonycc6c8362010-01-25 04:14:01 +00001978%
1979% A description of each parameter follows:
1980%
1981% o kernel: the Morphology/Convolution kernel
1982%
anthony999bb2c2010-02-18 12:38:01 +00001983% o scaling_factor:
1984% multiply all values (after normalization) by this factor if not
1985% zero. If the kernel is normalized regardless of any flags.
1986%
1987% o normalize_flags:
1988% GeometryFlags defining normalization method to use.
1989% specifically: NormalizeValue, CorrelateNormalizeValue,
1990% and/or PercentValue
anthonycc6c8362010-01-25 04:14:01 +00001991%
anthonyc4c86e02010-01-27 09:30:32 +00001992% This function is internal to this module only at this time, but can be
1993% exported to other modules if needed.
anthonycc6c8362010-01-25 04:14:01 +00001994*/
cristy6771f1e2010-03-05 19:43:39 +00001995MagickExport void ScaleKernelInfo(KernelInfo *kernel,
1996 const double scaling_factor,const GeometryFlags normalize_flags)
anthonycc6c8362010-01-25 04:14:01 +00001997{
cristy150989e2010-02-01 14:59:39 +00001998 register long
anthonycc6c8362010-01-25 04:14:01 +00001999 i;
2000
anthony999bb2c2010-02-18 12:38:01 +00002001 register double
2002 pos_scale,
2003 neg_scale;
2004
2005 pos_scale = 1.0;
2006 if ( (normalize_flags&NormalizeValue) != 0 ) {
2007 /* normalize kernel appropriately */
2008 if ( fabs(kernel->positive_range + kernel->negative_range) > MagickEpsilon )
2009 pos_scale = fabs(kernel->positive_range + kernel->negative_range);
anthonycc6c8362010-01-25 04:14:01 +00002010 else
anthony999bb2c2010-02-18 12:38:01 +00002011 pos_scale = kernel->positive_range; /* special zero-summing kernel */
2012 }
2013 /* force kernel into being a normalized zero-summing kernel */
2014 if ( (normalize_flags&CorrelateNormalizeValue) != 0 ) {
2015 pos_scale = ( fabs(kernel->positive_range) > MagickEpsilon )
2016 ? kernel->positive_range : 1.0;
2017 neg_scale = ( fabs(kernel->negative_range) > MagickEpsilon )
2018 ? -kernel->negative_range : 1.0;
2019 }
2020 else
2021 neg_scale = pos_scale;
2022
2023 /* finialize scaling_factor for positive and negative components */
2024 pos_scale = scaling_factor/pos_scale;
2025 neg_scale = scaling_factor/neg_scale;
2026 if ( (normalize_flags&PercentValue) != 0 ) {
2027 pos_scale /= 100.0;
2028 neg_scale /= 100.0;
anthonycc6c8362010-01-25 04:14:01 +00002029 }
2030
cristy150989e2010-02-01 14:59:39 +00002031 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00002032 if ( ! IsNan(kernel->values[i]) )
anthony999bb2c2010-02-18 12:38:01 +00002033 kernel->values[i] *= (kernel->values[i] >= 0) ? pos_scale : neg_scale;
anthonycc6c8362010-01-25 04:14:01 +00002034
anthony999bb2c2010-02-18 12:38:01 +00002035 /* convolution output range */
2036 kernel->positive_range *= pos_scale;
2037 kernel->negative_range *= neg_scale;
2038 /* maximum and minimum values in kernel */
2039 kernel->maximum *= (kernel->maximum >= 0.0) ? pos_scale : neg_scale;
2040 kernel->minimum *= (kernel->minimum >= 0.0) ? pos_scale : neg_scale;
2041
2042 /* swap kernel settings if user scaling factor is negative */
2043 if ( scaling_factor < MagickEpsilon ) {
2044 double t;
2045 t = kernel->positive_range;
2046 kernel->positive_range = kernel->negative_range;
2047 kernel->negative_range = t;
2048 t = kernel->maximum;
2049 kernel->maximum = kernel->minimum;
2050 kernel->minimum = 1;
2051 }
anthonycc6c8362010-01-25 04:14:01 +00002052
2053 return;
2054}
2055
2056/*
2057%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2058% %
2059% %
2060% %
anthony4fd27e22010-02-07 08:17:18 +00002061+ S h o w K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00002062% %
2063% %
2064% %
2065%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2066%
anthony4fd27e22010-02-07 08:17:18 +00002067% ShowKernelInfo() outputs the details of the given kernel defination to
2068% standard error, generally due to a users 'showkernel' option request.
anthony83ba99b2010-01-24 08:48:15 +00002069%
2070% The format of the ShowKernel method is:
2071%
anthony4fd27e22010-02-07 08:17:18 +00002072% void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00002073%
2074% A description of each parameter follows:
2075%
2076% o kernel: the Morphology/Convolution kernel
2077%
anthonyc4c86e02010-01-27 09:30:32 +00002078% This function is internal to this module only at this time. That may change
2079% in the future.
anthony83ba99b2010-01-24 08:48:15 +00002080*/
anthony4fd27e22010-02-07 08:17:18 +00002081MagickExport void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00002082{
cristy150989e2010-02-01 14:59:39 +00002083 long
anthony83ba99b2010-01-24 08:48:15 +00002084 i, u, v;
2085
2086 fprintf(stderr,
anthonycc6c8362010-01-25 04:14:01 +00002087 "Kernel \"%s\" of size %lux%lu%+ld%+ld with values from %.*lg to %.*lg\n",
anthony83ba99b2010-01-24 08:48:15 +00002088 MagickOptionToMnemonic(MagickKernelOptions, kernel->type),
2089 kernel->width, kernel->height,
cristyc99304f2010-02-01 15:26:27 +00002090 kernel->x, kernel->y,
2091 GetMagickPrecision(), kernel->minimum,
2092 GetMagickPrecision(), kernel->maximum);
anthonycc6c8362010-01-25 04:14:01 +00002093 fprintf(stderr, "Forming convolution output range from %.*lg to %.*lg%s\n",
cristyc99304f2010-02-01 15:26:27 +00002094 GetMagickPrecision(), kernel->negative_range,
2095 GetMagickPrecision(), kernel->positive_range,
anthonycc6c8362010-01-25 04:14:01 +00002096 /*kernel->normalized == MagickTrue ? " (normalized)" : */ "" );
cristy150989e2010-02-01 14:59:39 +00002097 for (i=v=0; v < (long) kernel->height; v++) {
anthony83ba99b2010-01-24 08:48:15 +00002098 fprintf(stderr,"%2ld:",v);
cristy150989e2010-02-01 14:59:39 +00002099 for (u=0; u < (long) kernel->width; u++, i++)
anthony83ba99b2010-01-24 08:48:15 +00002100 if ( IsNan(kernel->values[i]) )
2101 fprintf(stderr," %*s", GetMagickPrecision()+2, "nan");
2102 else
2103 fprintf(stderr," %*.*lg", GetMagickPrecision()+2,
2104 GetMagickPrecision(), kernel->values[i]);
2105 fprintf(stderr,"\n");
2106 }
2107}
anthonycc6c8362010-01-25 04:14:01 +00002108
2109/*
2110%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2111% %
2112% %
2113% %
anthony4fd27e22010-02-07 08:17:18 +00002114+ Z e r o K e r n e l N a n s %
anthonycc6c8362010-01-25 04:14:01 +00002115% %
2116% %
2117% %
2118%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2119%
2120% ZeroKernelNans() replaces any special 'nan' value that may be present in
2121% the kernel with a zero value. This is typically done when the kernel will
2122% be used in special hardware (GPU) convolution processors, to simply
2123% matters.
2124%
2125% The format of the ZeroKernelNans method is:
2126%
2127% voidZeroKernelNans (KernelInfo *kernel)
2128%
2129% A description of each parameter follows:
2130%
2131% o kernel: the Morphology/Convolution kernel
2132%
2133% FUTURE: return the information in a string for API usage.
2134*/
anthonyc4c86e02010-01-27 09:30:32 +00002135MagickExport void ZeroKernelNans(KernelInfo *kernel)
anthonycc6c8362010-01-25 04:14:01 +00002136{
cristy150989e2010-02-01 14:59:39 +00002137 register long
anthonycc6c8362010-01-25 04:14:01 +00002138 i;
2139
cristy150989e2010-02-01 14:59:39 +00002140 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00002141 if ( IsNan(kernel->values[i]) )
2142 kernel->values[i] = 0.0;
2143
2144 return;
2145}