blob: 8907ad6fc4a723f200fc32f90542fbfd4e52379c [file] [log] [blame]
cristy701db312009-11-20 03:14:08 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% M M OOO RRRR PPPP H H OOO L OOO GGGG Y Y %
7% MM MM O O R R P P H H O O L O O G Y Y %
8% M M M O O RRRR PPPP HHHHH O O L O O G GGG Y %
9% M M O O R R P H H O O L O O G G Y %
10% M M OOO R R P H H OOO LLLLL OOO GGG Y %
11% %
12% %
13% MagickCore Morphology Methods %
14% %
15% Software Design %
16% Anthony Thyssen %
anthonyc94cdb02010-01-06 08:15:29 +000017% January 2010 %
cristy701db312009-11-20 03:14:08 +000018% %
19% %
cristy16af1cb2009-12-11 21:38:29 +000020% Copyright 1999-2010 ImageMagick Studio LLC, a non-profit organization %
cristy701db312009-11-20 03:14:08 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
anthony602ab9b2010-01-05 08:06:50 +000036% Morpology is the the application of various kernals, of any size and even
37% shape, to a image in various ways (typically binary, but not always).
cristy701db312009-11-20 03:14:08 +000038%
anthony602ab9b2010-01-05 08:06:50 +000039% Convolution (weighted sum or average) is just one specific type of
40% morphology. Just one that is very common for image bluring and sharpening
41% effects. Not only 2D Gaussian blurring, but also 2-pass 1D Blurring.
42%
43% This module provides not only a general morphology function, and the ability
44% to apply more advanced or iterative morphologies, but also functions for the
45% generation of many different types of kernel arrays from user supplied
46% arguments. Prehaps even the generation of a kernel from a small image.
cristy701db312009-11-20 03:14:08 +000047*/
48
49/*
50 Include declarations.
51*/
52#include "magick/studio.h"
anthony602ab9b2010-01-05 08:06:50 +000053#include "magick/artifact.h"
cristy701db312009-11-20 03:14:08 +000054#include "magick/cache-view.h"
55#include "magick/color-private.h"
56#include "magick/enhance.h"
57#include "magick/exception.h"
58#include "magick/exception-private.h"
anthony602ab9b2010-01-05 08:06:50 +000059#include "magick/gem.h"
cristy701db312009-11-20 03:14:08 +000060#include "magick/hashmap.h"
61#include "magick/image.h"
cristybba804b2010-01-05 15:39:59 +000062#include "magick/image-private.h"
cristy701db312009-11-20 03:14:08 +000063#include "magick/list.h"
anthony29188a82010-01-22 10:12:34 +000064#include "magick/magick.h"
cristy701db312009-11-20 03:14:08 +000065#include "magick/memory_.h"
66#include "magick/monitor-private.h"
67#include "magick/morphology.h"
anthony602ab9b2010-01-05 08:06:50 +000068#include "magick/option.h"
cristy701db312009-11-20 03:14:08 +000069#include "magick/pixel-private.h"
70#include "magick/prepress.h"
71#include "magick/quantize.h"
72#include "magick/registry.h"
73#include "magick/semaphore.h"
74#include "magick/splay-tree.h"
75#include "magick/statistic.h"
76#include "magick/string_.h"
anthony602ab9b2010-01-05 08:06:50 +000077#include "magick/string-private.h"
78#include "magick/token.h"
79
anthonyc94cdb02010-01-06 08:15:29 +000080
anthony602ab9b2010-01-05 08:06:50 +000081/*
anthonyc94cdb02010-01-06 08:15:29 +000082 * The following test is for special floating point numbers of value NaN (not
83 * a number), that may be used within a Kernel Definition. NaN's are defined
84 * as part of the IEEE standard for floating point number representation.
anthony602ab9b2010-01-05 08:06:50 +000085 *
anthonyc94cdb02010-01-06 08:15:29 +000086 * These are used a Kernel value of NaN means that that kernal position is not
87 * part of the normal convolution or morphology process, and thus allowing the
88 * use of 'shaped' kernels.
anthony602ab9b2010-01-05 08:06:50 +000089 *
anthonyc94cdb02010-01-06 08:15:29 +000090 * Special Properities Two NaN's are never equal, even if they are from the
91 * same variable That is the IsNaN() macro is only true if the value is NaN.
anthony602ab9b2010-01-05 08:06:50 +000092 */
93#define IsNan(a) ((a)!=(a))
94
anthony29188a82010-01-22 10:12:34 +000095/*
96 * Other global definitions used by module
97 */
98static inline double MagickMin(const double x,const double y)
99{
100 return( x < y ? x : y);
101}
102static inline double MagickMax(const double x,const double y)
103{
104 return( x > y ? x : y);
105}
106#define Minimize(assign,value) assign=MagickMin(assign,value)
107#define Maximize(assign,value) assign=MagickMax(assign,value)
108
anthonyc4c86e02010-01-27 09:30:32 +0000109/* Currently these are only internal to this module */
110static void
anthony83ba99b2010-01-24 08:48:15 +0000111 RotateKernel(KernelInfo *, double),
anthonycc6c8362010-01-25 04:14:01 +0000112 ScaleKernel(KernelInfo *, double),
anthonyc4c86e02010-01-27 09:30:32 +0000113 ShowKernel(KernelInfo *);
anthony83ba99b2010-01-24 08:48:15 +0000114
anthony602ab9b2010-01-05 08:06:50 +0000115
116/*
117%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
118% %
119% %
120% %
anthony83ba99b2010-01-24 08:48:15 +0000121% A c q u i r e K e r n e l I n f o %
anthony602ab9b2010-01-05 08:06:50 +0000122% %
123% %
124% %
125%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
126%
cristy2be15382010-01-21 02:38:03 +0000127% AcquireKernelInfo() takes the given string (generally supplied by the
anthony602ab9b2010-01-05 08:06:50 +0000128% user) and converts it into a Morphology/Convolution Kernel. This allows
129% users to specify a kernel from a number of pre-defined kernels, or to fully
130% specify their own kernel for a specific Convolution or Morphology
131% Operation.
132%
133% The kernel so generated can be any rectangular array of floating point
134% values (doubles) with the 'control point' or 'pixel being affected'
135% anywhere within that array of values.
136%
anthony83ba99b2010-01-24 08:48:15 +0000137% Previously IM was restricted to a square of odd size using the exact
138% center as origin, this is no longer the case, and any rectangular kernel
139% with any value being declared the origin. This in turn allows the use of
140% highly asymmetrical kernels.
anthony602ab9b2010-01-05 08:06:50 +0000141%
142% The floating point values in the kernel can also include a special value
anthony83ba99b2010-01-24 08:48:15 +0000143% known as 'nan' or 'not a number' to indicate that this value is not part
144% of the kernel array. This allows you to shaped the kernel within its
145% rectangular area. That is 'nan' values provide a 'mask' for the kernel
146% shape. However at least one non-nan value must be provided for correct
147% working of a kernel.
anthony602ab9b2010-01-05 08:06:50 +0000148%
anthony83ba99b2010-01-24 08:48:15 +0000149% The returned kernel should be free using the DestroyKernelInfo() when you
150% are finished with it.
anthony602ab9b2010-01-05 08:06:50 +0000151%
152% Input kernel defintion strings can consist of any of three types.
153%
anthony29188a82010-01-22 10:12:34 +0000154% "name:args"
155% Select from one of the built in kernels, using the name and
156% geometry arguments supplied. See AcquireKernelBuiltIn()
anthony602ab9b2010-01-05 08:06:50 +0000157%
158% "WxH[+X+Y]:num, num, num ..."
159% a kernal of size W by H, with W*H floating point numbers following.
160% the 'center' can be optionally be defined at +X+Y (such that +0+0
anthony29188a82010-01-22 10:12:34 +0000161% is top left corner). If not defined the pixel in the center, for
162% odd sizes, or to the immediate top or left of center for even sizes
163% is automatically selected.
anthony602ab9b2010-01-05 08:06:50 +0000164%
anthony29188a82010-01-22 10:12:34 +0000165% "num, num, num, num, ..."
166% list of floating point numbers defining an 'old style' odd sized
167% square kernel. At least 9 values should be provided for a 3x3
168% square kernel, 25 for a 5x5 square kernel, 49 for 7x7, etc.
169% Values can be space or comma separated. This is not recommended.
anthony602ab9b2010-01-05 08:06:50 +0000170%
anthony83ba99b2010-01-24 08:48:15 +0000171% Note that 'name' kernels will start with an alphabetic character while the
172% new kernel specification has a ':' character in its specification string.
173% If neither is the case, it is assumed an old style of a simple list of
174% numbers generating a odd-sized square kernel has been given.
anthony602ab9b2010-01-05 08:06:50 +0000175%
176% The format of the AcquireKernal method is:
177%
cristy2be15382010-01-21 02:38:03 +0000178% KernelInfo *AcquireKernelInfo(const char *kernel_string)
anthony602ab9b2010-01-05 08:06:50 +0000179%
180% A description of each parameter follows:
181%
182% o kernel_string: the Morphology/Convolution kernel wanted.
183%
184*/
185
cristy2be15382010-01-21 02:38:03 +0000186MagickExport KernelInfo *AcquireKernelInfo(const char *kernel_string)
anthony602ab9b2010-01-05 08:06:50 +0000187{
cristy2be15382010-01-21 02:38:03 +0000188 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000189 *kernel;
190
191 char
192 token[MaxTextExtent];
193
194 register unsigned long
195 i;
196
197 const char
198 *p;
199
200 MagickStatusType
201 flags;
202
203 GeometryInfo
204 args;
205
anthony29188a82010-01-22 10:12:34 +0000206 double
207 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
208
anthony602ab9b2010-01-05 08:06:50 +0000209 assert(kernel_string != (const char *) NULL);
210 SetGeometryInfo(&args);
211
212 /* does it start with an alpha - Return a builtin kernel */
213 GetMagickToken(kernel_string,&p,token);
214 if ( isalpha((int)token[0]) )
215 {
216 long
217 type;
218
anthony29188a82010-01-22 10:12:34 +0000219 type=ParseMagickOption(MagickKernelOptions,MagickFalse,token);
anthony602ab9b2010-01-05 08:06:50 +0000220 if ( type < 0 || type == UserDefinedKernel )
cristy2be15382010-01-21 02:38:03 +0000221 return((KernelInfo *)NULL);
anthony602ab9b2010-01-05 08:06:50 +0000222
223 while (((isspace((int) ((unsigned char) *p)) != 0) ||
224 (*p == ',') || (*p == ':' )) && (*p != '\0'))
225 p++;
226 flags = ParseGeometry(p, &args);
227
228 /* special handling of missing values in input string */
229 if ( type == RectangleKernel ) {
230 if ( (flags & WidthValue) == 0 ) /* if no width then */
231 args.rho = args.sigma; /* then width = height */
232 if ( args.rho < 1.0 ) /* if width too small */
233 args.rho = 3; /* then width = 3 */
234 if ( args.sigma < 1.0 ) /* if height too small */
235 args.sigma = args.rho; /* then height = width */
236 if ( (flags & XValue) == 0 ) /* center offset if not defined */
237 args.xi = (double)(((long)args.rho-1)/2);
238 if ( (flags & YValue) == 0 )
239 args.psi = (double)(((long)args.sigma-1)/2);
240 }
241
cristy2be15382010-01-21 02:38:03 +0000242 return(AcquireKernelBuiltIn((KernelInfoType)type, &args));
anthony602ab9b2010-01-05 08:06:50 +0000243 }
244
cristy2be15382010-01-21 02:38:03 +0000245 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
246 if (kernel == (KernelInfo *)NULL)
anthony602ab9b2010-01-05 08:06:50 +0000247 return(kernel);
248 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
249 kernel->type = UserDefinedKernel;
cristyd43a46b2010-01-21 02:13:41 +0000250 kernel->signature = MagickSignature;
anthony602ab9b2010-01-05 08:06:50 +0000251
252 /* Has a ':' in argument - New user kernel specification */
253 p = strchr(kernel_string, ':');
254 if ( p != (char *) NULL)
255 {
anthony602ab9b2010-01-05 08:06:50 +0000256 /* ParseGeometry() needs the geometry separated! -- Arrgghh */
257 memcpy(token, kernel_string, p-kernel_string);
258 token[p-kernel_string] = '\0';
259 flags = ParseGeometry(token, &args);
anthony602ab9b2010-01-05 08:06:50 +0000260
anthony29188a82010-01-22 10:12:34 +0000261 /* Size handling and checks of geometry settings */
anthony602ab9b2010-01-05 08:06:50 +0000262 if ( (flags & WidthValue) == 0 ) /* if no width then */
263 args.rho = args.sigma; /* then width = height */
264 if ( args.rho < 1.0 ) /* if width too small */
265 args.rho = 1.0; /* then width = 1 */
266 if ( args.sigma < 1.0 ) /* if height too small */
267 args.sigma = args.rho; /* then height = width */
268 kernel->width = (unsigned long)args.rho;
269 kernel->height = (unsigned long)args.sigma;
270
271 /* Offset Handling and Checks */
272 if ( args.xi < 0.0 || args.psi < 0.0 )
anthony83ba99b2010-01-24 08:48:15 +0000273 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000274 kernel->offset_x = ((flags & XValue)!=0) ? (unsigned long)args.xi
275 : (kernel->width-1)/2;
276 kernel->offset_y = ((flags & YValue)!=0) ? (unsigned long)args.psi
277 : (kernel->height-1)/2;
278 if ( kernel->offset_x >= kernel->width ||
279 kernel->offset_y >= kernel->height )
anthony83ba99b2010-01-24 08:48:15 +0000280 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000281
282 p++; /* advance beyond the ':' */
283 }
284 else
285 { /* ELSE - Old old kernel specification, forming odd-square kernel */
286 /* count up number of values given */
287 p=(const char *) kernel_string;
cristya699b172010-01-06 16:48:49 +0000288 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
anthony29188a82010-01-22 10:12:34 +0000289 p++; /* ignore "'" chars for convolve filter usage - Cristy */
anthony602ab9b2010-01-05 08:06:50 +0000290 for (i=0; *p != '\0'; i++)
291 {
292 GetMagickToken(p,&p,token);
293 if (*token == ',')
294 GetMagickToken(p,&p,token);
295 }
296 /* set the size of the kernel - old sized square */
297 kernel->width = kernel->height= (unsigned long) sqrt((double) i+1.0);
298 kernel->offset_x = kernel->offset_y = (kernel->width-1)/2;
299 p=(const char *) kernel_string;
anthony29188a82010-01-22 10:12:34 +0000300 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
301 p++; /* ignore "'" chars for convolve filter usage - Cristy */
anthony602ab9b2010-01-05 08:06:50 +0000302 }
303
304 /* Read in the kernel values from rest of input string argument */
305 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
306 kernel->height*sizeof(double));
307 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000308 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000309
anthony29188a82010-01-22 10:12:34 +0000310 kernel->value_min = +MagickHuge;
311 kernel->value_max = -MagickHuge;
anthony602ab9b2010-01-05 08:06:50 +0000312 kernel->range_neg = kernel->range_pos = 0.0;
313 for (i=0; (i < kernel->width*kernel->height) && (*p != '\0'); i++)
314 {
315 GetMagickToken(p,&p,token);
316 if (*token == ',')
317 GetMagickToken(p,&p,token);
anthony29188a82010-01-22 10:12:34 +0000318 if ( LocaleCompare("nan",token) == 0
319 || LocaleCompare("-",token) == 0 ) {
320 kernel->values[i] = nan; /* do not include this value in kernel */
321 }
322 else {
323 kernel->values[i] = StringToDouble(token);
324 ( kernel->values[i] < 0)
325 ? ( kernel->range_neg += kernel->values[i] )
326 : ( kernel->range_pos += kernel->values[i] );
327 Minimize(kernel->value_min, kernel->values[i]);
328 Maximize(kernel->value_max, kernel->values[i]);
329 }
anthony602ab9b2010-01-05 08:06:50 +0000330 }
anthonycc6c8362010-01-25 04:14:01 +0000331 /* check that we recieved at least one real (non-nan) value! */
332 if ( kernel->value_min == MagickHuge )
333 return(DestroyKernelInfo(kernel));
anthony29188a82010-01-22 10:12:34 +0000334
anthonycc6c8362010-01-25 04:14:01 +0000335 /* This should not be needed for a fully defined kernel
anthony29188a82010-01-22 10:12:34 +0000336 * Perhaps an error should be reported instead!
anthonycc6c8362010-01-25 04:14:01 +0000337 * Kept for backward compatibility.
anthony29188a82010-01-22 10:12:34 +0000338 */
339 if ( i < kernel->width*kernel->height ) {
340 Minimize(kernel->value_min, kernel->values[i]);
341 Maximize(kernel->value_max, kernel->values[i]);
342 for ( ; i < kernel->width*kernel->height; i++)
343 kernel->values[i]=0.0;
344 }
anthony602ab9b2010-01-05 08:06:50 +0000345
346 return(kernel);
347}
348
349/*
350%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
351% %
352% %
353% %
354% A c q u i r e K e r n e l B u i l t I n %
355% %
356% %
357% %
358%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
359%
360% AcquireKernelBuiltIn() returned one of the 'named' built-in types of
361% kernels used for special purposes such as gaussian blurring, skeleton
362% pruning, and edge distance determination.
363%
364% They take a KernelType, and a set of geometry style arguments, which were
365% typically decoded from a user supplied string, or from a more complex
366% Morphology Method that was requested.
367%
368% The format of the AcquireKernalBuiltIn method is:
369%
cristy2be15382010-01-21 02:38:03 +0000370% KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000371% const GeometryInfo args)
372%
373% A description of each parameter follows:
374%
375% o type: the pre-defined type of kernel wanted
376%
377% o args: arguments defining or modifying the kernel
378%
379% Convolution Kernels
380%
381% Gaussian "[{radius}]x{sigma}"
382% Generate a two-dimentional gaussian kernel, as used by -gaussian
383% A sigma is required, (with the 'x'), due to historical reasons.
384%
385% NOTE: that the 'radius' is optional, but if provided can limit (clip)
386% the final size of the resulting kernel to a square 2*radius+1 in size.
387% The radius should be at least 2 times that of the sigma value, or
388% sever clipping and aliasing may result. If not given or set to 0 the
389% radius will be determined so as to produce the best minimal error
390% result, which is usally much larger than is normally needed.
391%
392% Blur "[{radius}]x{sigma}[+angle]"
393% As per Gaussian, but generates a 1 dimensional or linear gaussian
394% blur, at the angle given (current restricted to orthogonal angles).
395% If a 'radius' is given the kernel is clipped to a width of 2*radius+1.
396%
397% NOTE that two such blurs perpendicular to each other is equivelent to
398% -blur and the previous gaussian, but is often 10 or more times faster.
399%
400% Comet "[{width}]x{sigma}[+angle]"
401% Blur in one direction only, mush like how a bright object leaves
402% a comet like trail. The Kernel is actually half a gaussian curve,
403% Adding two such blurs in oppiste directions produces a Linear Blur.
404%
405% NOTE: that the first argument is the width of the kernel and not the
406% radius of the kernel.
407%
408% # Still to be implemented...
409% #
410% # Laplacian "{radius}x{sigma}"
411% # Laplacian (a mexican hat like) Function
412% #
413% # LOG "{radius},{sigma1},{sigma2}
414% # Laplacian of Gaussian
415% #
416% # DOG "{radius},{sigma1},{sigma2}
417% # Difference of Gaussians
418%
419% Boolean Kernels
420%
421% Rectangle "{geometry}"
422% Simply generate a rectangle of 1's with the size given. You can also
423% specify the location of the 'control point', otherwise the closest
424% pixel to the center of the rectangle is selected.
425%
426% Properly centered and odd sized rectangles work the best.
427%
428% Diamond "[{radius}]"
429% Generate a diamond shaped kernal with given radius to the points.
430% Kernel size will again be radius*2+1 square and defaults to radius 1,
431% generating a 3x3 kernel that is slightly larger than a square.
432%
433% Square "[{radius}]"
434% Generate a square shaped kernel of size radius*2+1, and defaulting
435% to a 3x3 (radius 1).
436%
437% Note that using a larger radius for the "Square" or the "Diamond"
438% is also equivelent to iterating the basic morphological method
439% that many times. However However iterating with the smaller radius 1
440% default is actually faster than using a larger kernel radius.
441%
442% Disk "[{radius}]
443% Generate a binary disk of the radius given, radius may be a float.
444% Kernel size will be ceil(radius)*2+1 square.
445% NOTE: Here are some disk shapes of specific interest
446% "disk:1" => "diamond" or "cross:1"
447% "disk:1.5" => "square"
448% "disk:2" => "diamond:2"
anthony83ba99b2010-01-24 08:48:15 +0000449% "disk:2.5" => a general disk shape of radius 2
anthony602ab9b2010-01-05 08:06:50 +0000450% "disk:2.9" => "square:2"
anthony83ba99b2010-01-24 08:48:15 +0000451% "disk:3.5" => default - octagonal/disk shape of radius 3
anthony602ab9b2010-01-05 08:06:50 +0000452% "disk:4.2" => roughly octagonal shape of radius 4
anthony83ba99b2010-01-24 08:48:15 +0000453% "disk:4.3" => a general disk shape of radius 4
anthony602ab9b2010-01-05 08:06:50 +0000454% After this all the kernel shape becomes more and more circular.
455%
456% Because a "disk" is more circular when using a larger radius, using a
457% larger radius is preferred over iterating the morphological operation.
458%
459% Plus "[{radius}]"
460% Generate a kernel in the shape of a 'plus' sign. The length of each
461% arm is also the radius, which defaults to 2.
462%
463% This kernel is not a good general morphological kernel, but is used
464% more for highlighting and marking any single pixels in an image using,
465% a "Dilate" or "Erode" method as appropriate.
anthonyc94cdb02010-01-06 08:15:29 +0000466%
anthony602ab9b2010-01-05 08:06:50 +0000467% NOTE: "plus:1" is equivelent to a "Diamond" kernel.
468%
469% Note that unlike other kernels iterating a plus does not produce the
470% same result as using a larger radius for the cross.
471%
472% Distance Measuring Kernels
473%
474% Chebyshev "[{radius}][x{scale}]" largest x or y distance (default r=1)
475% Manhatten "[{radius}][x{scale}]" square grid distance (default r=1)
anthonyc94cdb02010-01-06 08:15:29 +0000476% Euclidean "[{radius}][x{scale}]" direct distance (default r=1)
anthony602ab9b2010-01-05 08:06:50 +0000477%
478% Different types of distance measuring methods, which are used with the
479% a 'Distance' morphology method for generating a gradient based on
480% distance from an edge of a binary shape, though there is a technique
481% for handling a anti-aliased shape.
482%
anthonyc94cdb02010-01-06 08:15:29 +0000483% Chebyshev Distance (also known as Tchebychev Distance) is a value of
484% one to any neighbour, orthogonal or diagonal. One why of thinking of
485% it is the number of squares a 'King' or 'Queen' in chess needs to
486% traverse reach any other position on a chess board. It results in a
487% 'square' like distance function, but one where diagonals are closer
488% than expected.
anthony602ab9b2010-01-05 08:06:50 +0000489%
anthonyc94cdb02010-01-06 08:15:29 +0000490% Manhatten Distance (also known as Rectilinear Distance, or the Taxi
491% Cab metric), is the distance needed when you can only travel in
492% orthogonal (horizontal or vertical) only. It is the distance a 'Rook'
493% in chess would travel. It results in a diamond like distances, where
494% diagonals are further than expected.
anthony602ab9b2010-01-05 08:06:50 +0000495%
anthonyc94cdb02010-01-06 08:15:29 +0000496% Euclidean Distance is the 'direct' or 'as the crow flys distance.
497% However by default the kernel size only has a radius of 1, which
498% limits the distance to 'Knight' like moves, with only orthogonal and
499% diagonal measurements being correct. As such for the default kernel
500% you will get octagonal like distance function, which is reasonally
501% accurate.
502%
503% However if you use a larger radius such as "Euclidean:4" you will
504% get a much smoother distance gradient from the edge of the shape.
505% Of course a larger kernel is slower to use, and generally not needed.
506%
507% To allow the use of fractional distances that you get with diagonals
508% the actual distance is scaled by a fixed value which the user can
509% provide. This is not actually nessary for either ""Chebyshev" or
510% "Manhatten" distance kernels, but is done for all three distance
511% kernels. If no scale is provided it is set to a value of 100,
512% allowing for a maximum distance measurement of 655 pixels using a Q16
513% version of IM, from any edge. However for small images this can
514% result in quite a dark gradient.
515%
516% See the 'Distance' Morphological Method, for information of how it is
517% applied.
anthony602ab9b2010-01-05 08:06:50 +0000518%
519*/
520
cristy2be15382010-01-21 02:38:03 +0000521MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000522 const GeometryInfo *args)
523{
cristy2be15382010-01-21 02:38:03 +0000524 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000525 *kernel;
526
527 register unsigned long
528 i;
529
530 register long
531 u,
532 v;
533
534 double
535 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
536
cristy2be15382010-01-21 02:38:03 +0000537 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
538 if (kernel == (KernelInfo *) NULL)
anthony602ab9b2010-01-05 08:06:50 +0000539 return(kernel);
540 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
anthonyc94cdb02010-01-06 08:15:29 +0000541 kernel->value_min = kernel->value_max = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000542 kernel->range_neg = kernel->range_pos = 0.0;
543 kernel->type = type;
cristyd43a46b2010-01-21 02:13:41 +0000544 kernel->signature = MagickSignature;
anthony602ab9b2010-01-05 08:06:50 +0000545
546 switch(type) {
547 /* Convolution Kernels */
548 case GaussianKernel:
549 { double
550 sigma = fabs(args->sigma);
551
552 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
553
554 kernel->width = kernel->height =
555 GetOptimalKernelWidth2D(args->rho,sigma);
556 kernel->offset_x = kernel->offset_y = (kernel->width-1)/2;
557 kernel->range_neg = kernel->range_pos = 0.0;
558 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
559 kernel->height*sizeof(double));
560 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000561 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000562
563 sigma = 2.0*sigma*sigma; /* simplify the expression */
564 for ( i=0, v=-kernel->offset_y; v <= (long)kernel->offset_y; v++)
565 for ( u=-kernel->offset_x; u <= (long)kernel->offset_x; u++, i++)
566 kernel->range_pos += (
567 kernel->values[i] =
568 exp(-((double)(u*u+v*v))/sigma)
569 /* / (MagickPI*sigma) */ );
anthonyc94cdb02010-01-06 08:15:29 +0000570 kernel->value_min = 0;
571 kernel->value_max = kernel->values[
572 kernel->offset_y*kernel->width+kernel->offset_x ];
anthony602ab9b2010-01-05 08:06:50 +0000573
anthonycc6c8362010-01-25 04:14:01 +0000574 ScaleKernel(kernel, 0.0); /* Normalize Kernel Values */
anthony602ab9b2010-01-05 08:06:50 +0000575
576 break;
577 }
578 case BlurKernel:
579 { double
580 sigma = fabs(args->sigma);
581
582 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
583
584 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
585 kernel->offset_x = (kernel->width-1)/2;
586 kernel->height = 1;
587 kernel->offset_y = 0;
588 kernel->range_neg = kernel->range_pos = 0.0;
589 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
590 kernel->height*sizeof(double));
591 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000592 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000593
594#if 1
595#define KernelRank 3
596 /* Formula derived from GetBlurKernel() in "effect.c" (plus bug fix).
597 ** It generates a gaussian 3 times the width, and compresses it into
598 ** the expected range. This produces a closer normalization of the
599 ** resulting kernel, especially for very low sigma values.
600 ** As such while wierd it is prefered.
601 **
602 ** I am told this method originally came from Photoshop.
603 */
604 sigma *= KernelRank; /* simplify expanded curve */
605 v = (kernel->width*KernelRank-1)/2; /* start/end points to fit range */
606 (void) ResetMagickMemory(kernel->values,0, (size_t)
607 kernel->width*sizeof(double));
608 for ( u=-v; u <= v; u++) {
609 kernel->values[(u+v)/KernelRank] +=
610 exp(-((double)(u*u))/(2.0*sigma*sigma))
611 /* / (MagickSQ2PI*sigma/KernelRank) */ ;
612 }
613 for (i=0; i < kernel->width; i++)
614 kernel->range_pos += kernel->values[i];
615#else
616 for ( i=0, u=-kernel->offset_x; i < kernel->width; i++, u++)
617 kernel->range_pos += (
618 kernel->values[i] =
619 exp(-((double)(u*u))/(2.0*sigma*sigma))
620 /* / (MagickSQ2PI*sigma) */ );
621#endif
anthonyc94cdb02010-01-06 08:15:29 +0000622 kernel->value_min = 0;
623 kernel->value_max = kernel->values[ kernel->offset_x ];
anthonycc6c8362010-01-25 04:14:01 +0000624 /* Note that neither methods above generate a normalized kernel,
625 ** though it gets close. The kernel may be 'clipped' by a user defined
626 ** radius, producing a smaller (darker) kernel. Also for very small
627 ** sigma's (> 0.1) the central value becomes larger than one, and thus
628 ** producing a very bright kernel.
anthony602ab9b2010-01-05 08:06:50 +0000629 */
anthonycc6c8362010-01-25 04:14:01 +0000630
anthony602ab9b2010-01-05 08:06:50 +0000631 /* Normalize the 1D Gaussian Kernel
632 **
633 ** Because of this the divisor in the above kernel generator is
anthonyc94cdb02010-01-06 08:15:29 +0000634 ** not needed, so is not done above.
anthony602ab9b2010-01-05 08:06:50 +0000635 */
anthonycc6c8362010-01-25 04:14:01 +0000636 ScaleKernel(kernel, 0.0); /* Normalize Kernel Values */
637
anthony602ab9b2010-01-05 08:06:50 +0000638 /* rotate the kernel by given angle */
anthony83ba99b2010-01-24 08:48:15 +0000639 RotateKernel(kernel, args->xi);
anthony602ab9b2010-01-05 08:06:50 +0000640 break;
641 }
642 case CometKernel:
643 { double
644 sigma = fabs(args->sigma);
645
646 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
647
648 if ( args->rho < 1.0 )
649 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
650 else
651 kernel->width = (unsigned long)args->rho;
652 kernel->offset_x = kernel->offset_y = 0;
653 kernel->height = 1;
654 kernel->range_neg = kernel->range_pos = 0.0;
655 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
656 kernel->height*sizeof(double));
657 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000658 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000659
660 /* A comet blur is half a gaussian curve, so that the object is
661 ** blurred in one direction only. This may not be quite the right
662 ** curve so may change in the future. The function must be normalised.
663 */
664#if 1
665#define KernelRank 3
666 sigma *= KernelRank; /* simplify expanded curve */
667 v = kernel->width*KernelRank; /* start/end points to fit range */
668 (void) ResetMagickMemory(kernel->values,0, (size_t)
669 kernel->width*sizeof(double));
670 for ( u=0; u < v; u++) {
671 kernel->values[u/KernelRank] +=
672 exp(-((double)(u*u))/(2.0*sigma*sigma))
673 /* / (MagickSQ2PI*sigma/KernelRank) */ ;
674 }
675 for (i=0; i < kernel->width; i++)
676 kernel->range_pos += kernel->values[i];
677#else
678 for ( i=0; i < kernel->width; i++)
679 kernel->range_pos += (
680 kernel->values[i] =
681 exp(-((double)(i*i))/(2.0*sigma*sigma))
682 /* / (MagickSQ2PI*sigma) */ );
683#endif
anthonyc94cdb02010-01-06 08:15:29 +0000684 kernel->value_min = 0;
685 kernel->value_max = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000686
anthonycc6c8362010-01-25 04:14:01 +0000687 ScaleKernel(kernel, 0.0); /* Normalize Kernel Values */
anthony83ba99b2010-01-24 08:48:15 +0000688 RotateKernel(kernel, args->xi);
anthony602ab9b2010-01-05 08:06:50 +0000689 break;
690 }
691 /* Boolean Kernels */
692 case RectangleKernel:
693 case SquareKernel:
694 {
695 if ( type == SquareKernel )
696 {
697 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000698 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000699 else
700 kernel->width = kernel->height = 2*(long)args->rho+1;
701 kernel->offset_x = kernel->offset_y = (kernel->width-1)/2;
702 }
703 else {
cristy2be15382010-01-21 02:38:03 +0000704 /* NOTE: user defaults set in "AcquireKernelInfo()" */
anthony602ab9b2010-01-05 08:06:50 +0000705 if ( args->rho < 1.0 || args->sigma < 1.0 )
anthony83ba99b2010-01-24 08:48:15 +0000706 return(DestroyKernelInfo(kernel)); /* invalid args given */
anthony602ab9b2010-01-05 08:06:50 +0000707 kernel->width = (unsigned long)args->rho;
708 kernel->height = (unsigned long)args->sigma;
709 if ( args->xi < 0.0 || args->xi > (double)kernel->width ||
710 args->psi < 0.0 || args->psi > (double)kernel->height )
anthony83ba99b2010-01-24 08:48:15 +0000711 return(DestroyKernelInfo(kernel)); /* invalid args given */
anthony602ab9b2010-01-05 08:06:50 +0000712 kernel->offset_x = (unsigned long)args->xi;
713 kernel->offset_y = (unsigned long)args->psi;
714 }
715 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
716 kernel->height*sizeof(double));
717 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000718 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000719
anthonycc6c8362010-01-25 04:14:01 +0000720 /* set all kernel values to 1.0 */
anthony602ab9b2010-01-05 08:06:50 +0000721 u=kernel->width*kernel->height;
722 for ( i=0; i < (unsigned long)u; i++)
723 kernel->values[i] = 1.0;
anthonycc6c8362010-01-25 04:14:01 +0000724 kernel->value_min = kernel->value_max = 1.0; /* a flat shape */
anthonyc94cdb02010-01-06 08:15:29 +0000725 kernel->range_pos = (double) u;
anthonycc6c8362010-01-25 04:14:01 +0000726 break;
anthony602ab9b2010-01-05 08:06:50 +0000727 }
728 case DiamondKernel:
729 {
730 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000731 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000732 else
733 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
734 kernel->offset_x = kernel->offset_y = (kernel->width-1)/2;
735
736 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
737 kernel->height*sizeof(double));
738 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000739 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000740
anthonycc6c8362010-01-25 04:14:01 +0000741 /* set all kernel values within diamond area to 1.0 */
anthony602ab9b2010-01-05 08:06:50 +0000742 for ( i=0, v=-kernel->offset_y; v <= (long)kernel->offset_y; v++)
743 for ( u=-kernel->offset_x; u <= (long)kernel->offset_x; u++, i++)
744 if ((labs(u)+labs(v)) <= (long)kernel->offset_x)
745 kernel->range_pos += kernel->values[i] = 1.0;
746 else
747 kernel->values[i] = nan;
anthonycc6c8362010-01-25 04:14:01 +0000748 kernel->value_min = kernel->value_max = 1.0; /* a flat shape */
anthony602ab9b2010-01-05 08:06:50 +0000749 break;
750 }
751 case DiskKernel:
752 {
753 long
754 limit;
755
756 limit = (long)(args->rho*args->rho);
anthony83ba99b2010-01-24 08:48:15 +0000757 if (args->rho < 0.1) /* default radius approx 3.5 */
758 kernel->width = kernel->height = 7L, limit = 10L;
anthony602ab9b2010-01-05 08:06:50 +0000759 else
760 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
761 kernel->offset_x = kernel->offset_y = (kernel->width-1)/2;
762
763 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
764 kernel->height*sizeof(double));
765 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000766 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000767
anthonycc6c8362010-01-25 04:14:01 +0000768 /* set all kernel values within disk area to 1.0 */
anthony602ab9b2010-01-05 08:06:50 +0000769 for ( i=0, v=-kernel->offset_y; v <= (long)kernel->offset_y; v++)
770 for ( u=-kernel->offset_x; u <= (long)kernel->offset_x; u++, i++)
771 if ((u*u+v*v) <= limit)
772 kernel->range_pos += kernel->values[i] = 1.0;
773 else
774 kernel->values[i] = nan;
anthonycc6c8362010-01-25 04:14:01 +0000775 kernel->value_min = kernel->value_max = 1.0; /* a flat shape */
anthony602ab9b2010-01-05 08:06:50 +0000776 break;
777 }
778 case PlusKernel:
779 {
780 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000781 kernel->width = kernel->height = 5; /* default radius 2 */
anthony602ab9b2010-01-05 08:06:50 +0000782 else
783 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
784 kernel->offset_x = kernel->offset_y = (kernel->width-1)/2;
785
786 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
787 kernel->height*sizeof(double));
788 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000789 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000790
anthonycc6c8362010-01-25 04:14:01 +0000791 /* set all kernel values along axises to 1.0 */
anthony602ab9b2010-01-05 08:06:50 +0000792 for ( i=0, v=-kernel->offset_y; v <= (long)kernel->offset_y; v++)
793 for ( u=-kernel->offset_x; u <= (long)kernel->offset_x; u++, i++)
794 kernel->values[i] = (u == 0 || v == 0) ? 1.0 : nan;
anthonycc6c8362010-01-25 04:14:01 +0000795 kernel->value_min = kernel->value_max = 1.0; /* a flat shape */
anthony602ab9b2010-01-05 08:06:50 +0000796 kernel->range_pos = kernel->width*2.0 - 1.0;
797 break;
798 }
799 /* Distance Measuring Kernels */
800 case ChebyshevKernel:
801 {
802 double
803 scale;
804
805 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000806 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000807 else
808 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
809 kernel->offset_x = kernel->offset_y = (kernel->width-1)/2;
810
811 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
812 kernel->height*sizeof(double));
813 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000814 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000815
816 scale = (args->sigma < 1.0) ? 100.0 : args->sigma;
817 for ( i=0, v=-kernel->offset_y; v <= (long)kernel->offset_y; v++)
818 for ( u=-kernel->offset_x; u <= (long)kernel->offset_x; u++, i++)
819 kernel->range_pos += ( kernel->values[i] =
820 scale*((labs(u)>labs(v)) ? labs(u) : labs(v)) );
anthonyc94cdb02010-01-06 08:15:29 +0000821 kernel->value_max = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000822 break;
823 }
824 case ManhattenKernel:
825 {
826 double
827 scale;
828
829 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000830 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000831 else
832 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
833 kernel->offset_x = kernel->offset_y = (kernel->width-1)/2;
834
835 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
836 kernel->height*sizeof(double));
837 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000838 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000839
840 scale = (args->sigma < 1.0) ? 100.0 : args->sigma;
841 for ( i=0, v=-kernel->offset_y; v <= (long)kernel->offset_y; v++)
842 for ( u=-kernel->offset_x; u <= (long)kernel->offset_x; u++, i++)
843 kernel->range_pos += ( kernel->values[i] =
844 scale*(labs(u)+labs(v)) );
anthonyc94cdb02010-01-06 08:15:29 +0000845 kernel->value_max = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000846 break;
847 }
848 case EuclideanKernel:
849 {
850 double
851 scale;
852
853 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000854 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000855 else
856 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
857 kernel->offset_x = kernel->offset_y = (kernel->width-1)/2;
858
859 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
860 kernel->height*sizeof(double));
861 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000862 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000863
864 scale = (args->sigma < 1.0) ? 100.0 : args->sigma;
865 for ( i=0, v=-kernel->offset_y; v <= (long)kernel->offset_y; v++)
866 for ( u=-kernel->offset_x; u <= (long)kernel->offset_x; u++, i++)
867 kernel->range_pos += ( kernel->values[i] =
868 scale*sqrt((double)(u*u+v*v)) );
anthonyc94cdb02010-01-06 08:15:29 +0000869 kernel->value_max = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000870 break;
871 }
872 /* Undefined Kernels */
873 case LaplacianKernel:
874 case LOGKernel:
875 case DOGKernel:
876 assert("Kernel Type has not been defined yet");
877 /* FALL THRU */
878 default:
879 /* Generate a No-Op minimal kernel - 1x1 pixel */
880 kernel->values=(double *)AcquireQuantumMemory((size_t)1,sizeof(double));
881 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000882 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000883 kernel->width = kernel->height = 1;
884 kernel->offset_x = kernel->offset_x = 0;
885 kernel->type = UndefinedKernel;
anthonyc94cdb02010-01-06 08:15:29 +0000886 kernel->value_max =
887 kernel->range_pos =
888 kernel->values[0] = 1.0; /* a flat single-point no-op kernel! */
anthony602ab9b2010-01-05 08:06:50 +0000889 break;
890 }
891
892 return(kernel);
893}
anthonyc94cdb02010-01-06 08:15:29 +0000894
anthony602ab9b2010-01-05 08:06:50 +0000895/*
896%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
897% %
898% %
899% %
anthony83ba99b2010-01-24 08:48:15 +0000900% D e s t r o y K e r n e l I n f o %
anthony602ab9b2010-01-05 08:06:50 +0000901% %
902% %
903% %
904%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
905%
anthony83ba99b2010-01-24 08:48:15 +0000906% DestroyKernelInfo() frees the memory used by a Convolution/Morphology
907% kernel.
anthony602ab9b2010-01-05 08:06:50 +0000908%
anthony83ba99b2010-01-24 08:48:15 +0000909% The format of the DestroyKernelInfo method is:
anthony602ab9b2010-01-05 08:06:50 +0000910%
anthony83ba99b2010-01-24 08:48:15 +0000911% KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +0000912%
913% A description of each parameter follows:
914%
915% o kernel: the Morphology/Convolution kernel to be destroyed
916%
917*/
918
anthony83ba99b2010-01-24 08:48:15 +0000919MagickExport KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +0000920{
cristy2be15382010-01-21 02:38:03 +0000921 assert(kernel != (KernelInfo *) NULL);
anthony602ab9b2010-01-05 08:06:50 +0000922 kernel->values=(double *)RelinquishMagickMemory(kernel->values);
cristy2be15382010-01-21 02:38:03 +0000923 kernel=(KernelInfo *) RelinquishMagickMemory(kernel);
anthony602ab9b2010-01-05 08:06:50 +0000924 return(kernel);
925}
anthonyc94cdb02010-01-06 08:15:29 +0000926
927/*
928%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
929% %
930% %
931% %
anthony29188a82010-01-22 10:12:34 +0000932% M o r p h o l o g y I m a g e C h a n n e l %
anthony602ab9b2010-01-05 08:06:50 +0000933% %
934% %
935% %
936%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
937%
anthony29188a82010-01-22 10:12:34 +0000938% MorphologyImageChannel() applies a user supplied kernel to the image
939% according to the given mophology method.
anthony602ab9b2010-01-05 08:06:50 +0000940%
941% The given kernel is assumed to have been pre-scaled appropriatally, usally
942% by the kernel generator.
943%
944% The format of the MorphologyImage method is:
945%
anthony29188a82010-01-22 10:12:34 +0000946% Image *MorphologyImage(const Image *image, MorphologyMethod method,
947% const long iterations, KernelInfo *kernel, ExceptionInfo *exception)
948% Image *MorphologyImageChannel(const Image *image, const ChannelType
949% channel, MorphologyMethod method, const long iterations, KernelInfo
950% *kernel, ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +0000951%
952% A description of each parameter follows:
953%
954% o image: the image.
955%
956% o method: the morphology method to be applied.
957%
958% o iterations: apply the operation this many times (or no change).
959% A value of -1 means loop until no change found.
960% How this is applied may depend on the morphology method.
961% Typically this is a value of 1.
962%
963% o channel: the channel type.
964%
965% o kernel: An array of double representing the morphology kernel.
anthony29188a82010-01-22 10:12:34 +0000966% Warning: kernel may be normalized for the Convolve method.
anthony602ab9b2010-01-05 08:06:50 +0000967%
968% o exception: return any errors or warnings in this structure.
969%
970%
971% TODO: bias and auto-scale handling of the kernel for convolution
972% The given kernel is assumed to have been pre-scaled appropriatally, usally
973% by the kernel generator.
974%
975*/
976
anthony602ab9b2010-01-05 08:06:50 +0000977/* Internal function
978 * Apply the Morphology method with the given Kernel
anthony83ba99b2010-01-24 08:48:15 +0000979 * And return the number of pixels that changed.
anthony602ab9b2010-01-05 08:06:50 +0000980 */
981static unsigned long MorphologyApply(const Image *image, Image
982 *result_image, const MorphologyMethod method, const ChannelType channel,
cristy2be15382010-01-21 02:38:03 +0000983 const KernelInfo *kernel, ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +0000984{
cristy2be15382010-01-21 02:38:03 +0000985#define MorphologyTag "Morphology/Image"
anthony602ab9b2010-01-05 08:06:50 +0000986
987 long
anthony29188a82010-01-22 10:12:34 +0000988 progress;
anthony602ab9b2010-01-05 08:06:50 +0000989
990 unsigned long
anthony29188a82010-01-22 10:12:34 +0000991 y, offx, offy,
anthony602ab9b2010-01-05 08:06:50 +0000992 changed;
993
994 MagickBooleanType
995 status;
996
997 MagickPixelPacket
998 bias;
999
1000 CacheView
1001 *p_view,
1002 *q_view;
1003
1004 /*
1005 Apply Morphology to Image.
1006 */
1007 status=MagickTrue;
1008 changed=0;
1009 progress=0;
1010
1011 GetMagickPixelPacket(image,&bias);
1012 SetMagickPixelPacketBias(image,&bias);
anthonycc6c8362010-01-25 04:14:01 +00001013 /* Future: handle auto-bias from user, based on kernel input */
anthony602ab9b2010-01-05 08:06:50 +00001014
1015 p_view=AcquireCacheView(image);
1016 q_view=AcquireCacheView(result_image);
anthony29188a82010-01-22 10:12:34 +00001017
anthonycc6c8362010-01-25 04:14:01 +00001018 /* Some methods (including convolve) needs use a reflected kernel.
1019 * Adjust 'origin' offsets for this reflected kernel.
anthony29188a82010-01-22 10:12:34 +00001020 */
1021 offx = kernel->offset_x;
1022 offy = kernel->offset_y;
1023 switch(method) {
1024 case ErodeMorphology:
1025 case ErodeIntensityMorphology:
1026 /* kernel is not reflected */
1027 break;
1028 default:
1029 /* kernel needs to be reflected */
1030 offx = kernel->width-offx-1;
1031 offy = kernel->height-offy-1;
1032 break;
1033 }
1034
anthony602ab9b2010-01-05 08:06:50 +00001035#if defined(MAGICKCORE_OPENMP_SUPPORT)
1036 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1037#endif
anthony29188a82010-01-22 10:12:34 +00001038 for (y=0; y < image->rows; y++)
anthony602ab9b2010-01-05 08:06:50 +00001039 {
1040 MagickBooleanType
1041 sync;
1042
1043 register const PixelPacket
1044 *restrict p;
1045
1046 register const IndexPacket
1047 *restrict p_indexes;
1048
1049 register PixelPacket
1050 *restrict q;
1051
1052 register IndexPacket
1053 *restrict q_indexes;
1054
anthony29188a82010-01-22 10:12:34 +00001055 register unsigned long
anthony602ab9b2010-01-05 08:06:50 +00001056 x;
1057
anthony29188a82010-01-22 10:12:34 +00001058 unsigned long
anthony602ab9b2010-01-05 08:06:50 +00001059 r;
1060
1061 if (status == MagickFalse)
1062 continue;
anthony29188a82010-01-22 10:12:34 +00001063 p=GetCacheViewVirtualPixels(p_view, -offx, y-offy,
1064 image->columns+kernel->width, kernel->height, exception);
anthony602ab9b2010-01-05 08:06:50 +00001065 q=GetCacheViewAuthenticPixels(q_view,0,y,result_image->columns,1,
1066 exception);
1067 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1068 {
1069 status=MagickFalse;
1070 continue;
1071 }
1072 p_indexes=GetCacheViewVirtualIndexQueue(p_view);
1073 q_indexes=GetCacheViewAuthenticIndexQueue(q_view);
anthony29188a82010-01-22 10:12:34 +00001074 r = (image->columns+kernel->width)*offy+offx; /* constant */
1075
1076 for (x=0; x < image->columns; x++)
anthony602ab9b2010-01-05 08:06:50 +00001077 {
anthony29188a82010-01-22 10:12:34 +00001078 unsigned long
anthony602ab9b2010-01-05 08:06:50 +00001079 v;
1080
anthony29188a82010-01-22 10:12:34 +00001081 register unsigned long
anthony602ab9b2010-01-05 08:06:50 +00001082 u;
1083
1084 register const double
1085 *restrict k;
1086
1087 register const PixelPacket
1088 *restrict k_pixels;
1089
1090 register const IndexPacket
1091 *restrict k_indexes;
1092
1093 MagickPixelPacket
1094 result;
1095
anthony29188a82010-01-22 10:12:34 +00001096 /* Copy input to ouput image for unused channels
anthony83ba99b2010-01-24 08:48:15 +00001097 * This removes need for 'cloning' a new image every iteration
anthony29188a82010-01-22 10:12:34 +00001098 */
anthony602ab9b2010-01-05 08:06:50 +00001099 *q = p[r];
1100 if (image->colorspace == CMYKColorspace)
1101 q_indexes[x] = p_indexes[r];
1102
anthony29188a82010-01-22 10:12:34 +00001103 result.index=0; /* stop compiler warnings */
anthony602ab9b2010-01-05 08:06:50 +00001104 switch (method) {
1105 case ConvolveMorphology:
1106 result=bias;
1107 break; /* default result is the convolution bias */
anthony83ba99b2010-01-24 08:48:15 +00001108#if 1
1109 case DilateMorphology:
anthony29188a82010-01-22 10:12:34 +00001110 result.red =
1111 result.green =
1112 result.blue =
1113 result.opacity =
1114 result.index = -MagickHuge;
1115 break;
1116 case ErodeMorphology:
1117 result.red =
1118 result.green =
1119 result.blue =
1120 result.opacity =
1121 result.index = +MagickHuge;
1122 break;
1123#endif
anthony602ab9b2010-01-05 08:06:50 +00001124 default:
anthony29188a82010-01-22 10:12:34 +00001125 /* Otherwise just start with the original pixel value */
anthony602ab9b2010-01-05 08:06:50 +00001126 result.red = p[r].red;
1127 result.green = p[r].green;
1128 result.blue = p[r].blue;
1129 result.opacity = QuantumRange - p[r].opacity;
1130 if ( image->colorspace == CMYKColorspace)
1131 result.index = p_indexes[r];
1132 break;
1133 }
1134
1135 switch ( method ) {
1136 case ConvolveMorphology:
anthony29188a82010-01-22 10:12:34 +00001137 /* Weighted Average of pixels
1138 *
1139 * NOTE for correct working of this operation for asymetrical
1140 * kernels, the kernel needs to be applied in its reflected form.
1141 * That is its values needs to be reversed.
1142 */
anthony602ab9b2010-01-05 08:06:50 +00001143 if (((channel & OpacityChannel) == 0) ||
1144 (image->matte == MagickFalse))
1145 {
anthony29188a82010-01-22 10:12:34 +00001146 /* Convolution (no transparency) */
1147 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001148 k_pixels = p;
1149 k_indexes = p_indexes;
anthony29188a82010-01-22 10:12:34 +00001150 for (v=0; v < kernel->height; v++) {
1151 for (u=0; u < kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001152 if ( IsNan(*k) ) continue;
1153 result.red += (*k)*k_pixels[u].red;
1154 result.green += (*k)*k_pixels[u].green;
1155 result.blue += (*k)*k_pixels[u].blue;
anthony83ba99b2010-01-24 08:48:15 +00001156 /* result.opacity += not involved here */
anthony602ab9b2010-01-05 08:06:50 +00001157 if ( image->colorspace == CMYKColorspace)
1158 result.index += (*k)*k_indexes[u];
1159 }
1160 k_pixels += image->columns+kernel->width;
1161 k_indexes += image->columns+kernel->width;
1162 }
anthony602ab9b2010-01-05 08:06:50 +00001163 }
1164 else
1165 { /* Kernel & Alpha weighted Convolution */
1166 MagickRealType
1167 alpha, /* alpha value * kernel weighting */
1168 gamma; /* weighting divisor */
1169
1170 gamma=0.0;
anthony29188a82010-01-22 10:12:34 +00001171 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001172 k_pixels = p;
1173 k_indexes = p_indexes;
anthony29188a82010-01-22 10:12:34 +00001174 for (v=0; v < kernel->height; v++) {
1175 for (u=0; u < kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001176 if ( IsNan(*k) ) continue;
1177 alpha=(*k)*(QuantumScale*(QuantumRange-
1178 k_pixels[u].opacity));
1179 gamma += alpha;
1180 result.red += alpha*k_pixels[u].red;
1181 result.green += alpha*k_pixels[u].green;
1182 result.blue += alpha*k_pixels[u].blue;
anthony83ba99b2010-01-24 08:48:15 +00001183 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00001184 if ( image->colorspace == CMYKColorspace)
1185 result.index += alpha*k_indexes[u];
1186 }
1187 k_pixels += image->columns+kernel->width;
1188 k_indexes += image->columns+kernel->width;
1189 }
1190 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
anthony83ba99b2010-01-24 08:48:15 +00001191 result.red *= gamma;
1192 result.green *= gamma;
1193 result.blue *= gamma;
1194 result.opacity *= gamma;
1195 result.index *= gamma;
anthony602ab9b2010-01-05 08:06:50 +00001196 }
1197 break;
1198
anthony83ba99b2010-01-24 08:48:15 +00001199 case DilateMorphology:
anthony29188a82010-01-22 10:12:34 +00001200 /* Maximize Value within kernel shape
1201 *
1202 * NOTE for correct working of this operation for asymetrical
1203 * kernels, the kernel needs to be applied in its reflected form.
1204 * That is its values needs to be reversed.
1205 *
1206 * NOTE: in normal Greyscale Morphology, the kernel value should
1207 * be added to the real value, this is currently not done, due to
1208 * the nature of the boolean kernels being used.
1209 *
1210 */
1211 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001212 k_pixels = p;
1213 k_indexes = p_indexes;
anthony29188a82010-01-22 10:12:34 +00001214 for (v=0; v < kernel->height; v++) {
1215 for (u=0; u < kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001216 if ( IsNan(*k) || (*k) < 0.5 ) continue;
1217 Maximize(result.red, k_pixels[u].red);
1218 Maximize(result.green, k_pixels[u].green);
1219 Maximize(result.blue, k_pixels[u].blue);
1220 Maximize(result.opacity, QuantumRange-k_pixels[u].opacity);
1221 if ( image->colorspace == CMYKColorspace)
1222 Maximize(result.index, k_indexes[u]);
1223 }
1224 k_pixels += image->columns+kernel->width;
1225 k_indexes += image->columns+kernel->width;
1226 }
anthony602ab9b2010-01-05 08:06:50 +00001227 break;
1228
1229 case ErodeMorphology:
anthony29188a82010-01-22 10:12:34 +00001230 /* Minimize Value within kernel shape
1231 *
1232 * NOTE that the kernel is not reflected for this operation!
1233 *
1234 * NOTE: in normal Greyscale Morphology, the kernel value should
1235 * be added to the real value, this is currently not done, due to
1236 * the nature of the boolean kernels being used.
1237 */
anthony602ab9b2010-01-05 08:06:50 +00001238 k = kernel->values;
1239 k_pixels = p;
1240 k_indexes = p_indexes;
anthony29188a82010-01-22 10:12:34 +00001241 for (v=0; v < kernel->height; v++) {
1242 for (u=0; u < kernel->width; u++, k++) {
anthony602ab9b2010-01-05 08:06:50 +00001243 if ( IsNan(*k) || (*k) < 0.5 ) continue;
1244 Minimize(result.red, k_pixels[u].red);
1245 Minimize(result.green, k_pixels[u].green);
1246 Minimize(result.blue, k_pixels[u].blue);
1247 Minimize(result.opacity, QuantumRange-k_pixels[u].opacity);
1248 if ( image->colorspace == CMYKColorspace)
1249 Minimize(result.index, k_indexes[u]);
1250 }
1251 k_pixels += image->columns+kernel->width;
1252 k_indexes += image->columns+kernel->width;
1253 }
anthony602ab9b2010-01-05 08:06:50 +00001254 break;
1255
anthony83ba99b2010-01-24 08:48:15 +00001256 case DilateIntensityMorphology:
anthony29188a82010-01-22 10:12:34 +00001257 /* Select pixel with maximum intensity within kernel shape
1258 *
1259 * WARNING: the intensity test fails for CMYK and does not
1260 * take into account the moderating effect of teh alpha channel
1261 * on the intensity.
1262 *
1263 * NOTE for correct working of this operation for asymetrical
1264 * kernels, the kernel needs to be applied in its reflected form.
1265 * That is its values needs to be reversed.
1266 */
1267 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001268 k_pixels = p;
1269 k_indexes = p_indexes;
anthony29188a82010-01-22 10:12:34 +00001270 for (v=0; v < kernel->height; v++) {
1271 for (u=0; u < kernel->width; u++, k--) {
1272 if ( IsNan(*k) || (*k) < 0.5 ) continue; /* boolean kernel */
1273 if ( result.red == 0.0 ||
1274 PixelIntensity(&(k_pixels[u])) > PixelIntensity(q) ) {
1275 /* copy the whole pixel - no channel selection */
1276 *q = k_pixels[u];
1277 if ( result.red > 0.0 ) changed++;
1278 result.red = 1.0;
1279 }
anthony602ab9b2010-01-05 08:06:50 +00001280 }
1281 k_pixels += image->columns+kernel->width;
1282 k_indexes += image->columns+kernel->width;
1283 }
anthony602ab9b2010-01-05 08:06:50 +00001284 break;
1285
1286 case ErodeIntensityMorphology:
anthony29188a82010-01-22 10:12:34 +00001287 /* Select pixel with mimimum intensity within kernel shape
1288 *
1289 * WARNING: the intensity test fails for CMYK and does not
1290 * take into account the moderating effect of teh alpha channel
1291 * on the intensity.
1292 *
1293 * NOTE that the kernel is not reflected for this operation!
1294 */
anthony602ab9b2010-01-05 08:06:50 +00001295 k = kernel->values;
1296 k_pixels = p;
1297 k_indexes = p_indexes;
anthony29188a82010-01-22 10:12:34 +00001298 for (v=0; v < kernel->height; v++) {
1299 for (u=0; u < kernel->width; u++, k++) {
anthony602ab9b2010-01-05 08:06:50 +00001300 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony29188a82010-01-22 10:12:34 +00001301 if ( result.red == 0.0 ||
1302 PixelIntensity(&(k_pixels[u])) < PixelIntensity(q) ) {
1303 /* copy the whole pixel - no channel selection */
1304 *q = k_pixels[u];
1305 if ( result.red > 0.0 ) changed++;
1306 result.red = 1.0;
1307 }
anthony602ab9b2010-01-05 08:06:50 +00001308 }
1309 k_pixels += image->columns+kernel->width;
1310 k_indexes += image->columns+kernel->width;
1311 }
anthony602ab9b2010-01-05 08:06:50 +00001312 break;
1313
1314 case DistanceMorphology:
anthony29188a82010-01-22 10:12:34 +00001315 /* Add kernel value and select the minimum value found.
1316 * The result is a iterative distance from edge function.
1317 *
1318 * All Distance Kernels are symetrical, but that may not always
1319 * be the case. For example how about a distance from left edges?
1320 * To make it work correctly for asymetrical kernels the reflected
1321 * kernel needs to be applied.
1322 */
anthony602ab9b2010-01-05 08:06:50 +00001323#if 0
anthony83ba99b2010-01-24 08:48:15 +00001324 /* No need to do distance morphology if original value is zero
1325 * Unfortunatally I have not been able to get this right
1326 * when channel selection also becomes involved. -- Arrgghhh
1327 */
anthony602ab9b2010-01-05 08:06:50 +00001328 if ( ((channel & RedChannel) == 0 && p[r].red == 0)
1329 || ((channel & GreenChannel) == 0 && p[r].green == 0)
1330 || ((channel & BlueChannel) == 0 && p[r].blue == 0)
1331 || ((channel & OpacityChannel) == 0 && p[r].opacity == 0)
1332 || (( (channel & IndexChannel) == 0
1333 || image->colorspace != CMYKColorspace
1334 ) && p_indexes[x] ==0 )
1335 )
1336 break;
1337#endif
anthony29188a82010-01-22 10:12:34 +00001338 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001339 k_pixels = p;
1340 k_indexes = p_indexes;
anthony29188a82010-01-22 10:12:34 +00001341 for (v=0; v < kernel->height; v++) {
1342 for (u=0; u < kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001343 if ( IsNan(*k) ) continue;
1344 Minimize(result.red, (*k)+k_pixels[u].red);
1345 Minimize(result.green, (*k)+k_pixels[u].green);
1346 Minimize(result.blue, (*k)+k_pixels[u].blue);
1347 Minimize(result.opacity, (*k)+QuantumRange-k_pixels[u].opacity);
1348 if ( image->colorspace == CMYKColorspace)
1349 Minimize(result.index, (*k)+k_indexes[u]);
1350 }
1351 k_pixels += image->columns+kernel->width;
1352 k_indexes += image->columns+kernel->width;
1353 }
anthony602ab9b2010-01-05 08:06:50 +00001354 break;
1355
1356 case UndefinedMorphology:
1357 default:
1358 break; /* Do nothing */
anthony83ba99b2010-01-24 08:48:15 +00001359 }
1360 switch ( method ) {
1361 case UndefinedMorphology:
1362 case DilateIntensityMorphology:
1363 case ErodeIntensityMorphology:
1364 break; /* full pixel was directly assigned */
1365 default:
1366 /* Assign the results */
1367 if ((channel & RedChannel) != 0)
1368 q->red = ClampToQuantum(result.red);
1369 if ((channel & GreenChannel) != 0)
1370 q->green = ClampToQuantum(result.green);
1371 if ((channel & BlueChannel) != 0)
1372 q->blue = ClampToQuantum(result.blue);
1373 if ((channel & OpacityChannel) != 0
1374 && image->matte == MagickTrue )
1375 q->opacity = ClampToQuantum(QuantumRange-result.opacity);
1376 if ((channel & IndexChannel) != 0
1377 && image->colorspace == CMYKColorspace)
1378 q_indexes[x] = ClampToQuantum(result.index);
1379 break;
1380 }
1381 if ( ( p[r].red != q->red )
1382 || ( p[r].green != q->green )
1383 || ( p[r].blue != q->blue )
1384 || ( p[r].opacity != q->opacity )
1385 || ( image->colorspace == CMYKColorspace &&
1386 p_indexes[r] != q_indexes[x] ) )
1387 changed++; /* The pixel had some value changed! */
anthony602ab9b2010-01-05 08:06:50 +00001388 p++;
1389 q++;
anthony83ba99b2010-01-24 08:48:15 +00001390 } /* x */
anthony602ab9b2010-01-05 08:06:50 +00001391 sync=SyncCacheViewAuthenticPixels(q_view,exception);
1392 if (sync == MagickFalse)
1393 status=MagickFalse;
1394 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1395 {
1396 MagickBooleanType
1397 proceed;
1398
1399#if defined(MAGICKCORE_OPENMP_SUPPORT)
1400 #pragma omp critical (MagickCore_MorphologyImage)
1401#endif
1402 proceed=SetImageProgress(image,MorphologyTag,progress++,image->rows);
1403 if (proceed == MagickFalse)
1404 status=MagickFalse;
1405 }
anthony83ba99b2010-01-24 08:48:15 +00001406 } /* y */
anthony602ab9b2010-01-05 08:06:50 +00001407 result_image->type=image->type;
1408 q_view=DestroyCacheView(q_view);
1409 p_view=DestroyCacheView(p_view);
1410 return(status ? changed : 0);
1411}
1412
cristy2be15382010-01-21 02:38:03 +00001413MagickExport Image *MorphologyImage(const Image *image,MorphologyMethod method,
1414 const long iterations,KernelInfo *kernel, ExceptionInfo *exception)
1415{
1416 Image
1417 *morphology_image;
1418
1419 morphology_image=MorphologyImageChannel(image,DefaultChannels,method,
1420 iterations,kernel,exception);
1421 return(morphology_image);
1422}
1423
1424MagickExport Image *MorphologyImageChannel(const Image *image,
cristy672da672010-01-10 15:43:07 +00001425 const ChannelType channel, MorphologyMethod method, const long iterations,
cristy2be15382010-01-21 02:38:03 +00001426 KernelInfo *kernel, ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001427{
1428 unsigned long
1429 count,
1430 limit,
1431 changed;
1432
1433 Image
1434 *new_image,
1435 *old_image;
1436
anthonycc6c8362010-01-25 04:14:01 +00001437 const char
1438 *artifact;
1439
anthony602ab9b2010-01-05 08:06:50 +00001440 assert(image != (Image *) NULL);
1441 assert(image->signature == MagickSignature);
1442 assert(exception != (ExceptionInfo *) NULL);
1443 assert(exception->signature == MagickSignature);
1444
1445 if ( GetImageArtifact(image,"showkernel") != (const char *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001446 ShowKernel(kernel); /* request to display the kernel to stderr */
anthony602ab9b2010-01-05 08:06:50 +00001447
1448 if ( iterations == 0 )
1449 return((Image *)NULL); /* null operation - nothing to do! */
1450
1451 /* kernel must be valid at this point
1452 * (except maybe for posible future morphology methods like "Prune"
1453 */
cristy2be15382010-01-21 02:38:03 +00001454 assert(kernel != (KernelInfo *)NULL);
anthony602ab9b2010-01-05 08:06:50 +00001455
1456 count = 0;
anthony29188a82010-01-22 10:12:34 +00001457 changed = 1;
anthony602ab9b2010-01-05 08:06:50 +00001458 limit = iterations;
1459 if ( iterations < 0 )
1460 limit = image->columns > image->rows ? image->columns : image->rows;
1461
1462 /* Special morphology cases */
1463 switch( method ) {
1464 case CloseMorphology:
anthony83ba99b2010-01-24 08:48:15 +00001465 new_image = MorphologyImageChannel(image, channel, DilateMorphology,
anthony29188a82010-01-22 10:12:34 +00001466 iterations, kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001467 if (new_image == (Image *) NULL)
1468 return((Image *) NULL);
1469 method = ErodeMorphology;
1470 break;
1471 case OpenMorphology:
anthony29188a82010-01-22 10:12:34 +00001472 new_image = MorphologyImageChannel(image, channel, ErodeMorphology,
1473 iterations, kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001474 if (new_image == (Image *) NULL)
1475 return((Image *) NULL);
anthony83ba99b2010-01-24 08:48:15 +00001476 method = DilateMorphology;
anthony602ab9b2010-01-05 08:06:50 +00001477 break;
1478 case CloseIntensityMorphology:
anthony83ba99b2010-01-24 08:48:15 +00001479 new_image = MorphologyImageChannel(image, channel, DilateIntensityMorphology,
anthony29188a82010-01-22 10:12:34 +00001480 iterations, kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001481 if (new_image == (Image *) NULL)
1482 return((Image *) NULL);
1483 method = ErodeIntensityMorphology;
1484 break;
1485 case OpenIntensityMorphology:
anthony29188a82010-01-22 10:12:34 +00001486 new_image = MorphologyImageChannel(image, channel, ErodeIntensityMorphology,
1487 iterations, kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001488 if (new_image == (Image *) NULL)
1489 return((Image *) NULL);
anthony83ba99b2010-01-24 08:48:15 +00001490 method = DilateIntensityMorphology;
anthony602ab9b2010-01-05 08:06:50 +00001491 break;
1492
anthonyc94cdb02010-01-06 08:15:29 +00001493 case ConvolveMorphology:
anthonycc6c8362010-01-25 04:14:01 +00001494 /* Scale or Normalize kernel according to user wishes
1495 ** WARNING: this directly modifies the kernel
1496 ** which probably should not be done.
1497 */
1498 artifact = GetImageArtifact(image,"convolve:scale");
1499 if ( artifact != (char *)NULL )
1500 ScaleKernel(kernel, StringToDouble(artifact) );
anthonyc94cdb02010-01-06 08:15:29 +00001501 /* FALL-THRU */
anthony602ab9b2010-01-05 08:06:50 +00001502 default:
anthonycc6c8362010-01-25 04:14:01 +00001503 /* Do a morphology iteration just once at this point!
anthony602ab9b2010-01-05 08:06:50 +00001504 This ensures a new_image has been generated, but allows us
anthonyc94cdb02010-01-06 08:15:29 +00001505 to skip the creation of 'old_image' if it isn't needed.
anthony602ab9b2010-01-05 08:06:50 +00001506 */
1507 new_image=CloneImage(image,0,0,MagickTrue,exception);
1508 if (new_image == (Image *) NULL)
1509 return((Image *) NULL);
1510 if (SetImageStorageClass(new_image,DirectClass) == MagickFalse)
1511 {
1512 InheritException(exception,&new_image->exception);
1513 new_image=DestroyImage(new_image);
1514 return((Image *) NULL);
1515 }
1516 changed = MorphologyApply(image,new_image,method,channel,kernel,
1517 exception);
1518 count++;
1519 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
1520 fprintf(stderr, "Morphology %s:%lu => Changed %lu\n",
1521 MagickOptionToMnemonic(MagickMorphologyOptions, method),
1522 count, changed);
1523 }
1524
anthonycc6c8362010-01-25 04:14:01 +00001525 /* Repeat an interative morphology until count or no change reached */
anthony602ab9b2010-01-05 08:06:50 +00001526 if ( count < limit && changed > 0 ) {
1527 old_image = CloneImage(new_image,0,0,MagickTrue,exception);
1528 if (old_image == (Image *) NULL)
1529 return(DestroyImage(new_image));
1530 if (SetImageStorageClass(old_image,DirectClass) == MagickFalse)
1531 {
1532 InheritException(exception,&old_image->exception);
1533 old_image=DestroyImage(old_image);
1534 return(DestroyImage(new_image));
1535 }
1536 while( count < limit && changed != 0 )
1537 {
1538 Image *tmp = old_image;
1539 old_image = new_image;
1540 new_image = tmp;
1541 changed = MorphologyApply(old_image,new_image,method,channel,kernel,
1542 exception);
1543 count++;
1544 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
1545 fprintf(stderr, "Morphology %s:%lu => Changed %lu\n",
1546 MagickOptionToMnemonic(MagickMorphologyOptions, method),
1547 count, changed);
1548 }
1549 DestroyImage(old_image);
1550 }
1551
1552 return(new_image);
1553}
anthony83ba99b2010-01-24 08:48:15 +00001554
1555/*
1556%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1557% %
1558% %
1559% %
anthonyc4c86e02010-01-27 09:30:32 +00001560+ R o t a t e K e r n e l %
anthony83ba99b2010-01-24 08:48:15 +00001561% %
1562% %
1563% %
1564%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1565%
1566% RotateKernel() rotates the kernel by the angle given. Currently it is
1567% restricted to 90 degree angles, but this may be improved in the future.
1568%
1569% The format of the RotateKernel method is:
1570%
1571% void RotateKernel(KernelInfo *kernel, double angle)
1572%
1573% A description of each parameter follows:
1574%
1575% o kernel: the Morphology/Convolution kernel
1576%
1577% o angle: angle to rotate in degrees
1578%
anthonyc4c86e02010-01-27 09:30:32 +00001579% This function is only internel to this module, as it is not finalized,
1580% especially with regard to non-orthogonal angles, and rotation of larger
1581% 2D kernels.
anthony83ba99b2010-01-24 08:48:15 +00001582*/
anthonyc4c86e02010-01-27 09:30:32 +00001583MagickExport void RotateKernel(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00001584{
1585 /* WARNING: Currently assumes the kernel (rightly) is horizontally symetrical
1586 **
1587 ** TODO: expand beyond simple 90 degree rotates, flips and flops
1588 */
1589
1590 /* Modulus the angle */
1591 angle = fmod(angle, 360.0);
1592 if ( angle < 0 )
1593 angle += 360.0;
1594
1595 if ( 315.0 < angle || angle <= 45.0 )
1596 return; /* no change! - At least at this time */
1597
1598 switch (kernel->type) {
1599 /* These built-in kernels are cylindrical kernels, rotating is useless */
1600 case GaussianKernel:
1601 case LaplacianKernel:
1602 case LOGKernel:
1603 case DOGKernel:
1604 case DiskKernel:
1605 case ChebyshevKernel:
1606 case ManhattenKernel:
1607 case EuclideanKernel:
1608 return;
1609
1610 /* These may be rotatable at non-90 angles in the future */
1611 /* but simply rotating them in multiples of 90 degrees is useless */
1612 case SquareKernel:
1613 case DiamondKernel:
1614 case PlusKernel:
1615 return;
1616
1617 /* These only allows a +/-90 degree rotation (by transpose) */
1618 /* A 180 degree rotation is useless */
1619 case BlurKernel:
1620 case RectangleKernel:
1621 if ( 135.0 < angle && angle <= 225.0 )
1622 return;
1623 if ( 225.0 < angle && angle <= 315.0 )
1624 angle -= 180;
1625 break;
1626
1627 /* these are freely rotatable in 90 degree units */
1628 case CometKernel:
1629 case UndefinedKernel:
1630 case UserDefinedKernel:
1631 break;
1632 }
1633 if ( 135.0 < angle && angle <= 225.0 )
1634 {
1635 /* For a 180 degree rotation - also know as a reflection */
1636 /* This is actually a very very common operation! */
1637 /* Basically all that is needed is a reversal of the kernel data! */
1638 unsigned long
1639 i,j;
1640 register double
1641 *k,t;
1642
1643 k=kernel->values;
1644 for ( i=0, j=kernel->width*kernel->height-1; i<j; i++, j--)
1645 t=k[i], k[i]=k[j], k[j]=t;
1646
1647 kernel->offset_x = kernel->width - kernel->offset_x - 1;
1648 kernel->offset_y = kernel->width - kernel->offset_y - 1;
1649 angle = fmod(angle+180.0, 360.0);
1650 }
1651 if ( 45.0 < angle && angle <= 135.0 )
1652 { /* Do a transpose and a flop, of the image, which results in a 90
1653 * degree rotation using two mirror operations.
1654 *
1655 * WARNING: this assumes the original image was a 1 dimentional image
1656 * but currently that is the only built-ins it is applied to.
1657 */
1658 unsigned long
1659 t;
1660 t = kernel->width;
1661 kernel->width = kernel->height;
1662 kernel->height = t;
1663 t = kernel->offset_x;
1664 kernel->offset_x = kernel->offset_y;
1665 kernel->offset_y = t;
1666 angle = fmod(450.0 - angle, 360.0);
1667 }
1668 /* At this point angle should be between -45 (315) and +45 degrees
1669 * In the future some form of non-orthogonal angled rotates could be
1670 * performed here, posibily with a linear kernel restriction.
1671 */
1672
1673#if 0
1674 Not currently in use!
1675 { /* Do a flop, this assumes kernel is horizontally symetrical.
1676 * Each row of the kernel needs to be reversed!
1677 */
1678 unsigned long
1679 y;
1680 register unsigned long
1681 x,r;
1682 register double
1683 *k,t;
1684
1685 for ( y=0, k=kernel->values; y < kernel->height; y++, k+=kernel->width)
1686 for ( x=0, r=kernel->width-1; x<kernel->width/2; x++, r--)
1687 t=k[x], k[x]=k[r], k[r]=t;
1688
1689 kernel->offset_x = kernel->width - kernel->offset_x - 1;
1690 angle = fmod(angle+180.0, 360.0);
1691 }
1692#endif
1693 return;
1694}
1695
1696/*
1697%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1698% %
1699% %
1700% %
anthonycc6c8362010-01-25 04:14:01 +00001701+ S c a l e K e r n e l %
1702% %
1703% %
1704% %
1705%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1706%
1707% ScaleKernel() scales the kernel by the given amount. Scaling by value of
1708% zero will result in a normalization of the kernel.
1709%
1710% For positive kernels normalization scales the kernel so the addition os all
1711% values is 1.0. While for kernels where values add to zero it is scaled
1712% so that the convolution output range covers 1.0. In such 'zero kernels'
1713% it is generally recomended that the user also provides a 50% bias to the
1714% output results.
1715%
1716% Correct normalization assumes the 'range_*' attributes of the kernel
1717% structure have been correctly set during the kernel creation.
1718%
1719% The format of the ScaleKernel method is:
1720%
1721% void ScaleKernel(KernelInfo *kernel)
1722%
1723% A description of each parameter follows:
1724%
1725% o kernel: the Morphology/Convolution kernel
1726%
1727% o scale: multiple all values by this, if zero normalize instead.
1728%
anthonyc4c86e02010-01-27 09:30:32 +00001729% This function is internal to this module only at this time, but can be
1730% exported to other modules if needed.
anthonycc6c8362010-01-25 04:14:01 +00001731*/
anthonyc4c86e02010-01-27 09:30:32 +00001732MagickExport void ScaleKernel(KernelInfo *kernel, double scale)
anthonycc6c8362010-01-25 04:14:01 +00001733{
1734 register unsigned long
1735 i;
1736
1737 if ( fabs(scale) < MagickEpsilon ) {
1738 if ( fabs(kernel->range_pos + kernel->range_neg) < MagickEpsilon )
1739 scale = 1/(kernel->range_pos - kernel->range_neg); /* zero kernels */
1740 else
1741 scale = 1/(kernel->range_pos + kernel->range_neg); /* non-zero kernel */
1742 }
1743
1744 for (i=0; i < kernel->width*kernel->height; i++)
1745 if ( ! IsNan(kernel->values[i]) )
1746 kernel->values[i] *= scale;
1747
1748 kernel->range_pos *= scale; /* convolution output range */
1749 kernel->range_neg *= scale;
1750 kernel->value_max *= scale; /* maximum and minimum values in kernel */
1751 kernel->value_min *= scale;
1752
1753 return;
1754}
1755
1756/*
1757%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1758% %
1759% %
1760% %
1761+ S h o w K e r n e l %
anthony83ba99b2010-01-24 08:48:15 +00001762% %
1763% %
1764% %
1765%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1766%
1767% ShowKernel() Output the details of the given kernel defination to
1768% standard error, as per a users 'showkernel' option request.
1769%
1770% The format of the ShowKernel method is:
1771%
anthonycc6c8362010-01-25 04:14:01 +00001772% void ShowKernel(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00001773%
1774% A description of each parameter follows:
1775%
1776% o kernel: the Morphology/Convolution kernel
1777%
anthonyc4c86e02010-01-27 09:30:32 +00001778% This function is internal to this module only at this time. That may change
1779% in the future.
anthony83ba99b2010-01-24 08:48:15 +00001780*/
1781static void ShowKernel(KernelInfo *kernel)
1782{
1783 unsigned long
1784 i, u, v;
1785
1786 fprintf(stderr,
anthonycc6c8362010-01-25 04:14:01 +00001787 "Kernel \"%s\" of size %lux%lu%+ld%+ld with values from %.*lg to %.*lg\n",
anthony83ba99b2010-01-24 08:48:15 +00001788 MagickOptionToMnemonic(MagickKernelOptions, kernel->type),
1789 kernel->width, kernel->height,
1790 kernel->offset_x, kernel->offset_y,
anthonycc6c8362010-01-25 04:14:01 +00001791 GetMagickPrecision(), kernel->value_min,
1792 GetMagickPrecision(), kernel->value_max);
1793 fprintf(stderr, "Forming convolution output range from %.*lg to %.*lg%s\n",
1794 GetMagickPrecision(), kernel->range_neg,
1795 GetMagickPrecision(), kernel->range_pos,
1796 /*kernel->normalized == MagickTrue ? " (normalized)" : */ "" );
anthony83ba99b2010-01-24 08:48:15 +00001797 for (i=v=0; v < kernel->height; v++) {
1798 fprintf(stderr,"%2ld:",v);
1799 for (u=0; u < kernel->width; u++, i++)
1800 if ( IsNan(kernel->values[i]) )
1801 fprintf(stderr," %*s", GetMagickPrecision()+2, "nan");
1802 else
1803 fprintf(stderr," %*.*lg", GetMagickPrecision()+2,
1804 GetMagickPrecision(), kernel->values[i]);
1805 fprintf(stderr,"\n");
1806 }
1807}
anthonycc6c8362010-01-25 04:14:01 +00001808
1809/*
1810%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1811% %
1812% %
1813% %
1814+ Z e r o K e r n e l N a n s %
1815% %
1816% %
1817% %
1818%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1819%
1820% ZeroKernelNans() replaces any special 'nan' value that may be present in
1821% the kernel with a zero value. This is typically done when the kernel will
1822% be used in special hardware (GPU) convolution processors, to simply
1823% matters.
1824%
1825% The format of the ZeroKernelNans method is:
1826%
1827% voidZeroKernelNans (KernelInfo *kernel)
1828%
1829% A description of each parameter follows:
1830%
1831% o kernel: the Morphology/Convolution kernel
1832%
1833% FUTURE: return the information in a string for API usage.
1834*/
anthonyc4c86e02010-01-27 09:30:32 +00001835MagickExport void ZeroKernelNans(KernelInfo *kernel)
anthonycc6c8362010-01-25 04:14:01 +00001836{
1837 register unsigned long
1838 i;
1839
1840 for (i=0; i < kernel->width*kernel->height; i++)
1841 if ( IsNan(kernel->values[i]) )
1842 kernel->values[i] = 0.0;
1843
1844 return;
1845}