blob: b5b77198ba4e908d7a00fbd368139db0b4492c65 [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%
anthony1b2bc0a2010-05-12 05:25:22 +000036% Morpology is the the application of various kernels, of any size and even
anthony602ab9b2010-01-05 08:06:50 +000037% 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
anthony1b2bc0a2010-05-12 05:25:22 +000085 These are used a Kernel value of NaN means that that kernel position is not
cristya29d45f2010-03-05 21:14:54 +000086 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%
anthony7a01dcf2010-05-11 12:25:52 +0000145% The returned kernel should be freed using the DestroyKernelInfo() when you
146% are finished with it. Do not free this memory yourself.
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 ..."
anthony1b2bc0a2010-05-12 05:25:22 +0000155% a kernel of size W by H, with W*H floating point numbers following.
anthony602ab9b2010-01-05 08:06:50 +0000156% 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%
anthony7a01dcf2010-05-11 12:25:52 +0000172% You can define a 'list of kernels' which can be used by some morphology
173% operators A list is defined as a semi-colon seperated list kernels.
174%
anthonydbc89892010-05-12 07:05:27 +0000175% " kernel ; kernel ; kernel ; "
anthony7a01dcf2010-05-11 12:25:52 +0000176%
anthonydbc89892010-05-12 07:05:27 +0000177% Extra ';' characters are simply ignored.
anthony7a01dcf2010-05-11 12:25:52 +0000178%
anthony602ab9b2010-01-05 08:06:50 +0000179% The format of the AcquireKernal method is:
180%
cristy2be15382010-01-21 02:38:03 +0000181% KernelInfo *AcquireKernelInfo(const char *kernel_string)
anthony602ab9b2010-01-05 08:06:50 +0000182%
183% A description of each parameter follows:
184%
185% o kernel_string: the Morphology/Convolution kernel wanted.
186%
187*/
188
anthonyc84dce52010-05-07 05:42:23 +0000189/* This was separated so that it could be used as a separate
anthony5ef8e942010-05-11 06:51:12 +0000190** array input handling function, such as for -color-matrix
anthonyc84dce52010-05-07 05:42:23 +0000191*/
anthony5ef8e942010-05-11 06:51:12 +0000192static KernelInfo *ParseKernelArray(const char *kernel_string)
anthony602ab9b2010-01-05 08:06:50 +0000193{
cristy2be15382010-01-21 02:38:03 +0000194 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000195 *kernel;
196
197 char
198 token[MaxTextExtent];
199
anthony602ab9b2010-01-05 08:06:50 +0000200 const char
anthony5ef8e942010-05-11 06:51:12 +0000201 *p,
202 *end;
anthony602ab9b2010-01-05 08:06:50 +0000203
anthonyc84dce52010-05-07 05:42:23 +0000204 register long
205 i;
anthony602ab9b2010-01-05 08:06:50 +0000206
anthony29188a82010-01-22 10:12:34 +0000207 double
208 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
209
cristy2be15382010-01-21 02:38:03 +0000210 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
211 if (kernel == (KernelInfo *)NULL)
anthony602ab9b2010-01-05 08:06:50 +0000212 return(kernel);
213 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
anthony7a01dcf2010-05-11 12:25:52 +0000214 kernel->minimum = kernel->maximum = 0.0;
215 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000216 kernel->type = UserDefinedKernel;
anthony7a01dcf2010-05-11 12:25:52 +0000217 kernel->next = (KernelInfo *) NULL;
cristyd43a46b2010-01-21 02:13:41 +0000218 kernel->signature = MagickSignature;
anthony602ab9b2010-01-05 08:06:50 +0000219
anthony5ef8e942010-05-11 06:51:12 +0000220 /* find end of this specific kernel definition string */
221 end = strchr(kernel_string, ';');
222 if ( end == (char *) NULL )
223 end = strchr(kernel_string, '\0');
224
anthony602ab9b2010-01-05 08:06:50 +0000225 /* Has a ':' in argument - New user kernel specification */
226 p = strchr(kernel_string, ':');
anthony5ef8e942010-05-11 06:51:12 +0000227 if ( p != (char *) NULL && p < end)
anthony602ab9b2010-01-05 08:06:50 +0000228 {
anthonyc84dce52010-05-07 05:42:23 +0000229 MagickStatusType
230 flags;
231
232 GeometryInfo
233 args;
234
anthony602ab9b2010-01-05 08:06:50 +0000235 /* ParseGeometry() needs the geometry separated! -- Arrgghh */
cristy150989e2010-02-01 14:59:39 +0000236 memcpy(token, kernel_string, (size_t) (p-kernel_string));
anthony602ab9b2010-01-05 08:06:50 +0000237 token[p-kernel_string] = '\0';
anthonyc84dce52010-05-07 05:42:23 +0000238 SetGeometryInfo(&args);
anthony602ab9b2010-01-05 08:06:50 +0000239 flags = ParseGeometry(token, &args);
anthony602ab9b2010-01-05 08:06:50 +0000240
anthony29188a82010-01-22 10:12:34 +0000241 /* Size handling and checks of geometry settings */
anthony602ab9b2010-01-05 08:06:50 +0000242 if ( (flags & WidthValue) == 0 ) /* if no width then */
243 args.rho = args.sigma; /* then width = height */
244 if ( args.rho < 1.0 ) /* if width too small */
245 args.rho = 1.0; /* then width = 1 */
246 if ( args.sigma < 1.0 ) /* if height too small */
247 args.sigma = args.rho; /* then height = width */
248 kernel->width = (unsigned long)args.rho;
249 kernel->height = (unsigned long)args.sigma;
250
251 /* Offset Handling and Checks */
252 if ( args.xi < 0.0 || args.psi < 0.0 )
anthony83ba99b2010-01-24 08:48:15 +0000253 return(DestroyKernelInfo(kernel));
cristyc99304f2010-02-01 15:26:27 +0000254 kernel->x = ((flags & XValue)!=0) ? (long)args.xi
cristy150989e2010-02-01 14:59:39 +0000255 : (long) (kernel->width-1)/2;
cristyc99304f2010-02-01 15:26:27 +0000256 kernel->y = ((flags & YValue)!=0) ? (long)args.psi
cristy150989e2010-02-01 14:59:39 +0000257 : (long) (kernel->height-1)/2;
cristyc99304f2010-02-01 15:26:27 +0000258 if ( kernel->x >= (long) kernel->width ||
259 kernel->y >= (long) kernel->height )
anthony83ba99b2010-01-24 08:48:15 +0000260 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000261
262 p++; /* advance beyond the ':' */
263 }
264 else
anthonyc84dce52010-05-07 05:42:23 +0000265 { /* ELSE - Old old specification, forming odd-square kernel */
anthony602ab9b2010-01-05 08:06:50 +0000266 /* count up number of values given */
267 p=(const char *) kernel_string;
cristya699b172010-01-06 16:48:49 +0000268 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
anthony29188a82010-01-22 10:12:34 +0000269 p++; /* ignore "'" chars for convolve filter usage - Cristy */
anthony5ef8e942010-05-11 06:51:12 +0000270 for (i=0; p < end; i++)
anthony602ab9b2010-01-05 08:06:50 +0000271 {
272 GetMagickToken(p,&p,token);
273 if (*token == ',')
274 GetMagickToken(p,&p,token);
275 }
276 /* set the size of the kernel - old sized square */
277 kernel->width = kernel->height= (unsigned long) sqrt((double) i+1.0);
cristyc99304f2010-02-01 15:26:27 +0000278 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000279 p=(const char *) kernel_string;
anthony29188a82010-01-22 10:12:34 +0000280 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
281 p++; /* ignore "'" chars for convolve filter usage - Cristy */
anthony602ab9b2010-01-05 08:06:50 +0000282 }
283
284 /* Read in the kernel values from rest of input string argument */
285 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
286 kernel->height*sizeof(double));
287 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000288 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000289
cristyc99304f2010-02-01 15:26:27 +0000290 kernel->minimum = +MagickHuge;
291 kernel->maximum = -MagickHuge;
292 kernel->negative_range = kernel->positive_range = 0.0;
anthonyc84dce52010-05-07 05:42:23 +0000293
anthony5ef8e942010-05-11 06:51:12 +0000294 for (i=0; (i < (long) (kernel->width*kernel->height)) && (p < end); i++)
anthony602ab9b2010-01-05 08:06:50 +0000295 {
296 GetMagickToken(p,&p,token);
297 if (*token == ',')
298 GetMagickToken(p,&p,token);
anthony29188a82010-01-22 10:12:34 +0000299 if ( LocaleCompare("nan",token) == 0
anthonyc84dce52010-05-07 05:42:23 +0000300 || LocaleCompare("-",token) == 0 ) {
anthony29188a82010-01-22 10:12:34 +0000301 kernel->values[i] = nan; /* do not include this value in kernel */
302 }
303 else {
304 kernel->values[i] = StringToDouble(token);
305 ( kernel->values[i] < 0)
cristyc99304f2010-02-01 15:26:27 +0000306 ? ( kernel->negative_range += kernel->values[i] )
307 : ( kernel->positive_range += kernel->values[i] );
308 Minimize(kernel->minimum, kernel->values[i]);
309 Maximize(kernel->maximum, kernel->values[i]);
anthony29188a82010-01-22 10:12:34 +0000310 }
anthony602ab9b2010-01-05 08:06:50 +0000311 }
anthony29188a82010-01-22 10:12:34 +0000312
anthony5ef8e942010-05-11 06:51:12 +0000313 /* sanity check -- no more values in kernel definition */
314 GetMagickToken(p,&p,token);
315 if ( *token != '\0' && *token != ';' && *token != '\'' )
316 return(DestroyKernelInfo(kernel));
317
anthonyc84dce52010-05-07 05:42:23 +0000318#if 0
319 /* this was the old method of handling a incomplete kernel */
cristy150989e2010-02-01 14:59:39 +0000320 if ( i < (long) (kernel->width*kernel->height) ) {
cristyc99304f2010-02-01 15:26:27 +0000321 Minimize(kernel->minimum, kernel->values[i]);
322 Maximize(kernel->maximum, kernel->values[i]);
cristy150989e2010-02-01 14:59:39 +0000323 for ( ; i < (long) (kernel->width*kernel->height); i++)
anthony29188a82010-01-22 10:12:34 +0000324 kernel->values[i]=0.0;
325 }
anthonyc84dce52010-05-07 05:42:23 +0000326#else
327 /* Number of values for kernel was not enough - Report Error */
328 if ( i < (long) (kernel->width*kernel->height) )
329 return(DestroyKernelInfo(kernel));
330#endif
331
332 /* check that we recieved at least one real (non-nan) value! */
333 if ( kernel->minimum == MagickHuge )
334 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000335
336 return(kernel);
337}
anthonyc84dce52010-05-07 05:42:23 +0000338
anthony5ef8e942010-05-11 06:51:12 +0000339static KernelInfo *ParseNamedKernel(const char *kernel_string)
anthonyc84dce52010-05-07 05:42:23 +0000340{
341 char
342 token[MaxTextExtent];
343
anthony5ef8e942010-05-11 06:51:12 +0000344 long
345 type;
346
anthonyc84dce52010-05-07 05:42:23 +0000347 const char
anthony7a01dcf2010-05-11 12:25:52 +0000348 *p,
349 *end;
anthonyc84dce52010-05-07 05:42:23 +0000350
351 MagickStatusType
352 flags;
353
354 GeometryInfo
355 args;
356
anthonyc84dce52010-05-07 05:42:23 +0000357 /* Parse special 'named' kernel */
anthony5ef8e942010-05-11 06:51:12 +0000358 GetMagickToken(kernel_string,&p,token);
anthonyc84dce52010-05-07 05:42:23 +0000359 type=ParseMagickOption(MagickKernelOptions,MagickFalse,token);
360 if ( type < 0 || type == UserDefinedKernel )
anthony5ef8e942010-05-11 06:51:12 +0000361 return((KernelInfo *)NULL); /* not a valid named kernel */
anthonyc84dce52010-05-07 05:42:23 +0000362
363 while (((isspace((int) ((unsigned char) *p)) != 0) ||
anthony5ef8e942010-05-11 06:51:12 +0000364 (*p == ',') || (*p == ':' )) && (*p != '\0') && (*p != ';'))
anthonyc84dce52010-05-07 05:42:23 +0000365 p++;
anthony7a01dcf2010-05-11 12:25:52 +0000366
367 end = strchr(p, ';'); /* end of this kernel defintion */
368 if ( end == (char *) NULL )
369 end = strchr(p, '\0');
370
371 /* ParseGeometry() needs the geometry separated! -- Arrgghh */
372 memcpy(token, p, (size_t) (end-p));
373 token[end-p] = '\0';
anthonyc84dce52010-05-07 05:42:23 +0000374 SetGeometryInfo(&args);
anthony7a01dcf2010-05-11 12:25:52 +0000375 flags = ParseGeometry(token, &args);
anthonyc84dce52010-05-07 05:42:23 +0000376
377 /* special handling of missing values in input string */
378 switch( type ) {
anthony5ef8e942010-05-11 06:51:12 +0000379 case RectangleKernel:
380 if ( (flags & WidthValue) == 0 ) /* if no width then */
381 args.rho = args.sigma; /* then width = height */
382 if ( args.rho < 1.0 ) /* if width too small */
383 args.rho = 3; /* then width = 3 */
384 if ( args.sigma < 1.0 ) /* if height too small */
385 args.sigma = args.rho; /* then height = width */
386 if ( (flags & XValue) == 0 ) /* center offset if not defined */
387 args.xi = (double)(((long)args.rho-1)/2);
388 if ( (flags & YValue) == 0 )
389 args.psi = (double)(((long)args.sigma-1)/2);
390 break;
391 case SquareKernel:
392 case DiamondKernel:
393 case DiskKernel:
394 case PlusKernel:
395 /* If no scale given (a 0 scale is valid! - set it to 1.0 */
396 if ( (flags & HeightValue) == 0 )
397 args.sigma = 1.0;
398 break;
399 case ChebyshevKernel:
400 case ManhattenKernel:
401 case EuclideanKernel:
402 if ( (flags & HeightValue) == 0 )
403 args.sigma = 100.0; /* default distance scaling */
404 else if ( (flags & AspectValue ) != 0 ) /* '!' flag */
405 args.sigma = QuantumRange/args.sigma; /* maximum pixel distance */
406 else if ( (flags & PercentValue ) != 0 ) /* '%' flag */
407 args.sigma *= QuantumRange/100.0; /* percentage of color range */
408 break;
409 default:
410 break;
anthonyc84dce52010-05-07 05:42:23 +0000411 }
412
413 return(AcquireKernelBuiltIn((KernelInfoType)type, &args));
414}
415
anthony5ef8e942010-05-11 06:51:12 +0000416MagickExport KernelInfo *AcquireKernelInfo(const char *kernel_string)
417{
anthony7a01dcf2010-05-11 12:25:52 +0000418
419 KernelInfo
anthonydbc89892010-05-12 07:05:27 +0000420 *kernel,
421 *new_kernel,
422 *last_kernel;
anthony7a01dcf2010-05-11 12:25:52 +0000423
anthony5ef8e942010-05-11 06:51:12 +0000424 char
425 token[MaxTextExtent];
426
anthony7a01dcf2010-05-11 12:25:52 +0000427 const char
anthonydbc89892010-05-12 07:05:27 +0000428 *p;
anthony7a01dcf2010-05-11 12:25:52 +0000429
anthonydbc89892010-05-12 07:05:27 +0000430 p = kernel_string;
431 kernel = last_kernel = NULL;
anthony5ef8e942010-05-11 06:51:12 +0000432
anthonydbc89892010-05-12 07:05:27 +0000433 while ( GetMagickToken(p,NULL,token), *token != '\0' ) {
anthony7a01dcf2010-05-11 12:25:52 +0000434
anthonydbc89892010-05-12 07:05:27 +0000435 /* ignore multiple ';' following each other */
436 if ( *token != ';' ) {
anthony7a01dcf2010-05-11 12:25:52 +0000437
anthonydbc89892010-05-12 07:05:27 +0000438 /* tokens starting with alpha is a Named kernel */
439 if (isalpha((int) *token) == 0)
440 new_kernel = ParseKernelArray(p);
441 else /* otherwise a user defined kernel array */
442 new_kernel = ParseNamedKernel(p);
anthony7a01dcf2010-05-11 12:25:52 +0000443
anthonydbc89892010-05-12 07:05:27 +0000444 if ( last_kernel == (KernelInfo *) NULL ) {
445 /* first kernel: initialize the kernel list */
446 if ( new_kernel == (KernelInfo *) NULL )
447 return((KernelInfo *) NULL);
448 kernel = last_kernel = new_kernel;
449 }
450 else {
451 /* append kernel to end of the list */
452 if ( new_kernel == (KernelInfo *) NULL )
453 return(DestroyKernelInfo(kernel));
454 last_kernel->next = new_kernel;
455 last_kernel = new_kernel;
456 }
457 }
458
459 /* look for the next kernel in list */
460 p = strchr(p, ';');
461 if ( p == (char *) NULL )
462 break;
463 p++;
464
465 }
anthony7a01dcf2010-05-11 12:25:52 +0000466 return(kernel);
anthony5ef8e942010-05-11 06:51:12 +0000467}
468
anthony602ab9b2010-01-05 08:06:50 +0000469
470/*
471%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
472% %
473% %
474% %
475% A c q u i r e K e r n e l B u i l t I n %
476% %
477% %
478% %
479%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
480%
481% AcquireKernelBuiltIn() returned one of the 'named' built-in types of
482% kernels used for special purposes such as gaussian blurring, skeleton
483% pruning, and edge distance determination.
484%
485% They take a KernelType, and a set of geometry style arguments, which were
486% typically decoded from a user supplied string, or from a more complex
487% Morphology Method that was requested.
488%
489% The format of the AcquireKernalBuiltIn method is:
490%
cristy2be15382010-01-21 02:38:03 +0000491% KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000492% const GeometryInfo args)
493%
494% A description of each parameter follows:
495%
496% o type: the pre-defined type of kernel wanted
497%
498% o args: arguments defining or modifying the kernel
499%
500% Convolution Kernels
501%
anthony4fd27e22010-02-07 08:17:18 +0000502% Gaussian "{radius},{sigma}"
anthony602ab9b2010-01-05 08:06:50 +0000503% Generate a two-dimentional gaussian kernel, as used by -gaussian
504% A sigma is required, (with the 'x'), due to historical reasons.
505%
506% NOTE: that the 'radius' is optional, but if provided can limit (clip)
507% the final size of the resulting kernel to a square 2*radius+1 in size.
508% The radius should be at least 2 times that of the sigma value, or
509% sever clipping and aliasing may result. If not given or set to 0 the
510% radius will be determined so as to produce the best minimal error
511% result, which is usally much larger than is normally needed.
512%
anthony4fd27e22010-02-07 08:17:18 +0000513% Blur "{radius},{sigma},{angle}"
anthony602ab9b2010-01-05 08:06:50 +0000514% As per Gaussian, but generates a 1 dimensional or linear gaussian
515% blur, at the angle given (current restricted to orthogonal angles).
516% If a 'radius' is given the kernel is clipped to a width of 2*radius+1.
517%
518% NOTE that two such blurs perpendicular to each other is equivelent to
519% -blur and the previous gaussian, but is often 10 or more times faster.
520%
anthony4fd27e22010-02-07 08:17:18 +0000521% Comet "{width},{sigma},{angle}"
anthony602ab9b2010-01-05 08:06:50 +0000522% Blur in one direction only, mush like how a bright object leaves
523% a comet like trail. The Kernel is actually half a gaussian curve,
524% Adding two such blurs in oppiste directions produces a Linear Blur.
525%
526% NOTE: that the first argument is the width of the kernel and not the
527% radius of the kernel.
528%
529% # Still to be implemented...
530% #
anthony4fd27e22010-02-07 08:17:18 +0000531% # Sharpen "{radius},{sigma}
532% # Negated Gaussian (center zeroed and re-normalized),
533% # with a 2 unit positive peak. -- Check On line documentation
534% #
535% # Laplacian "{radius},{sigma}"
anthony602ab9b2010-01-05 08:06:50 +0000536% # Laplacian (a mexican hat like) Function
537% #
538% # LOG "{radius},{sigma1},{sigma2}
539% # Laplacian of Gaussian
540% #
541% # DOG "{radius},{sigma1},{sigma2}
anthony4fd27e22010-02-07 08:17:18 +0000542% # Difference of two Gaussians
543% #
544% # Filter2D
545% # Filter1D
546% # Set kernel values using a resize filter, and given scale (sigma)
547% # Cylindrical or Linear. Is this posible with an image?
548% #
anthony602ab9b2010-01-05 08:06:50 +0000549%
550% Boolean Kernels
551%
552% Rectangle "{geometry}"
553% Simply generate a rectangle of 1's with the size given. You can also
554% specify the location of the 'control point', otherwise the closest
555% pixel to the center of the rectangle is selected.
556%
557% Properly centered and odd sized rectangles work the best.
558%
anthony4fd27e22010-02-07 08:17:18 +0000559% Diamond "[{radius}[,{scale}]]"
anthony1b2bc0a2010-05-12 05:25:22 +0000560% Generate a diamond shaped kernel with given radius to the points.
anthony602ab9b2010-01-05 08:06:50 +0000561% Kernel size will again be radius*2+1 square and defaults to radius 1,
562% generating a 3x3 kernel that is slightly larger than a square.
563%
anthony4fd27e22010-02-07 08:17:18 +0000564% Square "[{radius}[,{scale}]]"
anthony602ab9b2010-01-05 08:06:50 +0000565% Generate a square shaped kernel of size radius*2+1, and defaulting
566% to a 3x3 (radius 1).
567%
568% Note that using a larger radius for the "Square" or the "Diamond"
569% is also equivelent to iterating the basic morphological method
570% that many times. However However iterating with the smaller radius 1
571% default is actually faster than using a larger kernel radius.
572%
anthony4fd27e22010-02-07 08:17:18 +0000573% Disk "[{radius}[,{scale}]]
anthony602ab9b2010-01-05 08:06:50 +0000574% Generate a binary disk of the radius given, radius may be a float.
575% Kernel size will be ceil(radius)*2+1 square.
576% NOTE: Here are some disk shapes of specific interest
577% "disk:1" => "diamond" or "cross:1"
578% "disk:1.5" => "square"
579% "disk:2" => "diamond:2"
anthony83ba99b2010-01-24 08:48:15 +0000580% "disk:2.5" => a general disk shape of radius 2
anthony602ab9b2010-01-05 08:06:50 +0000581% "disk:2.9" => "square:2"
anthony83ba99b2010-01-24 08:48:15 +0000582% "disk:3.5" => default - octagonal/disk shape of radius 3
anthony602ab9b2010-01-05 08:06:50 +0000583% "disk:4.2" => roughly octagonal shape of radius 4
anthony83ba99b2010-01-24 08:48:15 +0000584% "disk:4.3" => a general disk shape of radius 4
anthony602ab9b2010-01-05 08:06:50 +0000585% After this all the kernel shape becomes more and more circular.
586%
587% Because a "disk" is more circular when using a larger radius, using a
588% larger radius is preferred over iterating the morphological operation.
589%
anthony4fd27e22010-02-07 08:17:18 +0000590% Plus "[{radius}[,{scale}]]"
anthony602ab9b2010-01-05 08:06:50 +0000591% Generate a kernel in the shape of a 'plus' sign. The length of each
592% arm is also the radius, which defaults to 2.
593%
594% This kernel is not a good general morphological kernel, but is used
595% more for highlighting and marking any single pixels in an image using,
596% a "Dilate" or "Erode" method as appropriate.
anthonyc94cdb02010-01-06 08:15:29 +0000597%
anthony602ab9b2010-01-05 08:06:50 +0000598% NOTE: "plus:1" is equivelent to a "Diamond" kernel.
599%
600% Note that unlike other kernels iterating a plus does not produce the
601% same result as using a larger radius for the cross.
602%
603% Distance Measuring Kernels
604%
anthonyc84dce52010-05-07 05:42:23 +0000605% Chebyshev "[{radius}][x{scale}[%!]]"
606% Manhatten "[{radius}][x{scale}[%!]]"
607% Euclidean "[{radius}][x{scale}[%!]]"
anthony602ab9b2010-01-05 08:06:50 +0000608%
609% Different types of distance measuring methods, which are used with the
610% a 'Distance' morphology method for generating a gradient based on
611% distance from an edge of a binary shape, though there is a technique
612% for handling a anti-aliased shape.
613%
anthonyc94cdb02010-01-06 08:15:29 +0000614% Chebyshev Distance (also known as Tchebychev Distance) is a value of
615% one to any neighbour, orthogonal or diagonal. One why of thinking of
616% it is the number of squares a 'King' or 'Queen' in chess needs to
617% traverse reach any other position on a chess board. It results in a
618% 'square' like distance function, but one where diagonals are closer
619% than expected.
anthony602ab9b2010-01-05 08:06:50 +0000620%
anthonyc94cdb02010-01-06 08:15:29 +0000621% Manhatten Distance (also known as Rectilinear Distance, or the Taxi
622% Cab metric), is the distance needed when you can only travel in
623% orthogonal (horizontal or vertical) only. It is the distance a 'Rook'
624% in chess would travel. It results in a diamond like distances, where
625% diagonals are further than expected.
anthony602ab9b2010-01-05 08:06:50 +0000626%
anthonyc94cdb02010-01-06 08:15:29 +0000627% Euclidean Distance is the 'direct' or 'as the crow flys distance.
628% However by default the kernel size only has a radius of 1, which
629% limits the distance to 'Knight' like moves, with only orthogonal and
630% diagonal measurements being correct. As such for the default kernel
631% you will get octagonal like distance function, which is reasonally
632% accurate.
633%
634% However if you use a larger radius such as "Euclidean:4" you will
635% get a much smoother distance gradient from the edge of the shape.
636% Of course a larger kernel is slower to use, and generally not needed.
637%
638% To allow the use of fractional distances that you get with diagonals
639% the actual distance is scaled by a fixed value which the user can
640% provide. This is not actually nessary for either ""Chebyshev" or
641% "Manhatten" distance kernels, but is done for all three distance
642% kernels. If no scale is provided it is set to a value of 100,
643% allowing for a maximum distance measurement of 655 pixels using a Q16
644% version of IM, from any edge. However for small images this can
645% result in quite a dark gradient.
646%
647% See the 'Distance' Morphological Method, for information of how it is
648% applied.
anthony602ab9b2010-01-05 08:06:50 +0000649%
anthony4fd27e22010-02-07 08:17:18 +0000650% # Hit-n-Miss Kernel-Lists -- Still to be implemented
651% #
652% # specifically for Pruning, Thinning, Thickening
653% #
anthony602ab9b2010-01-05 08:06:50 +0000654*/
655
cristy2be15382010-01-21 02:38:03 +0000656MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000657 const GeometryInfo *args)
658{
cristy2be15382010-01-21 02:38:03 +0000659 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000660 *kernel;
661
cristy150989e2010-02-01 14:59:39 +0000662 register long
anthony602ab9b2010-01-05 08:06:50 +0000663 i;
664
665 register long
666 u,
667 v;
668
669 double
670 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
671
cristy2be15382010-01-21 02:38:03 +0000672 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
673 if (kernel == (KernelInfo *) NULL)
anthony602ab9b2010-01-05 08:06:50 +0000674 return(kernel);
675 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
cristyc99304f2010-02-01 15:26:27 +0000676 kernel->minimum = kernel->maximum = 0.0;
677 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000678 kernel->type = type;
anthony7a01dcf2010-05-11 12:25:52 +0000679 kernel->next = (KernelInfo *) NULL;
cristyd43a46b2010-01-21 02:13:41 +0000680 kernel->signature = MagickSignature;
anthony602ab9b2010-01-05 08:06:50 +0000681
682 switch(type) {
683 /* Convolution Kernels */
684 case GaussianKernel:
685 { double
686 sigma = fabs(args->sigma);
687
688 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
689
690 kernel->width = kernel->height =
691 GetOptimalKernelWidth2D(args->rho,sigma);
cristyc99304f2010-02-01 15:26:27 +0000692 kernel->x = kernel->y = (long) (kernel->width-1)/2;
693 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000694 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
695 kernel->height*sizeof(double));
696 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000697 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000698
699 sigma = 2.0*sigma*sigma; /* simplify the expression */
cristyc99304f2010-02-01 15:26:27 +0000700 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
701 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
702 kernel->positive_range += (
anthony602ab9b2010-01-05 08:06:50 +0000703 kernel->values[i] =
704 exp(-((double)(u*u+v*v))/sigma)
705 /* / (MagickPI*sigma) */ );
cristyc99304f2010-02-01 15:26:27 +0000706 kernel->minimum = 0;
707 kernel->maximum = kernel->values[
708 kernel->y*kernel->width+kernel->x ];
anthony602ab9b2010-01-05 08:06:50 +0000709
anthony999bb2c2010-02-18 12:38:01 +0000710 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
anthony602ab9b2010-01-05 08:06:50 +0000711
712 break;
713 }
714 case BlurKernel:
715 { double
716 sigma = fabs(args->sigma);
717
718 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
719
720 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
cristyc99304f2010-02-01 15:26:27 +0000721 kernel->x = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000722 kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +0000723 kernel->y = 0;
724 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000725 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
726 kernel->height*sizeof(double));
727 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000728 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000729
730#if 1
731#define KernelRank 3
732 /* Formula derived from GetBlurKernel() in "effect.c" (plus bug fix).
733 ** It generates a gaussian 3 times the width, and compresses it into
734 ** the expected range. This produces a closer normalization of the
735 ** resulting kernel, especially for very low sigma values.
736 ** As such while wierd it is prefered.
737 **
738 ** I am told this method originally came from Photoshop.
739 */
740 sigma *= KernelRank; /* simplify expanded curve */
cristy150989e2010-02-01 14:59:39 +0000741 v = (long) (kernel->width*KernelRank-1)/2; /* start/end points to fit range */
anthony602ab9b2010-01-05 08:06:50 +0000742 (void) ResetMagickMemory(kernel->values,0, (size_t)
743 kernel->width*sizeof(double));
744 for ( u=-v; u <= v; u++) {
745 kernel->values[(u+v)/KernelRank] +=
746 exp(-((double)(u*u))/(2.0*sigma*sigma))
747 /* / (MagickSQ2PI*sigma/KernelRank) */ ;
748 }
cristy150989e2010-02-01 14:59:39 +0000749 for (i=0; i < (long) kernel->width; i++)
cristyc99304f2010-02-01 15:26:27 +0000750 kernel->positive_range += kernel->values[i];
anthony602ab9b2010-01-05 08:06:50 +0000751#else
cristyc99304f2010-02-01 15:26:27 +0000752 for ( i=0, u=-kernel->x; i < kernel->width; i++, u++)
753 kernel->positive_range += (
anthony602ab9b2010-01-05 08:06:50 +0000754 kernel->values[i] =
755 exp(-((double)(u*u))/(2.0*sigma*sigma))
756 /* / (MagickSQ2PI*sigma) */ );
757#endif
cristyc99304f2010-02-01 15:26:27 +0000758 kernel->minimum = 0;
759 kernel->maximum = kernel->values[ kernel->x ];
anthonycc6c8362010-01-25 04:14:01 +0000760 /* Note that neither methods above generate a normalized kernel,
761 ** though it gets close. The kernel may be 'clipped' by a user defined
762 ** radius, producing a smaller (darker) kernel. Also for very small
763 ** sigma's (> 0.1) the central value becomes larger than one, and thus
764 ** producing a very bright kernel.
anthony602ab9b2010-01-05 08:06:50 +0000765 */
anthonycc6c8362010-01-25 04:14:01 +0000766
anthony602ab9b2010-01-05 08:06:50 +0000767 /* Normalize the 1D Gaussian Kernel
768 **
769 ** Because of this the divisor in the above kernel generator is
anthonyc94cdb02010-01-06 08:15:29 +0000770 ** not needed, so is not done above.
anthony602ab9b2010-01-05 08:06:50 +0000771 */
anthony999bb2c2010-02-18 12:38:01 +0000772 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
anthonycc6c8362010-01-25 04:14:01 +0000773
anthony602ab9b2010-01-05 08:06:50 +0000774 /* rotate the kernel by given angle */
anthony4fd27e22010-02-07 08:17:18 +0000775 RotateKernelInfo(kernel, args->xi);
anthony602ab9b2010-01-05 08:06:50 +0000776 break;
777 }
778 case CometKernel:
779 { double
780 sigma = fabs(args->sigma);
781
782 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
783
784 if ( args->rho < 1.0 )
785 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
786 else
787 kernel->width = (unsigned long)args->rho;
cristyc99304f2010-02-01 15:26:27 +0000788 kernel->x = kernel->y = 0;
anthony602ab9b2010-01-05 08:06:50 +0000789 kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +0000790 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000791 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
792 kernel->height*sizeof(double));
793 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000794 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000795
796 /* A comet blur is half a gaussian curve, so that the object is
797 ** blurred in one direction only. This may not be quite the right
798 ** curve so may change in the future. The function must be normalised.
799 */
800#if 1
801#define KernelRank 3
802 sigma *= KernelRank; /* simplify expanded curve */
cristy150989e2010-02-01 14:59:39 +0000803 v = (long) kernel->width*KernelRank; /* start/end points to fit range */
anthony602ab9b2010-01-05 08:06:50 +0000804 (void) ResetMagickMemory(kernel->values,0, (size_t)
805 kernel->width*sizeof(double));
806 for ( u=0; u < v; u++) {
807 kernel->values[u/KernelRank] +=
808 exp(-((double)(u*u))/(2.0*sigma*sigma))
809 /* / (MagickSQ2PI*sigma/KernelRank) */ ;
810 }
cristy150989e2010-02-01 14:59:39 +0000811 for (i=0; i < (long) kernel->width; i++)
cristyc99304f2010-02-01 15:26:27 +0000812 kernel->positive_range += kernel->values[i];
anthony602ab9b2010-01-05 08:06:50 +0000813#else
cristy150989e2010-02-01 14:59:39 +0000814 for ( i=0; i < (long) kernel->width; i++)
cristyc99304f2010-02-01 15:26:27 +0000815 kernel->positive_range += (
anthony602ab9b2010-01-05 08:06:50 +0000816 kernel->values[i] =
817 exp(-((double)(i*i))/(2.0*sigma*sigma))
818 /* / (MagickSQ2PI*sigma) */ );
819#endif
cristyc99304f2010-02-01 15:26:27 +0000820 kernel->minimum = 0;
821 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000822
anthony999bb2c2010-02-18 12:38:01 +0000823 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
824 RotateKernelInfo(kernel, args->xi); /* Rotate by angle */
anthony602ab9b2010-01-05 08:06:50 +0000825 break;
826 }
827 /* Boolean Kernels */
828 case RectangleKernel:
829 case SquareKernel:
830 {
anthony4fd27e22010-02-07 08:17:18 +0000831 double scale;
anthony602ab9b2010-01-05 08:06:50 +0000832 if ( type == SquareKernel )
833 {
834 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000835 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000836 else
cristy150989e2010-02-01 14:59:39 +0000837 kernel->width = kernel->height = (unsigned long) (2*args->rho+1);
cristyc99304f2010-02-01 15:26:27 +0000838 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony4fd27e22010-02-07 08:17:18 +0000839 scale = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +0000840 }
841 else {
cristy2be15382010-01-21 02:38:03 +0000842 /* NOTE: user defaults set in "AcquireKernelInfo()" */
anthony602ab9b2010-01-05 08:06:50 +0000843 if ( args->rho < 1.0 || args->sigma < 1.0 )
anthony83ba99b2010-01-24 08:48:15 +0000844 return(DestroyKernelInfo(kernel)); /* invalid args given */
anthony602ab9b2010-01-05 08:06:50 +0000845 kernel->width = (unsigned long)args->rho;
846 kernel->height = (unsigned long)args->sigma;
847 if ( args->xi < 0.0 || args->xi > (double)kernel->width ||
848 args->psi < 0.0 || args->psi > (double)kernel->height )
anthony83ba99b2010-01-24 08:48:15 +0000849 return(DestroyKernelInfo(kernel)); /* invalid args given */
cristyc99304f2010-02-01 15:26:27 +0000850 kernel->x = (long) args->xi;
851 kernel->y = (long) args->psi;
anthony4fd27e22010-02-07 08:17:18 +0000852 scale = 1.0;
anthony602ab9b2010-01-05 08:06:50 +0000853 }
854 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
855 kernel->height*sizeof(double));
856 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000857 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000858
anthonycc6c8362010-01-25 04:14:01 +0000859 /* set all kernel values to 1.0 */
cristy150989e2010-02-01 14:59:39 +0000860 u=(long) kernel->width*kernel->height;
861 for ( i=0; i < u; i++)
anthony4fd27e22010-02-07 08:17:18 +0000862 kernel->values[i] = scale;
863 kernel->minimum = kernel->maximum = scale; /* a flat shape */
864 kernel->positive_range = scale*u;
anthonycc6c8362010-01-25 04:14:01 +0000865 break;
anthony602ab9b2010-01-05 08:06:50 +0000866 }
867 case DiamondKernel:
868 {
869 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000870 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000871 else
872 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000873 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000874
875 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
876 kernel->height*sizeof(double));
877 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000878 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000879
anthony4fd27e22010-02-07 08:17:18 +0000880 /* set all kernel values within diamond area to scale given */
cristyc99304f2010-02-01 15:26:27 +0000881 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
882 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
883 if ((labs(u)+labs(v)) <= (long)kernel->x)
anthony4fd27e22010-02-07 08:17:18 +0000884 kernel->positive_range += kernel->values[i] = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +0000885 else
886 kernel->values[i] = nan;
anthony4fd27e22010-02-07 08:17:18 +0000887 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
anthony602ab9b2010-01-05 08:06:50 +0000888 break;
889 }
890 case DiskKernel:
891 {
892 long
893 limit;
894
895 limit = (long)(args->rho*args->rho);
anthony83ba99b2010-01-24 08:48:15 +0000896 if (args->rho < 0.1) /* default radius approx 3.5 */
897 kernel->width = kernel->height = 7L, limit = 10L;
anthony602ab9b2010-01-05 08:06:50 +0000898 else
899 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000900 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000901
902 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
903 kernel->height*sizeof(double));
904 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000905 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000906
anthonycc6c8362010-01-25 04:14:01 +0000907 /* set all kernel values within disk area to 1.0 */
cristyc99304f2010-02-01 15:26:27 +0000908 for ( i=0, v= -kernel->y; v <= (long)kernel->y; v++)
909 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony602ab9b2010-01-05 08:06:50 +0000910 if ((u*u+v*v) <= limit)
anthony4fd27e22010-02-07 08:17:18 +0000911 kernel->positive_range += kernel->values[i] = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +0000912 else
913 kernel->values[i] = nan;
anthony4fd27e22010-02-07 08:17:18 +0000914 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
anthony602ab9b2010-01-05 08:06:50 +0000915 break;
916 }
917 case PlusKernel:
918 {
919 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000920 kernel->width = kernel->height = 5; /* default radius 2 */
anthony602ab9b2010-01-05 08:06:50 +0000921 else
922 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000923 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000924
925 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
926 kernel->height*sizeof(double));
927 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000928 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000929
anthonycc6c8362010-01-25 04:14:01 +0000930 /* set all kernel values along axises to 1.0 */
cristyc99304f2010-02-01 15:26:27 +0000931 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
932 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony4fd27e22010-02-07 08:17:18 +0000933 kernel->values[i] = (u == 0 || v == 0) ? args->sigma : nan;
934 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
935 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
anthony602ab9b2010-01-05 08:06:50 +0000936 break;
937 }
938 /* Distance Measuring Kernels */
939 case ChebyshevKernel:
940 {
anthony602ab9b2010-01-05 08:06:50 +0000941 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000942 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000943 else
944 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000945 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000946
947 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
948 kernel->height*sizeof(double));
949 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000950 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000951
cristyc99304f2010-02-01 15:26:27 +0000952 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
953 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
954 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +0000955 args->sigma*((labs(u)>labs(v)) ? labs(u) : labs(v)) );
cristyc99304f2010-02-01 15:26:27 +0000956 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000957 break;
958 }
959 case ManhattenKernel:
960 {
anthony602ab9b2010-01-05 08:06:50 +0000961 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000962 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000963 else
964 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000965 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000966
967 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
968 kernel->height*sizeof(double));
969 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000970 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000971
cristyc99304f2010-02-01 15:26:27 +0000972 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
973 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
974 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +0000975 args->sigma*(labs(u)+labs(v)) );
cristyc99304f2010-02-01 15:26:27 +0000976 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000977 break;
978 }
979 case EuclideanKernel:
980 {
anthony602ab9b2010-01-05 08:06:50 +0000981 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000982 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000983 else
984 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000985 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000986
987 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
988 kernel->height*sizeof(double));
989 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000990 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000991
cristyc99304f2010-02-01 15:26:27 +0000992 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
993 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
994 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +0000995 args->sigma*sqrt((double)(u*u+v*v)) );
cristyc99304f2010-02-01 15:26:27 +0000996 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000997 break;
998 }
999 /* Undefined Kernels */
1000 case LaplacianKernel:
1001 case LOGKernel:
1002 case DOGKernel:
cristy150989e2010-02-01 14:59:39 +00001003 perror("Kernel Type has not been defined yet");
anthony602ab9b2010-01-05 08:06:50 +00001004 /* FALL THRU */
1005 default:
1006 /* Generate a No-Op minimal kernel - 1x1 pixel */
1007 kernel->values=(double *)AcquireQuantumMemory((size_t)1,sizeof(double));
1008 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001009 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001010 kernel->width = kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +00001011 kernel->x = kernel->x = 0;
anthony602ab9b2010-01-05 08:06:50 +00001012 kernel->type = UndefinedKernel;
cristyc99304f2010-02-01 15:26:27 +00001013 kernel->maximum =
1014 kernel->positive_range =
anthonyc94cdb02010-01-06 08:15:29 +00001015 kernel->values[0] = 1.0; /* a flat single-point no-op kernel! */
anthony602ab9b2010-01-05 08:06:50 +00001016 break;
1017 }
1018
1019 return(kernel);
1020}
anthonyc94cdb02010-01-06 08:15:29 +00001021
anthony602ab9b2010-01-05 08:06:50 +00001022/*
1023%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1024% %
1025% %
1026% %
cristy6771f1e2010-03-05 19:43:39 +00001027% C l o n e K e r n e l I n f o %
anthony4fd27e22010-02-07 08:17:18 +00001028% %
1029% %
1030% %
1031%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1032%
anthony1b2bc0a2010-05-12 05:25:22 +00001033% CloneKernelInfo() creates a new clone of the given Kernel List so that its
1034% can be modified without effecting the original. The cloned kernel should
1035% be destroyed using DestoryKernelInfo() when no longer needed.
anthony7a01dcf2010-05-11 12:25:52 +00001036%
cristye6365592010-04-02 17:31:23 +00001037% The format of the CloneKernelInfo method is:
anthony4fd27e22010-02-07 08:17:18 +00001038%
anthony930be612010-02-08 04:26:15 +00001039% KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +00001040%
1041% A description of each parameter follows:
1042%
1043% o kernel: the Morphology/Convolution kernel to be cloned
1044%
1045*/
cristyef656912010-03-05 19:54:59 +00001046MagickExport KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +00001047{
1048 register long
1049 i;
1050
cristy19eb6412010-04-23 14:42:29 +00001051 KernelInfo
anthony7a01dcf2010-05-11 12:25:52 +00001052 *new_kernel;
anthony4fd27e22010-02-07 08:17:18 +00001053
1054 assert(kernel != (KernelInfo *) NULL);
anthony7a01dcf2010-05-11 12:25:52 +00001055 new_kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
1056 if (new_kernel == (KernelInfo *) NULL)
1057 return(new_kernel);
1058 *new_kernel=(*kernel); /* copy values in structure */
anthony7a01dcf2010-05-11 12:25:52 +00001059
1060 /* replace the values with a copy of the values */
1061 new_kernel->values=(double *) AcquireQuantumMemory(kernel->width,
cristy19eb6412010-04-23 14:42:29 +00001062 kernel->height*sizeof(double));
anthony7a01dcf2010-05-11 12:25:52 +00001063 if (new_kernel->values == (double *) NULL)
1064 return(DestroyKernelInfo(new_kernel));
anthony4fd27e22010-02-07 08:17:18 +00001065 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthony7a01dcf2010-05-11 12:25:52 +00001066 new_kernel->values[i]=kernel->values[i];
anthony1b2bc0a2010-05-12 05:25:22 +00001067
1068 /* Also clone the next kernel in the kernel list */
1069 if ( kernel->next != (KernelInfo *) NULL ) {
1070 new_kernel->next = CloneKernelInfo(kernel->next);
1071 if ( new_kernel->next == (KernelInfo *) NULL )
1072 return(DestroyKernelInfo(new_kernel));
1073 }
1074
anthony7a01dcf2010-05-11 12:25:52 +00001075 return(new_kernel);
anthony4fd27e22010-02-07 08:17:18 +00001076}
1077
1078/*
1079%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1080% %
1081% %
1082% %
anthony83ba99b2010-01-24 08:48:15 +00001083% D e s t r o y K e r n e l I n f o %
anthony602ab9b2010-01-05 08:06:50 +00001084% %
1085% %
1086% %
1087%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1088%
anthony83ba99b2010-01-24 08:48:15 +00001089% DestroyKernelInfo() frees the memory used by a Convolution/Morphology
1090% kernel.
anthony602ab9b2010-01-05 08:06:50 +00001091%
anthony83ba99b2010-01-24 08:48:15 +00001092% The format of the DestroyKernelInfo method is:
anthony602ab9b2010-01-05 08:06:50 +00001093%
anthony83ba99b2010-01-24 08:48:15 +00001094% KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +00001095%
1096% A description of each parameter follows:
1097%
1098% o kernel: the Morphology/Convolution kernel to be destroyed
1099%
1100*/
1101
anthony83ba99b2010-01-24 08:48:15 +00001102MagickExport KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +00001103{
cristy2be15382010-01-21 02:38:03 +00001104 assert(kernel != (KernelInfo *) NULL);
anthony4fd27e22010-02-07 08:17:18 +00001105
anthony7a01dcf2010-05-11 12:25:52 +00001106 if ( kernel->next != (KernelInfo *) NULL )
1107 kernel->next = DestroyKernelInfo(kernel->next);
1108
1109 kernel->values = (double *)RelinquishMagickMemory(kernel->values);
1110 kernel = (KernelInfo *) RelinquishMagickMemory(kernel);
anthony602ab9b2010-01-05 08:06:50 +00001111 return(kernel);
1112}
anthonyc94cdb02010-01-06 08:15:29 +00001113
1114/*
1115%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1116% %
1117% %
1118% %
anthony29188a82010-01-22 10:12:34 +00001119% 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 +00001120% %
1121% %
1122% %
1123%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1124%
anthony29188a82010-01-22 10:12:34 +00001125% MorphologyImageChannel() applies a user supplied kernel to the image
1126% according to the given mophology method.
anthony602ab9b2010-01-05 08:06:50 +00001127%
1128% The given kernel is assumed to have been pre-scaled appropriatally, usally
1129% by the kernel generator.
1130%
1131% The format of the MorphologyImage method is:
1132%
cristyef656912010-03-05 19:54:59 +00001133% Image *MorphologyImage(const Image *image,MorphologyMethod method,
1134% const long iterations,KernelInfo *kernel,ExceptionInfo *exception)
anthony29188a82010-01-22 10:12:34 +00001135% Image *MorphologyImageChannel(const Image *image, const ChannelType
cristyef656912010-03-05 19:54:59 +00001136% channel,MorphologyMethod method,const long iterations,
1137% KernelInfo *kernel,ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001138%
1139% A description of each parameter follows:
1140%
1141% o image: the image.
1142%
1143% o method: the morphology method to be applied.
1144%
1145% o iterations: apply the operation this many times (or no change).
1146% A value of -1 means loop until no change found.
1147% How this is applied may depend on the morphology method.
1148% Typically this is a value of 1.
1149%
1150% o channel: the channel type.
1151%
1152% o kernel: An array of double representing the morphology kernel.
anthony29188a82010-01-22 10:12:34 +00001153% Warning: kernel may be normalized for the Convolve method.
anthony602ab9b2010-01-05 08:06:50 +00001154%
1155% o exception: return any errors or warnings in this structure.
1156%
1157%
1158% TODO: bias and auto-scale handling of the kernel for convolution
1159% The given kernel is assumed to have been pre-scaled appropriatally, usally
1160% by the kernel generator.
1161%
1162*/
1163
anthony930be612010-02-08 04:26:15 +00001164
anthony602ab9b2010-01-05 08:06:50 +00001165/* Internal function
anthony7a01dcf2010-05-11 12:25:52 +00001166 * Apply the low-level Morphology Method Primatives using the given Kernel
anthony930be612010-02-08 04:26:15 +00001167 * Returning the number of pixels that changed.
1168 * Two pre-created images must be provided, no image is created.
anthony602ab9b2010-01-05 08:06:50 +00001169 */
anthony7a01dcf2010-05-11 12:25:52 +00001170static unsigned long MorphologyPrimative(const Image *image, Image
anthony602ab9b2010-01-05 08:06:50 +00001171 *result_image, const MorphologyMethod method, const ChannelType channel,
cristy2be15382010-01-21 02:38:03 +00001172 const KernelInfo *kernel, ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001173{
cristy2be15382010-01-21 02:38:03 +00001174#define MorphologyTag "Morphology/Image"
anthony602ab9b2010-01-05 08:06:50 +00001175
1176 long
cristy150989e2010-02-01 14:59:39 +00001177 progress,
anthony29188a82010-01-22 10:12:34 +00001178 y, offx, offy,
anthony602ab9b2010-01-05 08:06:50 +00001179 changed;
1180
1181 MagickBooleanType
1182 status;
1183
1184 MagickPixelPacket
1185 bias;
1186
1187 CacheView
1188 *p_view,
1189 *q_view;
1190
anthony4fd27e22010-02-07 08:17:18 +00001191 /* Only the most basic morphology is actually performed by this routine */
anthony4fd27e22010-02-07 08:17:18 +00001192
anthony602ab9b2010-01-05 08:06:50 +00001193 /*
anthony4fd27e22010-02-07 08:17:18 +00001194 Apply Basic Morphology to Image.
anthony602ab9b2010-01-05 08:06:50 +00001195 */
1196 status=MagickTrue;
1197 changed=0;
1198 progress=0;
1199
1200 GetMagickPixelPacket(image,&bias);
1201 SetMagickPixelPacketBias(image,&bias);
anthonycc6c8362010-01-25 04:14:01 +00001202 /* Future: handle auto-bias from user, based on kernel input */
anthony602ab9b2010-01-05 08:06:50 +00001203
1204 p_view=AcquireCacheView(image);
1205 q_view=AcquireCacheView(result_image);
anthony29188a82010-01-22 10:12:34 +00001206
anthonycc6c8362010-01-25 04:14:01 +00001207 /* Some methods (including convolve) needs use a reflected kernel.
1208 * Adjust 'origin' offsets for this reflected kernel.
anthony29188a82010-01-22 10:12:34 +00001209 */
cristyc99304f2010-02-01 15:26:27 +00001210 offx = kernel->x;
1211 offy = kernel->y;
anthony29188a82010-01-22 10:12:34 +00001212 switch(method) {
anthony930be612010-02-08 04:26:15 +00001213 case ConvolveMorphology:
1214 case DilateMorphology:
1215 case DilateIntensityMorphology:
1216 case DistanceMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001217 /* kernel needs to used with reflection about origin */
cristy150989e2010-02-01 14:59:39 +00001218 offx = (long) kernel->width-offx-1;
1219 offy = (long) kernel->height-offy-1;
anthony29188a82010-01-22 10:12:34 +00001220 break;
anthony5ef8e942010-05-11 06:51:12 +00001221 case ErodeMorphology:
1222 case ErodeIntensityMorphology:
1223 case HitAndMissMorphology:
1224 case ThinningMorphology:
1225 case ThickenMorphology:
1226 /* kernel is user as is, without reflection */
1227 break;
anthony930be612010-02-08 04:26:15 +00001228 default:
anthony5ef8e942010-05-11 06:51:12 +00001229 perror("Not a low level Morphology Method");
anthony930be612010-02-08 04:26:15 +00001230 break;
anthony29188a82010-01-22 10:12:34 +00001231 }
1232
anthony602ab9b2010-01-05 08:06:50 +00001233#if defined(MAGICKCORE_OPENMP_SUPPORT)
1234 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1235#endif
cristy150989e2010-02-01 14:59:39 +00001236 for (y=0; y < (long) image->rows; y++)
anthony602ab9b2010-01-05 08:06:50 +00001237 {
1238 MagickBooleanType
1239 sync;
1240
1241 register const PixelPacket
1242 *restrict p;
1243
1244 register const IndexPacket
1245 *restrict p_indexes;
1246
1247 register PixelPacket
1248 *restrict q;
1249
1250 register IndexPacket
1251 *restrict q_indexes;
1252
cristy150989e2010-02-01 14:59:39 +00001253 register long
anthony602ab9b2010-01-05 08:06:50 +00001254 x;
1255
anthony29188a82010-01-22 10:12:34 +00001256 unsigned long
anthony602ab9b2010-01-05 08:06:50 +00001257 r;
1258
1259 if (status == MagickFalse)
1260 continue;
anthony29188a82010-01-22 10:12:34 +00001261 p=GetCacheViewVirtualPixels(p_view, -offx, y-offy,
1262 image->columns+kernel->width, kernel->height, exception);
anthony602ab9b2010-01-05 08:06:50 +00001263 q=GetCacheViewAuthenticPixels(q_view,0,y,result_image->columns,1,
1264 exception);
1265 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1266 {
1267 status=MagickFalse;
1268 continue;
1269 }
1270 p_indexes=GetCacheViewVirtualIndexQueue(p_view);
1271 q_indexes=GetCacheViewAuthenticIndexQueue(q_view);
anthony29188a82010-01-22 10:12:34 +00001272 r = (image->columns+kernel->width)*offy+offx; /* constant */
1273
cristy150989e2010-02-01 14:59:39 +00001274 for (x=0; x < (long) image->columns; x++)
anthony602ab9b2010-01-05 08:06:50 +00001275 {
cristy150989e2010-02-01 14:59:39 +00001276 long
anthony602ab9b2010-01-05 08:06:50 +00001277 v;
1278
cristy150989e2010-02-01 14:59:39 +00001279 register long
anthony602ab9b2010-01-05 08:06:50 +00001280 u;
1281
1282 register const double
1283 *restrict k;
1284
1285 register const PixelPacket
1286 *restrict k_pixels;
1287
1288 register const IndexPacket
1289 *restrict k_indexes;
1290
1291 MagickPixelPacket
anthony5ef8e942010-05-11 06:51:12 +00001292 result,
1293 min,
1294 max;
anthony602ab9b2010-01-05 08:06:50 +00001295
anthony29188a82010-01-22 10:12:34 +00001296 /* Copy input to ouput image for unused channels
anthony83ba99b2010-01-24 08:48:15 +00001297 * This removes need for 'cloning' a new image every iteration
anthony29188a82010-01-22 10:12:34 +00001298 */
anthony602ab9b2010-01-05 08:06:50 +00001299 *q = p[r];
1300 if (image->colorspace == CMYKColorspace)
1301 q_indexes[x] = p_indexes[r];
1302
anthony5ef8e942010-05-11 06:51:12 +00001303 /* Defaults */
1304 min.red =
1305 min.green =
1306 min.blue =
1307 min.opacity =
1308 min.index = (MagickRealType) QuantumRange;
1309 max.red =
1310 max.green =
1311 max.blue =
1312 max.opacity =
1313 max.index = (MagickRealType) 0;
1314 /* original pixel value */
1315 result.red = (MagickRealType) p[r].red;
1316 result.green = (MagickRealType) p[r].green;
1317 result.blue = (MagickRealType) p[r].blue;
1318 result.opacity = QuantumRange - (MagickRealType) p[r].opacity;
1319 if ( image->colorspace == CMYKColorspace)
1320 result.index = (MagickRealType) p_indexes[r];
1321
anthony602ab9b2010-01-05 08:06:50 +00001322 switch (method) {
1323 case ConvolveMorphology:
anthony930be612010-02-08 04:26:15 +00001324 /* Set the user defined bias of the weighted average output
1325 **
1326 ** FUTURE: provide some way for internal functions to disable
anthony5ef8e942010-05-11 06:51:12 +00001327 ** user provided bias and scaling effects.
anthony930be612010-02-08 04:26:15 +00001328 */
anthony602ab9b2010-01-05 08:06:50 +00001329 result=bias;
anthony930be612010-02-08 04:26:15 +00001330 break;
anthony4fd27e22010-02-07 08:17:18 +00001331 case DilateIntensityMorphology:
1332 case ErodeIntensityMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001333 result.red = 0.0; /* flag indicating when first match found */
anthony4fd27e22010-02-07 08:17:18 +00001334 break;
anthony602ab9b2010-01-05 08:06:50 +00001335 default:
anthony602ab9b2010-01-05 08:06:50 +00001336 break;
1337 }
1338
1339 switch ( method ) {
1340 case ConvolveMorphology:
anthony930be612010-02-08 04:26:15 +00001341 /* Weighted Average of pixels using reflected kernel
1342 **
1343 ** NOTE for correct working of this operation for asymetrical
1344 ** kernels, the kernel needs to be applied in its reflected form.
1345 ** That is its values needs to be reversed.
1346 **
1347 ** Correlation is actually the same as this but without reflecting
1348 ** the kernel, and thus 'lower-level' that Convolution. However
1349 ** as Convolution is the more common method used, and it does not
1350 ** really cost us much in terms of processing to use a reflected
anthony5ef8e942010-05-11 06:51:12 +00001351 ** kernel, so it is Convolution that is implemented.
anthony930be612010-02-08 04:26:15 +00001352 **
1353 ** Correlation will have its kernel reflected before calling
1354 ** this function to do a Convolve.
1355 **
1356 ** For more details of Correlation vs Convolution see
1357 ** http://www.cs.umd.edu/~djacobs/CMSC426/Convolution.pdf
1358 */
anthony5ef8e942010-05-11 06:51:12 +00001359 if (((channel & SyncChannels) != 0 ) &&
1360 (image->matte == MagickTrue))
1361 { /* Channel has a 'Sync' Flag, and Alpha Channel enabled.
1362 ** Weight the color channels with Alpha Channel so that
1363 ** transparent pixels are not part of the results.
1364 */
anthony602ab9b2010-01-05 08:06:50 +00001365 MagickRealType
anthony5ef8e942010-05-11 06:51:12 +00001366 alpha, /* color channel weighting : kernel*alpha */
1367 gamma; /* divisor, sum of weighting values */
anthony602ab9b2010-01-05 08:06:50 +00001368
1369 gamma=0.0;
anthony29188a82010-01-22 10:12:34 +00001370 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001371 k_pixels = p;
1372 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001373 for (v=0; v < (long) kernel->height; v++) {
1374 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001375 if ( IsNan(*k) ) continue;
1376 alpha=(*k)*(QuantumScale*(QuantumRange-
1377 k_pixels[u].opacity));
1378 gamma += alpha;
1379 result.red += alpha*k_pixels[u].red;
1380 result.green += alpha*k_pixels[u].green;
1381 result.blue += alpha*k_pixels[u].blue;
anthony83ba99b2010-01-24 08:48:15 +00001382 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00001383 if ( image->colorspace == CMYKColorspace)
1384 result.index += alpha*k_indexes[u];
1385 }
1386 k_pixels += image->columns+kernel->width;
1387 k_indexes += image->columns+kernel->width;
1388 }
1389 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
anthony83ba99b2010-01-24 08:48:15 +00001390 result.red *= gamma;
1391 result.green *= gamma;
1392 result.blue *= gamma;
1393 result.opacity *= gamma;
1394 result.index *= gamma;
anthony602ab9b2010-01-05 08:06:50 +00001395 }
anthony5ef8e942010-05-11 06:51:12 +00001396 else
1397 {
1398 /* No 'Sync' flag, or no Alpha involved.
1399 ** Convolution is simple individual channel weigthed sum.
1400 */
1401 k = &kernel->values[ kernel->width*kernel->height-1 ];
1402 k_pixels = p;
1403 k_indexes = p_indexes;
1404 for (v=0; v < (long) kernel->height; v++) {
1405 for (u=0; u < (long) kernel->width; u++, k--) {
1406 if ( IsNan(*k) ) continue;
1407 result.red += (*k)*k_pixels[u].red;
1408 result.green += (*k)*k_pixels[u].green;
1409 result.blue += (*k)*k_pixels[u].blue;
1410 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
1411 if ( image->colorspace == CMYKColorspace)
1412 result.index += (*k)*k_indexes[u];
1413 }
1414 k_pixels += image->columns+kernel->width;
1415 k_indexes += image->columns+kernel->width;
1416 }
1417 }
anthony602ab9b2010-01-05 08:06:50 +00001418 break;
1419
anthony4fd27e22010-02-07 08:17:18 +00001420 case ErodeMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001421 /* Minimum Value within kernel neighbourhood
anthony930be612010-02-08 04:26:15 +00001422 **
1423 ** NOTE that the kernel is not reflected for this operation!
1424 **
1425 ** NOTE: in normal Greyscale Morphology, the kernel value should
1426 ** be added to the real value, this is currently not done, due to
1427 ** the nature of the boolean kernels being used.
1428 */
anthony4fd27e22010-02-07 08:17:18 +00001429 k = kernel->values;
1430 k_pixels = p;
1431 k_indexes = p_indexes;
1432 for (v=0; v < (long) kernel->height; v++) {
1433 for (u=0; u < (long) kernel->width; u++, k++) {
1434 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony5ef8e942010-05-11 06:51:12 +00001435 Minimize(min.red, (double) k_pixels[u].red);
1436 Minimize(min.green, (double) k_pixels[u].green);
1437 Minimize(min.blue, (double) k_pixels[u].blue);
1438 Minimize(min.opacity,
anthonyd37a5cb2010-05-07 06:37:03 +00001439 QuantumRange-(double) k_pixels[u].opacity);
anthony4fd27e22010-02-07 08:17:18 +00001440 if ( image->colorspace == CMYKColorspace)
anthony5ef8e942010-05-11 06:51:12 +00001441 Minimize(min.index, (double) k_indexes[u]);
anthony4fd27e22010-02-07 08:17:18 +00001442 }
1443 k_pixels += image->columns+kernel->width;
1444 k_indexes += image->columns+kernel->width;
1445 }
1446 break;
1447
anthony5ef8e942010-05-11 06:51:12 +00001448
anthony83ba99b2010-01-24 08:48:15 +00001449 case DilateMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001450 /* Maximum Value within kernel neighbourhood
anthony930be612010-02-08 04:26:15 +00001451 **
1452 ** NOTE for correct working of this operation for asymetrical
1453 ** kernels, the kernel needs to be applied in its reflected form.
1454 ** That is its values needs to be reversed.
1455 **
1456 ** NOTE: in normal Greyscale Morphology, the kernel value should
1457 ** be added to the real value, this is currently not done, due to
1458 ** the nature of the boolean kernels being used.
1459 **
1460 */
anthony29188a82010-01-22 10:12:34 +00001461 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001462 k_pixels = p;
1463 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001464 for (v=0; v < (long) kernel->height; v++) {
1465 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001466 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony5ef8e942010-05-11 06:51:12 +00001467 Maximize(max.red, (double) k_pixels[u].red);
1468 Maximize(max.green, (double) k_pixels[u].green);
1469 Maximize(max.blue, (double) k_pixels[u].blue);
1470 Maximize(max.opacity,
anthonyd37a5cb2010-05-07 06:37:03 +00001471 QuantumRange-(double) k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00001472 if ( image->colorspace == CMYKColorspace)
anthony5ef8e942010-05-11 06:51:12 +00001473 Maximize(max.index, (double) k_indexes[u]);
anthony602ab9b2010-01-05 08:06:50 +00001474 }
1475 k_pixels += image->columns+kernel->width;
1476 k_indexes += image->columns+kernel->width;
1477 }
anthony602ab9b2010-01-05 08:06:50 +00001478 break;
1479
anthony5ef8e942010-05-11 06:51:12 +00001480 case HitAndMissMorphology:
1481 case ThinningMorphology:
1482 case ThickenMorphology:
1483 /* Minimum of Foreground Pixel minus Maxumum of Background Pixels
1484 **
1485 ** NOTE that the kernel is not reflected for this operation,
1486 ** and consists of both foreground and background pixel
1487 ** neighbourhoods, 0.0 for background, and 1.0 for foreground
1488 ** with either Nan or 0.5 values for don't care.
1489 **
1490 ** Note that this can produce negative results, though really
1491 ** only a positive match has any real value.
1492 */
1493 k = kernel->values;
1494 k_pixels = p;
1495 k_indexes = p_indexes;
1496 for (v=0; v < (long) kernel->height; v++) {
1497 for (u=0; u < (long) kernel->width; u++, k++) {
1498 if ( IsNan(*k) ) continue;
1499 if ( (*k) > 0.7 )
1500 { /* minimim of foreground pixels */
1501 Minimize(min.red, (double) k_pixels[u].red);
1502 Minimize(min.green, (double) k_pixels[u].green);
1503 Minimize(min.blue, (double) k_pixels[u].blue);
1504 Minimize(min.opacity,
1505 QuantumRange-(double) k_pixels[u].opacity);
1506 if ( image->colorspace == CMYKColorspace)
1507 Minimize(min.index, (double) k_indexes[u]);
1508 }
1509 else if ( (*k) < 0.3 )
1510 { /* maximum of background pixels */
1511 Maximize(max.red, (double) k_pixels[u].red);
1512 Maximize(max.green, (double) k_pixels[u].green);
1513 Maximize(max.blue, (double) k_pixels[u].blue);
1514 Maximize(max.opacity,
1515 QuantumRange-(double) k_pixels[u].opacity);
1516 if ( image->colorspace == CMYKColorspace)
1517 Maximize(max.index, (double) k_indexes[u]);
1518 }
1519 }
1520 k_pixels += image->columns+kernel->width;
1521 k_indexes += image->columns+kernel->width;
1522 }
1523 /* Pattern Match only if min fg larger than min bg pixels */
1524 min.red -= max.red; Maximize( min.red, 0.0 );
1525 min.green -= max.green; Maximize( min.green, 0.0 );
1526 min.blue -= max.blue; Maximize( min.blue, 0.0 );
1527 min.opacity -= max.opacity; Maximize( min.opacity, 0.0 );
1528 min.index -= max.index; Maximize( min.index, 0.0 );
1529 break;
1530
anthony4fd27e22010-02-07 08:17:18 +00001531 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001532 /* Select Pixel with Minimum Intensity within kernel neighbourhood
1533 **
1534 ** WARNING: the intensity test fails for CMYK and does not
1535 ** take into account the moderating effect of teh alpha channel
1536 ** on the intensity.
1537 **
1538 ** NOTE that the kernel is not reflected for this operation!
1539 */
anthony602ab9b2010-01-05 08:06:50 +00001540 k = kernel->values;
1541 k_pixels = p;
1542 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001543 for (v=0; v < (long) kernel->height; v++) {
1544 for (u=0; u < (long) kernel->width; u++, k++) {
anthony602ab9b2010-01-05 08:06:50 +00001545 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony4fd27e22010-02-07 08:17:18 +00001546 if ( result.red == 0.0 ||
1547 PixelIntensity(&(k_pixels[u])) < PixelIntensity(q) ) {
1548 /* copy the whole pixel - no channel selection */
1549 *q = k_pixels[u];
1550 if ( result.red > 0.0 ) changed++;
1551 result.red = 1.0;
1552 }
anthony602ab9b2010-01-05 08:06:50 +00001553 }
1554 k_pixels += image->columns+kernel->width;
1555 k_indexes += image->columns+kernel->width;
1556 }
anthony602ab9b2010-01-05 08:06:50 +00001557 break;
1558
anthony83ba99b2010-01-24 08:48:15 +00001559 case DilateIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001560 /* Select Pixel with Maximum Intensity within kernel neighbourhood
1561 **
1562 ** WARNING: the intensity test fails for CMYK and does not
1563 ** take into account the moderating effect of teh alpha channel
1564 ** on the intensity.
1565 **
1566 ** NOTE for correct working of this operation for asymetrical
1567 ** kernels, the kernel needs to be applied in its reflected form.
1568 ** That is its values needs to be reversed.
1569 */
anthony29188a82010-01-22 10:12:34 +00001570 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001571 k_pixels = p;
1572 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001573 for (v=0; v < (long) kernel->height; v++) {
1574 for (u=0; u < (long) kernel->width; u++, k--) {
anthony29188a82010-01-22 10:12:34 +00001575 if ( IsNan(*k) || (*k) < 0.5 ) continue; /* boolean kernel */
1576 if ( result.red == 0.0 ||
1577 PixelIntensity(&(k_pixels[u])) > PixelIntensity(q) ) {
1578 /* copy the whole pixel - no channel selection */
1579 *q = k_pixels[u];
1580 if ( result.red > 0.0 ) changed++;
1581 result.red = 1.0;
1582 }
anthony602ab9b2010-01-05 08:06:50 +00001583 }
1584 k_pixels += image->columns+kernel->width;
1585 k_indexes += image->columns+kernel->width;
1586 }
anthony602ab9b2010-01-05 08:06:50 +00001587 break;
1588
anthony5ef8e942010-05-11 06:51:12 +00001589
anthony602ab9b2010-01-05 08:06:50 +00001590 case DistanceMorphology:
anthony930be612010-02-08 04:26:15 +00001591 /* Add kernel Value and select the minimum value found.
1592 ** The result is a iterative distance from edge of image shape.
1593 **
1594 ** All Distance Kernels are symetrical, but that may not always
1595 ** be the case. For example how about a distance from left edges?
1596 ** To work correctly with asymetrical kernels the reflected kernel
1597 ** needs to be applied.
anthony5ef8e942010-05-11 06:51:12 +00001598 **
1599 ** Actually this is really a GreyErode with a negative kernel!
1600 **
anthony930be612010-02-08 04:26:15 +00001601 */
anthony29188a82010-01-22 10:12:34 +00001602 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001603 k_pixels = p;
1604 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001605 for (v=0; v < (long) kernel->height; v++) {
1606 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001607 if ( IsNan(*k) ) continue;
1608 Minimize(result.red, (*k)+k_pixels[u].red);
1609 Minimize(result.green, (*k)+k_pixels[u].green);
1610 Minimize(result.blue, (*k)+k_pixels[u].blue);
1611 Minimize(result.opacity, (*k)+QuantumRange-k_pixels[u].opacity);
1612 if ( image->colorspace == CMYKColorspace)
1613 Minimize(result.index, (*k)+k_indexes[u]);
1614 }
1615 k_pixels += image->columns+kernel->width;
1616 k_indexes += image->columns+kernel->width;
1617 }
anthony602ab9b2010-01-05 08:06:50 +00001618 break;
1619
1620 case UndefinedMorphology:
1621 default:
1622 break; /* Do nothing */
anthony83ba99b2010-01-24 08:48:15 +00001623 }
anthony5ef8e942010-05-11 06:51:12 +00001624 /* Final mathematics of results (combine with original image?)
1625 **
1626 ** NOTE: Difference Morphology operators Edge* and *Hat could also
1627 ** be done here but works better with iteration as a image difference
1628 ** in the controling function (below). Thicken and Thinning however
1629 ** should be done here so thay can be iterated correctly.
1630 */
1631 switch ( method ) {
1632 case HitAndMissMorphology:
1633 case ErodeMorphology:
1634 result = min; /* minimum of neighbourhood */
1635 break;
1636 case DilateMorphology:
1637 result = max; /* maximum of neighbourhood */
1638 break;
1639 case ThinningMorphology:
1640 /* subtract pattern match from original */
1641 result.red -= min.red;
1642 result.green -= min.green;
1643 result.blue -= min.blue;
1644 result.opacity -= min.opacity;
1645 result.index -= min.index;
1646 break;
1647 case ThickenMorphology:
1648 /* Union with original image (maximize) - or should this be + */
1649 Maximize( result.red, min.red );
1650 Maximize( result.green, min.green );
1651 Maximize( result.blue, min.blue );
1652 Maximize( result.opacity, min.opacity );
1653 Maximize( result.index, min.index );
1654 break;
1655 default:
1656 /* result directly calculated or assigned */
1657 break;
1658 }
1659 /* Assign the resulting pixel values - Clamping Result */
anthony83ba99b2010-01-24 08:48:15 +00001660 switch ( method ) {
1661 case UndefinedMorphology:
1662 case DilateIntensityMorphology:
1663 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001664 break; /* full pixel was directly assigned - not a channel method */
anthony83ba99b2010-01-24 08:48:15 +00001665 default:
anthony83ba99b2010-01-24 08:48:15 +00001666 if ((channel & RedChannel) != 0)
1667 q->red = ClampToQuantum(result.red);
1668 if ((channel & GreenChannel) != 0)
1669 q->green = ClampToQuantum(result.green);
1670 if ((channel & BlueChannel) != 0)
1671 q->blue = ClampToQuantum(result.blue);
1672 if ((channel & OpacityChannel) != 0
1673 && image->matte == MagickTrue )
1674 q->opacity = ClampToQuantum(QuantumRange-result.opacity);
1675 if ((channel & IndexChannel) != 0
1676 && image->colorspace == CMYKColorspace)
1677 q_indexes[x] = ClampToQuantum(result.index);
1678 break;
1679 }
anthony5ef8e942010-05-11 06:51:12 +00001680 /* Count up changed pixels */
anthony83ba99b2010-01-24 08:48:15 +00001681 if ( ( p[r].red != q->red )
1682 || ( p[r].green != q->green )
1683 || ( p[r].blue != q->blue )
1684 || ( p[r].opacity != q->opacity )
1685 || ( image->colorspace == CMYKColorspace &&
1686 p_indexes[r] != q_indexes[x] ) )
1687 changed++; /* The pixel had some value changed! */
anthony602ab9b2010-01-05 08:06:50 +00001688 p++;
1689 q++;
anthony83ba99b2010-01-24 08:48:15 +00001690 } /* x */
anthony602ab9b2010-01-05 08:06:50 +00001691 sync=SyncCacheViewAuthenticPixels(q_view,exception);
1692 if (sync == MagickFalse)
1693 status=MagickFalse;
1694 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1695 {
1696 MagickBooleanType
1697 proceed;
1698
1699#if defined(MAGICKCORE_OPENMP_SUPPORT)
1700 #pragma omp critical (MagickCore_MorphologyImage)
1701#endif
1702 proceed=SetImageProgress(image,MorphologyTag,progress++,image->rows);
1703 if (proceed == MagickFalse)
1704 status=MagickFalse;
1705 }
anthony83ba99b2010-01-24 08:48:15 +00001706 } /* y */
anthony602ab9b2010-01-05 08:06:50 +00001707 result_image->type=image->type;
1708 q_view=DestroyCacheView(q_view);
1709 p_view=DestroyCacheView(p_view);
cristy150989e2010-02-01 14:59:39 +00001710 return(status ? (unsigned long) changed : 0);
anthony602ab9b2010-01-05 08:06:50 +00001711}
1712
anthony4fd27e22010-02-07 08:17:18 +00001713
1714MagickExport Image *MorphologyImage(const Image *image, const MorphologyMethod
anthony930be612010-02-08 04:26:15 +00001715 method, const long iterations,const KernelInfo *kernel, ExceptionInfo
1716 *exception)
cristy2be15382010-01-21 02:38:03 +00001717{
1718 Image
1719 *morphology_image;
1720
1721 morphology_image=MorphologyImageChannel(image,DefaultChannels,method,
1722 iterations,kernel,exception);
1723 return(morphology_image);
1724}
1725
anthony4fd27e22010-02-07 08:17:18 +00001726
cristyef656912010-03-05 19:54:59 +00001727MagickExport Image *MorphologyImageChannel(const Image *image,
1728 const ChannelType channel,const MorphologyMethod method,
1729 const long iterations,const KernelInfo *kernel,ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001730{
anthony602ab9b2010-01-05 08:06:50 +00001731 Image
1732 *new_image,
anthony1b2bc0a2010-05-12 05:25:22 +00001733 *old_image,
anthony4fd27e22010-02-07 08:17:18 +00001734 *grad_image;
anthony602ab9b2010-01-05 08:06:50 +00001735
anthony4fd27e22010-02-07 08:17:18 +00001736 KernelInfo
anthony1b2bc0a2010-05-12 05:25:22 +00001737 *curr_kernel,
1738 *this_kernel;
anthony4fd27e22010-02-07 08:17:18 +00001739
1740 MorphologyMethod
1741 curr_method;
1742
anthony1b2bc0a2010-05-12 05:25:22 +00001743 unsigned long
1744 count,
1745 limit,
1746 changed,
1747 total_changed,
1748 kernel_number;
1749
anthony602ab9b2010-01-05 08:06:50 +00001750 assert(image != (Image *) NULL);
1751 assert(image->signature == MagickSignature);
anthony4fd27e22010-02-07 08:17:18 +00001752 assert(kernel != (KernelInfo *) NULL);
1753 assert(kernel->signature == MagickSignature);
anthony602ab9b2010-01-05 08:06:50 +00001754 assert(exception != (ExceptionInfo *) NULL);
1755 assert(exception->signature == MagickSignature);
1756
anthony602ab9b2010-01-05 08:06:50 +00001757 if ( iterations == 0 )
1758 return((Image *)NULL); /* null operation - nothing to do! */
1759
1760 /* kernel must be valid at this point
1761 * (except maybe for posible future morphology methods like "Prune"
1762 */
cristy2be15382010-01-21 02:38:03 +00001763 assert(kernel != (KernelInfo *)NULL);
anthony602ab9b2010-01-05 08:06:50 +00001764
anthony1b2bc0a2010-05-12 05:25:22 +00001765 new_image = (Image *) NULL; /* for cleanup */
1766 old_image = (Image *) NULL;
1767 grad_image = (Image *) NULL;
1768 curr_kernel = (KernelInfo *) NULL;
1769 count = 0; /* interation count */
1770 changed = 1; /* was last run succesfull */
anthony930be612010-02-08 04:26:15 +00001771 curr_kernel = (KernelInfo *)kernel; /* allow kernel and method */
1772 curr_method = method; /* to be changed as nessary */
anthony4fd27e22010-02-07 08:17:18 +00001773
cristy150989e2010-02-01 14:59:39 +00001774 limit = (unsigned long) iterations;
anthony602ab9b2010-01-05 08:06:50 +00001775 if ( iterations < 0 )
1776 limit = image->columns > image->rows ? image->columns : image->rows;
1777
anthony4fd27e22010-02-07 08:17:18 +00001778 /* Third-level morphology methods */
1779 switch( curr_method ) {
1780 case EdgeMorphology:
1781 grad_image = MorphologyImageChannel(image, channel,
1782 DilateMorphology, iterations, curr_kernel, exception);
anthony1b2bc0a2010-05-12 05:25:22 +00001783 if ( grad_image == (Image *) NULL )
1784 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00001785 /* FALL-THRU */
1786 case EdgeInMorphology:
1787 curr_method = ErodeMorphology;
anthony602ab9b2010-01-05 08:06:50 +00001788 break;
anthony4fd27e22010-02-07 08:17:18 +00001789 case EdgeOutMorphology:
1790 curr_method = DilateMorphology;
anthony602ab9b2010-01-05 08:06:50 +00001791 break;
anthony4fd27e22010-02-07 08:17:18 +00001792 case TopHatMorphology:
1793 curr_method = OpenMorphology;
1794 break;
1795 case BottomHatMorphology:
1796 curr_method = CloseMorphology;
1797 break;
1798 default:
anthony930be612010-02-08 04:26:15 +00001799 break; /* not a third-level method */
anthony4fd27e22010-02-07 08:17:18 +00001800 }
1801
1802 /* Second-level morphology methods */
1803 switch( curr_method ) {
anthony930be612010-02-08 04:26:15 +00001804 case OpenMorphology:
1805 /* Open is a Erode then a Dilate without reflection */
anthony4fd27e22010-02-07 08:17:18 +00001806 new_image = MorphologyImageChannel(image, channel,
1807 ErodeMorphology, iterations, curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001808 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00001809 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00001810 curr_method = DilateMorphology;
1811 break;
anthony602ab9b2010-01-05 08:06:50 +00001812 case OpenIntensityMorphology:
anthony4fd27e22010-02-07 08:17:18 +00001813 new_image = MorphologyImageChannel(image, channel,
1814 ErodeIntensityMorphology, iterations, curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001815 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00001816 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00001817 curr_method = DilateIntensityMorphology;
1818 break;
anthony930be612010-02-08 04:26:15 +00001819
1820 case CloseMorphology:
1821 /* Close is a Dilate then Erode using reflected kernel */
1822 /* A reflected kernel is needed for a Close */
1823 if ( curr_kernel == kernel )
1824 curr_kernel = CloneKernelInfo(kernel);
anthony1b2bc0a2010-05-12 05:25:22 +00001825 if (curr_kernel == (KernelInfo *) NULL)
1826 goto error_cleanup;
anthony930be612010-02-08 04:26:15 +00001827 RotateKernelInfo(curr_kernel,180);
1828 new_image = MorphologyImageChannel(image, channel,
1829 DilateMorphology, iterations, curr_kernel, exception);
1830 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00001831 goto error_cleanup;
anthony930be612010-02-08 04:26:15 +00001832 curr_method = ErodeMorphology;
1833 break;
anthony4fd27e22010-02-07 08:17:18 +00001834 case CloseIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001835 /* A reflected kernel is needed for a Close */
1836 if ( curr_kernel == kernel )
1837 curr_kernel = CloneKernelInfo(kernel);
anthony1b2bc0a2010-05-12 05:25:22 +00001838 if (curr_kernel == (KernelInfo *) NULL)
1839 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00001840 RotateKernelInfo(curr_kernel,180);
1841 new_image = MorphologyImageChannel(image, channel,
1842 DilateIntensityMorphology, iterations, curr_kernel, exception);
1843 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00001844 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00001845 curr_method = ErodeIntensityMorphology;
anthony602ab9b2010-01-05 08:06:50 +00001846 break;
1847
anthony930be612010-02-08 04:26:15 +00001848 case CorrelateMorphology:
1849 /* A Correlation is actually a Convolution with a reflected kernel.
1850 ** However a Convolution is a weighted sum with a reflected kernel.
1851 ** It may seem stange to convert a Correlation into a Convolution
1852 ** as the Correleation is the simplier method, but Convolution is
1853 ** much more commonly used, and it makes sense to implement it directly
1854 ** so as to avoid the need to duplicate the kernel when it is not
1855 ** required (which is typically the default).
1856 */
1857 if ( curr_kernel == kernel )
1858 curr_kernel = CloneKernelInfo(kernel);
anthony1b2bc0a2010-05-12 05:25:22 +00001859 if (curr_kernel == (KernelInfo *) NULL)
1860 goto error_cleanup;
anthony930be612010-02-08 04:26:15 +00001861 RotateKernelInfo(curr_kernel,180);
1862 curr_method = ConvolveMorphology;
1863 /* FALL-THRU into Correlate (weigthed sum without reflection) */
1864
anthonyc94cdb02010-01-06 08:15:29 +00001865 case ConvolveMorphology:
anthony1b2bc0a2010-05-12 05:25:22 +00001866 { /* Scale or Normalize kernel, according to user wishes
1867 ** before using it for the Convolve/Correlate method.
1868 **
1869 ** FUTURE: provide some way for internal functions to disable
1870 ** user bias and scaling effects.
1871 */
1872 const char
1873 *artifact = GetImageArtifact(image,"convolve:scale");
1874 if ( artifact != (char *)NULL ) {
1875 GeometryFlags
1876 flags;
1877 GeometryInfo
1878 args;
anthony999bb2c2010-02-18 12:38:01 +00001879
anthony1b2bc0a2010-05-12 05:25:22 +00001880 if ( curr_kernel == kernel )
1881 curr_kernel = CloneKernelInfo(kernel);
1882 if (curr_kernel == (KernelInfo *) NULL)
1883 goto error_cleanup;
1884 args.rho = 1.0;
1885 flags = (GeometryFlags) ParseGeometry(artifact, &args);
1886 ScaleKernelInfo(curr_kernel, args.rho, flags);
1887 }
anthony4fd27e22010-02-07 08:17:18 +00001888 }
anthony930be612010-02-08 04:26:15 +00001889 /* FALL-THRU to do the first, and typically the only iteration */
anthony4fd27e22010-02-07 08:17:18 +00001890
anthony602ab9b2010-01-05 08:06:50 +00001891 default:
anthony930be612010-02-08 04:26:15 +00001892 /* Do a single iteration using the Low-Level Morphology method!
1893 ** This ensures a "new_image" has been generated, but allows us to skip
1894 ** the creation of 'old_image' if no more iterations are needed.
1895 **
1896 ** The "curr_method" should also be set to a low-level method that is
anthony7a01dcf2010-05-11 12:25:52 +00001897 ** understood by the MorphologyPrimative() internal function.
anthony602ab9b2010-01-05 08:06:50 +00001898 */
1899 new_image=CloneImage(image,0,0,MagickTrue,exception);
1900 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00001901 goto error_cleanup;
anthony602ab9b2010-01-05 08:06:50 +00001902 if (SetImageStorageClass(new_image,DirectClass) == MagickFalse)
1903 {
1904 InheritException(exception,&new_image->exception);
anthony1b2bc0a2010-05-12 05:25:22 +00001905 goto exit_cleanup;
anthony602ab9b2010-01-05 08:06:50 +00001906 }
anthony1b2bc0a2010-05-12 05:25:22 +00001907 count++; /* iteration count */
anthony7a01dcf2010-05-11 12:25:52 +00001908 changed = MorphologyPrimative(image,new_image,curr_method,channel,
1909 curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001910 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
anthony1b2bc0a2010-05-12 05:25:22 +00001911 fprintf(stderr, "Morphology %s:%lu.%lu => Changed %lu\n",
anthony4fd27e22010-02-07 08:17:18 +00001912 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
anthony1b2bc0a2010-05-12 05:25:22 +00001913 count, 0L, changed);
anthony930be612010-02-08 04:26:15 +00001914 break;
anthony602ab9b2010-01-05 08:06:50 +00001915 }
anthony1b2bc0a2010-05-12 05:25:22 +00001916 /* At this point
1917 * + "curr_method" should be set to a low-level morphology method
1918 * + "count=1" if the first iteration of the first kernel has been done.
1919 * + "new_image" is defined and contains the current resulting image
1920 */
anthony602ab9b2010-01-05 08:06:50 +00001921
anthony1b2bc0a2010-05-12 05:25:22 +00001922 /* The Hit-And-Miss Morphology is not 'iterated' against the resulting
1923 ** image from the previous morphology step. It must always be applied
1924 ** to the original image, with the results collected into a union (maximimum
1925 ** or lighten) of all the results. Multiple kernels may be applied but
1926 ** an iteration of the morphology does nothing, so is ignored.
anthony7a01dcf2010-05-11 12:25:52 +00001927 **
anthony1b2bc0a2010-05-12 05:25:22 +00001928 ** The first kernel is guranteed to have been done, so lets continue the
1929 ** process, with the other kernels in the kernel list.
anthony7a01dcf2010-05-11 12:25:52 +00001930 */
anthony1b2bc0a2010-05-12 05:25:22 +00001931 if ( method == HitAndMissMorphology )
anthony7a01dcf2010-05-11 12:25:52 +00001932 {
anthony1b2bc0a2010-05-12 05:25:22 +00001933 if ( curr_kernel->next != (KernelInfo *) NULL ) {
1934 /* create a second working image */
1935 old_image = CloneImage(image,0,0,MagickTrue,exception);
1936 if (old_image == (Image *) NULL)
1937 goto error_cleanup;
1938 if (SetImageStorageClass(old_image,DirectClass) == MagickFalse)
1939 {
1940 InheritException(exception,&old_image->exception);
1941 goto exit_cleanup;
1942 }
anthony7a01dcf2010-05-11 12:25:52 +00001943
anthony1b2bc0a2010-05-12 05:25:22 +00001944 /* loop through rest of the kernels */
1945 this_kernel=curr_kernel->next;
1946 kernel_number=1;
1947 while( this_kernel != (KernelInfo *) NULL )
1948 {
1949 changed = MorphologyPrimative(image,old_image,curr_method,channel,
1950 this_kernel,exception);
1951 (void) CompositeImageChannel(new_image,
1952 (ChannelType) (channel & ~SyncChannels), LightenCompositeOp,
1953 old_image, 0, 0);
1954 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
1955 fprintf(stderr, "Morphology %s:%lu.%lu => Changed %lu\n",
1956 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
1957 count, kernel_number, changed);
1958 this_kernel = this_kernel->next;
1959 kernel_number++;
1960 }
1961 old_image=DestroyImage(old_image);
1962 }
1963 goto exit_cleanup;
anthony7a01dcf2010-05-11 12:25:52 +00001964 }
1965
anthony1b2bc0a2010-05-12 05:25:22 +00001966 /* Repeat the low-level morphology over all kernels
1967 until iteration count limit or no change from any kernel is found */
1968 if ( ( count < limit && changed > 0 ) ||
1969 curr_kernel->next != (KernelInfo *) NULL ) {
anthony930be612010-02-08 04:26:15 +00001970
anthony1b2bc0a2010-05-12 05:25:22 +00001971 /* create a second working image */
1972 old_image = CloneImage(image,0,0,MagickTrue,exception);
anthony602ab9b2010-01-05 08:06:50 +00001973 if (old_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00001974 goto error_cleanup;
anthony602ab9b2010-01-05 08:06:50 +00001975 if (SetImageStorageClass(old_image,DirectClass) == MagickFalse)
1976 {
1977 InheritException(exception,&old_image->exception);
anthony1b2bc0a2010-05-12 05:25:22 +00001978 goto exit_cleanup;
anthony602ab9b2010-01-05 08:06:50 +00001979 }
anthony1b2bc0a2010-05-12 05:25:22 +00001980
1981 /* reset variables for the first/next iteration, or next kernel) */
1982 kernel_number = 0;
1983 this_kernel = curr_kernel;
1984 total_changed = count != 0 ? changed : 0;
1985 if ( count != 0 && this_kernel != (KernelInfo *) NULL ) {
1986 count = 0; /* first iteration is not yet finished! */
1987 this_kernel = curr_kernel->next;
1988 kernel_number = 1;
1989 total_changed = changed;
1990 }
1991
1992 while ( count < limit ) {
1993 count++;
1994 while ( this_kernel != (KernelInfo *) NULL ) {
anthony602ab9b2010-01-05 08:06:50 +00001995 Image *tmp = old_image;
1996 old_image = new_image;
1997 new_image = tmp;
anthony7a01dcf2010-05-11 12:25:52 +00001998 changed = MorphologyPrimative(old_image,new_image,curr_method,channel,
anthony1b2bc0a2010-05-12 05:25:22 +00001999 this_kernel,exception);
anthony602ab9b2010-01-05 08:06:50 +00002000 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
anthony1b2bc0a2010-05-12 05:25:22 +00002001 fprintf(stderr, "Morphology %s:%lu.%lu => Changed %lu\n",
anthony4fd27e22010-02-07 08:17:18 +00002002 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
anthony1b2bc0a2010-05-12 05:25:22 +00002003 count, kernel_number, changed);
2004 total_changed += changed;
2005 this_kernel = this_kernel->next;
2006 kernel_number++;
anthony602ab9b2010-01-05 08:06:50 +00002007 }
anthony1b2bc0a2010-05-12 05:25:22 +00002008 if ( total_changed == 0 )
2009 break; /* no changes after processing all kernels - ABORT */
2010 /* prepare for next loop */
2011 total_changed = 0;
2012 kernel_number = 0;
2013 this_kernel = curr_kernel;
2014 }
cristy150989e2010-02-01 14:59:39 +00002015 old_image=DestroyImage(old_image);
anthony602ab9b2010-01-05 08:06:50 +00002016 }
anthony930be612010-02-08 04:26:15 +00002017
anthony1b2bc0a2010-05-12 05:25:22 +00002018 /* finished with kernel - destary any copy that was made */
anthony4fd27e22010-02-07 08:17:18 +00002019 if ( curr_kernel != kernel )
2020 curr_kernel=DestroyKernelInfo(curr_kernel);
2021
anthony7d10d742010-05-06 07:05:29 +00002022 /* Third-level Subtractive methods post-processing
2023 **
2024 ** The removal of any 'Sync' channel flag in the Image Compositon below
2025 ** ensures the compose method is applied in a purely mathematical way, only
2026 ** the selected channels, without any normal 'alpha blending' normally
2027 ** associated with the compose method.
2028 **
2029 ** Note "method" here is the 'original' morphological method, and not the
2030 ** 'current' morphological method used above to generate "new_image".
2031 */
anthony4fd27e22010-02-07 08:17:18 +00002032 switch( method ) {
2033 case EdgeOutMorphology:
2034 case EdgeInMorphology:
2035 case TopHatMorphology:
2036 case BottomHatMorphology:
anthony930be612010-02-08 04:26:15 +00002037 /* Get Difference relative to the original image */
cristyaeb2cbc2010-05-07 13:28:58 +00002038 (void) CompositeImageChannel(new_image, (ChannelType) (channel & ~SyncChannels),
anthony7d10d742010-05-06 07:05:29 +00002039 DifferenceCompositeOp, image, 0, 0);
anthony4fd27e22010-02-07 08:17:18 +00002040 break;
anthony7d10d742010-05-06 07:05:29 +00002041 case EdgeMorphology:
2042 /* Difference the Eroded image from the saved Dilated image */
cristyaeb2cbc2010-05-07 13:28:58 +00002043 (void) CompositeImageChannel(new_image, (ChannelType) (channel & ~SyncChannels),
anthony7d10d742010-05-06 07:05:29 +00002044 DifferenceCompositeOp, grad_image, 0, 0);
anthony4fd27e22010-02-07 08:17:18 +00002045 grad_image=DestroyImage(grad_image);
2046 break;
2047 default:
2048 break;
2049 }
anthony602ab9b2010-01-05 08:06:50 +00002050
2051 return(new_image);
anthony1b2bc0a2010-05-12 05:25:22 +00002052
2053 /* Yes goto's are bad, but in this case it makes cleanup lot more efficient */
2054error_cleanup:
2055 if ( new_image != (Image *) NULL )
2056 DestroyImage(new_image);
2057exit_cleanup:
2058 if ( old_image != (Image *) NULL )
2059 DestroyImage(old_image);
2060 if ( curr_kernel != (KernelInfo *) NULL && curr_kernel != kernel )
2061 curr_kernel=DestroyKernelInfo(curr_kernel);
2062 return(new_image);
anthony602ab9b2010-01-05 08:06:50 +00002063}
anthony83ba99b2010-01-24 08:48:15 +00002064
2065/*
2066%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2067% %
2068% %
2069% %
anthony4fd27e22010-02-07 08:17:18 +00002070+ R o t a t e K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00002071% %
2072% %
2073% %
2074%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2075%
anthony4fd27e22010-02-07 08:17:18 +00002076% RotateKernelInfo() rotates the kernel by the angle given. Currently it is
anthony83ba99b2010-01-24 08:48:15 +00002077% restricted to 90 degree angles, but this may be improved in the future.
2078%
anthony4fd27e22010-02-07 08:17:18 +00002079% The format of the RotateKernelInfo method is:
anthony83ba99b2010-01-24 08:48:15 +00002080%
anthony4fd27e22010-02-07 08:17:18 +00002081% void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00002082%
2083% A description of each parameter follows:
2084%
2085% o kernel: the Morphology/Convolution kernel
2086%
2087% o angle: angle to rotate in degrees
2088%
anthonyc4c86e02010-01-27 09:30:32 +00002089% This function is only internel to this module, as it is not finalized,
2090% especially with regard to non-orthogonal angles, and rotation of larger
2091% 2D kernels.
anthony83ba99b2010-01-24 08:48:15 +00002092*/
anthony4fd27e22010-02-07 08:17:18 +00002093static void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00002094{
anthony1b2bc0a2010-05-12 05:25:22 +00002095 /* angle the lower kernels first */
2096 if ( kernel->next != (KernelInfo *) NULL)
2097 RotateKernelInfo(kernel->next, angle);
2098
anthony83ba99b2010-01-24 08:48:15 +00002099 /* WARNING: Currently assumes the kernel (rightly) is horizontally symetrical
2100 **
2101 ** TODO: expand beyond simple 90 degree rotates, flips and flops
2102 */
2103
2104 /* Modulus the angle */
2105 angle = fmod(angle, 360.0);
2106 if ( angle < 0 )
2107 angle += 360.0;
2108
2109 if ( 315.0 < angle || angle <= 45.0 )
2110 return; /* no change! - At least at this time */
2111
2112 switch (kernel->type) {
2113 /* These built-in kernels are cylindrical kernels, rotating is useless */
2114 case GaussianKernel:
2115 case LaplacianKernel:
2116 case LOGKernel:
2117 case DOGKernel:
2118 case DiskKernel:
2119 case ChebyshevKernel:
2120 case ManhattenKernel:
2121 case EuclideanKernel:
2122 return;
2123
2124 /* These may be rotatable at non-90 angles in the future */
2125 /* but simply rotating them in multiples of 90 degrees is useless */
2126 case SquareKernel:
2127 case DiamondKernel:
2128 case PlusKernel:
2129 return;
2130
2131 /* These only allows a +/-90 degree rotation (by transpose) */
2132 /* A 180 degree rotation is useless */
2133 case BlurKernel:
2134 case RectangleKernel:
2135 if ( 135.0 < angle && angle <= 225.0 )
2136 return;
2137 if ( 225.0 < angle && angle <= 315.0 )
2138 angle -= 180;
2139 break;
2140
2141 /* these are freely rotatable in 90 degree units */
2142 case CometKernel:
2143 case UndefinedKernel:
2144 case UserDefinedKernel:
2145 break;
2146 }
2147 if ( 135.0 < angle && angle <= 225.0 )
2148 {
2149 /* For a 180 degree rotation - also know as a reflection */
2150 /* This is actually a very very common operation! */
2151 /* Basically all that is needed is a reversal of the kernel data! */
2152 unsigned long
2153 i,j;
2154 register double
2155 *k,t;
2156
2157 k=kernel->values;
2158 for ( i=0, j=kernel->width*kernel->height-1; i<j; i++, j--)
2159 t=k[i], k[i]=k[j], k[j]=t;
2160
anthony930be612010-02-08 04:26:15 +00002161 kernel->x = (long) kernel->width - kernel->x - 1;
2162 kernel->y = (long) kernel->height - kernel->y - 1;
anthony83ba99b2010-01-24 08:48:15 +00002163 angle = fmod(angle+180.0, 360.0);
2164 }
2165 if ( 45.0 < angle && angle <= 135.0 )
2166 { /* Do a transpose and a flop, of the image, which results in a 90
2167 * degree rotation using two mirror operations.
2168 *
2169 * WARNING: this assumes the original image was a 1 dimentional image
2170 * but currently that is the only built-ins it is applied to.
2171 */
cristy150989e2010-02-01 14:59:39 +00002172 long
anthony83ba99b2010-01-24 08:48:15 +00002173 t;
cristy150989e2010-02-01 14:59:39 +00002174 t = (long) kernel->width;
anthony83ba99b2010-01-24 08:48:15 +00002175 kernel->width = kernel->height;
cristy150989e2010-02-01 14:59:39 +00002176 kernel->height = (unsigned long) t;
cristyc99304f2010-02-01 15:26:27 +00002177 t = kernel->x;
2178 kernel->x = kernel->y;
2179 kernel->y = t;
anthony83ba99b2010-01-24 08:48:15 +00002180 angle = fmod(450.0 - angle, 360.0);
2181 }
2182 /* At this point angle should be between -45 (315) and +45 degrees
2183 * In the future some form of non-orthogonal angled rotates could be
2184 * performed here, posibily with a linear kernel restriction.
2185 */
2186
2187#if 0
2188 Not currently in use!
2189 { /* Do a flop, this assumes kernel is horizontally symetrical.
2190 * Each row of the kernel needs to be reversed!
2191 */
2192 unsigned long
2193 y;
cristy150989e2010-02-01 14:59:39 +00002194 register long
anthony83ba99b2010-01-24 08:48:15 +00002195 x,r;
2196 register double
2197 *k,t;
2198
2199 for ( y=0, k=kernel->values; y < kernel->height; y++, k+=kernel->width)
2200 for ( x=0, r=kernel->width-1; x<kernel->width/2; x++, r--)
2201 t=k[x], k[x]=k[r], k[r]=t;
2202
cristyc99304f2010-02-01 15:26:27 +00002203 kernel->x = kernel->width - kernel->x - 1;
anthony83ba99b2010-01-24 08:48:15 +00002204 angle = fmod(angle+180.0, 360.0);
2205 }
2206#endif
2207 return;
2208}
2209
2210/*
2211%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2212% %
2213% %
2214% %
cristy6771f1e2010-03-05 19:43:39 +00002215% S c a l e K e r n e l I n f o %
anthonycc6c8362010-01-25 04:14:01 +00002216% %
2217% %
2218% %
2219%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2220%
anthony1b2bc0a2010-05-12 05:25:22 +00002221% ScaleKernelInfo() scales the given kernel list by the given amount, with or
2222% without normalization of the sum of the kernel values (as per given flags).
anthonycc6c8362010-01-25 04:14:01 +00002223%
anthony999bb2c2010-02-18 12:38:01 +00002224% By default (no flags given) the values within the kernel is scaled
anthony1b2bc0a2010-05-12 05:25:22 +00002225% directly using given scaling factor without change.
anthonycc6c8362010-01-25 04:14:01 +00002226%
anthony1b2bc0a2010-05-12 05:25:22 +00002227% If any 'normalize_flags' are given the kernel will first be normalized and
2228% then further scaled by the scaling factor value given. A 'PercentValue'
2229% flag will cause the given scaling factor to be divided by one hundred
2230% percent.
anthony999bb2c2010-02-18 12:38:01 +00002231%
2232% Kernel normalization ('normalize_flags' given) is designed to ensure that
2233% any use of the kernel scaling factor with 'Convolve' or 'Correlate'
anthony1b2bc0a2010-05-12 05:25:22 +00002234% morphology methods will fall into -1.0 to +1.0 range. Note that for
2235% non-HDRI versions of IM this may cause images to have any negative results
2236% clipped, unless some 'bias' is used.
anthony999bb2c2010-02-18 12:38:01 +00002237%
2238% More specifically. Kernels which only contain positive values (such as a
2239% 'Gaussian' kernel) will be scaled so that those values sum to +1.0,
anthony1b2bc0a2010-05-12 05:25:22 +00002240% ensuring a 0.0 to +1.0 output range for non-HDRI images.
anthony999bb2c2010-02-18 12:38:01 +00002241%
2242% For Kernels that contain some negative values, (such as 'Sharpen' kernels)
2243% the kernel will be scaled by the absolute of the sum of kernel values, so
2244% that it will generally fall within the +/- 1.0 range.
2245%
2246% For kernels whose values sum to zero, (such as 'Laplician' kernels) kernel
2247% will be scaled by just the sum of the postive values, so that its output
2248% range will again fall into the +/- 1.0 range.
2249%
2250% For special kernels designed for locating shapes using 'Correlate', (often
2251% only containing +1 and -1 values, representing foreground/brackground
2252% matching) a special normalization method is provided to scale the positive
2253% values seperatally to those of the negative values, so the kernel will be
2254% forced to become a zero-sum kernel better suited to such searches.
2255%
anthony1b2bc0a2010-05-12 05:25:22 +00002256% WARNING: Correct normalization of the kernel assumes that the '*_range'
anthony999bb2c2010-02-18 12:38:01 +00002257% attributes within the kernel structure have been correctly set during the
2258% kernels creation.
2259%
2260% NOTE: The values used for 'normalize_flags' have been selected specifically
anthony1b2bc0a2010-05-12 05:25:22 +00002261% to match the use of geometry options, so that '!' means NormalizeValue,
2262% '^' means CorrelateNormalizeValue, and '%' means PercentValue. All other
anthony999bb2c2010-02-18 12:38:01 +00002263% GeometryFlags values are ignored.
anthonycc6c8362010-01-25 04:14:01 +00002264%
anthony4fd27e22010-02-07 08:17:18 +00002265% The format of the ScaleKernelInfo method is:
anthonycc6c8362010-01-25 04:14:01 +00002266%
anthony999bb2c2010-02-18 12:38:01 +00002267% void ScaleKernelInfo(KernelInfo *kernel, const double scaling_factor,
2268% const MagickStatusType normalize_flags )
anthonycc6c8362010-01-25 04:14:01 +00002269%
2270% A description of each parameter follows:
2271%
2272% o kernel: the Morphology/Convolution kernel
2273%
anthony999bb2c2010-02-18 12:38:01 +00002274% o scaling_factor:
2275% multiply all values (after normalization) by this factor if not
2276% zero. If the kernel is normalized regardless of any flags.
2277%
2278% o normalize_flags:
2279% GeometryFlags defining normalization method to use.
2280% specifically: NormalizeValue, CorrelateNormalizeValue,
2281% and/or PercentValue
anthonycc6c8362010-01-25 04:14:01 +00002282%
anthonyc4c86e02010-01-27 09:30:32 +00002283% This function is internal to this module only at this time, but can be
2284% exported to other modules if needed.
anthonycc6c8362010-01-25 04:14:01 +00002285*/
cristy6771f1e2010-03-05 19:43:39 +00002286MagickExport void ScaleKernelInfo(KernelInfo *kernel,
2287 const double scaling_factor,const GeometryFlags normalize_flags)
anthonycc6c8362010-01-25 04:14:01 +00002288{
cristy150989e2010-02-01 14:59:39 +00002289 register long
anthonycc6c8362010-01-25 04:14:01 +00002290 i;
2291
anthony999bb2c2010-02-18 12:38:01 +00002292 register double
2293 pos_scale,
2294 neg_scale;
2295
anthony1b2bc0a2010-05-12 05:25:22 +00002296 /* scale the lower kernels first */
2297 if ( kernel->next != (KernelInfo *) NULL)
2298 ScaleKernelInfo(kernel->next, scaling_factor, normalize_flags);
2299
anthony999bb2c2010-02-18 12:38:01 +00002300 pos_scale = 1.0;
2301 if ( (normalize_flags&NormalizeValue) != 0 ) {
2302 /* normalize kernel appropriately */
2303 if ( fabs(kernel->positive_range + kernel->negative_range) > MagickEpsilon )
2304 pos_scale = fabs(kernel->positive_range + kernel->negative_range);
anthonycc6c8362010-01-25 04:14:01 +00002305 else
anthony999bb2c2010-02-18 12:38:01 +00002306 pos_scale = kernel->positive_range; /* special zero-summing kernel */
2307 }
2308 /* force kernel into being a normalized zero-summing kernel */
2309 if ( (normalize_flags&CorrelateNormalizeValue) != 0 ) {
2310 pos_scale = ( fabs(kernel->positive_range) > MagickEpsilon )
2311 ? kernel->positive_range : 1.0;
2312 neg_scale = ( fabs(kernel->negative_range) > MagickEpsilon )
2313 ? -kernel->negative_range : 1.0;
2314 }
2315 else
2316 neg_scale = pos_scale;
2317
2318 /* finialize scaling_factor for positive and negative components */
2319 pos_scale = scaling_factor/pos_scale;
2320 neg_scale = scaling_factor/neg_scale;
2321 if ( (normalize_flags&PercentValue) != 0 ) {
2322 pos_scale /= 100.0;
2323 neg_scale /= 100.0;
anthonycc6c8362010-01-25 04:14:01 +00002324 }
2325
cristy150989e2010-02-01 14:59:39 +00002326 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00002327 if ( ! IsNan(kernel->values[i]) )
anthony999bb2c2010-02-18 12:38:01 +00002328 kernel->values[i] *= (kernel->values[i] >= 0) ? pos_scale : neg_scale;
anthonycc6c8362010-01-25 04:14:01 +00002329
anthony999bb2c2010-02-18 12:38:01 +00002330 /* convolution output range */
2331 kernel->positive_range *= pos_scale;
2332 kernel->negative_range *= neg_scale;
2333 /* maximum and minimum values in kernel */
2334 kernel->maximum *= (kernel->maximum >= 0.0) ? pos_scale : neg_scale;
2335 kernel->minimum *= (kernel->minimum >= 0.0) ? pos_scale : neg_scale;
2336
2337 /* swap kernel settings if user scaling factor is negative */
2338 if ( scaling_factor < MagickEpsilon ) {
2339 double t;
2340 t = kernel->positive_range;
2341 kernel->positive_range = kernel->negative_range;
2342 kernel->negative_range = t;
2343 t = kernel->maximum;
2344 kernel->maximum = kernel->minimum;
2345 kernel->minimum = 1;
2346 }
anthonycc6c8362010-01-25 04:14:01 +00002347
2348 return;
2349}
2350
2351/*
2352%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2353% %
2354% %
2355% %
anthony4fd27e22010-02-07 08:17:18 +00002356+ S h o w K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00002357% %
2358% %
2359% %
2360%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2361%
anthony4fd27e22010-02-07 08:17:18 +00002362% ShowKernelInfo() outputs the details of the given kernel defination to
2363% standard error, generally due to a users 'showkernel' option request.
anthony83ba99b2010-01-24 08:48:15 +00002364%
2365% The format of the ShowKernel method is:
2366%
anthony4fd27e22010-02-07 08:17:18 +00002367% void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00002368%
2369% A description of each parameter follows:
2370%
2371% o kernel: the Morphology/Convolution kernel
2372%
anthonyc4c86e02010-01-27 09:30:32 +00002373% This function is internal to this module only at this time. That may change
2374% in the future.
anthony83ba99b2010-01-24 08:48:15 +00002375*/
anthony4fd27e22010-02-07 08:17:18 +00002376MagickExport void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00002377{
anthony7a01dcf2010-05-11 12:25:52 +00002378 KernelInfo
2379 *k;
anthony83ba99b2010-01-24 08:48:15 +00002380
anthony7a01dcf2010-05-11 12:25:52 +00002381 long
2382 c, i, u, v;
2383
2384 for (c=0, k=kernel; k != (KernelInfo *) NULL; c++, k=k->next ) {
2385
2386 fprintf(stderr, "Kernel ");
2387 if ( kernel->next != (KernelInfo *) NULL )
2388 fprintf(stderr, " #%ld", c );
2389 fprintf(stderr, " \"%s\" of size %lux%lu%+ld%+ld ",
2390 MagickOptionToMnemonic(MagickKernelOptions, k->type),
2391 kernel->width, k->height,
2392 kernel->x, kernel->y );
2393 fprintf(stderr,
2394 " with values from %.*lg to %.*lg\n",
2395 GetMagickPrecision(), k->minimum,
2396 GetMagickPrecision(), k->maximum);
2397 fprintf(stderr, "Forming convolution output range from %.*lg to %.*lg%s\n",
2398 GetMagickPrecision(), k->negative_range,
2399 GetMagickPrecision(), k->positive_range,
2400 /*kernel->normalized == MagickTrue ? " (normalized)" : */ "" );
2401 for (i=v=0; v < (long) k->height; v++) {
2402 fprintf(stderr,"%2ld:",v);
2403 for (u=0; u < (long) k->width; u++, i++)
2404 if ( IsNan(k->values[i]) )
2405 fprintf(stderr," %*s", GetMagickPrecision()+2, "nan");
2406 else
2407 fprintf(stderr," %*.*lg", GetMagickPrecision()+2,
2408 GetMagickPrecision(), k->values[i]);
2409 fprintf(stderr,"\n");
2410 }
anthony83ba99b2010-01-24 08:48:15 +00002411 }
2412}
anthonycc6c8362010-01-25 04:14:01 +00002413
2414/*
2415%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2416% %
2417% %
2418% %
anthony4fd27e22010-02-07 08:17:18 +00002419+ Z e r o K e r n e l N a n s %
anthonycc6c8362010-01-25 04:14:01 +00002420% %
2421% %
2422% %
2423%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2424%
2425% ZeroKernelNans() replaces any special 'nan' value that may be present in
2426% the kernel with a zero value. This is typically done when the kernel will
2427% be used in special hardware (GPU) convolution processors, to simply
2428% matters.
2429%
2430% The format of the ZeroKernelNans method is:
2431%
2432% voidZeroKernelNans (KernelInfo *kernel)
2433%
2434% A description of each parameter follows:
2435%
2436% o kernel: the Morphology/Convolution kernel
2437%
2438% FUTURE: return the information in a string for API usage.
2439*/
anthonyc4c86e02010-01-27 09:30:32 +00002440MagickExport void ZeroKernelNans(KernelInfo *kernel)
anthonycc6c8362010-01-25 04:14:01 +00002441{
cristy150989e2010-02-01 14:59:39 +00002442 register long
anthonycc6c8362010-01-25 04:14:01 +00002443 i;
2444
anthony1b2bc0a2010-05-12 05:25:22 +00002445 /* scale the lower kernels first */
2446 if ( kernel->next != (KernelInfo *) NULL)
2447 ZeroKernelNans(kernel->next);
2448
cristy150989e2010-02-01 14:59:39 +00002449 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00002450 if ( IsNan(kernel->values[i]) )
2451 kernel->values[i] = 0.0;
2452
2453 return;
2454}