blob: 4f6b4c668abfb810c308af9dfe04ceb5741f2071 [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
anthonye108a3f2010-05-12 07:24:03 +0000430 unsigned long
431 kernel_number;
432
anthonydbc89892010-05-12 07:05:27 +0000433 p = kernel_string;
434 kernel = last_kernel = NULL;
anthonye108a3f2010-05-12 07:24:03 +0000435 kernel_number = 0;
anthony5ef8e942010-05-11 06:51:12 +0000436
anthonydbc89892010-05-12 07:05:27 +0000437 while ( GetMagickToken(p,NULL,token), *token != '\0' ) {
anthony7a01dcf2010-05-11 12:25:52 +0000438
anthonydbc89892010-05-12 07:05:27 +0000439 /* ignore multiple ';' following each other */
440 if ( *token != ';' ) {
anthony7a01dcf2010-05-11 12:25:52 +0000441
anthonydbc89892010-05-12 07:05:27 +0000442 /* tokens starting with alpha is a Named kernel */
443 if (isalpha((int) *token) == 0)
444 new_kernel = ParseKernelArray(p);
445 else /* otherwise a user defined kernel array */
446 new_kernel = ParseNamedKernel(p);
anthony7a01dcf2010-05-11 12:25:52 +0000447
anthonye108a3f2010-05-12 07:24:03 +0000448 /* Error handling -- this is not proper error handling! */
449 if ( new_kernel == (KernelInfo *) NULL ) {
450 fprintf(stderr, "Failed to parse kernel number #%lu\n", kernel_number);
451 if ( kernel != (KernelInfo *) NULL )
452 kernel=DestroyKernelInfo(kernel);
453 return((KernelInfo *) NULL);
anthonydbc89892010-05-12 07:05:27 +0000454 }
anthonye108a3f2010-05-12 07:24:03 +0000455
456 /* initialise or append the kernel list */
457 if ( last_kernel == (KernelInfo *) NULL )
458 kernel = last_kernel = new_kernel;
anthonydbc89892010-05-12 07:05:27 +0000459 else {
anthonydbc89892010-05-12 07:05:27 +0000460 last_kernel->next = new_kernel;
461 last_kernel = new_kernel;
462 }
463 }
464
465 /* look for the next kernel in list */
466 p = strchr(p, ';');
467 if ( p == (char *) NULL )
468 break;
469 p++;
470
471 }
anthony7a01dcf2010-05-11 12:25:52 +0000472 return(kernel);
anthony5ef8e942010-05-11 06:51:12 +0000473}
474
anthony602ab9b2010-01-05 08:06:50 +0000475
476/*
477%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
478% %
479% %
480% %
481% A c q u i r e K e r n e l B u i l t I n %
482% %
483% %
484% %
485%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
486%
487% AcquireKernelBuiltIn() returned one of the 'named' built-in types of
488% kernels used for special purposes such as gaussian blurring, skeleton
489% pruning, and edge distance determination.
490%
491% They take a KernelType, and a set of geometry style arguments, which were
492% typically decoded from a user supplied string, or from a more complex
493% Morphology Method that was requested.
494%
495% The format of the AcquireKernalBuiltIn method is:
496%
cristy2be15382010-01-21 02:38:03 +0000497% KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000498% const GeometryInfo args)
499%
500% A description of each parameter follows:
501%
502% o type: the pre-defined type of kernel wanted
503%
504% o args: arguments defining or modifying the kernel
505%
506% Convolution Kernels
507%
anthony4fd27e22010-02-07 08:17:18 +0000508% Gaussian "{radius},{sigma}"
anthony602ab9b2010-01-05 08:06:50 +0000509% Generate a two-dimentional gaussian kernel, as used by -gaussian
510% A sigma is required, (with the 'x'), due to historical reasons.
511%
512% NOTE: that the 'radius' is optional, but if provided can limit (clip)
513% the final size of the resulting kernel to a square 2*radius+1 in size.
514% The radius should be at least 2 times that of the sigma value, or
515% sever clipping and aliasing may result. If not given or set to 0 the
516% radius will be determined so as to produce the best minimal error
517% result, which is usally much larger than is normally needed.
518%
anthony4fd27e22010-02-07 08:17:18 +0000519% Blur "{radius},{sigma},{angle}"
anthony602ab9b2010-01-05 08:06:50 +0000520% As per Gaussian, but generates a 1 dimensional or linear gaussian
521% blur, at the angle given (current restricted to orthogonal angles).
522% If a 'radius' is given the kernel is clipped to a width of 2*radius+1.
523%
524% NOTE that two such blurs perpendicular to each other is equivelent to
525% -blur and the previous gaussian, but is often 10 or more times faster.
526%
anthony4fd27e22010-02-07 08:17:18 +0000527% Comet "{width},{sigma},{angle}"
anthony602ab9b2010-01-05 08:06:50 +0000528% Blur in one direction only, mush like how a bright object leaves
529% a comet like trail. The Kernel is actually half a gaussian curve,
530% Adding two such blurs in oppiste directions produces a Linear Blur.
531%
532% NOTE: that the first argument is the width of the kernel and not the
533% radius of the kernel.
534%
535% # Still to be implemented...
536% #
anthony4fd27e22010-02-07 08:17:18 +0000537% # Sharpen "{radius},{sigma}
538% # Negated Gaussian (center zeroed and re-normalized),
539% # with a 2 unit positive peak. -- Check On line documentation
540% #
541% # Laplacian "{radius},{sigma}"
anthony602ab9b2010-01-05 08:06:50 +0000542% # Laplacian (a mexican hat like) Function
543% #
544% # LOG "{radius},{sigma1},{sigma2}
545% # Laplacian of Gaussian
546% #
547% # DOG "{radius},{sigma1},{sigma2}
anthony4fd27e22010-02-07 08:17:18 +0000548% # Difference of two Gaussians
549% #
550% # Filter2D
551% # Filter1D
552% # Set kernel values using a resize filter, and given scale (sigma)
553% # Cylindrical or Linear. Is this posible with an image?
554% #
anthony602ab9b2010-01-05 08:06:50 +0000555%
556% Boolean Kernels
557%
558% Rectangle "{geometry}"
559% Simply generate a rectangle of 1's with the size given. You can also
560% specify the location of the 'control point', otherwise the closest
561% pixel to the center of the rectangle is selected.
562%
563% Properly centered and odd sized rectangles work the best.
564%
anthony4fd27e22010-02-07 08:17:18 +0000565% Diamond "[{radius}[,{scale}]]"
anthony1b2bc0a2010-05-12 05:25:22 +0000566% Generate a diamond shaped kernel with given radius to the points.
anthony602ab9b2010-01-05 08:06:50 +0000567% Kernel size will again be radius*2+1 square and defaults to radius 1,
568% generating a 3x3 kernel that is slightly larger than a square.
569%
anthony4fd27e22010-02-07 08:17:18 +0000570% Square "[{radius}[,{scale}]]"
anthony602ab9b2010-01-05 08:06:50 +0000571% Generate a square shaped kernel of size radius*2+1, and defaulting
572% to a 3x3 (radius 1).
573%
574% Note that using a larger radius for the "Square" or the "Diamond"
575% is also equivelent to iterating the basic morphological method
576% that many times. However However iterating with the smaller radius 1
577% default is actually faster than using a larger kernel radius.
578%
anthony4fd27e22010-02-07 08:17:18 +0000579% Disk "[{radius}[,{scale}]]
anthony602ab9b2010-01-05 08:06:50 +0000580% Generate a binary disk of the radius given, radius may be a float.
581% Kernel size will be ceil(radius)*2+1 square.
582% NOTE: Here are some disk shapes of specific interest
583% "disk:1" => "diamond" or "cross:1"
584% "disk:1.5" => "square"
585% "disk:2" => "diamond:2"
anthony83ba99b2010-01-24 08:48:15 +0000586% "disk:2.5" => a general disk shape of radius 2
anthony602ab9b2010-01-05 08:06:50 +0000587% "disk:2.9" => "square:2"
anthony83ba99b2010-01-24 08:48:15 +0000588% "disk:3.5" => default - octagonal/disk shape of radius 3
anthony602ab9b2010-01-05 08:06:50 +0000589% "disk:4.2" => roughly octagonal shape of radius 4
anthony83ba99b2010-01-24 08:48:15 +0000590% "disk:4.3" => a general disk shape of radius 4
anthony602ab9b2010-01-05 08:06:50 +0000591% After this all the kernel shape becomes more and more circular.
592%
593% Because a "disk" is more circular when using a larger radius, using a
594% larger radius is preferred over iterating the morphological operation.
595%
anthony4fd27e22010-02-07 08:17:18 +0000596% Plus "[{radius}[,{scale}]]"
anthony602ab9b2010-01-05 08:06:50 +0000597% Generate a kernel in the shape of a 'plus' sign. The length of each
598% arm is also the radius, which defaults to 2.
599%
600% This kernel is not a good general morphological kernel, but is used
601% more for highlighting and marking any single pixels in an image using,
602% a "Dilate" or "Erode" method as appropriate.
anthonyc94cdb02010-01-06 08:15:29 +0000603%
anthony602ab9b2010-01-05 08:06:50 +0000604% NOTE: "plus:1" is equivelent to a "Diamond" kernel.
605%
606% Note that unlike other kernels iterating a plus does not produce the
607% same result as using a larger radius for the cross.
608%
609% Distance Measuring Kernels
610%
anthonyc84dce52010-05-07 05:42:23 +0000611% Chebyshev "[{radius}][x{scale}[%!]]"
612% Manhatten "[{radius}][x{scale}[%!]]"
613% Euclidean "[{radius}][x{scale}[%!]]"
anthony602ab9b2010-01-05 08:06:50 +0000614%
615% Different types of distance measuring methods, which are used with the
616% a 'Distance' morphology method for generating a gradient based on
617% distance from an edge of a binary shape, though there is a technique
618% for handling a anti-aliased shape.
619%
anthonyc94cdb02010-01-06 08:15:29 +0000620% Chebyshev Distance (also known as Tchebychev Distance) is a value of
621% one to any neighbour, orthogonal or diagonal. One why of thinking of
622% it is the number of squares a 'King' or 'Queen' in chess needs to
623% traverse reach any other position on a chess board. It results in a
624% 'square' like distance function, but one where diagonals are closer
625% than expected.
anthony602ab9b2010-01-05 08:06:50 +0000626%
anthonyc94cdb02010-01-06 08:15:29 +0000627% Manhatten Distance (also known as Rectilinear Distance, or the Taxi
628% Cab metric), is the distance needed when you can only travel in
629% orthogonal (horizontal or vertical) only. It is the distance a 'Rook'
630% in chess would travel. It results in a diamond like distances, where
631% diagonals are further than expected.
anthony602ab9b2010-01-05 08:06:50 +0000632%
anthonyc94cdb02010-01-06 08:15:29 +0000633% Euclidean Distance is the 'direct' or 'as the crow flys distance.
634% However by default the kernel size only has a radius of 1, which
635% limits the distance to 'Knight' like moves, with only orthogonal and
636% diagonal measurements being correct. As such for the default kernel
637% you will get octagonal like distance function, which is reasonally
638% accurate.
639%
640% However if you use a larger radius such as "Euclidean:4" you will
641% get a much smoother distance gradient from the edge of the shape.
642% Of course a larger kernel is slower to use, and generally not needed.
643%
644% To allow the use of fractional distances that you get with diagonals
645% the actual distance is scaled by a fixed value which the user can
646% provide. This is not actually nessary for either ""Chebyshev" or
647% "Manhatten" distance kernels, but is done for all three distance
648% kernels. If no scale is provided it is set to a value of 100,
649% allowing for a maximum distance measurement of 655 pixels using a Q16
650% version of IM, from any edge. However for small images this can
651% result in quite a dark gradient.
652%
653% See the 'Distance' Morphological Method, for information of how it is
654% applied.
anthony602ab9b2010-01-05 08:06:50 +0000655%
anthony4fd27e22010-02-07 08:17:18 +0000656% # Hit-n-Miss Kernel-Lists -- Still to be implemented
657% #
658% # specifically for Pruning, Thinning, Thickening
659% #
anthony602ab9b2010-01-05 08:06:50 +0000660*/
661
cristy2be15382010-01-21 02:38:03 +0000662MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000663 const GeometryInfo *args)
664{
cristy2be15382010-01-21 02:38:03 +0000665 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000666 *kernel;
667
cristy150989e2010-02-01 14:59:39 +0000668 register long
anthony602ab9b2010-01-05 08:06:50 +0000669 i;
670
671 register long
672 u,
673 v;
674
675 double
676 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
677
cristy2be15382010-01-21 02:38:03 +0000678 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
679 if (kernel == (KernelInfo *) NULL)
anthony602ab9b2010-01-05 08:06:50 +0000680 return(kernel);
681 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
cristyc99304f2010-02-01 15:26:27 +0000682 kernel->minimum = kernel->maximum = 0.0;
683 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000684 kernel->type = type;
anthony7a01dcf2010-05-11 12:25:52 +0000685 kernel->next = (KernelInfo *) NULL;
cristyd43a46b2010-01-21 02:13:41 +0000686 kernel->signature = MagickSignature;
anthony602ab9b2010-01-05 08:06:50 +0000687
688 switch(type) {
689 /* Convolution Kernels */
690 case GaussianKernel:
691 { double
692 sigma = fabs(args->sigma);
693
694 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
695
696 kernel->width = kernel->height =
697 GetOptimalKernelWidth2D(args->rho,sigma);
cristyc99304f2010-02-01 15:26:27 +0000698 kernel->x = kernel->y = (long) (kernel->width-1)/2;
699 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000700 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
701 kernel->height*sizeof(double));
702 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000703 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000704
705 sigma = 2.0*sigma*sigma; /* simplify the expression */
cristyc99304f2010-02-01 15:26:27 +0000706 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
707 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
708 kernel->positive_range += (
anthony602ab9b2010-01-05 08:06:50 +0000709 kernel->values[i] =
710 exp(-((double)(u*u+v*v))/sigma)
711 /* / (MagickPI*sigma) */ );
cristyc99304f2010-02-01 15:26:27 +0000712 kernel->minimum = 0;
713 kernel->maximum = kernel->values[
714 kernel->y*kernel->width+kernel->x ];
anthony602ab9b2010-01-05 08:06:50 +0000715
anthony999bb2c2010-02-18 12:38:01 +0000716 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
anthony602ab9b2010-01-05 08:06:50 +0000717
718 break;
719 }
720 case BlurKernel:
721 { double
722 sigma = fabs(args->sigma);
723
724 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
725
726 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
cristyc99304f2010-02-01 15:26:27 +0000727 kernel->x = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000728 kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +0000729 kernel->y = 0;
730 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000731 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
732 kernel->height*sizeof(double));
733 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000734 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000735
736#if 1
737#define KernelRank 3
738 /* Formula derived from GetBlurKernel() in "effect.c" (plus bug fix).
739 ** It generates a gaussian 3 times the width, and compresses it into
740 ** the expected range. This produces a closer normalization of the
741 ** resulting kernel, especially for very low sigma values.
742 ** As such while wierd it is prefered.
743 **
744 ** I am told this method originally came from Photoshop.
745 */
746 sigma *= KernelRank; /* simplify expanded curve */
cristy150989e2010-02-01 14:59:39 +0000747 v = (long) (kernel->width*KernelRank-1)/2; /* start/end points to fit range */
anthony602ab9b2010-01-05 08:06:50 +0000748 (void) ResetMagickMemory(kernel->values,0, (size_t)
749 kernel->width*sizeof(double));
750 for ( u=-v; u <= v; u++) {
751 kernel->values[(u+v)/KernelRank] +=
752 exp(-((double)(u*u))/(2.0*sigma*sigma))
753 /* / (MagickSQ2PI*sigma/KernelRank) */ ;
754 }
cristy150989e2010-02-01 14:59:39 +0000755 for (i=0; i < (long) kernel->width; i++)
cristyc99304f2010-02-01 15:26:27 +0000756 kernel->positive_range += kernel->values[i];
anthony602ab9b2010-01-05 08:06:50 +0000757#else
cristyc99304f2010-02-01 15:26:27 +0000758 for ( i=0, u=-kernel->x; i < kernel->width; i++, u++)
759 kernel->positive_range += (
anthony602ab9b2010-01-05 08:06:50 +0000760 kernel->values[i] =
761 exp(-((double)(u*u))/(2.0*sigma*sigma))
762 /* / (MagickSQ2PI*sigma) */ );
763#endif
cristyc99304f2010-02-01 15:26:27 +0000764 kernel->minimum = 0;
765 kernel->maximum = kernel->values[ kernel->x ];
anthonycc6c8362010-01-25 04:14:01 +0000766 /* Note that neither methods above generate a normalized kernel,
767 ** though it gets close. The kernel may be 'clipped' by a user defined
768 ** radius, producing a smaller (darker) kernel. Also for very small
769 ** sigma's (> 0.1) the central value becomes larger than one, and thus
770 ** producing a very bright kernel.
anthony602ab9b2010-01-05 08:06:50 +0000771 */
anthonycc6c8362010-01-25 04:14:01 +0000772
anthony602ab9b2010-01-05 08:06:50 +0000773 /* Normalize the 1D Gaussian Kernel
774 **
775 ** Because of this the divisor in the above kernel generator is
anthonyc94cdb02010-01-06 08:15:29 +0000776 ** not needed, so is not done above.
anthony602ab9b2010-01-05 08:06:50 +0000777 */
anthony999bb2c2010-02-18 12:38:01 +0000778 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
anthonycc6c8362010-01-25 04:14:01 +0000779
anthony602ab9b2010-01-05 08:06:50 +0000780 /* rotate the kernel by given angle */
anthony4fd27e22010-02-07 08:17:18 +0000781 RotateKernelInfo(kernel, args->xi);
anthony602ab9b2010-01-05 08:06:50 +0000782 break;
783 }
784 case CometKernel:
785 { double
786 sigma = fabs(args->sigma);
787
788 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
789
790 if ( args->rho < 1.0 )
791 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
792 else
793 kernel->width = (unsigned long)args->rho;
cristyc99304f2010-02-01 15:26:27 +0000794 kernel->x = kernel->y = 0;
anthony602ab9b2010-01-05 08:06:50 +0000795 kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +0000796 kernel->negative_range = kernel->positive_range = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000797 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
798 kernel->height*sizeof(double));
799 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000800 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000801
802 /* A comet blur is half a gaussian curve, so that the object is
803 ** blurred in one direction only. This may not be quite the right
804 ** curve so may change in the future. The function must be normalised.
805 */
806#if 1
807#define KernelRank 3
808 sigma *= KernelRank; /* simplify expanded curve */
cristy150989e2010-02-01 14:59:39 +0000809 v = (long) kernel->width*KernelRank; /* start/end points to fit range */
anthony602ab9b2010-01-05 08:06:50 +0000810 (void) ResetMagickMemory(kernel->values,0, (size_t)
811 kernel->width*sizeof(double));
812 for ( u=0; u < v; u++) {
813 kernel->values[u/KernelRank] +=
814 exp(-((double)(u*u))/(2.0*sigma*sigma))
815 /* / (MagickSQ2PI*sigma/KernelRank) */ ;
816 }
cristy150989e2010-02-01 14:59:39 +0000817 for (i=0; i < (long) kernel->width; i++)
cristyc99304f2010-02-01 15:26:27 +0000818 kernel->positive_range += kernel->values[i];
anthony602ab9b2010-01-05 08:06:50 +0000819#else
cristy150989e2010-02-01 14:59:39 +0000820 for ( i=0; i < (long) kernel->width; i++)
cristyc99304f2010-02-01 15:26:27 +0000821 kernel->positive_range += (
anthony602ab9b2010-01-05 08:06:50 +0000822 kernel->values[i] =
823 exp(-((double)(i*i))/(2.0*sigma*sigma))
824 /* / (MagickSQ2PI*sigma) */ );
825#endif
cristyc99304f2010-02-01 15:26:27 +0000826 kernel->minimum = 0;
827 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000828
anthony999bb2c2010-02-18 12:38:01 +0000829 ScaleKernelInfo(kernel, 1.0, NormalizeValue); /* Normalize */
830 RotateKernelInfo(kernel, args->xi); /* Rotate by angle */
anthony602ab9b2010-01-05 08:06:50 +0000831 break;
832 }
833 /* Boolean Kernels */
834 case RectangleKernel:
835 case SquareKernel:
836 {
anthony4fd27e22010-02-07 08:17:18 +0000837 double scale;
anthony602ab9b2010-01-05 08:06:50 +0000838 if ( type == SquareKernel )
839 {
840 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000841 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000842 else
cristy150989e2010-02-01 14:59:39 +0000843 kernel->width = kernel->height = (unsigned long) (2*args->rho+1);
cristyc99304f2010-02-01 15:26:27 +0000844 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony4fd27e22010-02-07 08:17:18 +0000845 scale = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +0000846 }
847 else {
cristy2be15382010-01-21 02:38:03 +0000848 /* NOTE: user defaults set in "AcquireKernelInfo()" */
anthony602ab9b2010-01-05 08:06:50 +0000849 if ( args->rho < 1.0 || args->sigma < 1.0 )
anthony83ba99b2010-01-24 08:48:15 +0000850 return(DestroyKernelInfo(kernel)); /* invalid args given */
anthony602ab9b2010-01-05 08:06:50 +0000851 kernel->width = (unsigned long)args->rho;
852 kernel->height = (unsigned long)args->sigma;
853 if ( args->xi < 0.0 || args->xi > (double)kernel->width ||
854 args->psi < 0.0 || args->psi > (double)kernel->height )
anthony83ba99b2010-01-24 08:48:15 +0000855 return(DestroyKernelInfo(kernel)); /* invalid args given */
cristyc99304f2010-02-01 15:26:27 +0000856 kernel->x = (long) args->xi;
857 kernel->y = (long) args->psi;
anthony4fd27e22010-02-07 08:17:18 +0000858 scale = 1.0;
anthony602ab9b2010-01-05 08:06:50 +0000859 }
860 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
861 kernel->height*sizeof(double));
862 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000863 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000864
anthonycc6c8362010-01-25 04:14:01 +0000865 /* set all kernel values to 1.0 */
cristy150989e2010-02-01 14:59:39 +0000866 u=(long) kernel->width*kernel->height;
867 for ( i=0; i < u; i++)
anthony4fd27e22010-02-07 08:17:18 +0000868 kernel->values[i] = scale;
869 kernel->minimum = kernel->maximum = scale; /* a flat shape */
870 kernel->positive_range = scale*u;
anthonycc6c8362010-01-25 04:14:01 +0000871 break;
anthony602ab9b2010-01-05 08:06:50 +0000872 }
873 case DiamondKernel:
874 {
875 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000876 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000877 else
878 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000879 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000880
881 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
882 kernel->height*sizeof(double));
883 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000884 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000885
anthony4fd27e22010-02-07 08:17:18 +0000886 /* set all kernel values within diamond area to scale given */
cristyc99304f2010-02-01 15:26:27 +0000887 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
888 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
889 if ((labs(u)+labs(v)) <= (long)kernel->x)
anthony4fd27e22010-02-07 08:17:18 +0000890 kernel->positive_range += kernel->values[i] = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +0000891 else
892 kernel->values[i] = nan;
anthony4fd27e22010-02-07 08:17:18 +0000893 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
anthony602ab9b2010-01-05 08:06:50 +0000894 break;
895 }
896 case DiskKernel:
897 {
898 long
899 limit;
900
901 limit = (long)(args->rho*args->rho);
anthony83ba99b2010-01-24 08:48:15 +0000902 if (args->rho < 0.1) /* default radius approx 3.5 */
903 kernel->width = kernel->height = 7L, limit = 10L;
anthony602ab9b2010-01-05 08:06:50 +0000904 else
905 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000906 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000907
908 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
909 kernel->height*sizeof(double));
910 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000911 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000912
anthonycc6c8362010-01-25 04:14:01 +0000913 /* set all kernel values within disk area to 1.0 */
cristyc99304f2010-02-01 15:26:27 +0000914 for ( i=0, v= -kernel->y; v <= (long)kernel->y; v++)
915 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony602ab9b2010-01-05 08:06:50 +0000916 if ((u*u+v*v) <= limit)
anthony4fd27e22010-02-07 08:17:18 +0000917 kernel->positive_range += kernel->values[i] = args->sigma;
anthony602ab9b2010-01-05 08:06:50 +0000918 else
919 kernel->values[i] = nan;
anthony4fd27e22010-02-07 08:17:18 +0000920 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
anthony602ab9b2010-01-05 08:06:50 +0000921 break;
922 }
923 case PlusKernel:
924 {
925 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000926 kernel->width = kernel->height = 5; /* default radius 2 */
anthony602ab9b2010-01-05 08:06:50 +0000927 else
928 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000929 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000930
931 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
932 kernel->height*sizeof(double));
933 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000934 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000935
anthonycc6c8362010-01-25 04:14:01 +0000936 /* set all kernel values along axises to 1.0 */
cristyc99304f2010-02-01 15:26:27 +0000937 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
938 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
anthony4fd27e22010-02-07 08:17:18 +0000939 kernel->values[i] = (u == 0 || v == 0) ? args->sigma : nan;
940 kernel->minimum = kernel->maximum = args->sigma; /* a flat shape */
941 kernel->positive_range = args->sigma*(kernel->width*2.0 - 1.0);
anthony602ab9b2010-01-05 08:06:50 +0000942 break;
943 }
944 /* Distance Measuring Kernels */
945 case ChebyshevKernel:
946 {
anthony602ab9b2010-01-05 08:06:50 +0000947 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000948 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000949 else
950 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000951 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000952
953 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
954 kernel->height*sizeof(double));
955 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000956 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000957
cristyc99304f2010-02-01 15:26:27 +0000958 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
959 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
960 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +0000961 args->sigma*((labs(u)>labs(v)) ? labs(u) : labs(v)) );
cristyc99304f2010-02-01 15:26:27 +0000962 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000963 break;
964 }
965 case ManhattenKernel:
966 {
anthony602ab9b2010-01-05 08:06:50 +0000967 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000968 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000969 else
970 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000971 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000972
973 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
974 kernel->height*sizeof(double));
975 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000976 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000977
cristyc99304f2010-02-01 15:26:27 +0000978 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
979 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
980 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +0000981 args->sigma*(labs(u)+labs(v)) );
cristyc99304f2010-02-01 15:26:27 +0000982 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000983 break;
984 }
985 case EuclideanKernel:
986 {
anthony602ab9b2010-01-05 08:06:50 +0000987 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000988 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000989 else
990 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
cristyc99304f2010-02-01 15:26:27 +0000991 kernel->x = kernel->y = (long) (kernel->width-1)/2;
anthony602ab9b2010-01-05 08:06:50 +0000992
993 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
994 kernel->height*sizeof(double));
995 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +0000996 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000997
cristyc99304f2010-02-01 15:26:27 +0000998 for ( i=0, v=-kernel->y; v <= (long)kernel->y; v++)
999 for ( u=-kernel->x; u <= (long)kernel->x; u++, i++)
1000 kernel->positive_range += ( kernel->values[i] =
anthonyc84dce52010-05-07 05:42:23 +00001001 args->sigma*sqrt((double)(u*u+v*v)) );
cristyc99304f2010-02-01 15:26:27 +00001002 kernel->maximum = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +00001003 break;
1004 }
1005 /* Undefined Kernels */
1006 case LaplacianKernel:
1007 case LOGKernel:
1008 case DOGKernel:
cristy150989e2010-02-01 14:59:39 +00001009 perror("Kernel Type has not been defined yet");
anthony602ab9b2010-01-05 08:06:50 +00001010 /* FALL THRU */
1011 default:
1012 /* Generate a No-Op minimal kernel - 1x1 pixel */
1013 kernel->values=(double *)AcquireQuantumMemory((size_t)1,sizeof(double));
1014 if (kernel->values == (double *) NULL)
anthony83ba99b2010-01-24 08:48:15 +00001015 return(DestroyKernelInfo(kernel));
anthony602ab9b2010-01-05 08:06:50 +00001016 kernel->width = kernel->height = 1;
cristyc99304f2010-02-01 15:26:27 +00001017 kernel->x = kernel->x = 0;
anthony602ab9b2010-01-05 08:06:50 +00001018 kernel->type = UndefinedKernel;
cristyc99304f2010-02-01 15:26:27 +00001019 kernel->maximum =
1020 kernel->positive_range =
anthonyc94cdb02010-01-06 08:15:29 +00001021 kernel->values[0] = 1.0; /* a flat single-point no-op kernel! */
anthony602ab9b2010-01-05 08:06:50 +00001022 break;
1023 }
1024
1025 return(kernel);
1026}
anthonyc94cdb02010-01-06 08:15:29 +00001027
anthony602ab9b2010-01-05 08:06:50 +00001028/*
1029%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1030% %
1031% %
1032% %
cristy6771f1e2010-03-05 19:43:39 +00001033% C l o n e K e r n e l I n f o %
anthony4fd27e22010-02-07 08:17:18 +00001034% %
1035% %
1036% %
1037%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1038%
anthony1b2bc0a2010-05-12 05:25:22 +00001039% CloneKernelInfo() creates a new clone of the given Kernel List so that its
1040% can be modified without effecting the original. The cloned kernel should
1041% be destroyed using DestoryKernelInfo() when no longer needed.
anthony7a01dcf2010-05-11 12:25:52 +00001042%
cristye6365592010-04-02 17:31:23 +00001043% The format of the CloneKernelInfo method is:
anthony4fd27e22010-02-07 08:17:18 +00001044%
anthony930be612010-02-08 04:26:15 +00001045% KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +00001046%
1047% A description of each parameter follows:
1048%
1049% o kernel: the Morphology/Convolution kernel to be cloned
1050%
1051*/
cristyef656912010-03-05 19:54:59 +00001052MagickExport KernelInfo *CloneKernelInfo(const KernelInfo *kernel)
anthony4fd27e22010-02-07 08:17:18 +00001053{
1054 register long
1055 i;
1056
cristy19eb6412010-04-23 14:42:29 +00001057 KernelInfo
anthony7a01dcf2010-05-11 12:25:52 +00001058 *new_kernel;
anthony4fd27e22010-02-07 08:17:18 +00001059
1060 assert(kernel != (KernelInfo *) NULL);
anthony7a01dcf2010-05-11 12:25:52 +00001061 new_kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
1062 if (new_kernel == (KernelInfo *) NULL)
1063 return(new_kernel);
1064 *new_kernel=(*kernel); /* copy values in structure */
anthony7a01dcf2010-05-11 12:25:52 +00001065
1066 /* replace the values with a copy of the values */
1067 new_kernel->values=(double *) AcquireQuantumMemory(kernel->width,
cristy19eb6412010-04-23 14:42:29 +00001068 kernel->height*sizeof(double));
anthony7a01dcf2010-05-11 12:25:52 +00001069 if (new_kernel->values == (double *) NULL)
1070 return(DestroyKernelInfo(new_kernel));
anthony4fd27e22010-02-07 08:17:18 +00001071 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthony7a01dcf2010-05-11 12:25:52 +00001072 new_kernel->values[i]=kernel->values[i];
anthony1b2bc0a2010-05-12 05:25:22 +00001073
1074 /* Also clone the next kernel in the kernel list */
1075 if ( kernel->next != (KernelInfo *) NULL ) {
1076 new_kernel->next = CloneKernelInfo(kernel->next);
1077 if ( new_kernel->next == (KernelInfo *) NULL )
1078 return(DestroyKernelInfo(new_kernel));
1079 }
1080
anthony7a01dcf2010-05-11 12:25:52 +00001081 return(new_kernel);
anthony4fd27e22010-02-07 08:17:18 +00001082}
1083
1084/*
1085%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1086% %
1087% %
1088% %
anthony83ba99b2010-01-24 08:48:15 +00001089% D e s t r o y K e r n e l I n f o %
anthony602ab9b2010-01-05 08:06:50 +00001090% %
1091% %
1092% %
1093%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1094%
anthony83ba99b2010-01-24 08:48:15 +00001095% DestroyKernelInfo() frees the memory used by a Convolution/Morphology
1096% kernel.
anthony602ab9b2010-01-05 08:06:50 +00001097%
anthony83ba99b2010-01-24 08:48:15 +00001098% The format of the DestroyKernelInfo method is:
anthony602ab9b2010-01-05 08:06:50 +00001099%
anthony83ba99b2010-01-24 08:48:15 +00001100% KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +00001101%
1102% A description of each parameter follows:
1103%
1104% o kernel: the Morphology/Convolution kernel to be destroyed
1105%
1106*/
1107
anthony83ba99b2010-01-24 08:48:15 +00001108MagickExport KernelInfo *DestroyKernelInfo(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +00001109{
cristy2be15382010-01-21 02:38:03 +00001110 assert(kernel != (KernelInfo *) NULL);
anthony4fd27e22010-02-07 08:17:18 +00001111
anthony7a01dcf2010-05-11 12:25:52 +00001112 if ( kernel->next != (KernelInfo *) NULL )
1113 kernel->next = DestroyKernelInfo(kernel->next);
1114
1115 kernel->values = (double *)RelinquishMagickMemory(kernel->values);
1116 kernel = (KernelInfo *) RelinquishMagickMemory(kernel);
anthony602ab9b2010-01-05 08:06:50 +00001117 return(kernel);
1118}
anthonyc94cdb02010-01-06 08:15:29 +00001119
1120/*
1121%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1122% %
1123% %
1124% %
anthony29188a82010-01-22 10:12:34 +00001125% 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 +00001126% %
1127% %
1128% %
1129%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1130%
anthony29188a82010-01-22 10:12:34 +00001131% MorphologyImageChannel() applies a user supplied kernel to the image
1132% according to the given mophology method.
anthony602ab9b2010-01-05 08:06:50 +00001133%
1134% The given kernel is assumed to have been pre-scaled appropriatally, usally
1135% by the kernel generator.
1136%
1137% The format of the MorphologyImage method is:
1138%
cristyef656912010-03-05 19:54:59 +00001139% Image *MorphologyImage(const Image *image,MorphologyMethod method,
1140% const long iterations,KernelInfo *kernel,ExceptionInfo *exception)
anthony29188a82010-01-22 10:12:34 +00001141% Image *MorphologyImageChannel(const Image *image, const ChannelType
cristyef656912010-03-05 19:54:59 +00001142% channel,MorphologyMethod method,const long iterations,
1143% KernelInfo *kernel,ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001144%
1145% A description of each parameter follows:
1146%
1147% o image: the image.
1148%
1149% o method: the morphology method to be applied.
1150%
1151% o iterations: apply the operation this many times (or no change).
1152% A value of -1 means loop until no change found.
1153% How this is applied may depend on the morphology method.
1154% Typically this is a value of 1.
1155%
1156% o channel: the channel type.
1157%
1158% o kernel: An array of double representing the morphology kernel.
anthony29188a82010-01-22 10:12:34 +00001159% Warning: kernel may be normalized for the Convolve method.
anthony602ab9b2010-01-05 08:06:50 +00001160%
1161% o exception: return any errors or warnings in this structure.
1162%
1163%
1164% TODO: bias and auto-scale handling of the kernel for convolution
1165% The given kernel is assumed to have been pre-scaled appropriatally, usally
1166% by the kernel generator.
1167%
1168*/
1169
anthony930be612010-02-08 04:26:15 +00001170
anthony602ab9b2010-01-05 08:06:50 +00001171/* Internal function
anthony7a01dcf2010-05-11 12:25:52 +00001172 * Apply the low-level Morphology Method Primatives using the given Kernel
anthony930be612010-02-08 04:26:15 +00001173 * Returning the number of pixels that changed.
1174 * Two pre-created images must be provided, no image is created.
anthony602ab9b2010-01-05 08:06:50 +00001175 */
anthony7a01dcf2010-05-11 12:25:52 +00001176static unsigned long MorphologyPrimative(const Image *image, Image
anthony602ab9b2010-01-05 08:06:50 +00001177 *result_image, const MorphologyMethod method, const ChannelType channel,
cristy2be15382010-01-21 02:38:03 +00001178 const KernelInfo *kernel, ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001179{
cristy2be15382010-01-21 02:38:03 +00001180#define MorphologyTag "Morphology/Image"
anthony602ab9b2010-01-05 08:06:50 +00001181
1182 long
cristy150989e2010-02-01 14:59:39 +00001183 progress,
anthony29188a82010-01-22 10:12:34 +00001184 y, offx, offy,
anthony602ab9b2010-01-05 08:06:50 +00001185 changed;
1186
1187 MagickBooleanType
1188 status;
1189
1190 MagickPixelPacket
1191 bias;
1192
1193 CacheView
1194 *p_view,
1195 *q_view;
1196
anthony4fd27e22010-02-07 08:17:18 +00001197 /* Only the most basic morphology is actually performed by this routine */
anthony4fd27e22010-02-07 08:17:18 +00001198
anthony602ab9b2010-01-05 08:06:50 +00001199 /*
anthony4fd27e22010-02-07 08:17:18 +00001200 Apply Basic Morphology to Image.
anthony602ab9b2010-01-05 08:06:50 +00001201 */
1202 status=MagickTrue;
1203 changed=0;
1204 progress=0;
1205
1206 GetMagickPixelPacket(image,&bias);
1207 SetMagickPixelPacketBias(image,&bias);
anthonycc6c8362010-01-25 04:14:01 +00001208 /* Future: handle auto-bias from user, based on kernel input */
anthony602ab9b2010-01-05 08:06:50 +00001209
1210 p_view=AcquireCacheView(image);
1211 q_view=AcquireCacheView(result_image);
anthony29188a82010-01-22 10:12:34 +00001212
anthonycc6c8362010-01-25 04:14:01 +00001213 /* Some methods (including convolve) needs use a reflected kernel.
1214 * Adjust 'origin' offsets for this reflected kernel.
anthony29188a82010-01-22 10:12:34 +00001215 */
cristyc99304f2010-02-01 15:26:27 +00001216 offx = kernel->x;
1217 offy = kernel->y;
anthony29188a82010-01-22 10:12:34 +00001218 switch(method) {
anthony930be612010-02-08 04:26:15 +00001219 case ConvolveMorphology:
1220 case DilateMorphology:
1221 case DilateIntensityMorphology:
1222 case DistanceMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001223 /* kernel needs to used with reflection about origin */
cristy150989e2010-02-01 14:59:39 +00001224 offx = (long) kernel->width-offx-1;
1225 offy = (long) kernel->height-offy-1;
anthony29188a82010-01-22 10:12:34 +00001226 break;
anthony5ef8e942010-05-11 06:51:12 +00001227 case ErodeMorphology:
1228 case ErodeIntensityMorphology:
1229 case HitAndMissMorphology:
1230 case ThinningMorphology:
1231 case ThickenMorphology:
1232 /* kernel is user as is, without reflection */
1233 break;
anthony930be612010-02-08 04:26:15 +00001234 default:
anthony5ef8e942010-05-11 06:51:12 +00001235 perror("Not a low level Morphology Method");
anthony930be612010-02-08 04:26:15 +00001236 break;
anthony29188a82010-01-22 10:12:34 +00001237 }
1238
anthony602ab9b2010-01-05 08:06:50 +00001239#if defined(MAGICKCORE_OPENMP_SUPPORT)
1240 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1241#endif
cristy150989e2010-02-01 14:59:39 +00001242 for (y=0; y < (long) image->rows; y++)
anthony602ab9b2010-01-05 08:06:50 +00001243 {
1244 MagickBooleanType
1245 sync;
1246
1247 register const PixelPacket
1248 *restrict p;
1249
1250 register const IndexPacket
1251 *restrict p_indexes;
1252
1253 register PixelPacket
1254 *restrict q;
1255
1256 register IndexPacket
1257 *restrict q_indexes;
1258
cristy150989e2010-02-01 14:59:39 +00001259 register long
anthony602ab9b2010-01-05 08:06:50 +00001260 x;
1261
anthony29188a82010-01-22 10:12:34 +00001262 unsigned long
anthony602ab9b2010-01-05 08:06:50 +00001263 r;
1264
1265 if (status == MagickFalse)
1266 continue;
anthony29188a82010-01-22 10:12:34 +00001267 p=GetCacheViewVirtualPixels(p_view, -offx, y-offy,
1268 image->columns+kernel->width, kernel->height, exception);
anthony602ab9b2010-01-05 08:06:50 +00001269 q=GetCacheViewAuthenticPixels(q_view,0,y,result_image->columns,1,
1270 exception);
1271 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1272 {
1273 status=MagickFalse;
1274 continue;
1275 }
1276 p_indexes=GetCacheViewVirtualIndexQueue(p_view);
1277 q_indexes=GetCacheViewAuthenticIndexQueue(q_view);
anthony29188a82010-01-22 10:12:34 +00001278 r = (image->columns+kernel->width)*offy+offx; /* constant */
1279
cristy150989e2010-02-01 14:59:39 +00001280 for (x=0; x < (long) image->columns; x++)
anthony602ab9b2010-01-05 08:06:50 +00001281 {
cristy150989e2010-02-01 14:59:39 +00001282 long
anthony602ab9b2010-01-05 08:06:50 +00001283 v;
1284
cristy150989e2010-02-01 14:59:39 +00001285 register long
anthony602ab9b2010-01-05 08:06:50 +00001286 u;
1287
1288 register const double
1289 *restrict k;
1290
1291 register const PixelPacket
1292 *restrict k_pixels;
1293
1294 register const IndexPacket
1295 *restrict k_indexes;
1296
1297 MagickPixelPacket
anthony5ef8e942010-05-11 06:51:12 +00001298 result,
1299 min,
1300 max;
anthony602ab9b2010-01-05 08:06:50 +00001301
anthony29188a82010-01-22 10:12:34 +00001302 /* Copy input to ouput image for unused channels
anthony83ba99b2010-01-24 08:48:15 +00001303 * This removes need for 'cloning' a new image every iteration
anthony29188a82010-01-22 10:12:34 +00001304 */
anthony602ab9b2010-01-05 08:06:50 +00001305 *q = p[r];
1306 if (image->colorspace == CMYKColorspace)
1307 q_indexes[x] = p_indexes[r];
1308
anthony5ef8e942010-05-11 06:51:12 +00001309 /* Defaults */
1310 min.red =
1311 min.green =
1312 min.blue =
1313 min.opacity =
1314 min.index = (MagickRealType) QuantumRange;
1315 max.red =
1316 max.green =
1317 max.blue =
1318 max.opacity =
1319 max.index = (MagickRealType) 0;
1320 /* original pixel value */
1321 result.red = (MagickRealType) p[r].red;
1322 result.green = (MagickRealType) p[r].green;
1323 result.blue = (MagickRealType) p[r].blue;
1324 result.opacity = QuantumRange - (MagickRealType) p[r].opacity;
1325 if ( image->colorspace == CMYKColorspace)
1326 result.index = (MagickRealType) p_indexes[r];
1327
anthony602ab9b2010-01-05 08:06:50 +00001328 switch (method) {
1329 case ConvolveMorphology:
anthony930be612010-02-08 04:26:15 +00001330 /* Set the user defined bias of the weighted average output
1331 **
1332 ** FUTURE: provide some way for internal functions to disable
anthony5ef8e942010-05-11 06:51:12 +00001333 ** user provided bias and scaling effects.
anthony930be612010-02-08 04:26:15 +00001334 */
anthony602ab9b2010-01-05 08:06:50 +00001335 result=bias;
anthony930be612010-02-08 04:26:15 +00001336 break;
anthony4fd27e22010-02-07 08:17:18 +00001337 case DilateIntensityMorphology:
1338 case ErodeIntensityMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001339 result.red = 0.0; /* flag indicating when first match found */
anthony4fd27e22010-02-07 08:17:18 +00001340 break;
anthony602ab9b2010-01-05 08:06:50 +00001341 default:
anthony602ab9b2010-01-05 08:06:50 +00001342 break;
1343 }
1344
1345 switch ( method ) {
1346 case ConvolveMorphology:
anthony930be612010-02-08 04:26:15 +00001347 /* Weighted Average of pixels using reflected kernel
1348 **
1349 ** NOTE for correct working of this operation for asymetrical
1350 ** kernels, the kernel needs to be applied in its reflected form.
1351 ** That is its values needs to be reversed.
1352 **
1353 ** Correlation is actually the same as this but without reflecting
1354 ** the kernel, and thus 'lower-level' that Convolution. However
1355 ** as Convolution is the more common method used, and it does not
1356 ** really cost us much in terms of processing to use a reflected
anthony5ef8e942010-05-11 06:51:12 +00001357 ** kernel, so it is Convolution that is implemented.
anthony930be612010-02-08 04:26:15 +00001358 **
1359 ** Correlation will have its kernel reflected before calling
1360 ** this function to do a Convolve.
1361 **
1362 ** For more details of Correlation vs Convolution see
1363 ** http://www.cs.umd.edu/~djacobs/CMSC426/Convolution.pdf
1364 */
anthony5ef8e942010-05-11 06:51:12 +00001365 if (((channel & SyncChannels) != 0 ) &&
1366 (image->matte == MagickTrue))
1367 { /* Channel has a 'Sync' Flag, and Alpha Channel enabled.
1368 ** Weight the color channels with Alpha Channel so that
1369 ** transparent pixels are not part of the results.
1370 */
anthony602ab9b2010-01-05 08:06:50 +00001371 MagickRealType
anthony5ef8e942010-05-11 06:51:12 +00001372 alpha, /* color channel weighting : kernel*alpha */
1373 gamma; /* divisor, sum of weighting values */
anthony602ab9b2010-01-05 08:06:50 +00001374
1375 gamma=0.0;
anthony29188a82010-01-22 10:12:34 +00001376 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001377 k_pixels = p;
1378 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001379 for (v=0; v < (long) kernel->height; v++) {
1380 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001381 if ( IsNan(*k) ) continue;
1382 alpha=(*k)*(QuantumScale*(QuantumRange-
1383 k_pixels[u].opacity));
1384 gamma += alpha;
1385 result.red += alpha*k_pixels[u].red;
1386 result.green += alpha*k_pixels[u].green;
1387 result.blue += alpha*k_pixels[u].blue;
anthony83ba99b2010-01-24 08:48:15 +00001388 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00001389 if ( image->colorspace == CMYKColorspace)
1390 result.index += alpha*k_indexes[u];
1391 }
1392 k_pixels += image->columns+kernel->width;
1393 k_indexes += image->columns+kernel->width;
1394 }
1395 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
anthony83ba99b2010-01-24 08:48:15 +00001396 result.red *= gamma;
1397 result.green *= gamma;
1398 result.blue *= gamma;
1399 result.opacity *= gamma;
1400 result.index *= gamma;
anthony602ab9b2010-01-05 08:06:50 +00001401 }
anthony5ef8e942010-05-11 06:51:12 +00001402 else
1403 {
1404 /* No 'Sync' flag, or no Alpha involved.
1405 ** Convolution is simple individual channel weigthed sum.
1406 */
1407 k = &kernel->values[ kernel->width*kernel->height-1 ];
1408 k_pixels = p;
1409 k_indexes = p_indexes;
1410 for (v=0; v < (long) kernel->height; v++) {
1411 for (u=0; u < (long) kernel->width; u++, k--) {
1412 if ( IsNan(*k) ) continue;
1413 result.red += (*k)*k_pixels[u].red;
1414 result.green += (*k)*k_pixels[u].green;
1415 result.blue += (*k)*k_pixels[u].blue;
1416 result.opacity += (*k)*(QuantumRange-k_pixels[u].opacity);
1417 if ( image->colorspace == CMYKColorspace)
1418 result.index += (*k)*k_indexes[u];
1419 }
1420 k_pixels += image->columns+kernel->width;
1421 k_indexes += image->columns+kernel->width;
1422 }
1423 }
anthony602ab9b2010-01-05 08:06:50 +00001424 break;
1425
anthony4fd27e22010-02-07 08:17:18 +00001426 case ErodeMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001427 /* Minimum Value within kernel neighbourhood
anthony930be612010-02-08 04:26:15 +00001428 **
1429 ** NOTE that the kernel is not reflected for this operation!
1430 **
1431 ** NOTE: in normal Greyscale Morphology, the kernel value should
1432 ** be added to the real value, this is currently not done, due to
1433 ** the nature of the boolean kernels being used.
1434 */
anthony4fd27e22010-02-07 08:17:18 +00001435 k = kernel->values;
1436 k_pixels = p;
1437 k_indexes = p_indexes;
1438 for (v=0; v < (long) kernel->height; v++) {
1439 for (u=0; u < (long) kernel->width; u++, k++) {
1440 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony5ef8e942010-05-11 06:51:12 +00001441 Minimize(min.red, (double) k_pixels[u].red);
1442 Minimize(min.green, (double) k_pixels[u].green);
1443 Minimize(min.blue, (double) k_pixels[u].blue);
1444 Minimize(min.opacity,
anthonyd37a5cb2010-05-07 06:37:03 +00001445 QuantumRange-(double) k_pixels[u].opacity);
anthony4fd27e22010-02-07 08:17:18 +00001446 if ( image->colorspace == CMYKColorspace)
anthony5ef8e942010-05-11 06:51:12 +00001447 Minimize(min.index, (double) k_indexes[u]);
anthony4fd27e22010-02-07 08:17:18 +00001448 }
1449 k_pixels += image->columns+kernel->width;
1450 k_indexes += image->columns+kernel->width;
1451 }
1452 break;
1453
anthony5ef8e942010-05-11 06:51:12 +00001454
anthony83ba99b2010-01-24 08:48:15 +00001455 case DilateMorphology:
anthony5ef8e942010-05-11 06:51:12 +00001456 /* Maximum Value within kernel neighbourhood
anthony930be612010-02-08 04:26:15 +00001457 **
1458 ** NOTE for correct working of this operation for asymetrical
1459 ** kernels, the kernel needs to be applied in its reflected form.
1460 ** That is its values needs to be reversed.
1461 **
1462 ** NOTE: in normal Greyscale Morphology, the kernel value should
1463 ** be added to the real value, this is currently not done, due to
1464 ** the nature of the boolean kernels being used.
1465 **
1466 */
anthony29188a82010-01-22 10:12:34 +00001467 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001468 k_pixels = p;
1469 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001470 for (v=0; v < (long) kernel->height; v++) {
1471 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001472 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony5ef8e942010-05-11 06:51:12 +00001473 Maximize(max.red, (double) k_pixels[u].red);
1474 Maximize(max.green, (double) k_pixels[u].green);
1475 Maximize(max.blue, (double) k_pixels[u].blue);
1476 Maximize(max.opacity,
anthonyd37a5cb2010-05-07 06:37:03 +00001477 QuantumRange-(double) k_pixels[u].opacity);
anthony602ab9b2010-01-05 08:06:50 +00001478 if ( image->colorspace == CMYKColorspace)
anthony5ef8e942010-05-11 06:51:12 +00001479 Maximize(max.index, (double) k_indexes[u]);
anthony602ab9b2010-01-05 08:06:50 +00001480 }
1481 k_pixels += image->columns+kernel->width;
1482 k_indexes += image->columns+kernel->width;
1483 }
anthony602ab9b2010-01-05 08:06:50 +00001484 break;
1485
anthony5ef8e942010-05-11 06:51:12 +00001486 case HitAndMissMorphology:
1487 case ThinningMorphology:
1488 case ThickenMorphology:
1489 /* Minimum of Foreground Pixel minus Maxumum of Background Pixels
1490 **
1491 ** NOTE that the kernel is not reflected for this operation,
1492 ** and consists of both foreground and background pixel
1493 ** neighbourhoods, 0.0 for background, and 1.0 for foreground
1494 ** with either Nan or 0.5 values for don't care.
1495 **
1496 ** Note that this can produce negative results, though really
1497 ** only a positive match has any real value.
1498 */
1499 k = kernel->values;
1500 k_pixels = p;
1501 k_indexes = p_indexes;
1502 for (v=0; v < (long) kernel->height; v++) {
1503 for (u=0; u < (long) kernel->width; u++, k++) {
1504 if ( IsNan(*k) ) continue;
1505 if ( (*k) > 0.7 )
1506 { /* minimim of foreground pixels */
1507 Minimize(min.red, (double) k_pixels[u].red);
1508 Minimize(min.green, (double) k_pixels[u].green);
1509 Minimize(min.blue, (double) k_pixels[u].blue);
1510 Minimize(min.opacity,
1511 QuantumRange-(double) k_pixels[u].opacity);
1512 if ( image->colorspace == CMYKColorspace)
1513 Minimize(min.index, (double) k_indexes[u]);
1514 }
1515 else if ( (*k) < 0.3 )
1516 { /* maximum of background pixels */
1517 Maximize(max.red, (double) k_pixels[u].red);
1518 Maximize(max.green, (double) k_pixels[u].green);
1519 Maximize(max.blue, (double) k_pixels[u].blue);
1520 Maximize(max.opacity,
1521 QuantumRange-(double) k_pixels[u].opacity);
1522 if ( image->colorspace == CMYKColorspace)
1523 Maximize(max.index, (double) k_indexes[u]);
1524 }
1525 }
1526 k_pixels += image->columns+kernel->width;
1527 k_indexes += image->columns+kernel->width;
1528 }
1529 /* Pattern Match only if min fg larger than min bg pixels */
1530 min.red -= max.red; Maximize( min.red, 0.0 );
1531 min.green -= max.green; Maximize( min.green, 0.0 );
1532 min.blue -= max.blue; Maximize( min.blue, 0.0 );
1533 min.opacity -= max.opacity; Maximize( min.opacity, 0.0 );
1534 min.index -= max.index; Maximize( min.index, 0.0 );
1535 break;
1536
anthony4fd27e22010-02-07 08:17:18 +00001537 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001538 /* Select Pixel with Minimum Intensity within kernel neighbourhood
1539 **
1540 ** WARNING: the intensity test fails for CMYK and does not
1541 ** take into account the moderating effect of teh alpha channel
1542 ** on the intensity.
1543 **
1544 ** NOTE that the kernel is not reflected for this operation!
1545 */
anthony602ab9b2010-01-05 08:06:50 +00001546 k = kernel->values;
1547 k_pixels = p;
1548 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001549 for (v=0; v < (long) kernel->height; v++) {
1550 for (u=0; u < (long) kernel->width; u++, k++) {
anthony602ab9b2010-01-05 08:06:50 +00001551 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony4fd27e22010-02-07 08:17:18 +00001552 if ( result.red == 0.0 ||
1553 PixelIntensity(&(k_pixels[u])) < PixelIntensity(q) ) {
1554 /* copy the whole pixel - no channel selection */
1555 *q = k_pixels[u];
1556 if ( result.red > 0.0 ) changed++;
1557 result.red = 1.0;
1558 }
anthony602ab9b2010-01-05 08:06:50 +00001559 }
1560 k_pixels += image->columns+kernel->width;
1561 k_indexes += image->columns+kernel->width;
1562 }
anthony602ab9b2010-01-05 08:06:50 +00001563 break;
1564
anthony83ba99b2010-01-24 08:48:15 +00001565 case DilateIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001566 /* Select Pixel with Maximum Intensity within kernel neighbourhood
1567 **
1568 ** WARNING: the intensity test fails for CMYK and does not
1569 ** take into account the moderating effect of teh alpha channel
1570 ** on the intensity.
1571 **
1572 ** NOTE for correct working of this operation for asymetrical
1573 ** kernels, the kernel needs to be applied in its reflected form.
1574 ** That is its values needs to be reversed.
1575 */
anthony29188a82010-01-22 10:12:34 +00001576 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001577 k_pixels = p;
1578 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001579 for (v=0; v < (long) kernel->height; v++) {
1580 for (u=0; u < (long) kernel->width; u++, k--) {
anthony29188a82010-01-22 10:12:34 +00001581 if ( IsNan(*k) || (*k) < 0.5 ) continue; /* boolean kernel */
1582 if ( result.red == 0.0 ||
1583 PixelIntensity(&(k_pixels[u])) > PixelIntensity(q) ) {
1584 /* copy the whole pixel - no channel selection */
1585 *q = k_pixels[u];
1586 if ( result.red > 0.0 ) changed++;
1587 result.red = 1.0;
1588 }
anthony602ab9b2010-01-05 08:06:50 +00001589 }
1590 k_pixels += image->columns+kernel->width;
1591 k_indexes += image->columns+kernel->width;
1592 }
anthony602ab9b2010-01-05 08:06:50 +00001593 break;
1594
anthony5ef8e942010-05-11 06:51:12 +00001595
anthony602ab9b2010-01-05 08:06:50 +00001596 case DistanceMorphology:
anthony930be612010-02-08 04:26:15 +00001597 /* Add kernel Value and select the minimum value found.
1598 ** The result is a iterative distance from edge of image shape.
1599 **
1600 ** All Distance Kernels are symetrical, but that may not always
1601 ** be the case. For example how about a distance from left edges?
1602 ** To work correctly with asymetrical kernels the reflected kernel
1603 ** needs to be applied.
anthony5ef8e942010-05-11 06:51:12 +00001604 **
1605 ** Actually this is really a GreyErode with a negative kernel!
1606 **
anthony930be612010-02-08 04:26:15 +00001607 */
anthony29188a82010-01-22 10:12:34 +00001608 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001609 k_pixels = p;
1610 k_indexes = p_indexes;
cristy150989e2010-02-01 14:59:39 +00001611 for (v=0; v < (long) kernel->height; v++) {
1612 for (u=0; u < (long) kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001613 if ( IsNan(*k) ) continue;
1614 Minimize(result.red, (*k)+k_pixels[u].red);
1615 Minimize(result.green, (*k)+k_pixels[u].green);
1616 Minimize(result.blue, (*k)+k_pixels[u].blue);
1617 Minimize(result.opacity, (*k)+QuantumRange-k_pixels[u].opacity);
1618 if ( image->colorspace == CMYKColorspace)
1619 Minimize(result.index, (*k)+k_indexes[u]);
1620 }
1621 k_pixels += image->columns+kernel->width;
1622 k_indexes += image->columns+kernel->width;
1623 }
anthony602ab9b2010-01-05 08:06:50 +00001624 break;
1625
1626 case UndefinedMorphology:
1627 default:
1628 break; /* Do nothing */
anthony83ba99b2010-01-24 08:48:15 +00001629 }
anthony5ef8e942010-05-11 06:51:12 +00001630 /* Final mathematics of results (combine with original image?)
1631 **
1632 ** NOTE: Difference Morphology operators Edge* and *Hat could also
1633 ** be done here but works better with iteration as a image difference
1634 ** in the controling function (below). Thicken and Thinning however
1635 ** should be done here so thay can be iterated correctly.
1636 */
1637 switch ( method ) {
1638 case HitAndMissMorphology:
1639 case ErodeMorphology:
1640 result = min; /* minimum of neighbourhood */
1641 break;
1642 case DilateMorphology:
1643 result = max; /* maximum of neighbourhood */
1644 break;
1645 case ThinningMorphology:
1646 /* subtract pattern match from original */
1647 result.red -= min.red;
1648 result.green -= min.green;
1649 result.blue -= min.blue;
1650 result.opacity -= min.opacity;
1651 result.index -= min.index;
1652 break;
1653 case ThickenMorphology:
1654 /* Union with original image (maximize) - or should this be + */
1655 Maximize( result.red, min.red );
1656 Maximize( result.green, min.green );
1657 Maximize( result.blue, min.blue );
1658 Maximize( result.opacity, min.opacity );
1659 Maximize( result.index, min.index );
1660 break;
1661 default:
1662 /* result directly calculated or assigned */
1663 break;
1664 }
1665 /* Assign the resulting pixel values - Clamping Result */
anthony83ba99b2010-01-24 08:48:15 +00001666 switch ( method ) {
1667 case UndefinedMorphology:
1668 case DilateIntensityMorphology:
1669 case ErodeIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001670 break; /* full pixel was directly assigned - not a channel method */
anthony83ba99b2010-01-24 08:48:15 +00001671 default:
anthony83ba99b2010-01-24 08:48:15 +00001672 if ((channel & RedChannel) != 0)
1673 q->red = ClampToQuantum(result.red);
1674 if ((channel & GreenChannel) != 0)
1675 q->green = ClampToQuantum(result.green);
1676 if ((channel & BlueChannel) != 0)
1677 q->blue = ClampToQuantum(result.blue);
1678 if ((channel & OpacityChannel) != 0
1679 && image->matte == MagickTrue )
1680 q->opacity = ClampToQuantum(QuantumRange-result.opacity);
1681 if ((channel & IndexChannel) != 0
1682 && image->colorspace == CMYKColorspace)
1683 q_indexes[x] = ClampToQuantum(result.index);
1684 break;
1685 }
anthony5ef8e942010-05-11 06:51:12 +00001686 /* Count up changed pixels */
anthony83ba99b2010-01-24 08:48:15 +00001687 if ( ( p[r].red != q->red )
1688 || ( p[r].green != q->green )
1689 || ( p[r].blue != q->blue )
1690 || ( p[r].opacity != q->opacity )
1691 || ( image->colorspace == CMYKColorspace &&
1692 p_indexes[r] != q_indexes[x] ) )
1693 changed++; /* The pixel had some value changed! */
anthony602ab9b2010-01-05 08:06:50 +00001694 p++;
1695 q++;
anthony83ba99b2010-01-24 08:48:15 +00001696 } /* x */
anthony602ab9b2010-01-05 08:06:50 +00001697 sync=SyncCacheViewAuthenticPixels(q_view,exception);
1698 if (sync == MagickFalse)
1699 status=MagickFalse;
1700 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1701 {
1702 MagickBooleanType
1703 proceed;
1704
1705#if defined(MAGICKCORE_OPENMP_SUPPORT)
1706 #pragma omp critical (MagickCore_MorphologyImage)
1707#endif
1708 proceed=SetImageProgress(image,MorphologyTag,progress++,image->rows);
1709 if (proceed == MagickFalse)
1710 status=MagickFalse;
1711 }
anthony83ba99b2010-01-24 08:48:15 +00001712 } /* y */
anthony602ab9b2010-01-05 08:06:50 +00001713 result_image->type=image->type;
1714 q_view=DestroyCacheView(q_view);
1715 p_view=DestroyCacheView(p_view);
cristy150989e2010-02-01 14:59:39 +00001716 return(status ? (unsigned long) changed : 0);
anthony602ab9b2010-01-05 08:06:50 +00001717}
1718
anthony4fd27e22010-02-07 08:17:18 +00001719
1720MagickExport Image *MorphologyImage(const Image *image, const MorphologyMethod
anthony930be612010-02-08 04:26:15 +00001721 method, const long iterations,const KernelInfo *kernel, ExceptionInfo
1722 *exception)
cristy2be15382010-01-21 02:38:03 +00001723{
1724 Image
1725 *morphology_image;
1726
1727 morphology_image=MorphologyImageChannel(image,DefaultChannels,method,
1728 iterations,kernel,exception);
1729 return(morphology_image);
1730}
1731
anthony4fd27e22010-02-07 08:17:18 +00001732
cristyef656912010-03-05 19:54:59 +00001733MagickExport Image *MorphologyImageChannel(const Image *image,
1734 const ChannelType channel,const MorphologyMethod method,
1735 const long iterations,const KernelInfo *kernel,ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001736{
anthony602ab9b2010-01-05 08:06:50 +00001737 Image
1738 *new_image,
anthony1b2bc0a2010-05-12 05:25:22 +00001739 *old_image,
anthony4fd27e22010-02-07 08:17:18 +00001740 *grad_image;
anthony602ab9b2010-01-05 08:06:50 +00001741
anthony4fd27e22010-02-07 08:17:18 +00001742 KernelInfo
anthony1b2bc0a2010-05-12 05:25:22 +00001743 *curr_kernel,
1744 *this_kernel;
anthony4fd27e22010-02-07 08:17:18 +00001745
1746 MorphologyMethod
1747 curr_method;
1748
anthony1b2bc0a2010-05-12 05:25:22 +00001749 unsigned long
1750 count,
1751 limit,
1752 changed,
1753 total_changed,
1754 kernel_number;
1755
anthony602ab9b2010-01-05 08:06:50 +00001756 assert(image != (Image *) NULL);
1757 assert(image->signature == MagickSignature);
anthony4fd27e22010-02-07 08:17:18 +00001758 assert(kernel != (KernelInfo *) NULL);
1759 assert(kernel->signature == MagickSignature);
anthony602ab9b2010-01-05 08:06:50 +00001760 assert(exception != (ExceptionInfo *) NULL);
1761 assert(exception->signature == MagickSignature);
1762
anthony602ab9b2010-01-05 08:06:50 +00001763 if ( iterations == 0 )
1764 return((Image *)NULL); /* null operation - nothing to do! */
1765
1766 /* kernel must be valid at this point
1767 * (except maybe for posible future morphology methods like "Prune"
1768 */
cristy2be15382010-01-21 02:38:03 +00001769 assert(kernel != (KernelInfo *)NULL);
anthony602ab9b2010-01-05 08:06:50 +00001770
anthony1b2bc0a2010-05-12 05:25:22 +00001771 new_image = (Image *) NULL; /* for cleanup */
1772 old_image = (Image *) NULL;
1773 grad_image = (Image *) NULL;
1774 curr_kernel = (KernelInfo *) NULL;
1775 count = 0; /* interation count */
1776 changed = 1; /* was last run succesfull */
anthony930be612010-02-08 04:26:15 +00001777 curr_kernel = (KernelInfo *)kernel; /* allow kernel and method */
1778 curr_method = method; /* to be changed as nessary */
anthony4fd27e22010-02-07 08:17:18 +00001779
cristy150989e2010-02-01 14:59:39 +00001780 limit = (unsigned long) iterations;
anthony602ab9b2010-01-05 08:06:50 +00001781 if ( iterations < 0 )
1782 limit = image->columns > image->rows ? image->columns : image->rows;
1783
anthony4fd27e22010-02-07 08:17:18 +00001784 /* Third-level morphology methods */
1785 switch( curr_method ) {
1786 case EdgeMorphology:
1787 grad_image = MorphologyImageChannel(image, channel,
1788 DilateMorphology, iterations, curr_kernel, exception);
anthony1b2bc0a2010-05-12 05:25:22 +00001789 if ( grad_image == (Image *) NULL )
1790 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00001791 /* FALL-THRU */
1792 case EdgeInMorphology:
1793 curr_method = ErodeMorphology;
anthony602ab9b2010-01-05 08:06:50 +00001794 break;
anthony4fd27e22010-02-07 08:17:18 +00001795 case EdgeOutMorphology:
1796 curr_method = DilateMorphology;
anthony602ab9b2010-01-05 08:06:50 +00001797 break;
anthony4fd27e22010-02-07 08:17:18 +00001798 case TopHatMorphology:
1799 curr_method = OpenMorphology;
1800 break;
1801 case BottomHatMorphology:
1802 curr_method = CloseMorphology;
1803 break;
1804 default:
anthony930be612010-02-08 04:26:15 +00001805 break; /* not a third-level method */
anthony4fd27e22010-02-07 08:17:18 +00001806 }
1807
1808 /* Second-level morphology methods */
1809 switch( curr_method ) {
anthony930be612010-02-08 04:26:15 +00001810 case OpenMorphology:
1811 /* Open is a Erode then a Dilate without reflection */
anthony4fd27e22010-02-07 08:17:18 +00001812 new_image = MorphologyImageChannel(image, channel,
1813 ErodeMorphology, iterations, curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001814 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00001815 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00001816 curr_method = DilateMorphology;
1817 break;
anthony602ab9b2010-01-05 08:06:50 +00001818 case OpenIntensityMorphology:
anthony4fd27e22010-02-07 08:17:18 +00001819 new_image = MorphologyImageChannel(image, channel,
1820 ErodeIntensityMorphology, iterations, curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001821 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00001822 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00001823 curr_method = DilateIntensityMorphology;
1824 break;
anthony930be612010-02-08 04:26:15 +00001825
1826 case CloseMorphology:
1827 /* Close is a Dilate then Erode using reflected kernel */
1828 /* A reflected kernel is needed for a Close */
1829 if ( curr_kernel == kernel )
1830 curr_kernel = CloneKernelInfo(kernel);
anthony1b2bc0a2010-05-12 05:25:22 +00001831 if (curr_kernel == (KernelInfo *) NULL)
1832 goto error_cleanup;
anthony930be612010-02-08 04:26:15 +00001833 RotateKernelInfo(curr_kernel,180);
1834 new_image = MorphologyImageChannel(image, channel,
1835 DilateMorphology, iterations, curr_kernel, exception);
1836 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00001837 goto error_cleanup;
anthony930be612010-02-08 04:26:15 +00001838 curr_method = ErodeMorphology;
1839 break;
anthony4fd27e22010-02-07 08:17:18 +00001840 case CloseIntensityMorphology:
anthony930be612010-02-08 04:26:15 +00001841 /* A reflected kernel is needed for a Close */
1842 if ( curr_kernel == kernel )
1843 curr_kernel = CloneKernelInfo(kernel);
anthony1b2bc0a2010-05-12 05:25:22 +00001844 if (curr_kernel == (KernelInfo *) NULL)
1845 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00001846 RotateKernelInfo(curr_kernel,180);
1847 new_image = MorphologyImageChannel(image, channel,
1848 DilateIntensityMorphology, iterations, curr_kernel, exception);
1849 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00001850 goto error_cleanup;
anthony4fd27e22010-02-07 08:17:18 +00001851 curr_method = ErodeIntensityMorphology;
anthony602ab9b2010-01-05 08:06:50 +00001852 break;
1853
anthony930be612010-02-08 04:26:15 +00001854 case CorrelateMorphology:
1855 /* A Correlation is actually a Convolution with a reflected kernel.
1856 ** However a Convolution is a weighted sum with a reflected kernel.
1857 ** It may seem stange to convert a Correlation into a Convolution
1858 ** as the Correleation is the simplier method, but Convolution is
1859 ** much more commonly used, and it makes sense to implement it directly
1860 ** so as to avoid the need to duplicate the kernel when it is not
1861 ** required (which is typically the default).
1862 */
1863 if ( curr_kernel == kernel )
1864 curr_kernel = CloneKernelInfo(kernel);
anthony1b2bc0a2010-05-12 05:25:22 +00001865 if (curr_kernel == (KernelInfo *) NULL)
1866 goto error_cleanup;
anthony930be612010-02-08 04:26:15 +00001867 RotateKernelInfo(curr_kernel,180);
1868 curr_method = ConvolveMorphology;
1869 /* FALL-THRU into Correlate (weigthed sum without reflection) */
1870
anthonyc94cdb02010-01-06 08:15:29 +00001871 case ConvolveMorphology:
anthony1b2bc0a2010-05-12 05:25:22 +00001872 { /* Scale or Normalize kernel, according to user wishes
1873 ** before using it for the Convolve/Correlate method.
1874 **
1875 ** FUTURE: provide some way for internal functions to disable
1876 ** user bias and scaling effects.
1877 */
1878 const char
1879 *artifact = GetImageArtifact(image,"convolve:scale");
1880 if ( artifact != (char *)NULL ) {
1881 GeometryFlags
1882 flags;
1883 GeometryInfo
1884 args;
anthony999bb2c2010-02-18 12:38:01 +00001885
anthony1b2bc0a2010-05-12 05:25:22 +00001886 if ( curr_kernel == kernel )
1887 curr_kernel = CloneKernelInfo(kernel);
1888 if (curr_kernel == (KernelInfo *) NULL)
1889 goto error_cleanup;
1890 args.rho = 1.0;
1891 flags = (GeometryFlags) ParseGeometry(artifact, &args);
1892 ScaleKernelInfo(curr_kernel, args.rho, flags);
1893 }
anthony4fd27e22010-02-07 08:17:18 +00001894 }
anthony930be612010-02-08 04:26:15 +00001895 /* FALL-THRU to do the first, and typically the only iteration */
anthony4fd27e22010-02-07 08:17:18 +00001896
anthony602ab9b2010-01-05 08:06:50 +00001897 default:
anthony930be612010-02-08 04:26:15 +00001898 /* Do a single iteration using the Low-Level Morphology method!
1899 ** This ensures a "new_image" has been generated, but allows us to skip
1900 ** the creation of 'old_image' if no more iterations are needed.
1901 **
1902 ** The "curr_method" should also be set to a low-level method that is
anthony7a01dcf2010-05-11 12:25:52 +00001903 ** understood by the MorphologyPrimative() internal function.
anthony602ab9b2010-01-05 08:06:50 +00001904 */
1905 new_image=CloneImage(image,0,0,MagickTrue,exception);
1906 if (new_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00001907 goto error_cleanup;
anthony602ab9b2010-01-05 08:06:50 +00001908 if (SetImageStorageClass(new_image,DirectClass) == MagickFalse)
1909 {
1910 InheritException(exception,&new_image->exception);
anthony1b2bc0a2010-05-12 05:25:22 +00001911 goto exit_cleanup;
anthony602ab9b2010-01-05 08:06:50 +00001912 }
anthony1b2bc0a2010-05-12 05:25:22 +00001913 count++; /* iteration count */
anthony7a01dcf2010-05-11 12:25:52 +00001914 changed = MorphologyPrimative(image,new_image,curr_method,channel,
1915 curr_kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001916 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
anthony1b2bc0a2010-05-12 05:25:22 +00001917 fprintf(stderr, "Morphology %s:%lu.%lu => Changed %lu\n",
anthony4fd27e22010-02-07 08:17:18 +00001918 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
anthony1b2bc0a2010-05-12 05:25:22 +00001919 count, 0L, changed);
anthony930be612010-02-08 04:26:15 +00001920 break;
anthony602ab9b2010-01-05 08:06:50 +00001921 }
anthony1b2bc0a2010-05-12 05:25:22 +00001922 /* At this point
1923 * + "curr_method" should be set to a low-level morphology method
1924 * + "count=1" if the first iteration of the first kernel has been done.
1925 * + "new_image" is defined and contains the current resulting image
1926 */
anthony602ab9b2010-01-05 08:06:50 +00001927
anthony1b2bc0a2010-05-12 05:25:22 +00001928 /* The Hit-And-Miss Morphology is not 'iterated' against the resulting
1929 ** image from the previous morphology step. It must always be applied
1930 ** to the original image, with the results collected into a union (maximimum
1931 ** or lighten) of all the results. Multiple kernels may be applied but
1932 ** an iteration of the morphology does nothing, so is ignored.
anthony7a01dcf2010-05-11 12:25:52 +00001933 **
anthony1b2bc0a2010-05-12 05:25:22 +00001934 ** The first kernel is guranteed to have been done, so lets continue the
1935 ** process, with the other kernels in the kernel list.
anthony7a01dcf2010-05-11 12:25:52 +00001936 */
anthony1b2bc0a2010-05-12 05:25:22 +00001937 if ( method == HitAndMissMorphology )
anthony7a01dcf2010-05-11 12:25:52 +00001938 {
anthony1b2bc0a2010-05-12 05:25:22 +00001939 if ( curr_kernel->next != (KernelInfo *) NULL ) {
1940 /* create a second working image */
1941 old_image = CloneImage(image,0,0,MagickTrue,exception);
1942 if (old_image == (Image *) NULL)
1943 goto error_cleanup;
1944 if (SetImageStorageClass(old_image,DirectClass) == MagickFalse)
1945 {
1946 InheritException(exception,&old_image->exception);
1947 goto exit_cleanup;
1948 }
anthony7a01dcf2010-05-11 12:25:52 +00001949
anthony1b2bc0a2010-05-12 05:25:22 +00001950 /* loop through rest of the kernels */
1951 this_kernel=curr_kernel->next;
1952 kernel_number=1;
1953 while( this_kernel != (KernelInfo *) NULL )
1954 {
1955 changed = MorphologyPrimative(image,old_image,curr_method,channel,
1956 this_kernel,exception);
1957 (void) CompositeImageChannel(new_image,
1958 (ChannelType) (channel & ~SyncChannels), LightenCompositeOp,
1959 old_image, 0, 0);
1960 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
1961 fprintf(stderr, "Morphology %s:%lu.%lu => Changed %lu\n",
1962 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
1963 count, kernel_number, changed);
1964 this_kernel = this_kernel->next;
1965 kernel_number++;
1966 }
1967 old_image=DestroyImage(old_image);
1968 }
1969 goto exit_cleanup;
anthony7a01dcf2010-05-11 12:25:52 +00001970 }
1971
anthony1b2bc0a2010-05-12 05:25:22 +00001972 /* Repeat the low-level morphology over all kernels
1973 until iteration count limit or no change from any kernel is found */
1974 if ( ( count < limit && changed > 0 ) ||
1975 curr_kernel->next != (KernelInfo *) NULL ) {
anthony930be612010-02-08 04:26:15 +00001976
anthony1b2bc0a2010-05-12 05:25:22 +00001977 /* create a second working image */
1978 old_image = CloneImage(image,0,0,MagickTrue,exception);
anthony602ab9b2010-01-05 08:06:50 +00001979 if (old_image == (Image *) NULL)
anthony1b2bc0a2010-05-12 05:25:22 +00001980 goto error_cleanup;
anthony602ab9b2010-01-05 08:06:50 +00001981 if (SetImageStorageClass(old_image,DirectClass) == MagickFalse)
1982 {
1983 InheritException(exception,&old_image->exception);
anthony1b2bc0a2010-05-12 05:25:22 +00001984 goto exit_cleanup;
anthony602ab9b2010-01-05 08:06:50 +00001985 }
anthony1b2bc0a2010-05-12 05:25:22 +00001986
1987 /* reset variables for the first/next iteration, or next kernel) */
1988 kernel_number = 0;
1989 this_kernel = curr_kernel;
1990 total_changed = count != 0 ? changed : 0;
1991 if ( count != 0 && this_kernel != (KernelInfo *) NULL ) {
1992 count = 0; /* first iteration is not yet finished! */
1993 this_kernel = curr_kernel->next;
1994 kernel_number = 1;
1995 total_changed = changed;
1996 }
1997
1998 while ( count < limit ) {
1999 count++;
2000 while ( this_kernel != (KernelInfo *) NULL ) {
anthony602ab9b2010-01-05 08:06:50 +00002001 Image *tmp = old_image;
2002 old_image = new_image;
2003 new_image = tmp;
anthony7a01dcf2010-05-11 12:25:52 +00002004 changed = MorphologyPrimative(old_image,new_image,curr_method,channel,
anthony1b2bc0a2010-05-12 05:25:22 +00002005 this_kernel,exception);
anthony602ab9b2010-01-05 08:06:50 +00002006 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
anthony1b2bc0a2010-05-12 05:25:22 +00002007 fprintf(stderr, "Morphology %s:%lu.%lu => Changed %lu\n",
anthony4fd27e22010-02-07 08:17:18 +00002008 MagickOptionToMnemonic(MagickMorphologyOptions, curr_method),
anthony1b2bc0a2010-05-12 05:25:22 +00002009 count, kernel_number, changed);
2010 total_changed += changed;
2011 this_kernel = this_kernel->next;
2012 kernel_number++;
anthony602ab9b2010-01-05 08:06:50 +00002013 }
anthony1b2bc0a2010-05-12 05:25:22 +00002014 if ( total_changed == 0 )
2015 break; /* no changes after processing all kernels - ABORT */
2016 /* prepare for next loop */
2017 total_changed = 0;
2018 kernel_number = 0;
2019 this_kernel = curr_kernel;
2020 }
cristy150989e2010-02-01 14:59:39 +00002021 old_image=DestroyImage(old_image);
anthony602ab9b2010-01-05 08:06:50 +00002022 }
anthony930be612010-02-08 04:26:15 +00002023
anthony1b2bc0a2010-05-12 05:25:22 +00002024 /* finished with kernel - destary any copy that was made */
anthony4fd27e22010-02-07 08:17:18 +00002025 if ( curr_kernel != kernel )
2026 curr_kernel=DestroyKernelInfo(curr_kernel);
2027
anthony7d10d742010-05-06 07:05:29 +00002028 /* Third-level Subtractive methods post-processing
2029 **
2030 ** The removal of any 'Sync' channel flag in the Image Compositon below
2031 ** ensures the compose method is applied in a purely mathematical way, only
2032 ** the selected channels, without any normal 'alpha blending' normally
2033 ** associated with the compose method.
2034 **
2035 ** Note "method" here is the 'original' morphological method, and not the
2036 ** 'current' morphological method used above to generate "new_image".
2037 */
anthony4fd27e22010-02-07 08:17:18 +00002038 switch( method ) {
2039 case EdgeOutMorphology:
2040 case EdgeInMorphology:
2041 case TopHatMorphology:
2042 case BottomHatMorphology:
anthony930be612010-02-08 04:26:15 +00002043 /* Get Difference relative to the original image */
cristyaeb2cbc2010-05-07 13:28:58 +00002044 (void) CompositeImageChannel(new_image, (ChannelType) (channel & ~SyncChannels),
anthony7d10d742010-05-06 07:05:29 +00002045 DifferenceCompositeOp, image, 0, 0);
anthony4fd27e22010-02-07 08:17:18 +00002046 break;
anthony7d10d742010-05-06 07:05:29 +00002047 case EdgeMorphology:
2048 /* Difference the Eroded image from the saved Dilated image */
cristyaeb2cbc2010-05-07 13:28:58 +00002049 (void) CompositeImageChannel(new_image, (ChannelType) (channel & ~SyncChannels),
anthony7d10d742010-05-06 07:05:29 +00002050 DifferenceCompositeOp, grad_image, 0, 0);
anthony4fd27e22010-02-07 08:17:18 +00002051 grad_image=DestroyImage(grad_image);
2052 break;
2053 default:
2054 break;
2055 }
anthony602ab9b2010-01-05 08:06:50 +00002056
2057 return(new_image);
anthony1b2bc0a2010-05-12 05:25:22 +00002058
2059 /* Yes goto's are bad, but in this case it makes cleanup lot more efficient */
2060error_cleanup:
2061 if ( new_image != (Image *) NULL )
2062 DestroyImage(new_image);
2063exit_cleanup:
2064 if ( old_image != (Image *) NULL )
2065 DestroyImage(old_image);
2066 if ( curr_kernel != (KernelInfo *) NULL && curr_kernel != kernel )
2067 curr_kernel=DestroyKernelInfo(curr_kernel);
2068 return(new_image);
anthony602ab9b2010-01-05 08:06:50 +00002069}
anthony83ba99b2010-01-24 08:48:15 +00002070
2071/*
2072%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2073% %
2074% %
2075% %
anthony4fd27e22010-02-07 08:17:18 +00002076+ R o t a t e K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00002077% %
2078% %
2079% %
2080%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2081%
anthony4fd27e22010-02-07 08:17:18 +00002082% RotateKernelInfo() rotates the kernel by the angle given. Currently it is
anthony83ba99b2010-01-24 08:48:15 +00002083% restricted to 90 degree angles, but this may be improved in the future.
2084%
anthony4fd27e22010-02-07 08:17:18 +00002085% The format of the RotateKernelInfo method is:
anthony83ba99b2010-01-24 08:48:15 +00002086%
anthony4fd27e22010-02-07 08:17:18 +00002087% void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00002088%
2089% A description of each parameter follows:
2090%
2091% o kernel: the Morphology/Convolution kernel
2092%
2093% o angle: angle to rotate in degrees
2094%
anthonyc4c86e02010-01-27 09:30:32 +00002095% This function is only internel to this module, as it is not finalized,
2096% especially with regard to non-orthogonal angles, and rotation of larger
2097% 2D kernels.
anthony83ba99b2010-01-24 08:48:15 +00002098*/
anthony4fd27e22010-02-07 08:17:18 +00002099static void RotateKernelInfo(KernelInfo *kernel, double angle)
anthony83ba99b2010-01-24 08:48:15 +00002100{
anthony1b2bc0a2010-05-12 05:25:22 +00002101 /* angle the lower kernels first */
2102 if ( kernel->next != (KernelInfo *) NULL)
2103 RotateKernelInfo(kernel->next, angle);
2104
anthony83ba99b2010-01-24 08:48:15 +00002105 /* WARNING: Currently assumes the kernel (rightly) is horizontally symetrical
2106 **
2107 ** TODO: expand beyond simple 90 degree rotates, flips and flops
2108 */
2109
2110 /* Modulus the angle */
2111 angle = fmod(angle, 360.0);
2112 if ( angle < 0 )
2113 angle += 360.0;
2114
2115 if ( 315.0 < angle || angle <= 45.0 )
2116 return; /* no change! - At least at this time */
2117
2118 switch (kernel->type) {
2119 /* These built-in kernels are cylindrical kernels, rotating is useless */
2120 case GaussianKernel:
2121 case LaplacianKernel:
2122 case LOGKernel:
2123 case DOGKernel:
2124 case DiskKernel:
2125 case ChebyshevKernel:
2126 case ManhattenKernel:
2127 case EuclideanKernel:
2128 return;
2129
2130 /* These may be rotatable at non-90 angles in the future */
2131 /* but simply rotating them in multiples of 90 degrees is useless */
2132 case SquareKernel:
2133 case DiamondKernel:
2134 case PlusKernel:
2135 return;
2136
2137 /* These only allows a +/-90 degree rotation (by transpose) */
2138 /* A 180 degree rotation is useless */
2139 case BlurKernel:
2140 case RectangleKernel:
2141 if ( 135.0 < angle && angle <= 225.0 )
2142 return;
2143 if ( 225.0 < angle && angle <= 315.0 )
2144 angle -= 180;
2145 break;
2146
2147 /* these are freely rotatable in 90 degree units */
2148 case CometKernel:
2149 case UndefinedKernel:
2150 case UserDefinedKernel:
2151 break;
2152 }
2153 if ( 135.0 < angle && angle <= 225.0 )
2154 {
2155 /* For a 180 degree rotation - also know as a reflection */
2156 /* This is actually a very very common operation! */
2157 /* Basically all that is needed is a reversal of the kernel data! */
2158 unsigned long
2159 i,j;
2160 register double
2161 *k,t;
2162
2163 k=kernel->values;
2164 for ( i=0, j=kernel->width*kernel->height-1; i<j; i++, j--)
2165 t=k[i], k[i]=k[j], k[j]=t;
2166
anthony930be612010-02-08 04:26:15 +00002167 kernel->x = (long) kernel->width - kernel->x - 1;
2168 kernel->y = (long) kernel->height - kernel->y - 1;
anthony83ba99b2010-01-24 08:48:15 +00002169 angle = fmod(angle+180.0, 360.0);
2170 }
2171 if ( 45.0 < angle && angle <= 135.0 )
2172 { /* Do a transpose and a flop, of the image, which results in a 90
2173 * degree rotation using two mirror operations.
2174 *
2175 * WARNING: this assumes the original image was a 1 dimentional image
2176 * but currently that is the only built-ins it is applied to.
2177 */
cristy150989e2010-02-01 14:59:39 +00002178 long
anthony83ba99b2010-01-24 08:48:15 +00002179 t;
cristy150989e2010-02-01 14:59:39 +00002180 t = (long) kernel->width;
anthony83ba99b2010-01-24 08:48:15 +00002181 kernel->width = kernel->height;
cristy150989e2010-02-01 14:59:39 +00002182 kernel->height = (unsigned long) t;
cristyc99304f2010-02-01 15:26:27 +00002183 t = kernel->x;
2184 kernel->x = kernel->y;
2185 kernel->y = t;
anthony83ba99b2010-01-24 08:48:15 +00002186 angle = fmod(450.0 - angle, 360.0);
2187 }
2188 /* At this point angle should be between -45 (315) and +45 degrees
2189 * In the future some form of non-orthogonal angled rotates could be
2190 * performed here, posibily with a linear kernel restriction.
2191 */
2192
2193#if 0
2194 Not currently in use!
2195 { /* Do a flop, this assumes kernel is horizontally symetrical.
2196 * Each row of the kernel needs to be reversed!
2197 */
2198 unsigned long
2199 y;
cristy150989e2010-02-01 14:59:39 +00002200 register long
anthony83ba99b2010-01-24 08:48:15 +00002201 x,r;
2202 register double
2203 *k,t;
2204
2205 for ( y=0, k=kernel->values; y < kernel->height; y++, k+=kernel->width)
2206 for ( x=0, r=kernel->width-1; x<kernel->width/2; x++, r--)
2207 t=k[x], k[x]=k[r], k[r]=t;
2208
cristyc99304f2010-02-01 15:26:27 +00002209 kernel->x = kernel->width - kernel->x - 1;
anthony83ba99b2010-01-24 08:48:15 +00002210 angle = fmod(angle+180.0, 360.0);
2211 }
2212#endif
2213 return;
2214}
2215
2216/*
2217%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2218% %
2219% %
2220% %
cristy6771f1e2010-03-05 19:43:39 +00002221% S c a l e K e r n e l I n f o %
anthonycc6c8362010-01-25 04:14:01 +00002222% %
2223% %
2224% %
2225%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2226%
anthony1b2bc0a2010-05-12 05:25:22 +00002227% ScaleKernelInfo() scales the given kernel list by the given amount, with or
2228% without normalization of the sum of the kernel values (as per given flags).
anthonycc6c8362010-01-25 04:14:01 +00002229%
anthony999bb2c2010-02-18 12:38:01 +00002230% By default (no flags given) the values within the kernel is scaled
anthony1b2bc0a2010-05-12 05:25:22 +00002231% directly using given scaling factor without change.
anthonycc6c8362010-01-25 04:14:01 +00002232%
anthony1b2bc0a2010-05-12 05:25:22 +00002233% If any 'normalize_flags' are given the kernel will first be normalized and
2234% then further scaled by the scaling factor value given. A 'PercentValue'
2235% flag will cause the given scaling factor to be divided by one hundred
2236% percent.
anthony999bb2c2010-02-18 12:38:01 +00002237%
2238% Kernel normalization ('normalize_flags' given) is designed to ensure that
2239% any use of the kernel scaling factor with 'Convolve' or 'Correlate'
anthony1b2bc0a2010-05-12 05:25:22 +00002240% morphology methods will fall into -1.0 to +1.0 range. Note that for
2241% non-HDRI versions of IM this may cause images to have any negative results
2242% clipped, unless some 'bias' is used.
anthony999bb2c2010-02-18 12:38:01 +00002243%
2244% More specifically. Kernels which only contain positive values (such as a
2245% 'Gaussian' kernel) will be scaled so that those values sum to +1.0,
anthony1b2bc0a2010-05-12 05:25:22 +00002246% ensuring a 0.0 to +1.0 output range for non-HDRI images.
anthony999bb2c2010-02-18 12:38:01 +00002247%
2248% For Kernels that contain some negative values, (such as 'Sharpen' kernels)
2249% the kernel will be scaled by the absolute of the sum of kernel values, so
2250% that it will generally fall within the +/- 1.0 range.
2251%
2252% For kernels whose values sum to zero, (such as 'Laplician' kernels) kernel
2253% will be scaled by just the sum of the postive values, so that its output
2254% range will again fall into the +/- 1.0 range.
2255%
2256% For special kernels designed for locating shapes using 'Correlate', (often
2257% only containing +1 and -1 values, representing foreground/brackground
2258% matching) a special normalization method is provided to scale the positive
2259% values seperatally to those of the negative values, so the kernel will be
2260% forced to become a zero-sum kernel better suited to such searches.
2261%
anthony1b2bc0a2010-05-12 05:25:22 +00002262% WARNING: Correct normalization of the kernel assumes that the '*_range'
anthony999bb2c2010-02-18 12:38:01 +00002263% attributes within the kernel structure have been correctly set during the
2264% kernels creation.
2265%
2266% NOTE: The values used for 'normalize_flags' have been selected specifically
anthony1b2bc0a2010-05-12 05:25:22 +00002267% to match the use of geometry options, so that '!' means NormalizeValue,
2268% '^' means CorrelateNormalizeValue, and '%' means PercentValue. All other
anthony999bb2c2010-02-18 12:38:01 +00002269% GeometryFlags values are ignored.
anthonycc6c8362010-01-25 04:14:01 +00002270%
anthony4fd27e22010-02-07 08:17:18 +00002271% The format of the ScaleKernelInfo method is:
anthonycc6c8362010-01-25 04:14:01 +00002272%
anthony999bb2c2010-02-18 12:38:01 +00002273% void ScaleKernelInfo(KernelInfo *kernel, const double scaling_factor,
2274% const MagickStatusType normalize_flags )
anthonycc6c8362010-01-25 04:14:01 +00002275%
2276% A description of each parameter follows:
2277%
2278% o kernel: the Morphology/Convolution kernel
2279%
anthony999bb2c2010-02-18 12:38:01 +00002280% o scaling_factor:
2281% multiply all values (after normalization) by this factor if not
2282% zero. If the kernel is normalized regardless of any flags.
2283%
2284% o normalize_flags:
2285% GeometryFlags defining normalization method to use.
2286% specifically: NormalizeValue, CorrelateNormalizeValue,
2287% and/or PercentValue
anthonycc6c8362010-01-25 04:14:01 +00002288%
anthonyc4c86e02010-01-27 09:30:32 +00002289% This function is internal to this module only at this time, but can be
2290% exported to other modules if needed.
anthonycc6c8362010-01-25 04:14:01 +00002291*/
cristy6771f1e2010-03-05 19:43:39 +00002292MagickExport void ScaleKernelInfo(KernelInfo *kernel,
2293 const double scaling_factor,const GeometryFlags normalize_flags)
anthonycc6c8362010-01-25 04:14:01 +00002294{
cristy150989e2010-02-01 14:59:39 +00002295 register long
anthonycc6c8362010-01-25 04:14:01 +00002296 i;
2297
anthony999bb2c2010-02-18 12:38:01 +00002298 register double
2299 pos_scale,
2300 neg_scale;
2301
anthony1b2bc0a2010-05-12 05:25:22 +00002302 /* scale the lower kernels first */
2303 if ( kernel->next != (KernelInfo *) NULL)
2304 ScaleKernelInfo(kernel->next, scaling_factor, normalize_flags);
2305
anthony999bb2c2010-02-18 12:38:01 +00002306 pos_scale = 1.0;
2307 if ( (normalize_flags&NormalizeValue) != 0 ) {
2308 /* normalize kernel appropriately */
2309 if ( fabs(kernel->positive_range + kernel->negative_range) > MagickEpsilon )
2310 pos_scale = fabs(kernel->positive_range + kernel->negative_range);
anthonycc6c8362010-01-25 04:14:01 +00002311 else
anthony999bb2c2010-02-18 12:38:01 +00002312 pos_scale = kernel->positive_range; /* special zero-summing kernel */
2313 }
2314 /* force kernel into being a normalized zero-summing kernel */
2315 if ( (normalize_flags&CorrelateNormalizeValue) != 0 ) {
2316 pos_scale = ( fabs(kernel->positive_range) > MagickEpsilon )
2317 ? kernel->positive_range : 1.0;
2318 neg_scale = ( fabs(kernel->negative_range) > MagickEpsilon )
2319 ? -kernel->negative_range : 1.0;
2320 }
2321 else
2322 neg_scale = pos_scale;
2323
2324 /* finialize scaling_factor for positive and negative components */
2325 pos_scale = scaling_factor/pos_scale;
2326 neg_scale = scaling_factor/neg_scale;
2327 if ( (normalize_flags&PercentValue) != 0 ) {
2328 pos_scale /= 100.0;
2329 neg_scale /= 100.0;
anthonycc6c8362010-01-25 04:14:01 +00002330 }
2331
cristy150989e2010-02-01 14:59:39 +00002332 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00002333 if ( ! IsNan(kernel->values[i]) )
anthony999bb2c2010-02-18 12:38:01 +00002334 kernel->values[i] *= (kernel->values[i] >= 0) ? pos_scale : neg_scale;
anthonycc6c8362010-01-25 04:14:01 +00002335
anthony999bb2c2010-02-18 12:38:01 +00002336 /* convolution output range */
2337 kernel->positive_range *= pos_scale;
2338 kernel->negative_range *= neg_scale;
2339 /* maximum and minimum values in kernel */
2340 kernel->maximum *= (kernel->maximum >= 0.0) ? pos_scale : neg_scale;
2341 kernel->minimum *= (kernel->minimum >= 0.0) ? pos_scale : neg_scale;
2342
2343 /* swap kernel settings if user scaling factor is negative */
2344 if ( scaling_factor < MagickEpsilon ) {
2345 double t;
2346 t = kernel->positive_range;
2347 kernel->positive_range = kernel->negative_range;
2348 kernel->negative_range = t;
2349 t = kernel->maximum;
2350 kernel->maximum = kernel->minimum;
2351 kernel->minimum = 1;
2352 }
anthonycc6c8362010-01-25 04:14:01 +00002353
2354 return;
2355}
2356
2357/*
2358%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2359% %
2360% %
2361% %
anthony4fd27e22010-02-07 08:17:18 +00002362+ S h o w K e r n e l I n f o %
anthony83ba99b2010-01-24 08:48:15 +00002363% %
2364% %
2365% %
2366%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2367%
anthony4fd27e22010-02-07 08:17:18 +00002368% ShowKernelInfo() outputs the details of the given kernel defination to
2369% standard error, generally due to a users 'showkernel' option request.
anthony83ba99b2010-01-24 08:48:15 +00002370%
2371% The format of the ShowKernel method is:
2372%
anthony4fd27e22010-02-07 08:17:18 +00002373% void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00002374%
2375% A description of each parameter follows:
2376%
2377% o kernel: the Morphology/Convolution kernel
2378%
anthonyc4c86e02010-01-27 09:30:32 +00002379% This function is internal to this module only at this time. That may change
2380% in the future.
anthony83ba99b2010-01-24 08:48:15 +00002381*/
anthony4fd27e22010-02-07 08:17:18 +00002382MagickExport void ShowKernelInfo(KernelInfo *kernel)
anthony83ba99b2010-01-24 08:48:15 +00002383{
anthony7a01dcf2010-05-11 12:25:52 +00002384 KernelInfo
2385 *k;
anthony83ba99b2010-01-24 08:48:15 +00002386
anthony7a01dcf2010-05-11 12:25:52 +00002387 long
2388 c, i, u, v;
2389
2390 for (c=0, k=kernel; k != (KernelInfo *) NULL; c++, k=k->next ) {
2391
2392 fprintf(stderr, "Kernel ");
2393 if ( kernel->next != (KernelInfo *) NULL )
2394 fprintf(stderr, " #%ld", c );
2395 fprintf(stderr, " \"%s\" of size %lux%lu%+ld%+ld ",
2396 MagickOptionToMnemonic(MagickKernelOptions, k->type),
2397 kernel->width, k->height,
2398 kernel->x, kernel->y );
2399 fprintf(stderr,
2400 " with values from %.*lg to %.*lg\n",
2401 GetMagickPrecision(), k->minimum,
2402 GetMagickPrecision(), k->maximum);
2403 fprintf(stderr, "Forming convolution output range from %.*lg to %.*lg%s\n",
2404 GetMagickPrecision(), k->negative_range,
2405 GetMagickPrecision(), k->positive_range,
2406 /*kernel->normalized == MagickTrue ? " (normalized)" : */ "" );
2407 for (i=v=0; v < (long) k->height; v++) {
2408 fprintf(stderr,"%2ld:",v);
2409 for (u=0; u < (long) k->width; u++, i++)
2410 if ( IsNan(k->values[i]) )
2411 fprintf(stderr," %*s", GetMagickPrecision()+2, "nan");
2412 else
2413 fprintf(stderr," %*.*lg", GetMagickPrecision()+2,
2414 GetMagickPrecision(), k->values[i]);
2415 fprintf(stderr,"\n");
2416 }
anthony83ba99b2010-01-24 08:48:15 +00002417 }
2418}
anthonycc6c8362010-01-25 04:14:01 +00002419
2420/*
2421%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2422% %
2423% %
2424% %
anthony4fd27e22010-02-07 08:17:18 +00002425+ Z e r o K e r n e l N a n s %
anthonycc6c8362010-01-25 04:14:01 +00002426% %
2427% %
2428% %
2429%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2430%
2431% ZeroKernelNans() replaces any special 'nan' value that may be present in
2432% the kernel with a zero value. This is typically done when the kernel will
2433% be used in special hardware (GPU) convolution processors, to simply
2434% matters.
2435%
2436% The format of the ZeroKernelNans method is:
2437%
2438% voidZeroKernelNans (KernelInfo *kernel)
2439%
2440% A description of each parameter follows:
2441%
2442% o kernel: the Morphology/Convolution kernel
2443%
2444% FUTURE: return the information in a string for API usage.
2445*/
anthonyc4c86e02010-01-27 09:30:32 +00002446MagickExport void ZeroKernelNans(KernelInfo *kernel)
anthonycc6c8362010-01-25 04:14:01 +00002447{
cristy150989e2010-02-01 14:59:39 +00002448 register long
anthonycc6c8362010-01-25 04:14:01 +00002449 i;
2450
anthony1b2bc0a2010-05-12 05:25:22 +00002451 /* scale the lower kernels first */
2452 if ( kernel->next != (KernelInfo *) NULL)
2453 ZeroKernelNans(kernel->next);
2454
cristy150989e2010-02-01 14:59:39 +00002455 for (i=0; i < (long) (kernel->width*kernel->height); i++)
anthonycc6c8362010-01-25 04:14:01 +00002456 if ( IsNan(kernel->values[i]) )
2457 kernel->values[i] = 0.0;
2458
2459 return;
2460}