blob: 8b804d45e2d09ec11ffcc5c2517efed348a8997f [file] [log] [blame]
cristy701db312009-11-20 03:14:08 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% M M OOO RRRR PPPP H H OOO L OOO GGGG Y Y %
7% MM MM O O R R P P H H O O L O O G Y Y %
8% M M M O O RRRR PPPP HHHHH O O L O O G GGG Y %
9% M M O O R R P H H O O L O O G G Y %
10% M M OOO R R P H H OOO LLLLL OOO GGG Y %
11% %
12% %
13% MagickCore Morphology Methods %
14% %
15% Software Design %
16% Anthony Thyssen %
anthonyc94cdb02010-01-06 08:15:29 +000017% January 2010 %
cristy701db312009-11-20 03:14:08 +000018% %
19% %
cristy16af1cb2009-12-11 21:38:29 +000020% Copyright 1999-2010 ImageMagick Studio LLC, a non-profit organization %
cristy701db312009-11-20 03:14:08 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
anthony602ab9b2010-01-05 08:06:50 +000036% Morpology is the the application of various kernals, of any size and even
37% shape, to a image in various ways (typically binary, but not always).
cristy701db312009-11-20 03:14:08 +000038%
anthony602ab9b2010-01-05 08:06:50 +000039% Convolution (weighted sum or average) is just one specific type of
40% morphology. Just one that is very common for image bluring and sharpening
41% effects. Not only 2D Gaussian blurring, but also 2-pass 1D Blurring.
42%
43% This module provides not only a general morphology function, and the ability
44% to apply more advanced or iterative morphologies, but also functions for the
45% generation of many different types of kernel arrays from user supplied
46% arguments. Prehaps even the generation of a kernel from a small image.
cristy701db312009-11-20 03:14:08 +000047*/
48
49/*
50 Include declarations.
51*/
52#include "magick/studio.h"
anthony602ab9b2010-01-05 08:06:50 +000053#include "magick/artifact.h"
cristy701db312009-11-20 03:14:08 +000054#include "magick/cache-view.h"
55#include "magick/color-private.h"
56#include "magick/enhance.h"
57#include "magick/exception.h"
58#include "magick/exception-private.h"
anthony602ab9b2010-01-05 08:06:50 +000059#include "magick/gem.h"
cristy701db312009-11-20 03:14:08 +000060#include "magick/hashmap.h"
61#include "magick/image.h"
cristybba804b2010-01-05 15:39:59 +000062#include "magick/image-private.h"
cristy701db312009-11-20 03:14:08 +000063#include "magick/list.h"
anthony29188a82010-01-22 10:12:34 +000064#include "magick/magick.h"
cristy701db312009-11-20 03:14:08 +000065#include "magick/memory_.h"
66#include "magick/monitor-private.h"
67#include "magick/morphology.h"
anthony602ab9b2010-01-05 08:06:50 +000068#include "magick/option.h"
cristy701db312009-11-20 03:14:08 +000069#include "magick/pixel-private.h"
70#include "magick/prepress.h"
71#include "magick/quantize.h"
72#include "magick/registry.h"
73#include "magick/semaphore.h"
74#include "magick/splay-tree.h"
75#include "magick/statistic.h"
76#include "magick/string_.h"
anthony602ab9b2010-01-05 08:06:50 +000077#include "magick/string-private.h"
78#include "magick/token.h"
cristya29d45f2010-03-05 21:14:54 +000079
anthony602ab9b2010-01-05 08:06:50 +000080/*
cristya29d45f2010-03-05 21:14:54 +000081 The following test is for special floating point numbers of value NaN (not
82 a number), that may be used within a Kernel Definition. NaN's are defined
83 as part of the IEEE standard for floating point number representation.
84
85 These are used a Kernel value of NaN means that that kernal position is not
86 part of the normal convolution or morphology process, and thus allowing the
87 use of 'shaped' kernels.
88
89 Special properities two NaN's are never equal, even if they are from the
90 same variable That is the IsNaN() macro is only true if the value is NaN.
91*/
anthony602ab9b2010-01-05 08:06:50 +000092#define IsNan(a) ((a)!=(a))
93
anthony29188a82010-01-22 10:12:34 +000094/*
cristya29d45f2010-03-05 21:14:54 +000095 Other global definitions used by module.
96*/
anthony29188a82010-01-22 10:12:34 +000097static inline double MagickMin(const double x,const double y)
98{
99 return( x < y ? x : y);
100}
101static inline double MagickMax(const double x,const double y)
102{
103 return( x > y ? x : y);
104}
105#define Minimize(assign,value) assign=MagickMin(assign,value)
106#define Maximize(assign,value) assign=MagickMax(assign,value)
107
anthonyc4c86e02010-01-27 09:30:32 +0000108/* Currently these are only internal to this module */
109static void
cristyef656912010-03-05 19:54:59 +0000110 RotateKernelInfo(KernelInfo *, double);
anthony602ab9b2010-01-05 08:06:50 +0000111
112/*
113%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
114% %
115% %
116% %
anthony83ba99b2010-01-24 08:48:15 +0000117% A c q u i r e K e r n e l I n f o %
anthony602ab9b2010-01-05 08:06:50 +0000118% %
119% %
120% %
121%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
122%
cristy2be15382010-01-21 02:38:03 +0000123% AcquireKernelInfo() takes the given string (generally supplied by the
anthony602ab9b2010-01-05 08:06:50 +0000124% user) and converts it into a Morphology/Convolution Kernel. This allows
125% users to specify a kernel from a number of pre-defined kernels, or to fully
126% specify their own kernel for a specific Convolution or Morphology
127% Operation.
128%
129% The kernel so generated can be any rectangular array of floating point
130% values (doubles) with the 'control point' or 'pixel being affected'
131% anywhere within that array of values.
132%
anthony83ba99b2010-01-24 08:48:15 +0000133% Previously IM was restricted to a square of odd size using the exact
134% center as origin, this is no longer the case, and any rectangular kernel
135% with any value being declared the origin. This in turn allows the use of
136% highly asymmetrical kernels.
anthony602ab9b2010-01-05 08:06:50 +0000137%
138% The floating point values in the kernel can also include a special value
anthony83ba99b2010-01-24 08:48:15 +0000139% known as 'nan' or 'not a number' to indicate that this value is not part
140% of the kernel array. This allows you to shaped the kernel within its
141% rectangular area. That is 'nan' values provide a 'mask' for the kernel
142% shape. However at least one non-nan value must be provided for correct
143% working of a kernel.
anthony602ab9b2010-01-05 08:06:50 +0000144%
anthony83ba99b2010-01-24 08:48:15 +0000145% The returned kernel should be free using the DestroyKernelInfo() when you
146% are finished with it.
anthony602ab9b2010-01-05 08:06:50 +0000147%
148% Input kernel defintion strings can consist of any of three types.
149%
anthony29188a82010-01-22 10:12:34 +0000150% "name:args"
151% Select from one of the built in kernels, using the name and
152% geometry arguments supplied. See AcquireKernelBuiltIn()
anthony602ab9b2010-01-05 08:06:50 +0000153%
154% "WxH[+X+Y]:num, num, num ..."
155% a kernal of size W by H, with W*H floating point numbers following.
156% the 'center' can be optionally be defined at +X+Y (such that +0+0
anthony29188a82010-01-22 10:12:34 +0000157% is top left corner). If not defined the pixel in the center, for
158% odd sizes, or to the immediate top or left of center for even sizes
159% is automatically selected.
anthony602ab9b2010-01-05 08:06:50 +0000160%
anthony29188a82010-01-22 10:12:34 +0000161% "num, num, num, num, ..."
162% list of floating point numbers defining an 'old style' odd sized
163% square kernel. At least 9 values should be provided for a 3x3
164% square kernel, 25 for a 5x5 square kernel, 49 for 7x7, etc.
165% Values can be space or comma separated. This is not recommended.
anthony602ab9b2010-01-05 08:06:50 +0000166%
anthony83ba99b2010-01-24 08:48:15 +0000167% Note that 'name' kernels will start with an alphabetic character while the
168% new kernel specification has a ':' character in its specification string.
169% If neither is the case, it is assumed an old style of a simple list of
170% numbers generating a odd-sized square kernel has been given.
anthony602ab9b2010-01-05 08:06:50 +0000171%
172% The format of the AcquireKernal method is:
173%
cristy2be15382010-01-21 02:38:03 +0000174% KernelInfo *AcquireKernelInfo(const char *kernel_string)
anthony602ab9b2010-01-05 08:06:50 +0000175%
176% A description of each parameter follows:
177%
178% o kernel_string: the Morphology/Convolution kernel wanted.
179%
180*/
181
anthonyc84dce52010-05-07 05:42:23 +0000182/* This was separated so that it could be used as a separate
anthony5ef8e942010-05-11 06:51:12 +0000183** array input handling function, such as for -color-matrix
anthonyc84dce52010-05-07 05:42:23 +0000184*/
anthony5ef8e942010-05-11 06:51:12 +0000185static KernelInfo *ParseKernelArray(const char *kernel_string)
anthony602ab9b2010-01-05 08:06:50 +0000186{
cristy2be15382010-01-21 02:38:03 +0000187 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000188 *kernel;
189
190 char
191 token[MaxTextExtent];
192
anthony602ab9b2010-01-05 08:06:50 +0000193 const char
anthony5ef8e942010-05-11 06:51:12 +0000194 *p,
195 *end;
anthony602ab9b2010-01-05 08:06:50 +0000196
anthonyc84dce52010-05-07 05:42:23 +0000197 register long
198 i;
anthony602ab9b2010-01-05 08:06:50 +0000199
anthony29188a82010-01-22 10:12:34 +0000200 double
201 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
202
cristy2be15382010-01-21 02:38:03 +0000203 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
204 if (kernel == (KernelInfo *)NULL)
anthony602ab9b2010-01-05 08:06:50 +0000205 return(kernel);
206 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
207 kernel->type = UserDefinedKernel;
cristyd43a46b2010-01-21 02:13:41 +0000208 kernel->signature = MagickSignature;
anthony602ab9b2010-01-05 08:06:50 +0000209
anthony5ef8e942010-05-11 06:51:12 +0000210 /* find end of this specific kernel definition string */
211 end = strchr(kernel_string, ';');
212 if ( end == (char *) NULL )
213 end = strchr(kernel_string, '\0');
214
anthony602ab9b2010-01-05 08:06:50 +0000215 /* Has a ':' in argument - New user kernel specification */
216 p = strchr(kernel_string, ':');
anthony5ef8e942010-05-11 06:51:12 +0000217 if ( p != (char *) NULL && p < end)
anthony602ab9b2010-01-05 08:06:50 +0000218 {
anthonyc84dce52010-05-07 05:42:23 +0000219 MagickStatusType
220 flags;
221
222 GeometryInfo
223 args;
224
anthony602ab9b2010-01-05 08:06:50 +0000225 /* ParseGeometry() needs the geometry separated! -- Arrgghh */
cristy150989e2010-02-01 14:59:39 +0000226 memcpy(token, kernel_string, (size_t) (p-kernel_string));
anthony602ab9b2010-01-05 08:06:50 +0000227 token[p-kernel_string] = '\0';
anthonyc84dce52010-05-07 05:42:23 +0000228 SetGeometryInfo(&args);
anthony602ab9b2010-01-05 08:06:50 +0000229 flags = ParseGeometry(token, &args);
anthony602ab9b2010-01-05 08:06:50 +0000230
anthony29188a82010-01-22 10:12:34 +0000231 /* Size handling and checks of geometry settings */
anthony602ab9b2010-01-05 08:06:50 +0000232 if ( (flags & WidthValue) == 0 ) /* if no width then */
233 args.rho = args.sigma; /* then width = height */
234 if ( args.rho < 1.0 ) /* if width too small */
235 args.rho = 1.0; /* then width = 1 */
236 if ( args.sigma < 1.0 ) /* if height too small */
237 args.sigma = args.rho; /* then height = width */
238 kernel->width = (unsigned long)args.rho;
239 kernel->height = (unsigned long)args.sigma;
240
241 /* Offset Handling and Checks */
242 if ( args.xi < 0.0 || args.psi < 0.0 )
anthony83ba99b2010-01-24 08:48:15 +0000243 return(DestroyKernelInfo(kernel));
cristyc99304f2010-02-01 15:26:27 +0000244 kernel->x = ((flags & XValue)!=0) ? (long)args.xi
cristy150989e2010-02-01 14:59:39 +0000245 : (long) (kernel->width-1)/2;
cristyc99304f2010-02-01 15:26:27 +0000246 kernel->y = ((flags & YValue)!=0) ? (long)args.psi
cristy150989e2010-02-01 14:59:39 +0000247 : (long) (kernel->height-1)/2;
cristyc99304f2010-02-01 15:26:27 +0000248 if ( kernel->x >= (long) kernel->width ||
249 kernel->y >= (long) kernel->height )
anthony83ba99b2010-01-24 08:48:15 +0000250 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000251
252 p++; /* advance beyond the ':' */
253 }
254 else
anthonyc84dce52010-05-07 05:42:23 +0000255 { /* ELSE - Old old specification, forming odd-square kernel */
anthony602ab9b2010-01-05 08:06:50 +0000256 /* count up number of values given */
257 p=(const char *) kernel_string;
cristya699b172010-01-06 16:48:49 +0000258 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
anthony29188a82010-01-22 10:12:34 +0000259 p++; /* ignore "'" chars for convolve filter usage - Cristy */
anthony5ef8e942010-05-11 06:51:12 +0000260 for (i=0; p < end; i++)
anthony602ab9b2010-01-05 08:06:50 +0000261 {
262 GetMagickToken(p,&p,token);
263 if (*token == ',')
264 GetMagickToken(p,&p,token);
265 }
266 /* set the size of the kernel - old sized square */
267 kernel->width = kernel->height= (unsigned long) sqrt((double) i+1.0);
cristyc99304f2010-02-01 15:26:27 +0000268 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000269 p=(const char *) kernel_string;
anthony29188a82010-01-22 10:12:34 +0000270 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
271 p++; /* ignore "'" chars for convolve filter usage - Cristy */
anthony602ab9b2010-01-05 08:06:50 +0000272 }
273
274 /* Read in the kernel values from rest of input string argument */
275 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
276 kernel->height*sizeof(double));
277 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000278 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000279
cristyc99304f2010-02-01 15:26:27 +0000280 kernel->minimum = +MagickHuge;
281 kernel->maximum = -MagickHuge;
282 kernel->negative_range = kernel->positive_range = 0.0;
anthonyc84dce52010-05-07 05:42:23 +0000283
anthony5ef8e942010-05-11 06:51:12 +0000284 for (i=0; (i < (long) (kernel->width*kernel->height)) && (p < end); i++)
anthony602ab9b2010-01-05 08:06:50 +0000285 {
286 GetMagickToken(p,&p,token);
287 if (*token == ',')
288 GetMagickToken(p,&p,token);
anthony29188a82010-01-22 10:12:34 +0000289 if ( LocaleCompare("nan",token) == 0
anthonyc84dce52010-05-07 05:42:23 +0000290 || LocaleCompare("-",token) == 0 ) {
anthony29188a82010-01-22 10:12:34 +0000291 kernel->values[i] = nan; /* do not include this value in kernel */
292 }
293 else {
294 kernel->values[i] = StringToDouble(token);
295 ( kernel->values[i] < 0)
cristyc99304f2010-02-01 15:26:27 +0000296 ? ( kernel->negative_range += kernel->values[i] )
297 : ( kernel->positive_range += kernel->values[i] );
298 Minimize(kernel->minimum, kernel->values[i]);
299 Maximize(kernel->maximum, kernel->values[i]);
anthony29188a82010-01-22 10:12:34 +0000300 }
anthony602ab9b2010-01-05 08:06:50 +0000301 }
anthony29188a82010-01-22 10:12:34 +0000302
anthony5ef8e942010-05-11 06:51:12 +0000303 /* sanity check -- no more values in kernel definition */
304 GetMagickToken(p,&p,token);
305 if ( *token != '\0' && *token != ';' && *token != '\'' )
306 return(DestroyKernelInfo(kernel));
307
anthonyc84dce52010-05-07 05:42:23 +0000308#if 0
309 /* this was the old method of handling a incomplete kernel */
cristy150989e2010-02-01 14:59:39 +0000310 if ( i < (long) (kernel->width*kernel->height) ) {
cristyc99304f2010-02-01 15:26:27 +0000311 Minimize(kernel->minimum, kernel->values[i]);
312 Maximize(kernel->maximum, kernel->values[i]);
cristy150989e2010-02-01 14:59:39 +0000313 for ( ; i < (long) (kernel->width*kernel->height); i++)
anthony29188a82010-01-22 10:12:34 +0000314 kernel->values[i]=0.0;
315 }
anthonyc84dce52010-05-07 05:42:23 +0000316#else
317 /* Number of values for kernel was not enough - Report Error */
318 if ( i < (long) (kernel->width*kernel->height) )
319 return(DestroyKernelInfo(kernel));
320#endif
321
322 /* check that we recieved at least one real (non-nan) value! */
323 if ( kernel->minimum == MagickHuge )
324 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000325
326 return(kernel);
327}
anthonyc84dce52010-05-07 05:42:23 +0000328
anthony5ef8e942010-05-11 06:51:12 +0000329static KernelInfo *ParseNamedKernel(const char *kernel_string)
anthonyc84dce52010-05-07 05:42:23 +0000330{
331 char
332 token[MaxTextExtent];
333
anthony5ef8e942010-05-11 06:51:12 +0000334 long
335 type;
336
anthonyc84dce52010-05-07 05:42:23 +0000337 const char
338 *p;
339
340 MagickStatusType
341 flags;
342
343 GeometryInfo
344 args;
345
anthonyc84dce52010-05-07 05:42:23 +0000346 /* Parse special 'named' kernel */
anthony5ef8e942010-05-11 06:51:12 +0000347 GetMagickToken(kernel_string,&p,token);
anthonyc84dce52010-05-07 05:42:23 +0000348 type=ParseMagickOption(MagickKernelOptions,MagickFalse,token);
349 if ( type < 0 || type == UserDefinedKernel )
anthony5ef8e942010-05-11 06:51:12 +0000350 return((KernelInfo *)NULL); /* not a valid named kernel */
anthonyc84dce52010-05-07 05:42:23 +0000351
352 while (((isspace((int) ((unsigned char) *p)) != 0) ||
anthony5ef8e942010-05-11 06:51:12 +0000353 (*p == ',') || (*p == ':' )) && (*p != '\0') && (*p != ';'))
anthonyc84dce52010-05-07 05:42:23 +0000354 p++;
355 SetGeometryInfo(&args);
356 flags = ParseGeometry(p, &args);
357
358 /* special handling of missing values in input string */
359 switch( type ) {
anthony5ef8e942010-05-11 06:51:12 +0000360 case RectangleKernel:
361 if ( (flags & WidthValue) == 0 ) /* if no width then */
362 args.rho = args.sigma; /* then width = height */
363 if ( args.rho < 1.0 ) /* if width too small */
364 args.rho = 3; /* then width = 3 */
365 if ( args.sigma < 1.0 ) /* if height too small */
366 args.sigma = args.rho; /* then height = width */
367 if ( (flags & XValue) == 0 ) /* center offset if not defined */
368 args.xi = (double)(((long)args.rho-1)/2);
369 if ( (flags & YValue) == 0 )
370 args.psi = (double)(((long)args.sigma-1)/2);
371 break;
372 case SquareKernel:
373 case DiamondKernel:
374 case DiskKernel:
375 case PlusKernel:
376 /* If no scale given (a 0 scale is valid! - set it to 1.0 */
377 if ( (flags & HeightValue) == 0 )
378 args.sigma = 1.0;
379 break;
380 case ChebyshevKernel:
381 case ManhattenKernel:
382 case EuclideanKernel:
383 if ( (flags & HeightValue) == 0 )
384 args.sigma = 100.0; /* default distance scaling */
385 else if ( (flags & AspectValue ) != 0 ) /* '!' flag */
386 args.sigma = QuantumRange/args.sigma; /* maximum pixel distance */
387 else if ( (flags & PercentValue ) != 0 ) /* '%' flag */
388 args.sigma *= QuantumRange/100.0; /* percentage of color range */
389 break;
390 default:
391 break;
anthonyc84dce52010-05-07 05:42:23 +0000392 }
393
394 return(AcquireKernelBuiltIn((KernelInfoType)type, &args));
395}
396
anthony5ef8e942010-05-11 06:51:12 +0000397MagickExport KernelInfo *AcquireKernelInfo(const char *kernel_string)
398{
399 char
400 token[MaxTextExtent];
401
402 /* If it does not start with an alpha - Its is a user defined kernel array */
403 GetMagickToken(kernel_string,NULL,token);
404 if (isalpha((int) ((unsigned char) *token)) == 0)
405 return(ParseKernelArray(kernel_string));
406
407 return(ParseNamedKernel(kernel_string));
408}
409
anthony602ab9b2010-01-05 08:06:50 +0000410
411/*
412%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
413% %
414% %
415% %
416% A c q u i r e K e r n e l B u i l t I n %
417% %
418% %
419% %
420%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
421%
422% AcquireKernelBuiltIn() returned one of the 'named' built-in types of
423% kernels used for special purposes such as gaussian blurring, skeleton
424% pruning, and edge distance determination.
425%
426% They take a KernelType, and a set of geometry style arguments, which were
427% typically decoded from a user supplied string, or from a more complex
428% Morphology Method that was requested.
429%
430% The format of the AcquireKernalBuiltIn method is:
431%
cristy2be15382010-01-21 02:38:03 +0000432% KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000433% const GeometryInfo args)
434%
435% A description of each parameter follows:
436%
437% o type: the pre-defined type of kernel wanted
438%
439% o args: arguments defining or modifying the kernel
440%
441% Convolution Kernels
442%
anthony4fd27e22010-02-07 08:17:18 +0000443% Gaussian "{radius},{sigma}"
anthony602ab9b2010-01-05 08:06:50 +0000444% Generate a two-dimentional gaussian kernel, as used by -gaussian
445% A sigma is required, (with the 'x'), due to historical reasons.
446%
447% NOTE: that the 'radius' is optional, but if provided can limit (clip)
448% the final size of the resulting kernel to a square 2*radius+1 in size.
449% The radius should be at least 2 times that of the sigma value, or
450% sever clipping and aliasing may result. If not given or set to 0 the
451% radius will be determined so as to produce the best minimal error
452% result, which is usally much larger than is normally needed.
453%
anthony4fd27e22010-02-07 08:17:18 +0000454% Blur "{radius},{sigma},{angle}"
anthony602ab9b2010-01-05 08:06:50 +0000455% As per Gaussian, but generates a 1 dimensional or linear gaussian
456% blur, at the angle given (current restricted to orthogonal angles).
457% If a 'radius' is given the kernel is clipped to a width of 2*radius+1.
458%
459% NOTE that two such blurs perpendicular to each other is equivelent to
460% -blur and the previous gaussian, but is often 10 or more times faster.
461%
anthony4fd27e22010-02-07 08:17:18 +0000462% Comet "{width},{sigma},{angle}"
anthony602ab9b2010-01-05 08:06:50 +0000463% Blur in one direction only, mush like how a bright object leaves
464% a comet like trail. The Kernel is actually half a gaussian curve,
465% Adding two such blurs in oppiste directions produces a Linear Blur.
466%
467% NOTE: that the first argument is the width of the kernel and not the
468% radius of the kernel.
469%
470% # Still to be implemented...
471% #
anthony4fd27e22010-02-07 08:17:18 +0000472% # Sharpen "{radius},{sigma}
473% # Negated Gaussian (center zeroed and re-normalized),
474% # with a 2 unit positive peak. -- Check On line documentation
475% #
476% # Laplacian "{radius},{sigma}"
anthony602ab9b2010-01-05 08:06:50 +0000477% # Laplacian (a mexican hat like) Function
478% #
479% # LOG "{radius},{sigma1},{sigma2}
480% # Laplacian of Gaussian
481% #
482% # DOG "{radius},{sigma1},{sigma2}
anthony4fd27e22010-02-07 08:17:18 +0000483% # Difference of two Gaussians
484% #
485% # Filter2D
486% # Filter1D
487% # Set kernel values using a resize filter, and given scale (sigma)
488% # Cylindrical or Linear. Is this posible with an image?
489% #
anthony602ab9b2010-01-05 08:06:50 +0000490%
491% Boolean Kernels
492%
493% Rectangle "{geometry}"
494% Simply generate a rectangle of 1's with the size given. You can also
495% specify the location of the 'control point', otherwise the closest
496% pixel to the center of the rectangle is selected.
497%
498% Properly centered and odd sized rectangles work the best.
499%
anthony4fd27e22010-02-07 08:17:18 +0000500% Diamond "[{radius}[,{scale}]]"
anthony602ab9b2010-01-05 08:06:50 +0000501% Generate a diamond shaped kernal with given radius to the points.
502% Kernel size will again be radius*2+1 square and defaults to radius 1,
503% generating a 3x3 kernel that is slightly larger than a square.
504%
anthony4fd27e22010-02-07 08:17:18 +0000505% Square "[{radius}[,{scale}]]"
anthony602ab9b2010-01-05 08:06:50 +0000506% Generate a square shaped kernel of size radius*2+1, and defaulting
507% to a 3x3 (radius 1).
508%
509% Note that using a larger radius for the "Square" or the "Diamond"
510% is also equivelent to iterating the basic morphological method
511% that many times. However However iterating with the smaller radius 1
512% default is actually faster than using a larger kernel radius.
513%
anthony4fd27e22010-02-07 08:17:18 +0000514% Disk "[{radius}[,{scale}]]
anthony602ab9b2010-01-05 08:06:50 +0000515% Generate a binary disk of the radius given, radius may be a float.
516% Kernel size will be ceil(radius)*2+1 square.
517% NOTE: Here are some disk shapes of specific interest
518% "disk:1" => "diamond" or "cross:1"
519% "disk:1.5" => "square"
520% "disk:2" => "diamond:2"
anthony83ba99b2010-01-24 08:48:15 +0000521% "disk:2.5" => a general disk shape of radius 2
anthony602ab9b2010-01-05 08:06:50 +0000522% "disk:2.9" => "square:2"
anthony83ba99b2010-01-24 08:48:15 +0000523% "disk:3.5" => default - octagonal/disk shape of radius 3
anthony602ab9b2010-01-05 08:06:50 +0000524% "disk:4.2" => roughly octagonal shape of radius 4
anthony83ba99b2010-01-24 08:48:15 +0000525% "disk:4.3" => a general disk shape of radius 4
anthony602ab9b2010-01-05 08:06:50 +0000526% After this all the kernel shape becomes more and more circular.
527%
528% Because a "disk" is more circular when using a larger radius, using a
529% larger radius is preferred over iterating the morphological operation.
530%
anthony4fd27e22010-02-07 08:17:18 +0000531% Plus "[{radius}[,{scale}]]"
anthony602ab9b2010-01-05 08:06:50 +0000532% Generate a kernel in the shape of a 'plus' sign. The length of each
533% arm is also the radius, which defaults to 2.
534%
535% This kernel is not a good general morphological kernel, but is used
536% more for highlighting and marking any single pixels in an image using,
537% a "Dilate" or "Erode" method as appropriate.
anthonyc94cdb02010-01-06 08:15:29 +0000538%
anthony602ab9b2010-01-05 08:06:50 +0000539% NOTE: "plus:1" is equivelent to a "Diamond" kernel.
540%
541% Note that unlike other kernels iterating a plus does not produce the
542% same result as using a larger radius for the cross.
543%
544% Distance Measuring Kernels
545%
anthonyc84dce52010-05-07 05:42:23 +0000546% Chebyshev "[{radius}][x{scale}[%!]]"
547% Manhatten "[{radius}][x{scale}[%!]]"
548% Euclidean "[{radius}][x{scale}[%!]]"
anthony602ab9b2010-01-05 08:06:50 +0000549%
550% Different types of distance measuring methods, which are used with the
551% a 'Distance' morphology method for generating a gradient based on
552% distance from an edge of a binary shape, though there is a technique
553% for handling a anti-aliased shape.
554%
anthonyc94cdb02010-01-06 08:15:29 +0000555% Chebyshev Distance (also known as Tchebychev Distance) is a value of
556% one to any neighbour, orthogonal or diagonal. One why of thinking of
557% it is the number of squares a 'King' or 'Queen' in chess needs to
558% traverse reach any other position on a chess board. It results in a
559% 'square' like distance function, but one where diagonals are closer
560% than expected.
anthony602ab9b2010-01-05 08:06:50 +0000561%
anthonyc94cdb02010-01-06 08:15:29 +0000562% Manhatten Distance (also known as Rectilinear Distance, or the Taxi
563% Cab metric), is the distance needed when you can only travel in
564% orthogonal (horizontal or vertical) only. It is the distance a 'Rook'
565% in chess would travel. It results in a diamond like distances, where
566% diagonals are further than expected.
anthony602ab9b2010-01-05 08:06:50 +0000567%
anthonyc94cdb02010-01-06 08:15:29 +0000568% Euclidean Distance is the 'direct' or 'as the crow flys distance.
569% However by default the kernel size only has a radius of 1, which
570% limits the distance to 'Knight' like moves, with only orthogonal and
571% diagonal measurements being correct. As such for the default kernel
572% you will get octagonal like distance function, which is reasonally
573% accurate.
574%
575% However if you use a larger radius such as "Euclidean:4" you will
576% get a much smoother distance gradient from the edge of the shape.
577% Of course a larger kernel is slower to use, and generally not needed.
578%
579% To allow the use of fractional distances that you get with diagonals
580% the actual distance is scaled by a fixed value which the user can
581% provide. This is not actually nessary for either ""Chebyshev" or
582% "Manhatten" distance kernels, but is done for all three distance
583% kernels. If no scale is provided it is set to a value of 100,
584% allowing for a maximum distance measurement of 655 pixels using a Q16
585% version of IM, from any edge. However for small images this can
586% result in quite a dark gradient.
587%
588% See the 'Distance' Morphological Method, for information of how it is
589% applied.
anthony602ab9b2010-01-05 08:06:50 +0000590%
anthony4fd27e22010-02-07 08:17:18 +0000591% # Hit-n-Miss Kernel-Lists -- Still to be implemented
592% #
593% # specifically for Pruning, Thinning, Thickening
594% #
anthony602ab9b2010-01-05 08:06:50 +0000595*/
596
cristy2be15382010-01-21 02:38:03 +0000597MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000598 const GeometryInfo *args)
599{
cristy2be15382010-01-21 02:38:03 +0000600 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000601 *kernel;
602
cristy150989e2010-02-01 14:59:39 +0000603 register long
anthony602ab9b2010-01-05 08:06:50 +0000604 i;
605
606 register long
607 u,
608 v;
609
610 double
611 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
612
cristy2be15382010-01-21 02:38:03 +0000613 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
614 if (kernel == (KernelInfo *) NULL)
anthony602ab9b2010-01-05 08:06:50 +0000615 return(kernel);
616 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
cristyc99304f2010-02-01 15:26:27 +0000617 kernel->minimum = kernel->maximum = 0.0;
618 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000619 kernel->type = type;
cristyd43a46b2010-01-21 02:13:41 +0000620 kernel->signature = MagickSignature;
anthony602ab9b2010-01-05 08:06:50 +0000621
622 switch(type) {
623 /* Convolution Kernels */
624 case GaussianKernel:
625 { double
626 sigma = fabs(args->sigma);
627
628 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
629
630 kernel->width = kernel->height =
631 GetOptimalKernelWidth2D(args->rho,sigma);
cristyc99304f2010-02-01 15:26:27 +0000632 kernel->x = kernel->y = (long) (kernel->width-1)/2;
633 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000634 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
635 kernel->height*sizeof(double));
636 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000637 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000638
639 sigma = 2.0*sigma*sigma; /* simplify the expression */
cristyc99304f2010-02-01 15:26:27 +0000640 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
641 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
642 kernel->positive_range += (
anthony602ab9b2010-01-05 08:06:50 +0000643 kernel->values[i] =
644 exp(-((double)(u*u+v*v))/sigma)
645 /* / (MagickPI*sigma) */ );
cristyc99304f2010-02-01 15:26:27 +0000646 kernel->minimum = 0;
647 kernel->maximum = kernel->values[
648 kernel->y*kernel->width+kernel->x ];
anthony602ab9b2010-01-05 08:06:50 +0000649
anthony999bb2c2010-02-18 12:38:01 +0000650 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
anthony602ab9b2010-01-05 08:06:50 +0000651
652 break;
653 }
654 case BlurKernel:
655 { double
656 sigma = fabs(args->sigma);
657
658 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
659
660 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
cristyc99304f2010-02-01 15:26:27 +0000661 kernel->x = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000662 kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +0000663 kernel->y = 0;
664 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000665 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
666 kernel->height*sizeof(double));
667 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000668 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000669
670#if 1
671#define KernelRank 3
672 /* Formula derived from GetBlurKernel() in "effect.c" (plus bug fix).
673 ** It generates a gaussian 3 times the width, and compresses it into
674 ** the expected range. This produces a closer normalization of the
675 ** resulting kernel, especially for very low sigma values.
676 ** As such while wierd it is prefered.
677 **
678 ** I am told this method originally came from Photoshop.
679 */
680 sigma *= KernelRank; /* simplify expanded curve */
cristy150989e2010-02-01 14:59:39 +0000681 v = (long) (kernel->width*KernelRank-1)/2; /* start/end points to fit range */
anthony602ab9b2010-01-05 08:06:50 +0000682 (void) ResetMagickMemory(kernel->values,0, (size_t)
683 kernel->width*sizeof(double));
684 for ( u=-v; u <= v; u++) {
685 kernel->values[(u+v)/KernelRank] +=
686 exp(-((double)(u*u))/(2.0*sigma*sigma))
687 /* / (MagickSQ2PI*sigma/KernelRank) */ ;
688 }
cristy150989e2010-02-01 14:59:39 +0000689 for (i=0; i < (long) kernel->width; i++)
cristyc99304f2010-02-01 15:26:27 +0000690 kernel->positive_range += kernel->values[i];
anthony602ab9b2010-01-05 08:06:50 +0000691#else
cristyc99304f2010-02-01 15:26:27 +0000692 for ( i=0, u=-kernel->x; i < kernel->width; i++, u++)
693 kernel->positive_range += (
anthony602ab9b2010-01-05 08:06:50 +0000694 kernel->values[i] =
695 exp(-((double)(u*u))/(2.0*sigma*sigma))
696 /* / (MagickSQ2PI*sigma) */ );
697#endif
cristyc99304f2010-02-01 15:26:27 +0000698 kernel->minimum = 0;
699 kernel->maximum = kernel->values[ kernel->x ];
anthonycc6c8362010-01-25 04:14:01 +0000700 /* Note that neither methods above generate a normalized kernel,
701 ** though it gets close. The kernel may be 'clipped' by a user defined
702 ** radius, producing a smaller (darker) kernel. Also for very small
703 ** sigma's (> 0.1) the central value becomes larger than one, and thus
704 ** producing a very bright kernel.
anthony602ab9b2010-01-05 08:06:50 +0000705 */
anthonycc6c8362010-01-25 04:14:01 +0000706
anthony602ab9b2010-01-05 08:06:50 +0000707 /* Normalize the 1D Gaussian Kernel
708 **
709 ** Because of this the divisor in the above kernel generator is
anthonyc94cdb02010-01-06 08:15:29 +0000710 ** not needed, so is not done above.
anthony602ab9b2010-01-05 08:06:50 +0000711 */
anthony999bb2c2010-02-18 12:38:01 +0000712 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
anthonycc6c8362010-01-25 04:14:01 +0000713
anthony602ab9b2010-01-05 08:06:50 +0000714 /* rotate the kernel by given angle */
anthony4fd27e22010-02-07 08:17:18 +0000715 RotateKernelInfo(kernel, args->xi);
anthony602ab9b2010-01-05 08:06:50 +0000716 break;
717 }
718 case CometKernel:
719 { double
720 sigma = fabs(args->sigma);
721
722 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
723
724 if ( args->rho < 1.0 )
725 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
726 else
727 kernel->width = (unsigned long)args->rho;
cristyc99304f2010-02-01 15:26:27 +0000728 kernel->x = kernel->y = 0;
anthony602ab9b2010-01-05 08:06:50 +0000729 kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +0000730 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000731 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
732 kernel->height*sizeof(double));
733 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000734 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000735
736 /* A comet blur is half a gaussian curve, so that the object is
737 ** blurred in one direction only. This may not be quite the right
738 ** curve so may change in the future. The function must be normalised.
739 */
740#if 1
741#define KernelRank 3
742 sigma *= KernelRank; /* simplify expanded curve */
cristy150989e2010-02-01 14:59:39 +0000743 v = (long) kernel->width*KernelRank; /* start/end points to fit range */
anthony602ab9b2010-01-05 08:06:50 +0000744 (void) ResetMagickMemory(kernel->values,0, (size_t)
745 kernel->width*sizeof(double));
746 for ( u=0; u < v; u++) {
747 kernel->values[u/KernelRank] +=
748 exp(-((double)(u*u))/(2.0*sigma*sigma))
749 /* / (MagickSQ2PI*sigma/KernelRank) */ ;
750 }
cristy150989e2010-02-01 14:59:39 +0000751 for (i=0; i < (long) kernel->width; i++)
cristyc99304f2010-02-01 15:26:27 +0000752 kernel->positive_range += kernel->values[i];
anthony602ab9b2010-01-05 08:06:50 +0000753#else
cristy150989e2010-02-01 14:59:39 +0000754 for ( i=0; i < (long) kernel->width; i++)
cristyc99304f2010-02-01 15:26:27 +0000755 kernel->positive_range += (
anthony602ab9b2010-01-05 08:06:50 +0000756 kernel->values[i] =
757 exp(-((double)(i*i))/(2.0*sigma*sigma))
758 /* / (MagickSQ2PI*sigma) */ );
759#endif
cristyc99304f2010-02-01 15:26:27 +0000760 kernel->minimum = 0;
761 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000762
anthony999bb2c2010-02-18 12:38:01 +0000763 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
764 RotateKernelInfo(kernel, args->xi); /* Rotate by angle */
anthony602ab9b2010-01-05 08:06:50 +0000765 break;
766 }
767 /* Boolean Kernels */
768 case RectangleKernel:
769 case SquareKernel:
770 {
anthony4fd27e22010-02-07 08:17:18 +0000771 double scale;
anthony602ab9b2010-01-05 08:06:50 +0000772 if ( type == SquareKernel )
773 {
774 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000775 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000776 else
cristy150989e2010-02-01 14:59:39 +0000777 kernel->width = kernel->height = (unsigned long) (2*args->rho+1);
cristyc99304f2010-02-01 15:26:27 +0000778 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony4fd27e22010-02-07 08:17:18 +0000779 scale = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +0000780 }
781 else {
cristy2be15382010-01-21 02:38:03 +0000782 /* NOTE: user defaults set in "AcquireKernelInfo()" */
anthony602ab9b2010-01-05 08:06:50 +0000783 if ( args->rho < 1.0 || args->sigma < 1.0 )
anthony83ba99b2010-01-24 08:48:15 +0000784 return(DestroyKernelInfo(kernel)); /* invalid args given */
anthony602ab9b2010-01-05 08:06:50 +0000785 kernel->width = (unsigned long)args->rho;
786 kernel->height = (unsigned long)args->sigma;
787 if ( args->xi < 0.0 || args->xi > (double)kernel->width ||
788 args->psi < 0.0 || args->psi > (double)kernel->height )
anthony83ba99b2010-01-24 08:48:15 +0000789 return(DestroyKernelInfo(kernel)); /* invalid args given */
cristyc99304f2010-02-01 15:26:27 +0000790 kernel->x = (long) args->xi;
791 kernel->y = (long) args->psi;
anthony4fd27e22010-02-07 08:17:18 +0000792 scale = 1.0;
anthony602ab9b2010-01-05 08:06:50 +0000793 }
794 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
795 kernel->height*sizeof(double));
796 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000797 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000798
anthonycc6c8362010-01-25 04:14:01 +0000799 /* set all kernel values to 1.0 */
cristy150989e2010-02-01 14:59:39 +0000800 u=(long) kernel->width*kernel->height;
801 for ( i=0; i < u; i++)
anthony4fd27e22010-02-07 08:17:18 +0000802 kernel->values[i] = scale;
803 kernel->minimum = kernel->maximum = scale; /* a flat shape */
804 kernel->positive_range = scale*u;
anthonycc6c8362010-01-25 04:14:01 +0000805 break;
anthony602ab9b2010-01-05 08:06:50 +0000806 }
807 case DiamondKernel:
808 {
809 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000810 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000811 else
812 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000813 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000814
815 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
816 kernel->height*sizeof(double));
817 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000818 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000819
anthony4fd27e22010-02-07 08:17:18 +0000820 /* set all kernel values within diamond area to scale given */
cristyc99304f2010-02-01 15:26:27 +0000821 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
822 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
823 if ((labs(u)+labs(v)) <= (long)kernel->x)
anthony4fd27e22010-02-07 08:17:18 +0000824 kernel->positive_range += kernel->values[i] = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +0000825 else
826 kernel->values[i] = nan;
anthony4fd27e22010-02-07 08:17:18 +0000827 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
anthony602ab9b2010-01-05 08:06:50 +0000828 break;
829 }
830 case DiskKernel:
831 {
832 long
833 limit;
834
835 limit = (long)(args->rho*args->rho);
anthony83ba99b2010-01-24 08:48:15 +0000836 if (args->rho < 0.1) /* default radius approx 3.5 */
837 kernel->width = kernel->height = 7L, limit = 10L;
anthony602ab9b2010-01-05 08:06:50 +0000838 else
839 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000840 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000841
842 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
843 kernel->height*sizeof(double));
844 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000845 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000846
anthonycc6c8362010-01-25 04:14:01 +0000847 /* set all kernel values within disk area to 1.0 */
cristyc99304f2010-02-01 15:26:27 +0000848 for ( i=0, v= -kernel->y; v <= (long)kernel->y; v++)
849 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony602ab9b2010-01-05 08:06:50 +0000850 if ((u*u+v*v) <= limit)
anthony4fd27e22010-02-07 08:17:18 +0000851 kernel->positive_range += kernel->values[i] = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +0000852 else
853 kernel->values[i] = nan;
anthony4fd27e22010-02-07 08:17:18 +0000854 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
anthony602ab9b2010-01-05 08:06:50 +0000855 break;
856 }
857 case PlusKernel:
858 {
859 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000860 kernel->width = kernel->height = 5; /* default radius 2 */
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
anthonycc6c8362010-01-25 04:14:01 +0000870 /* set all kernel values along axises to 1.0 */
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++)
anthony4fd27e22010-02-07 08:17:18 +0000873 kernel->values[i] = (u == 0 || v == 0) ? args->sigma : nan;
874 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
875 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
anthony602ab9b2010-01-05 08:06:50 +0000876 break;
877 }
878 /* Distance Measuring Kernels */
879 case ChebyshevKernel:
880 {
anthony602ab9b2010-01-05 08:06:50 +0000881 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000882 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000883 else
884 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000885 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000886
887 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
888 kernel->height*sizeof(double));
889 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000890 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000891
cristyc99304f2010-02-01 15:26:27 +0000892 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
893 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
894 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +0000895 args->sigma*((labs(u)>labs(v)) ? labs(u) : labs(v)) );
cristyc99304f2010-02-01 15:26:27 +0000896 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000897 break;
898 }
899 case ManhattenKernel:
900 {
anthony602ab9b2010-01-05 08:06:50 +0000901 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000902 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000903 else
904 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000905 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000906
907 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
908 kernel->height*sizeof(double));
909 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000910 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000911
cristyc99304f2010-02-01 15:26:27 +0000912 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
913 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
914 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +0000915 args->sigma*(labs(u)+labs(v)) );
cristyc99304f2010-02-01 15:26:27 +0000916 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000917 break;
918 }
919 case EuclideanKernel:
920 {
anthony602ab9b2010-01-05 08:06:50 +0000921 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000922 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000923 else
924 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000925 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000926
927 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
928 kernel->height*sizeof(double));
929 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000930 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000931
cristyc99304f2010-02-01 15:26:27 +0000932 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
933 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
934 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +0000935 args->sigma*sqrt((double)(u*u+v*v)) );
cristyc99304f2010-02-01 15:26:27 +0000936 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000937 break;
938 }
939 /* Undefined Kernels */
940 case LaplacianKernel:
941 case LOGKernel:
942 case DOGKernel:
cristy150989e2010-02-01 14:59:39 +0000943 perror("Kernel Type has not been defined yet");
anthony602ab9b2010-01-05 08:06:50 +0000944 /* FALL THRU */
945 default:
946 /* Generate a No-Op minimal kernel - 1x1 pixel */
947 kernel->values=(double *)AcquireQuantumMemory((size_t)1,sizeof(double));
948 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000949 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000950 kernel->width = kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +0000951 kernel->x = kernel->x = 0;
anthony602ab9b2010-01-05 08:06:50 +0000952 kernel->type = UndefinedKernel;
cristyc99304f2010-02-01 15:26:27 +0000953 kernel->maximum =
954 kernel->positive_range =
anthonyc94cdb02010-01-06 08:15:29 +0000955 kernel->values[0] = 1.0; /* a flat single-point no-op kernel! */
anthony602ab9b2010-01-05 08:06:50 +0000956 break;
957 }
958
959 return(kernel);
960}
anthonyc94cdb02010-01-06 08:15:29 +0000961
anthony602ab9b2010-01-05 08:06:50 +0000962/*
963%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
964% %
965% %
966% %
cristy6771f1e2010-03-05 19:43:39 +0000967% C l o n e K e r n e l I n f o %
anthony4fd27e22010-02-07 08:17:18 +0000968% %
969% %
970% %
971%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
972%
973% CloneKernelInfo() creates a new clone of the given Kernel so that its can
974% be modified without effecting the original. The cloned kernel should be
975% destroyed using DestoryKernelInfo() when no longer needed.
976%
cristye6365592010-04-02 17:31:23 +0000977% The format of the CloneKernelInfo method is:
anthony4fd27e22010-02-07 08:17:18 +0000978%
anthony930be612010-02-08 04:26:15 +0000979% KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +0000980%
981% A description of each parameter follows:
982%
983% o kernel: the Morphology/Convolution kernel to be cloned
984%
985*/
cristyef656912010-03-05 19:54:59 +0000986MagickExport KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +0000987{
988 register long
989 i;
990
cristy19eb6412010-04-23 14:42:29 +0000991 KernelInfo
992 *kernel_info;
anthony4fd27e22010-02-07 08:17:18 +0000993
994 assert(kernel != (KernelInfo *) NULL);
cristy19eb6412010-04-23 14:42:29 +0000995 kernel_info=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
996 if (kernel_info == (KernelInfo *) NULL)
997 return(kernel_info);
998 *kernel_info=(*kernel); /* copy values in structure */
999 kernel_info->values=(double *) AcquireQuantumMemory(kernel->width,
1000 kernel->height*sizeof(double));
1001 if (kernel_info->values == (double *) NULL)
1002 return(DestroyKernelInfo(kernel_info));
anthony4fd27e22010-02-07 08:17:18 +00001003 for (i=0; i < (long) (kernel->width*kernel->height); i++)
cristy19eb6412010-04-23 14:42:29 +00001004 kernel_info->values[i]=kernel->values[i];
1005 return(kernel_info);
anthony4fd27e22010-02-07 08:17:18 +00001006}
1007
1008/*
1009%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1010% %
1011% %
1012% %
anthony83ba99b2010-01-24 08:48:15 +00001013% D e s t r o y K e r n e l I n f o %
anthony602ab9b2010-01-05 08:06:50 +00001014% %
1015% %
1016% %
1017%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1018%
anthony83ba99b2010-01-24 08:48:15 +00001019% DestroyKernelInfo() frees the memory used by a Convolution/Morphology
1020% kernel.
anthony602ab9b2010-01-05 08:06:50 +00001021%
anthony83ba99b2010-01-24 08:48:15 +00001022% The format of the DestroyKernelInfo method is:
anthony602ab9b2010-01-05 08:06:50 +00001023%
anthony83ba99b2010-01-24 08:48:15 +00001024% KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +00001025%
1026% A description of each parameter follows:
1027%
1028% o kernel: the Morphology/Convolution kernel to be destroyed
1029%
1030*/
1031
anthony83ba99b2010-01-24 08:48:15 +00001032MagickExport KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +00001033{
cristy2be15382010-01-21 02:38:03 +00001034 assert(kernel != (KernelInfo *) NULL);
anthony4fd27e22010-02-07 08:17:18 +00001035
1036 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
1037 kernel->height*sizeof(double));
anthony602ab9b2010-01-05 08:06:50 +00001038 kernel->values=(double *)RelinquishMagickMemory(kernel->values);
cristy2be15382010-01-21 02:38:03 +00001039 kernel=(KernelInfo *) RelinquishMagickMemory(kernel);
anthony602ab9b2010-01-05 08:06:50 +00001040 return(kernel);
1041}
anthonyc94cdb02010-01-06 08:15:29 +00001042
1043/*
1044%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1045% %
1046% %
1047% %
anthony29188a82010-01-22 10:12:34 +00001048% 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 +00001049% %
1050% %
1051% %
1052%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1053%
anthony29188a82010-01-22 10:12:34 +00001054% MorphologyImageChannel() applies a user supplied kernel to the image
1055% according to the given mophology method.
anthony602ab9b2010-01-05 08:06:50 +00001056%
1057% The given kernel is assumed to have been pre-scaled appropriatally, usally
1058% by the kernel generator.
1059%
1060% The format of the MorphologyImage method is:
1061%
cristyef656912010-03-05 19:54:59 +00001062% Image *MorphologyImage(const Image *image,MorphologyMethod method,
1063% const long iterations,KernelInfo *kernel,ExceptionInfo *exception)
anthony29188a82010-01-22 10:12:34 +00001064% Image *MorphologyImageChannel(const Image *image, const ChannelType
cristyef656912010-03-05 19:54:59 +00001065% channel,MorphologyMethod method,const long iterations,
1066% KernelInfo *kernel,ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001067%
1068% A description of each parameter follows:
1069%
1070% o image: the image.
1071%
1072% o method: the morphology method to be applied.
1073%
1074% o iterations: apply the operation this many times (or no change).
1075% A value of -1 means loop until no change found.
1076% How this is applied may depend on the morphology method.
1077% Typically this is a value of 1.
1078%
1079% o channel: the channel type.
1080%
1081% o kernel: An array of double representing the morphology kernel.
anthony29188a82010-01-22 10:12:34 +00001082% Warning: kernel may be normalized for the Convolve method.
anthony602ab9b2010-01-05 08:06:50 +00001083%
1084% o exception: return any errors or warnings in this structure.
1085%
1086%
1087% TODO: bias and auto-scale handling of the kernel for convolution
1088% The given kernel is assumed to have been pre-scaled appropriatally, usally
1089% by the kernel generator.
1090%
1091*/
1092
anthony930be612010-02-08 04:26:15 +00001093
anthony602ab9b2010-01-05 08:06:50 +00001094/* Internal function
anthony930be612010-02-08 04:26:15 +00001095 * Apply the Low-Level Morphology Method using the given Kernel
1096 * Returning the number of pixels that changed.
1097 * Two pre-created images must be provided, no image is created.
anthony602ab9b2010-01-05 08:06:50 +00001098 */
1099static unsigned long MorphologyApply(const Image *image, Image
1100 *result_image, const MorphologyMethod method, const ChannelType channel,
cristy2be15382010-01-21 02:38:03 +00001101 const KernelInfo *kernel, ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001102{
cristy2be15382010-01-21 02:38:03 +00001103#define MorphologyTag "Morphology/Image"
anthony602ab9b2010-01-05 08:06:50 +00001104
1105 long
cristy150989e2010-02-01 14:59:39 +00001106 progress,
anthony29188a82010-01-22 10:12:34 +00001107 y, offx, offy,
anthony602ab9b2010-01-05 08:06:50 +00001108 changed;
1109
1110 MagickBooleanType
1111 status;
1112
1113 MagickPixelPacket
1114 bias;
1115
1116 CacheView
1117 *p_view,
1118 *q_view;
1119
anthony4fd27e22010-02-07 08:17:18 +00001120 /* Only the most basic morphology is actually performed by this routine */
anthony4fd27e22010-02-07 08:17:18 +00001121
anthony602ab9b2010-01-05 08:06:50 +00001122 /*
anthony4fd27e22010-02-07 08:17:18 +00001123 Apply Basic Morphology to Image.
anthony602ab9b2010-01-05 08:06:50 +00001124 */
1125 status=MagickTrue;
1126 changed=0;
1127 progress=0;
1128
1129 GetMagickPixelPacket(image,&bias);
1130 SetMagickPixelPacketBias(image,&bias);
anthonycc6c8362010-01-25 04:14:01 +00001131 /* Future: handle auto-bias from user, based on kernel input */
anthony602ab9b2010-01-05 08:06:50 +00001132
1133 p_view=AcquireCacheView(image);
1134 q_view=AcquireCacheView(result_image);
anthony29188a82010-01-22 10:12:34 +00001135
anthonycc6c8362010-01-25 04:14:01 +00001136 /* Some methods (including convolve) needs use a reflected kernel.
1137 * Adjust 'origin' offsets for this reflected kernel.
anthony29188a82010-01-22 10:12:34 +00001138 */
cristyc99304f2010-02-01 15:26:27 +00001139 offx = kernel->x;
1140 offy = kernel->y;
anthony29188a82010-01-22 10:12:34 +00001141 switch(method) {
anthony930be612010-02-08 04:26:15 +00001142 case ConvolveMorphology:
1143 case DilateMorphology:
1144 case DilateIntensityMorphology:
1145 case DistanceMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001146 /* kernel needs to used with reflection about origin */
cristy150989e2010-02-01 14:59:39 +00001147 offx = (long) kernel->width-offx-1;
1148 offy = (long) kernel->height-offy-1;
anthony29188a82010-01-22 10:12:34 +00001149 break;
anthony5ef8e942010-05-11 06:51:12 +00001150 case ErodeMorphology:
1151 case ErodeIntensityMorphology:
1152 case HitAndMissMorphology:
1153 case ThinningMorphology:
1154 case ThickenMorphology:
1155 /* kernel is user as is, without reflection */
1156 break;
anthony930be612010-02-08 04:26:15 +00001157 default:
anthony5ef8e942010-05-11 06:51:12 +00001158 perror("Not a low level Morphology Method");
anthony930be612010-02-08 04:26:15 +00001159 break;
anthony29188a82010-01-22 10:12:34 +00001160 }
1161
anthony602ab9b2010-01-05 08:06:50 +00001162#if defined(MAGICKCORE_OPENMP_SUPPORT)
1163 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1164#endif
cristy150989e2010-02-01 14:59:39 +00001165 for (y=0; y < (long) image->rows; y++)
anthony602ab9b2010-01-05 08:06:50 +00001166 {
1167 MagickBooleanType
1168 sync;
1169
1170 register const PixelPacket
1171 *restrict p;
1172
1173 register const IndexPacket
1174 *restrict p_indexes;
1175
1176 register PixelPacket
1177 *restrict q;
1178
1179 register IndexPacket
1180 *restrict q_indexes;
1181
cristy150989e2010-02-01 14:59:39 +00001182 register long
anthony602ab9b2010-01-05 08:06:50 +00001183 x;
1184
anthony29188a82010-01-22 10:12:34 +00001185 unsigned long
anthony602ab9b2010-01-05 08:06:50 +00001186 r;
1187
1188 if (status == MagickFalse)
1189 continue;
anthony29188a82010-01-22 10:12:34 +00001190 p=GetCacheViewVirtualPixels(p_view, -offx, y-offy,
1191 image->columns+kernel->width, kernel->height, exception);
anthony602ab9b2010-01-05 08:06:50 +00001192 q=GetCacheViewAuthenticPixels(q_view,0,y,result_image->columns,1,
1193 exception);
1194 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1195 {
1196 status=MagickFalse;
1197 continue;
1198 }
1199 p_indexes=GetCacheViewVirtualIndexQueue(p_view);
1200 q_indexes=GetCacheViewAuthenticIndexQueue(q_view);
anthony29188a82010-01-22 10:12:34 +00001201 r = (image->columns+kernel->width)*offy+offx; /* constant */
1202
cristy150989e2010-02-01 14:59:39 +00001203 for (x=0; x < (long) image->columns; x++)
anthony602ab9b2010-01-05 08:06:50 +00001204 {
cristy150989e2010-02-01 14:59:39 +00001205 long
anthony602ab9b2010-01-05 08:06:50 +00001206 v;
1207
cristy150989e2010-02-01 14:59:39 +00001208 register long
anthony602ab9b2010-01-05 08:06:50 +00001209 u;
1210
1211 register const double
1212 *restrict k;
1213
1214 register const PixelPacket
1215 *restrict k_pixels;
1216
1217 register const IndexPacket
1218 *restrict k_indexes;
1219
1220 MagickPixelPacket
anthony5ef8e942010-05-11 06:51:12 +00001221 result,
1222 min,
1223 max;
anthony602ab9b2010-01-05 08:06:50 +00001224
anthony29188a82010-01-22 10:12:34 +00001225 /* Copy input to ouput image for unused channels
anthony83ba99b2010-01-24 08:48:15 +00001226 * This removes need for 'cloning' a new image every iteration
anthony29188a82010-01-22 10:12:34 +00001227 */
anthony602ab9b2010-01-05 08:06:50 +00001228 *q = p[r];
1229 if (image->colorspace == CMYKColorspace)
1230 q_indexes[x] = p_indexes[r];
1231
anthony5ef8e942010-05-11 06:51:12 +00001232 /* Defaults */
1233 min.red =
1234 min.green =
1235 min.blue =
1236 min.opacity =
1237 min.index = (MagickRealType) QuantumRange;
1238 max.red =
1239 max.green =
1240 max.blue =
1241 max.opacity =
1242 max.index = (MagickRealType) 0;
1243 /* original pixel value */
1244 result.red = (MagickRealType) p[r].red;
1245 result.green = (MagickRealType) p[r].green;
1246 result.blue = (MagickRealType) p[r].blue;
1247 result.opacity = QuantumRange - (MagickRealType) p[r].opacity;
1248 if ( image->colorspace == CMYKColorspace)
1249 result.index = (MagickRealType) p_indexes[r];
1250
anthony602ab9b2010-01-05 08:06:50 +00001251 switch (method) {
1252 case ConvolveMorphology:
anthony930be612010-02-08 04:26:15 +00001253 /* Set the user defined bias of the weighted average output
1254 **
1255 ** FUTURE: provide some way for internal functions to disable
anthony5ef8e942010-05-11 06:51:12 +00001256 ** user provided bias and scaling effects.
anthony930be612010-02-08 04:26:15 +00001257 */
anthony602ab9b2010-01-05 08:06:50 +00001258 result=bias;
anthony930be612010-02-08 04:26:15 +00001259 break;
anthony4fd27e22010-02-07 08:17:18 +00001260 case DilateIntensityMorphology:
1261 case ErodeIntensityMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001262 result.red = 0.0; /* flag indicating when first match found */
anthony4fd27e22010-02-07 08:17:18 +00001263 break;
anthony602ab9b2010-01-05 08:06:50 +00001264 default:
anthony602ab9b2010-01-05 08:06:50 +00001265 break;
1266 }
1267
1268 switch ( method ) {
1269 case ConvolveMorphology:
anthony930be612010-02-08 04:26:15 +00001270 /* Weighted Average of pixels using reflected kernel
1271 **
1272 ** NOTE for correct working of this operation for asymetrical
1273 ** kernels, the kernel needs to be applied in its reflected form.
1274 ** That is its values needs to be reversed.
1275 **
1276 ** Correlation is actually the same as this but without reflecting
1277 ** the kernel, and thus 'lower-level' that Convolution. However
1278 ** as Convolution is the more common method used, and it does not
1279 ** really cost us much in terms of processing to use a reflected
anthony5ef8e942010-05-11 06:51:12 +00001280 ** kernel, so it is Convolution that is implemented.
anthony930be612010-02-08 04:26:15 +00001281 **
1282 ** Correlation will have its kernel reflected before calling
1283 ** this function to do a Convolve.
1284 **
1285 ** For more details of Correlation vs Convolution see
1286 ** http://www.cs.umd.edu/~djacobs/CMSC426/Convolution.pdf
1287 */
anthony5ef8e942010-05-11 06:51:12 +00001288 if (((channel & SyncChannels) != 0 ) &&
1289 (image->matte == MagickTrue))
1290 { /* Channel has a 'Sync' Flag, and Alpha Channel enabled.
1291 ** Weight the color channels with Alpha Channel so that
1292 ** transparent pixels are not part of the results.
1293 */
anthony602ab9b2010-01-05 08:06:50 +00001294 MagickRealType
anthony5ef8e942010-05-11 06:51:12 +00001295 alpha, /* color channel weighting : kernel*alpha */
1296 gamma; /* divisor, sum of weighting values */
anthony602ab9b2010-01-05 08:06:50 +00001297
1298 gamma=0.0;
anthony29188a82010-01-22 10:12:34 +00001299 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001300 k_pixels = p;
1301 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001302 for (v=0; v < (long) kernel->height; v++) {
1303 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001304 if ( IsNan(*k) ) continue;
1305 alpha=(*k)*(QuantumScale*(QuantumRange-
1306 k_pixels[u].opacity));
1307 gamma += alpha;
1308 result.red += alpha*k_pixels[u].red;
1309 result.green += alpha*k_pixels[u].green;
1310 result.blue += alpha*k_pixels[u].blue;
anthony83ba99b2010-01-24 08:48:15 +00001311 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00001312 if ( image->colorspace == CMYKColorspace)
1313 result.index += alpha*k_indexes[u];
1314 }
1315 k_pixels += image->columns+kernel->width;
1316 k_indexes += image->columns+kernel->width;
1317 }
1318 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
anthony83ba99b2010-01-24 08:48:15 +00001319 result.red *= gamma;
1320 result.green *= gamma;
1321 result.blue *= gamma;
1322 result.opacity *= gamma;
1323 result.index *= gamma;
anthony602ab9b2010-01-05 08:06:50 +00001324 }
anthony5ef8e942010-05-11 06:51:12 +00001325 else
1326 {
1327 /* No 'Sync' flag, or no Alpha involved.
1328 ** Convolution is simple individual channel weigthed sum.
1329 */
1330 k = &kernel->values[ kernel->width*kernel->height-1 ];
1331 k_pixels = p;
1332 k_indexes = p_indexes;
1333 for (v=0; v < (long) kernel->height; v++) {
1334 for (u=0; u < (long) kernel->width; u++, k--) {
1335 if ( IsNan(*k) ) continue;
1336 result.red += (*k)*k_pixels[u].red;
1337 result.green += (*k)*k_pixels[u].green;
1338 result.blue += (*k)*k_pixels[u].blue;
1339 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
1340 if ( image->colorspace == CMYKColorspace)
1341 result.index += (*k)*k_indexes[u];
1342 }
1343 k_pixels += image->columns+kernel->width;
1344 k_indexes += image->columns+kernel->width;
1345 }
1346 }
anthony602ab9b2010-01-05 08:06:50 +00001347 break;
1348
anthony4fd27e22010-02-07 08:17:18 +00001349 case ErodeMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001350 /* Minimum Value within kernel neighbourhood
anthony930be612010-02-08 04:26:15 +00001351 **
1352 ** NOTE that the kernel is not reflected for this operation!
1353 **
1354 ** NOTE: in normal Greyscale Morphology, the kernel value should
1355 ** be added to the real value, this is currently not done, due to
1356 ** the nature of the boolean kernels being used.
1357 */
anthony4fd27e22010-02-07 08:17:18 +00001358 k = kernel->values;
1359 k_pixels = p;
1360 k_indexes = p_indexes;
1361 for (v=0; v < (long) kernel->height; v++) {
1362 for (u=0; u < (long) kernel->width; u++, k++) {
1363 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony5ef8e942010-05-11 06:51:12 +00001364 Minimize(min.red, (double) k_pixels[u].red);
1365 Minimize(min.green, (double) k_pixels[u].green);
1366 Minimize(min.blue, (double) k_pixels[u].blue);
1367 Minimize(min.opacity,
anthonyd37a5cb2010-05-07 06:37:03 +00001368 QuantumRange-(double) k_pixels[u].opacity);
anthony4fd27e22010-02-07 08:17:18 +00001369 if ( image->colorspace == CMYKColorspace)
anthony5ef8e942010-05-11 06:51:12 +00001370 Minimize(min.index, (double) k_indexes[u]);
anthony4fd27e22010-02-07 08:17:18 +00001371 }
1372 k_pixels += image->columns+kernel->width;
1373 k_indexes += image->columns+kernel->width;
1374 }
1375 break;
1376
anthony5ef8e942010-05-11 06:51:12 +00001377
anthony83ba99b2010-01-24 08:48:15 +00001378 case DilateMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001379 /* Maximum Value within kernel neighbourhood
anthony930be612010-02-08 04:26:15 +00001380 **
1381 ** NOTE for correct working of this operation for asymetrical
1382 ** kernels, the kernel needs to be applied in its reflected form.
1383 ** That is its values needs to be reversed.
1384 **
1385 ** NOTE: in normal Greyscale Morphology, the kernel value should
1386 ** be added to the real value, this is currently not done, due to
1387 ** the nature of the boolean kernels being used.
1388 **
1389 */
anthony29188a82010-01-22 10:12:34 +00001390 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001391 k_pixels = p;
1392 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001393 for (v=0; v < (long) kernel->height; v++) {
1394 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001395 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony5ef8e942010-05-11 06:51:12 +00001396 Maximize(max.red, (double) k_pixels[u].red);
1397 Maximize(max.green, (double) k_pixels[u].green);
1398 Maximize(max.blue, (double) k_pixels[u].blue);
1399 Maximize(max.opacity,
anthonyd37a5cb2010-05-07 06:37:03 +00001400 QuantumRange-(double) k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00001401 if ( image->colorspace == CMYKColorspace)
anthony5ef8e942010-05-11 06:51:12 +00001402 Maximize(max.index, (double) k_indexes[u]);
anthony602ab9b2010-01-05 08:06:50 +00001403 }
1404 k_pixels += image->columns+kernel->width;
1405 k_indexes += image->columns+kernel->width;
1406 }
anthony602ab9b2010-01-05 08:06:50 +00001407 break;
1408
anthony5ef8e942010-05-11 06:51:12 +00001409 case HitAndMissMorphology:
1410 case ThinningMorphology:
1411 case ThickenMorphology:
1412 /* Minimum of Foreground Pixel minus Maxumum of Background Pixels
1413 **
1414 ** NOTE that the kernel is not reflected for this operation,
1415 ** and consists of both foreground and background pixel
1416 ** neighbourhoods, 0.0 for background, and 1.0 for foreground
1417 ** with either Nan or 0.5 values for don't care.
1418 **
1419 ** Note that this can produce negative results, though really
1420 ** only a positive match has any real value.
1421 */
1422 k = kernel->values;
1423 k_pixels = p;
1424 k_indexes = p_indexes;
1425 for (v=0; v < (long) kernel->height; v++) {
1426 for (u=0; u < (long) kernel->width; u++, k++) {
1427 if ( IsNan(*k) ) continue;
1428 if ( (*k) > 0.7 )
1429 { /* minimim of foreground pixels */
1430 Minimize(min.red, (double) k_pixels[u].red);
1431 Minimize(min.green, (double) k_pixels[u].green);
1432 Minimize(min.blue, (double) k_pixels[u].blue);
1433 Minimize(min.opacity,
1434 QuantumRange-(double) k_pixels[u].opacity);
1435 if ( image->colorspace == CMYKColorspace)
1436 Minimize(min.index, (double) k_indexes[u]);
1437 }
1438 else if ( (*k) < 0.3 )
1439 { /* maximum of background pixels */
1440 Maximize(max.red, (double) k_pixels[u].red);
1441 Maximize(max.green, (double) k_pixels[u].green);
1442 Maximize(max.blue, (double) k_pixels[u].blue);
1443 Maximize(max.opacity,
1444 QuantumRange-(double) k_pixels[u].opacity);
1445 if ( image->colorspace == CMYKColorspace)
1446 Maximize(max.index, (double) k_indexes[u]);
1447 }
1448 }
1449 k_pixels += image->columns+kernel->width;
1450 k_indexes += image->columns+kernel->width;
1451 }
1452 /* Pattern Match only if min fg larger than min bg pixels */
1453 min.red -= max.red; Maximize( min.red, 0.0 );
1454 min.green -= max.green; Maximize( min.green, 0.0 );
1455 min.blue -= max.blue; Maximize( min.blue, 0.0 );
1456 min.opacity -= max.opacity; Maximize( min.opacity, 0.0 );
1457 min.index -= max.index; Maximize( min.index, 0.0 );
1458 break;
1459
anthony4fd27e22010-02-07 08:17:18 +00001460 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001461 /* Select Pixel with Minimum Intensity within kernel neighbourhood
1462 **
1463 ** WARNING: the intensity test fails for CMYK and does not
1464 ** take into account the moderating effect of teh alpha channel
1465 ** on the intensity.
1466 **
1467 ** NOTE that the kernel is not reflected for this operation!
1468 */
anthony602ab9b2010-01-05 08:06:50 +00001469 k = kernel->values;
1470 k_pixels = p;
1471 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001472 for (v=0; v < (long) kernel->height; v++) {
1473 for (u=0; u < (long) kernel->width; u++, k++) {
anthony602ab9b2010-01-05 08:06:50 +00001474 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony4fd27e22010-02-07 08:17:18 +00001475 if ( result.red == 0.0 ||
1476 PixelIntensity(&(k_pixels[u])) < PixelIntensity(q) ) {
1477 /* copy the whole pixel - no channel selection */
1478 *q = k_pixels[u];
1479 if ( result.red > 0.0 ) changed++;
1480 result.red = 1.0;
1481 }
anthony602ab9b2010-01-05 08:06:50 +00001482 }
1483 k_pixels += image->columns+kernel->width;
1484 k_indexes += image->columns+kernel->width;
1485 }
anthony602ab9b2010-01-05 08:06:50 +00001486 break;
1487
anthony83ba99b2010-01-24 08:48:15 +00001488 case DilateIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001489 /* Select Pixel with Maximum Intensity within kernel neighbourhood
1490 **
1491 ** WARNING: the intensity test fails for CMYK and does not
1492 ** take into account the moderating effect of teh alpha channel
1493 ** on the intensity.
1494 **
1495 ** NOTE for correct working of this operation for asymetrical
1496 ** kernels, the kernel needs to be applied in its reflected form.
1497 ** That is its values needs to be reversed.
1498 */
anthony29188a82010-01-22 10:12:34 +00001499 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001500 k_pixels = p;
1501 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001502 for (v=0; v < (long) kernel->height; v++) {
1503 for (u=0; u < (long) kernel->width; u++, k--) {
anthony29188a82010-01-22 10:12:34 +00001504 if ( IsNan(*k) || (*k) < 0.5 ) continue; /* boolean kernel */
1505 if ( result.red == 0.0 ||
1506 PixelIntensity(&(k_pixels[u])) > PixelIntensity(q) ) {
1507 /* copy the whole pixel - no channel selection */
1508 *q = k_pixels[u];
1509 if ( result.red > 0.0 ) changed++;
1510 result.red = 1.0;
1511 }
anthony602ab9b2010-01-05 08:06:50 +00001512 }
1513 k_pixels += image->columns+kernel->width;
1514 k_indexes += image->columns+kernel->width;
1515 }
anthony602ab9b2010-01-05 08:06:50 +00001516 break;
1517
anthony5ef8e942010-05-11 06:51:12 +00001518
anthony602ab9b2010-01-05 08:06:50 +00001519 case DistanceMorphology:
anthony930be612010-02-08 04:26:15 +00001520 /* Add kernel Value and select the minimum value found.
1521 ** The result is a iterative distance from edge of image shape.
1522 **
1523 ** All Distance Kernels are symetrical, but that may not always
1524 ** be the case. For example how about a distance from left edges?
1525 ** To work correctly with asymetrical kernels the reflected kernel
1526 ** needs to be applied.
anthony5ef8e942010-05-11 06:51:12 +00001527 **
1528 ** Actually this is really a GreyErode with a negative kernel!
1529 **
anthony930be612010-02-08 04:26:15 +00001530 */
anthony602ab9b2010-01-05 08:06:50 +00001531#if 0
anthony930be612010-02-08 04:26:15 +00001532 /* No need to do distance morphology if original value is zero
1533 ** Unfortunatally I have not been able to get this right
1534 ** when channel selection also becomes involved. -- Arrgghhh
1535 */
1536 if ( ((channel & RedChannel) == 0 && p[r].red == 0)
1537 || ((channel & GreenChannel) == 0 && p[r].green == 0)
1538 || ((channel & BlueChannel) == 0 && p[r].blue == 0)
1539 || ((channel & OpacityChannel) == 0 && p[r].opacity == 0)
1540 || (( (channel & IndexChannel) == 0
1541 || image->colorspace != CMYKColorspace
1542 ) && p_indexes[x] ==0 )
1543 )
1544 break;
anthony602ab9b2010-01-05 08:06:50 +00001545#endif
anthony29188a82010-01-22 10:12:34 +00001546 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001547 k_pixels = p;
1548 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001549 for (v=0; v < (long) kernel->height; v++) {
1550 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001551 if ( IsNan(*k) ) continue;
1552 Minimize(result.red, (*k)+k_pixels[u].red);
1553 Minimize(result.green, (*k)+k_pixels[u].green);
1554 Minimize(result.blue, (*k)+k_pixels[u].blue);
1555 Minimize(result.opacity, (*k)+QuantumRange-k_pixels[u].opacity);
1556 if ( image->colorspace == CMYKColorspace)
1557 Minimize(result.index, (*k)+k_indexes[u]);
1558 }
1559 k_pixels += image->columns+kernel->width;
1560 k_indexes += image->columns+kernel->width;
1561 }
anthony602ab9b2010-01-05 08:06:50 +00001562 break;
1563
1564 case UndefinedMorphology:
1565 default:
1566 break; /* Do nothing */
anthony83ba99b2010-01-24 08:48:15 +00001567 }
anthony5ef8e942010-05-11 06:51:12 +00001568 /* Final mathematics of results (combine with original image?)
1569 **
1570 ** NOTE: Difference Morphology operators Edge* and *Hat could also
1571 ** be done here but works better with iteration as a image difference
1572 ** in the controling function (below). Thicken and Thinning however
1573 ** should be done here so thay can be iterated correctly.
1574 */
1575 switch ( method ) {
1576 case HitAndMissMorphology:
1577 case ErodeMorphology:
1578 result = min; /* minimum of neighbourhood */
1579 break;
1580 case DilateMorphology:
1581 result = max; /* maximum of neighbourhood */
1582 break;
1583 case ThinningMorphology:
1584 /* subtract pattern match from original */
1585 result.red -= min.red;
1586 result.green -= min.green;
1587 result.blue -= min.blue;
1588 result.opacity -= min.opacity;
1589 result.index -= min.index;
1590 break;
1591 case ThickenMorphology:
1592 /* Union with original image (maximize) - or should this be + */
1593 Maximize( result.red, min.red );
1594 Maximize( result.green, min.green );
1595 Maximize( result.blue, min.blue );
1596 Maximize( result.opacity, min.opacity );
1597 Maximize( result.index, min.index );
1598 break;
1599 default:
1600 /* result directly calculated or assigned */
1601 break;
1602 }
1603 /* Assign the resulting pixel values - Clamping Result */
anthony83ba99b2010-01-24 08:48:15 +00001604 switch ( method ) {
1605 case UndefinedMorphology:
1606 case DilateIntensityMorphology:
1607 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001608 break; /* full pixel was directly assigned - not a channel method */
anthony83ba99b2010-01-24 08:48:15 +00001609 default:
anthony83ba99b2010-01-24 08:48:15 +00001610 if ((channel & RedChannel) != 0)
1611 q->red = ClampToQuantum(result.red);
1612 if ((channel & GreenChannel) != 0)
1613 q->green = ClampToQuantum(result.green);
1614 if ((channel & BlueChannel) != 0)
1615 q->blue = ClampToQuantum(result.blue);
1616 if ((channel & OpacityChannel) != 0
1617 && image->matte == MagickTrue )
1618 q->opacity = ClampToQuantum(QuantumRange-result.opacity);
1619 if ((channel & IndexChannel) != 0
1620 && image->colorspace == CMYKColorspace)
1621 q_indexes[x] = ClampToQuantum(result.index);
1622 break;
1623 }
anthony5ef8e942010-05-11 06:51:12 +00001624 /* Count up changed pixels */
anthony83ba99b2010-01-24 08:48:15 +00001625 if ( ( p[r].red != q->red )
1626 || ( p[r].green != q->green )
1627 || ( p[r].blue != q->blue )
1628 || ( p[r].opacity != q->opacity )
1629 || ( image->colorspace == CMYKColorspace &&
1630 p_indexes[r] != q_indexes[x] ) )
1631 changed++; /* The pixel had some value changed! */
anthony602ab9b2010-01-05 08:06:50 +00001632 p++;
1633 q++;
anthony83ba99b2010-01-24 08:48:15 +00001634 } /* x */
anthony602ab9b2010-01-05 08:06:50 +00001635 sync=SyncCacheViewAuthenticPixels(q_view,exception);
1636 if (sync == MagickFalse)
1637 status=MagickFalse;
1638 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1639 {
1640 MagickBooleanType
1641 proceed;
1642
1643#if defined(MAGICKCORE_OPENMP_SUPPORT)
1644 #pragma omp critical (MagickCore_MorphologyImage)
1645#endif
1646 proceed=SetImageProgress(image,MorphologyTag,progress++,image->rows);
1647 if (proceed == MagickFalse)
1648 status=MagickFalse;
1649 }
anthony83ba99b2010-01-24 08:48:15 +00001650 } /* y */
anthony602ab9b2010-01-05 08:06:50 +00001651 result_image->type=image->type;
1652 q_view=DestroyCacheView(q_view);
1653 p_view=DestroyCacheView(p_view);
cristy150989e2010-02-01 14:59:39 +00001654 return(status ? (unsigned long) changed : 0);
anthony602ab9b2010-01-05 08:06:50 +00001655}
1656
anthony4fd27e22010-02-07 08:17:18 +00001657
1658MagickExport Image *MorphologyImage(const Image *image, const MorphologyMethod
anthony930be612010-02-08 04:26:15 +00001659 method, const long iterations,const KernelInfo *kernel, ExceptionInfo
1660 *exception)
cristy2be15382010-01-21 02:38:03 +00001661{
1662 Image
1663 *morphology_image;
1664
1665 morphology_image=MorphologyImageChannel(image,DefaultChannels,method,
1666 iterations,kernel,exception);
1667 return(morphology_image);
1668}
1669
anthony4fd27e22010-02-07 08:17:18 +00001670
cristyef656912010-03-05 19:54:59 +00001671MagickExport Image *MorphologyImageChannel(const Image *image,
1672 const ChannelType channel,const MorphologyMethod method,
1673 const long iterations,const KernelInfo *kernel,ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001674{
cristy150989e2010-02-01 14:59:39 +00001675 long
1676 count;
anthony602ab9b2010-01-05 08:06:50 +00001677
1678 Image
1679 *new_image,
anthony4fd27e22010-02-07 08:17:18 +00001680 *old_image,
1681 *grad_image;
anthony602ab9b2010-01-05 08:06:50 +00001682
anthonycc6c8362010-01-25 04:14:01 +00001683 const char
1684 *artifact;
1685
cristy150989e2010-02-01 14:59:39 +00001686 unsigned long
1687 changed,
1688 limit;
1689
anthony4fd27e22010-02-07 08:17:18 +00001690 KernelInfo
1691 *curr_kernel;
1692
1693 MorphologyMethod
1694 curr_method;
1695
anthony602ab9b2010-01-05 08:06:50 +00001696 assert(image != (Image *) NULL);
1697 assert(image->signature == MagickSignature);
anthony4fd27e22010-02-07 08:17:18 +00001698 assert(kernel != (KernelInfo *) NULL);
1699 assert(kernel->signature == MagickSignature);
anthony602ab9b2010-01-05 08:06:50 +00001700 assert(exception != (ExceptionInfo *) NULL);
1701 assert(exception->signature == MagickSignature);
1702
anthony602ab9b2010-01-05 08:06:50 +00001703 if ( iterations == 0 )
1704 return((Image *)NULL); /* null operation - nothing to do! */
1705
1706 /* kernel must be valid at this point
1707 * (except maybe for posible future morphology methods like "Prune"
1708 */
cristy2be15382010-01-21 02:38:03 +00001709 assert(kernel != (KernelInfo *)NULL);
anthony602ab9b2010-01-05 08:06:50 +00001710
anthony4fd27e22010-02-07 08:17:18 +00001711 count = 0; /* interation count */
1712 changed = 1; /* if compound method assume image was changed */
anthony930be612010-02-08 04:26:15 +00001713 curr_kernel = (KernelInfo *)kernel; /* allow kernel and method */
1714 curr_method = method; /* to be changed as nessary */
anthony4fd27e22010-02-07 08:17:18 +00001715
cristy150989e2010-02-01 14:59:39 +00001716 limit = (unsigned long) iterations;
anthony602ab9b2010-01-05 08:06:50 +00001717 if ( iterations < 0 )
1718 limit = image->columns > image->rows ? image->columns : image->rows;
1719
anthony4fd27e22010-02-07 08:17:18 +00001720 /* Third-level morphology methods */
cristy5ee247a2010-02-12 15:42:34 +00001721 grad_image=(Image *) NULL;
anthony4fd27e22010-02-07 08:17:18 +00001722 switch( curr_method ) {
1723 case EdgeMorphology:
1724 grad_image = MorphologyImageChannel(image, channel,
1725 DilateMorphology, iterations, curr_kernel, exception);
1726 /* FALL-THRU */
1727 case EdgeInMorphology:
1728 curr_method = ErodeMorphology;
anthony602ab9b2010-01-05 08:06:50 +00001729 break;
anthony4fd27e22010-02-07 08:17:18 +00001730 case EdgeOutMorphology:
1731 curr_method = DilateMorphology;
anthony602ab9b2010-01-05 08:06:50 +00001732 break;
anthony4fd27e22010-02-07 08:17:18 +00001733 case TopHatMorphology:
1734 curr_method = OpenMorphology;
1735 break;
1736 case BottomHatMorphology:
1737 curr_method = CloseMorphology;
1738 break;
1739 default:
anthony930be612010-02-08 04:26:15 +00001740 break; /* not a third-level method */
anthony4fd27e22010-02-07 08:17:18 +00001741 }
1742
1743 /* Second-level morphology methods */
1744 switch( curr_method ) {
anthony930be612010-02-08 04:26:15 +00001745 case OpenMorphology:
1746 /* Open is a Erode then a Dilate without reflection */
anthony4fd27e22010-02-07 08:17:18 +00001747 new_image = MorphologyImageChannel(image, channel,
1748 ErodeMorphology, iterations, curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001749 if (new_image == (Image *) NULL)
1750 return((Image *) NULL);
anthony4fd27e22010-02-07 08:17:18 +00001751 curr_method = DilateMorphology;
1752 break;
anthony602ab9b2010-01-05 08:06:50 +00001753 case OpenIntensityMorphology:
anthony4fd27e22010-02-07 08:17:18 +00001754 new_image = MorphologyImageChannel(image, channel,
1755 ErodeIntensityMorphology, iterations, curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001756 if (new_image == (Image *) NULL)
1757 return((Image *) NULL);
anthony4fd27e22010-02-07 08:17:18 +00001758 curr_method = DilateIntensityMorphology;
1759 break;
anthony930be612010-02-08 04:26:15 +00001760
1761 case CloseMorphology:
1762 /* Close is a Dilate then Erode using reflected kernel */
1763 /* A reflected kernel is needed for a Close */
1764 if ( curr_kernel == kernel )
1765 curr_kernel = CloneKernelInfo(kernel);
1766 RotateKernelInfo(curr_kernel,180);
1767 new_image = MorphologyImageChannel(image, channel,
1768 DilateMorphology, iterations, curr_kernel, exception);
1769 if (new_image == (Image *) NULL)
1770 return((Image *) NULL);
1771 curr_method = ErodeMorphology;
1772 break;
anthony4fd27e22010-02-07 08:17:18 +00001773 case CloseIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001774 /* A reflected kernel is needed for a Close */
1775 if ( curr_kernel == kernel )
1776 curr_kernel = CloneKernelInfo(kernel);
anthony4fd27e22010-02-07 08:17:18 +00001777 RotateKernelInfo(curr_kernel,180);
1778 new_image = MorphologyImageChannel(image, channel,
1779 DilateIntensityMorphology, iterations, curr_kernel, exception);
1780 if (new_image == (Image *) NULL)
1781 return((Image *) NULL);
1782 curr_method = ErodeIntensityMorphology;
anthony602ab9b2010-01-05 08:06:50 +00001783 break;
1784
anthony930be612010-02-08 04:26:15 +00001785 case CorrelateMorphology:
1786 /* A Correlation is actually a Convolution with a reflected kernel.
1787 ** However a Convolution is a weighted sum with a reflected kernel.
1788 ** It may seem stange to convert a Correlation into a Convolution
1789 ** as the Correleation is the simplier method, but Convolution is
1790 ** much more commonly used, and it makes sense to implement it directly
1791 ** so as to avoid the need to duplicate the kernel when it is not
1792 ** required (which is typically the default).
1793 */
1794 if ( curr_kernel == kernel )
1795 curr_kernel = CloneKernelInfo(kernel);
1796 RotateKernelInfo(curr_kernel,180);
1797 curr_method = ConvolveMorphology;
1798 /* FALL-THRU into Correlate (weigthed sum without reflection) */
1799
anthonyc94cdb02010-01-06 08:15:29 +00001800 case ConvolveMorphology:
anthony4fd27e22010-02-07 08:17:18 +00001801 /* Scale or Normalize kernel, according to user wishes
anthony930be612010-02-08 04:26:15 +00001802 ** before using it for the Convolve/Correlate method.
1803 **
1804 ** FUTURE: provide some way for internal functions to disable
1805 ** user bias and scaling effects.
anthonycc6c8362010-01-25 04:14:01 +00001806 */
1807 artifact = GetImageArtifact(image,"convolve:scale");
anthony4fd27e22010-02-07 08:17:18 +00001808 if ( artifact != (char *)NULL ) {
cristy19eb6412010-04-23 14:42:29 +00001809 GeometryFlags
anthony999bb2c2010-02-18 12:38:01 +00001810 flags;
1811 GeometryInfo
1812 args;
1813
anthony930be612010-02-08 04:26:15 +00001814 if ( curr_kernel == kernel )
1815 curr_kernel = CloneKernelInfo(kernel);
anthony999bb2c2010-02-18 12:38:01 +00001816
1817 args.rho = 1.0;
cristy19eb6412010-04-23 14:42:29 +00001818 flags = (GeometryFlags) ParseGeometry(artifact, &args);
anthony999bb2c2010-02-18 12:38:01 +00001819 ScaleKernelInfo(curr_kernel, args.rho, flags);
anthony4fd27e22010-02-07 08:17:18 +00001820 }
anthony930be612010-02-08 04:26:15 +00001821 /* FALL-THRU to do the first, and typically the only iteration */
anthony4fd27e22010-02-07 08:17:18 +00001822
anthony602ab9b2010-01-05 08:06:50 +00001823 default:
anthony930be612010-02-08 04:26:15 +00001824 /* Do a single iteration using the Low-Level Morphology method!
1825 ** This ensures a "new_image" has been generated, but allows us to skip
1826 ** the creation of 'old_image' if no more iterations are needed.
1827 **
1828 ** The "curr_method" should also be set to a low-level method that is
1829 ** understood by the MorphologyApply() internal function.
anthony602ab9b2010-01-05 08:06:50 +00001830 */
1831 new_image=CloneImage(image,0,0,MagickTrue,exception);
1832 if (new_image == (Image *) NULL)
1833 return((Image *) NULL);
1834 if (SetImageStorageClass(new_image,DirectClass) == MagickFalse)
1835 {
1836 InheritException(exception,&new_image->exception);
1837 new_image=DestroyImage(new_image);
1838 return((Image *) NULL);
1839 }
anthony4fd27e22010-02-07 08:17:18 +00001840 changed = MorphologyApply(image,new_image,curr_method,channel,curr_kernel,
anthony602ab9b2010-01-05 08:06:50 +00001841 exception);
1842 count++;
1843 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
cristy150989e2010-02-01 14:59:39 +00001844 fprintf(stderr, "Morphology %s:%ld => Changed %lu\n",
anthony4fd27e22010-02-07 08:17:18 +00001845 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
anthony602ab9b2010-01-05 08:06:50 +00001846 count, changed);
anthony930be612010-02-08 04:26:15 +00001847 break;
anthony602ab9b2010-01-05 08:06:50 +00001848 }
1849
anthony930be612010-02-08 04:26:15 +00001850 /* At this point the "curr_method" should not only be set to a low-level
1851 ** method that is understood by the MorphologyApply() internal function,
1852 ** but "new_image" should now be defined, as the image to apply the
1853 ** "curr_method" to.
1854 */
1855
1856 /* Repeat the low-level morphology until count or no change reached */
cristy150989e2010-02-01 14:59:39 +00001857 if ( count < (long) limit && changed > 0 ) {
anthony602ab9b2010-01-05 08:06:50 +00001858 old_image = CloneImage(new_image,0,0,MagickTrue,exception);
1859 if (old_image == (Image *) NULL)
1860 return(DestroyImage(new_image));
1861 if (SetImageStorageClass(old_image,DirectClass) == MagickFalse)
1862 {
1863 InheritException(exception,&old_image->exception);
1864 old_image=DestroyImage(old_image);
1865 return(DestroyImage(new_image));
1866 }
cristy150989e2010-02-01 14:59:39 +00001867 while( count < (long) limit && changed != 0 )
anthony602ab9b2010-01-05 08:06:50 +00001868 {
1869 Image *tmp = old_image;
1870 old_image = new_image;
1871 new_image = tmp;
anthony4fd27e22010-02-07 08:17:18 +00001872 changed = MorphologyApply(old_image,new_image,curr_method,channel,
1873 curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001874 count++;
1875 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
cristy150989e2010-02-01 14:59:39 +00001876 fprintf(stderr, "Morphology %s:%ld => Changed %lu\n",
anthony4fd27e22010-02-07 08:17:18 +00001877 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
anthony602ab9b2010-01-05 08:06:50 +00001878 count, changed);
1879 }
cristy150989e2010-02-01 14:59:39 +00001880 old_image=DestroyImage(old_image);
anthony602ab9b2010-01-05 08:06:50 +00001881 }
anthony930be612010-02-08 04:26:15 +00001882
1883 /* We are finished with kernel - destroy it if we made a clone */
anthony4fd27e22010-02-07 08:17:18 +00001884 if ( curr_kernel != kernel )
1885 curr_kernel=DestroyKernelInfo(curr_kernel);
1886
anthony7d10d742010-05-06 07:05:29 +00001887 /* Third-level Subtractive methods post-processing
1888 **
1889 ** The removal of any 'Sync' channel flag in the Image Compositon below
1890 ** ensures the compose method is applied in a purely mathematical way, only
1891 ** the selected channels, without any normal 'alpha blending' normally
1892 ** associated with the compose method.
1893 **
1894 ** Note "method" here is the 'original' morphological method, and not the
1895 ** 'current' morphological method used above to generate "new_image".
1896 */
anthony4fd27e22010-02-07 08:17:18 +00001897 switch( method ) {
1898 case EdgeOutMorphology:
1899 case EdgeInMorphology:
1900 case TopHatMorphology:
1901 case BottomHatMorphology:
anthony930be612010-02-08 04:26:15 +00001902 /* Get Difference relative to the original image */
cristyaeb2cbc2010-05-07 13:28:58 +00001903 (void) CompositeImageChannel(new_image, (ChannelType) (channel & ~SyncChannels),
anthony7d10d742010-05-06 07:05:29 +00001904 DifferenceCompositeOp, image, 0, 0);
anthony4fd27e22010-02-07 08:17:18 +00001905 break;
anthony7d10d742010-05-06 07:05:29 +00001906 case EdgeMorphology:
1907 /* Difference the Eroded image from the saved Dilated image */
cristyaeb2cbc2010-05-07 13:28:58 +00001908 (void) CompositeImageChannel(new_image, (ChannelType) (channel & ~SyncChannels),
anthony7d10d742010-05-06 07:05:29 +00001909 DifferenceCompositeOp, grad_image, 0, 0);
anthony4fd27e22010-02-07 08:17:18 +00001910 grad_image=DestroyImage(grad_image);
1911 break;
1912 default:
1913 break;
1914 }
anthony602ab9b2010-01-05 08:06:50 +00001915
1916 return(new_image);
1917}
anthony83ba99b2010-01-24 08:48:15 +00001918
1919/*
1920%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1921% %
1922% %
1923% %
anthony4fd27e22010-02-07 08:17:18 +00001924+ R o t a t e K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00001925% %
1926% %
1927% %
1928%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1929%
anthony4fd27e22010-02-07 08:17:18 +00001930% RotateKernelInfo() rotates the kernel by the angle given. Currently it is
anthony83ba99b2010-01-24 08:48:15 +00001931% restricted to 90 degree angles, but this may be improved in the future.
1932%
anthony4fd27e22010-02-07 08:17:18 +00001933% The format of the RotateKernelInfo method is:
anthony83ba99b2010-01-24 08:48:15 +00001934%
anthony4fd27e22010-02-07 08:17:18 +00001935% void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00001936%
1937% A description of each parameter follows:
1938%
1939% o kernel: the Morphology/Convolution kernel
1940%
1941% o angle: angle to rotate in degrees
1942%
anthonyc4c86e02010-01-27 09:30:32 +00001943% This function is only internel to this module, as it is not finalized,
1944% especially with regard to non-orthogonal angles, and rotation of larger
1945% 2D kernels.
anthony83ba99b2010-01-24 08:48:15 +00001946*/
anthony4fd27e22010-02-07 08:17:18 +00001947static void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00001948{
1949 /* WARNING: Currently assumes the kernel (rightly) is horizontally symetrical
1950 **
1951 ** TODO: expand beyond simple 90 degree rotates, flips and flops
1952 */
1953
1954 /* Modulus the angle */
1955 angle = fmod(angle, 360.0);
1956 if ( angle < 0 )
1957 angle += 360.0;
1958
1959 if ( 315.0 < angle || angle <= 45.0 )
1960 return; /* no change! - At least at this time */
1961
1962 switch (kernel->type) {
1963 /* These built-in kernels are cylindrical kernels, rotating is useless */
1964 case GaussianKernel:
1965 case LaplacianKernel:
1966 case LOGKernel:
1967 case DOGKernel:
1968 case DiskKernel:
1969 case ChebyshevKernel:
1970 case ManhattenKernel:
1971 case EuclideanKernel:
1972 return;
1973
1974 /* These may be rotatable at non-90 angles in the future */
1975 /* but simply rotating them in multiples of 90 degrees is useless */
1976 case SquareKernel:
1977 case DiamondKernel:
1978 case PlusKernel:
1979 return;
1980
1981 /* These only allows a +/-90 degree rotation (by transpose) */
1982 /* A 180 degree rotation is useless */
1983 case BlurKernel:
1984 case RectangleKernel:
1985 if ( 135.0 < angle && angle <= 225.0 )
1986 return;
1987 if ( 225.0 < angle && angle <= 315.0 )
1988 angle -= 180;
1989 break;
1990
1991 /* these are freely rotatable in 90 degree units */
1992 case CometKernel:
1993 case UndefinedKernel:
1994 case UserDefinedKernel:
1995 break;
1996 }
1997 if ( 135.0 < angle && angle <= 225.0 )
1998 {
1999 /* For a 180 degree rotation - also know as a reflection */
2000 /* This is actually a very very common operation! */
2001 /* Basically all that is needed is a reversal of the kernel data! */
2002 unsigned long
2003 i,j;
2004 register double
2005 *k,t;
2006
2007 k=kernel->values;
2008 for ( i=0, j=kernel->width*kernel->height-1; i<j; i++, j--)
2009 t=k[i], k[i]=k[j], k[j]=t;
2010
anthony930be612010-02-08 04:26:15 +00002011 kernel->x = (long) kernel->width - kernel->x - 1;
2012 kernel->y = (long) kernel->height - kernel->y - 1;
anthony83ba99b2010-01-24 08:48:15 +00002013 angle = fmod(angle+180.0, 360.0);
2014 }
2015 if ( 45.0 < angle && angle <= 135.0 )
2016 { /* Do a transpose and a flop, of the image, which results in a 90
2017 * degree rotation using two mirror operations.
2018 *
2019 * WARNING: this assumes the original image was a 1 dimentional image
2020 * but currently that is the only built-ins it is applied to.
2021 */
cristy150989e2010-02-01 14:59:39 +00002022 long
anthony83ba99b2010-01-24 08:48:15 +00002023 t;
cristy150989e2010-02-01 14:59:39 +00002024 t = (long) kernel->width;
anthony83ba99b2010-01-24 08:48:15 +00002025 kernel->width = kernel->height;
cristy150989e2010-02-01 14:59:39 +00002026 kernel->height = (unsigned long) t;
cristyc99304f2010-02-01 15:26:27 +00002027 t = kernel->x;
2028 kernel->x = kernel->y;
2029 kernel->y = t;
anthony83ba99b2010-01-24 08:48:15 +00002030 angle = fmod(450.0 - angle, 360.0);
2031 }
2032 /* At this point angle should be between -45 (315) and +45 degrees
2033 * In the future some form of non-orthogonal angled rotates could be
2034 * performed here, posibily with a linear kernel restriction.
2035 */
2036
2037#if 0
2038 Not currently in use!
2039 { /* Do a flop, this assumes kernel is horizontally symetrical.
2040 * Each row of the kernel needs to be reversed!
2041 */
2042 unsigned long
2043 y;
cristy150989e2010-02-01 14:59:39 +00002044 register long
anthony83ba99b2010-01-24 08:48:15 +00002045 x,r;
2046 register double
2047 *k,t;
2048
2049 for ( y=0, k=kernel->values; y < kernel->height; y++, k+=kernel->width)
2050 for ( x=0, r=kernel->width-1; x<kernel->width/2; x++, r--)
2051 t=k[x], k[x]=k[r], k[r]=t;
2052
cristyc99304f2010-02-01 15:26:27 +00002053 kernel->x = kernel->width - kernel->x - 1;
anthony83ba99b2010-01-24 08:48:15 +00002054 angle = fmod(angle+180.0, 360.0);
2055 }
2056#endif
2057 return;
2058}
2059
2060/*
2061%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2062% %
2063% %
2064% %
cristy6771f1e2010-03-05 19:43:39 +00002065% S c a l e K e r n e l I n f o %
anthonycc6c8362010-01-25 04:14:01 +00002066% %
2067% %
2068% %
2069%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2070%
anthony999bb2c2010-02-18 12:38:01 +00002071% ScaleKernelInfo() scales the kernel by the given amount, with or without
2072% normalization of the sum of the kernel values.
anthonycc6c8362010-01-25 04:14:01 +00002073%
anthony999bb2c2010-02-18 12:38:01 +00002074% By default (no flags given) the values within the kernel is scaled
2075% according the given scaling factor.
anthonycc6c8362010-01-25 04:14:01 +00002076%
anthony999bb2c2010-02-18 12:38:01 +00002077% If any 'normalize_flags' are given the kernel will be normalized and then
2078% further scaled by the scaleing factor value given. A 'PercentValue' flag
2079% will cause the given scaling factor to be divided by one hundred percent.
2080%
2081% Kernel normalization ('normalize_flags' given) is designed to ensure that
2082% any use of the kernel scaling factor with 'Convolve' or 'Correlate'
2083% morphology methods will fall into -1.0 to +1.0 range. Note however that
2084% for non-HDRI versions of IM this may cause images to have any negative
2085% results clipped, unless some 'clip' any negative output from 'Convolve'
2086% with the use of some kernels.
2087%
2088% More specifically. Kernels which only contain positive values (such as a
2089% 'Gaussian' kernel) will be scaled so that those values sum to +1.0,
2090% ensuring a 0.0 to +1.0 convolution output range for non-HDRI images.
2091%
2092% For Kernels that contain some negative values, (such as 'Sharpen' kernels)
2093% the kernel will be scaled by the absolute of the sum of kernel values, so
2094% that it will generally fall within the +/- 1.0 range.
2095%
2096% For kernels whose values sum to zero, (such as 'Laplician' kernels) kernel
2097% will be scaled by just the sum of the postive values, so that its output
2098% range will again fall into the +/- 1.0 range.
2099%
2100% For special kernels designed for locating shapes using 'Correlate', (often
2101% only containing +1 and -1 values, representing foreground/brackground
2102% matching) a special normalization method is provided to scale the positive
2103% values seperatally to those of the negative values, so the kernel will be
2104% forced to become a zero-sum kernel better suited to such searches.
2105%
2106% WARNING: Correct normalization of the kernal assumes that the '*_range'
2107% attributes within the kernel structure have been correctly set during the
2108% kernels creation.
2109%
2110% NOTE: The values used for 'normalize_flags' have been selected specifically
2111% to match the use of geometry options, so that '!' means NormalizeValue, '^'
2112% means CorrelateNormalizeValue, and '%' means PercentValue. All other
2113% GeometryFlags values are ignored.
anthonycc6c8362010-01-25 04:14:01 +00002114%
anthony4fd27e22010-02-07 08:17:18 +00002115% The format of the ScaleKernelInfo method is:
anthonycc6c8362010-01-25 04:14:01 +00002116%
anthony999bb2c2010-02-18 12:38:01 +00002117% void ScaleKernelInfo(KernelInfo *kernel, const double scaling_factor,
2118% const MagickStatusType normalize_flags )
anthonycc6c8362010-01-25 04:14:01 +00002119%
2120% A description of each parameter follows:
2121%
2122% o kernel: the Morphology/Convolution kernel
2123%
anthony999bb2c2010-02-18 12:38:01 +00002124% o scaling_factor:
2125% multiply all values (after normalization) by this factor if not
2126% zero. If the kernel is normalized regardless of any flags.
2127%
2128% o normalize_flags:
2129% GeometryFlags defining normalization method to use.
2130% specifically: NormalizeValue, CorrelateNormalizeValue,
2131% and/or PercentValue
anthonycc6c8362010-01-25 04:14:01 +00002132%
anthonyc4c86e02010-01-27 09:30:32 +00002133% This function is internal to this module only at this time, but can be
2134% exported to other modules if needed.
anthonycc6c8362010-01-25 04:14:01 +00002135*/
cristy6771f1e2010-03-05 19:43:39 +00002136MagickExport void ScaleKernelInfo(KernelInfo *kernel,
2137 const double scaling_factor,const GeometryFlags normalize_flags)
anthonycc6c8362010-01-25 04:14:01 +00002138{
cristy150989e2010-02-01 14:59:39 +00002139 register long
anthonycc6c8362010-01-25 04:14:01 +00002140 i;
2141
anthony999bb2c2010-02-18 12:38:01 +00002142 register double
2143 pos_scale,
2144 neg_scale;
2145
2146 pos_scale = 1.0;
2147 if ( (normalize_flags&NormalizeValue) != 0 ) {
2148 /* normalize kernel appropriately */
2149 if ( fabs(kernel->positive_range + kernel->negative_range) > MagickEpsilon )
2150 pos_scale = fabs(kernel->positive_range + kernel->negative_range);
anthonycc6c8362010-01-25 04:14:01 +00002151 else
anthony999bb2c2010-02-18 12:38:01 +00002152 pos_scale = kernel->positive_range; /* special zero-summing kernel */
2153 }
2154 /* force kernel into being a normalized zero-summing kernel */
2155 if ( (normalize_flags&CorrelateNormalizeValue) != 0 ) {
2156 pos_scale = ( fabs(kernel->positive_range) > MagickEpsilon )
2157 ? kernel->positive_range : 1.0;
2158 neg_scale = ( fabs(kernel->negative_range) > MagickEpsilon )
2159 ? -kernel->negative_range : 1.0;
2160 }
2161 else
2162 neg_scale = pos_scale;
2163
2164 /* finialize scaling_factor for positive and negative components */
2165 pos_scale = scaling_factor/pos_scale;
2166 neg_scale = scaling_factor/neg_scale;
2167 if ( (normalize_flags&PercentValue) != 0 ) {
2168 pos_scale /= 100.0;
2169 neg_scale /= 100.0;
anthonycc6c8362010-01-25 04:14:01 +00002170 }
2171
cristy150989e2010-02-01 14:59:39 +00002172 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00002173 if ( ! IsNan(kernel->values[i]) )
anthony999bb2c2010-02-18 12:38:01 +00002174 kernel->values[i] *= (kernel->values[i] >= 0) ? pos_scale : neg_scale;
anthonycc6c8362010-01-25 04:14:01 +00002175
anthony999bb2c2010-02-18 12:38:01 +00002176 /* convolution output range */
2177 kernel->positive_range *= pos_scale;
2178 kernel->negative_range *= neg_scale;
2179 /* maximum and minimum values in kernel */
2180 kernel->maximum *= (kernel->maximum >= 0.0) ? pos_scale : neg_scale;
2181 kernel->minimum *= (kernel->minimum >= 0.0) ? pos_scale : neg_scale;
2182
2183 /* swap kernel settings if user scaling factor is negative */
2184 if ( scaling_factor < MagickEpsilon ) {
2185 double t;
2186 t = kernel->positive_range;
2187 kernel->positive_range = kernel->negative_range;
2188 kernel->negative_range = t;
2189 t = kernel->maximum;
2190 kernel->maximum = kernel->minimum;
2191 kernel->minimum = 1;
2192 }
anthonycc6c8362010-01-25 04:14:01 +00002193
2194 return;
2195}
2196
2197/*
2198%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2199% %
2200% %
2201% %
anthony4fd27e22010-02-07 08:17:18 +00002202+ S h o w K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00002203% %
2204% %
2205% %
2206%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2207%
anthony4fd27e22010-02-07 08:17:18 +00002208% ShowKernelInfo() outputs the details of the given kernel defination to
2209% standard error, generally due to a users 'showkernel' option request.
anthony83ba99b2010-01-24 08:48:15 +00002210%
2211% The format of the ShowKernel method is:
2212%
anthony4fd27e22010-02-07 08:17:18 +00002213% void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00002214%
2215% A description of each parameter follows:
2216%
2217% o kernel: the Morphology/Convolution kernel
2218%
anthonyc4c86e02010-01-27 09:30:32 +00002219% This function is internal to this module only at this time. That may change
2220% in the future.
anthony83ba99b2010-01-24 08:48:15 +00002221*/
anthony4fd27e22010-02-07 08:17:18 +00002222MagickExport void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00002223{
cristy150989e2010-02-01 14:59:39 +00002224 long
anthony83ba99b2010-01-24 08:48:15 +00002225 i, u, v;
2226
2227 fprintf(stderr,
anthonycc6c8362010-01-25 04:14:01 +00002228 "Kernel \"%s\" of size %lux%lu%+ld%+ld with values from %.*lg to %.*lg\n",
anthony83ba99b2010-01-24 08:48:15 +00002229 MagickOptionToMnemonic(MagickKernelOptions, kernel->type),
2230 kernel->width, kernel->height,
cristyc99304f2010-02-01 15:26:27 +00002231 kernel->x, kernel->y,
2232 GetMagickPrecision(), kernel->minimum,
2233 GetMagickPrecision(), kernel->maximum);
anthonycc6c8362010-01-25 04:14:01 +00002234 fprintf(stderr, "Forming convolution output range from %.*lg to %.*lg%s\n",
cristyc99304f2010-02-01 15:26:27 +00002235 GetMagickPrecision(), kernel->negative_range,
2236 GetMagickPrecision(), kernel->positive_range,
anthonycc6c8362010-01-25 04:14:01 +00002237 /*kernel->normalized == MagickTrue ? " (normalized)" : */ "" );
cristy150989e2010-02-01 14:59:39 +00002238 for (i=v=0; v < (long) kernel->height; v++) {
anthony83ba99b2010-01-24 08:48:15 +00002239 fprintf(stderr,"%2ld:",v);
cristy150989e2010-02-01 14:59:39 +00002240 for (u=0; u < (long) kernel->width; u++, i++)
anthony83ba99b2010-01-24 08:48:15 +00002241 if ( IsNan(kernel->values[i]) )
2242 fprintf(stderr," %*s", GetMagickPrecision()+2, "nan");
2243 else
2244 fprintf(stderr," %*.*lg", GetMagickPrecision()+2,
2245 GetMagickPrecision(), kernel->values[i]);
2246 fprintf(stderr,"\n");
2247 }
2248}
anthonycc6c8362010-01-25 04:14:01 +00002249
2250/*
2251%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2252% %
2253% %
2254% %
anthony4fd27e22010-02-07 08:17:18 +00002255+ Z e r o K e r n e l N a n s %
anthonycc6c8362010-01-25 04:14:01 +00002256% %
2257% %
2258% %
2259%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2260%
2261% ZeroKernelNans() replaces any special 'nan' value that may be present in
2262% the kernel with a zero value. This is typically done when the kernel will
2263% be used in special hardware (GPU) convolution processors, to simply
2264% matters.
2265%
2266% The format of the ZeroKernelNans method is:
2267%
2268% voidZeroKernelNans (KernelInfo *kernel)
2269%
2270% A description of each parameter follows:
2271%
2272% o kernel: the Morphology/Convolution kernel
2273%
2274% FUTURE: return the information in a string for API usage.
2275*/
anthonyc4c86e02010-01-27 09:30:32 +00002276MagickExport void ZeroKernelNans(KernelInfo *kernel)
anthonycc6c8362010-01-25 04:14:01 +00002277{
cristy150989e2010-02-01 14:59:39 +00002278 register long
anthonycc6c8362010-01-25 04:14:01 +00002279 i;
2280
cristy150989e2010-02-01 14:59:39 +00002281 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00002282 if ( IsNan(kernel->values[i]) )
2283 kernel->values[i] = 0.0;
2284
2285 return;
2286}