blob: ebab9fa462ed7e420351cb150202d42b4bb0954c [file] [log] [blame]
cristy701db312009-11-20 03:14:08 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% M M OOO RRRR PPPP H H OOO L OOO GGGG Y Y %
7% MM MM O O R R P P H H O O L O O G Y Y %
8% M M M O O RRRR PPPP HHHHH O O L O O G GGG Y %
9% M M O O R R P H H O O L O O G G Y %
10% M M OOO R R P H H OOO LLLLL OOO GGG Y %
11% %
12% %
13% MagickCore Morphology Methods %
14% %
15% Software Design %
16% Anthony Thyssen %
anthonyc94cdb02010-01-06 08:15:29 +000017% January 2010 %
cristy701db312009-11-20 03:14:08 +000018% %
19% %
cristy16af1cb2009-12-11 21:38:29 +000020% Copyright 1999-2010 ImageMagick Studio LLC, a non-profit organization %
cristy701db312009-11-20 03:14:08 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
anthony602ab9b2010-01-05 08:06:50 +000036% Morpology is the the application of various kernals, of any size and even
37% shape, to a image in various ways (typically binary, but not always).
cristy701db312009-11-20 03:14:08 +000038%
anthony602ab9b2010-01-05 08:06:50 +000039% Convolution (weighted sum or average) is just one specific type of
40% morphology. Just one that is very common for image bluring and sharpening
41% effects. Not only 2D Gaussian blurring, but also 2-pass 1D Blurring.
42%
43% This module provides not only a general morphology function, and the ability
44% to apply more advanced or iterative morphologies, but also functions for the
45% generation of many different types of kernel arrays from user supplied
46% arguments. Prehaps even the generation of a kernel from a small image.
cristy701db312009-11-20 03:14:08 +000047*/
48
49/*
50 Include declarations.
51*/
52#include "magick/studio.h"
anthony602ab9b2010-01-05 08:06:50 +000053#include "magick/artifact.h"
cristy701db312009-11-20 03:14:08 +000054#include "magick/cache-view.h"
55#include "magick/color-private.h"
56#include "magick/enhance.h"
57#include "magick/exception.h"
58#include "magick/exception-private.h"
anthony602ab9b2010-01-05 08:06:50 +000059#include "magick/gem.h"
cristy701db312009-11-20 03:14:08 +000060#include "magick/hashmap.h"
61#include "magick/image.h"
cristybba804b2010-01-05 15:39:59 +000062#include "magick/image-private.h"
cristy701db312009-11-20 03:14:08 +000063#include "magick/list.h"
64#include "magick/memory_.h"
65#include "magick/monitor-private.h"
66#include "magick/morphology.h"
anthony602ab9b2010-01-05 08:06:50 +000067#include "magick/option.h"
cristy701db312009-11-20 03:14:08 +000068#include "magick/pixel-private.h"
69#include "magick/prepress.h"
70#include "magick/quantize.h"
71#include "magick/registry.h"
72#include "magick/semaphore.h"
73#include "magick/splay-tree.h"
74#include "magick/statistic.h"
75#include "magick/string_.h"
anthony602ab9b2010-01-05 08:06:50 +000076#include "magick/string-private.h"
77#include "magick/token.h"
78
anthonyc94cdb02010-01-06 08:15:29 +000079
anthony602ab9b2010-01-05 08:06:50 +000080/*
anthonyc94cdb02010-01-06 08:15:29 +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.
anthony602ab9b2010-01-05 08:06:50 +000084 *
anthonyc94cdb02010-01-06 08:15:29 +000085 * These are used a Kernel value of NaN means that that kernal position is not
86 * part of the normal convolution or morphology process, and thus allowing the
87 * use of 'shaped' kernels.
anthony602ab9b2010-01-05 08:06:50 +000088 *
anthonyc94cdb02010-01-06 08:15:29 +000089 * 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.
anthony602ab9b2010-01-05 08:06:50 +000091 */
92#define IsNan(a) ((a)!=(a))
93
94
95/*
96%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
97% %
98% %
99% %
100% A c q u i r e K e r n e l F r o m S t r i n g %
101% %
102% %
103% %
104%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
105%
106% AcquireKernelFromString() takes the given string (generally supplied by the
107% user) and converts it into a Morphology/Convolution Kernel. This allows
108% users to specify a kernel from a number of pre-defined kernels, or to fully
109% specify their own kernel for a specific Convolution or Morphology
110% Operation.
111%
112% The kernel so generated can be any rectangular array of floating point
113% values (doubles) with the 'control point' or 'pixel being affected'
114% anywhere within that array of values.
115%
116% ASIDE: Previously IM was restricted to a square of odd size using the exact
117% center.
118%
119% The floating point values in the kernel can also include a special value
120% known as 'NaN' or 'not a number' to indicate that this value is not part
121% of the kernel array. This allows you to specify a non-rectangular shaped
122% kernel, for use in Morphological operators, without the need for some type
123% of kernal mask.
124%
125% The returned kernel should be freed using the DestroyKernel() when you are
126% finished with it.
127%
128% Input kernel defintion strings can consist of any of three types.
129%
130% "num, num, num, num, ..."
131% list of floating point numbers defining an 'old style' odd sized
132% square kernel. At least 9 values should be provided for a 3x3
133% square kernel, 25 for a 5x5 square kernel, 49 for 7x7, etc.
134% Values can be space or comma separated.
135%
136% "WxH[+X+Y]:num, num, num ..."
137% a kernal of size W by H, with W*H floating point numbers following.
138% the 'center' can be optionally be defined at +X+Y (such that +0+0
139% is top left corner). If not defined a pixel closest to the center
140% of the array is automatically defined.
141%
142% "name:args"
143% Select from one of the built in kernels. See AcquireKernelBuiltIn()
144%
145% Note that 'name' kernels will start with an alphabetic character
146% while the new kernel specification has a ':' character in its
147% specification.
148%
149% TODO: bias and auto-scale handling of the kernel
150% The given kernel is assumed to have been pre-scaled appropriatally, usally
151% by the kernel generator.
152%
153% The format of the AcquireKernal method is:
154%
155% MagickKernel *AcquireKernelFromString(const char *kernel_string)
156%
157% A description of each parameter follows:
158%
159% o kernel_string: the Morphology/Convolution kernel wanted.
160%
161*/
162
163MagickExport MagickKernel *AcquireKernelFromString(const char *kernel_string)
164{
165 MagickKernel
166 *kernel;
167
168 char
169 token[MaxTextExtent];
170
171 register unsigned long
172 i;
173
174 const char
175 *p;
176
177 MagickStatusType
178 flags;
179
180 GeometryInfo
181 args;
182
183 assert(kernel_string != (const char *) NULL);
184 SetGeometryInfo(&args);
185
186 /* does it start with an alpha - Return a builtin kernel */
187 GetMagickToken(kernel_string,&p,token);
188 if ( isalpha((int)token[0]) )
189 {
190 long
191 type;
192
193 type=ParseMagickOption(MagickKernelOptions,MagickFalse,token);
194 if ( type < 0 || type == UserDefinedKernel )
195 return((MagickKernel *)NULL);
196
197 while (((isspace((int) ((unsigned char) *p)) != 0) ||
198 (*p == ',') || (*p == ':' )) && (*p != '\0'))
199 p++;
200 flags = ParseGeometry(p, &args);
201
202 /* special handling of missing values in input string */
203 if ( type == RectangleKernel ) {
204 if ( (flags & WidthValue) == 0 ) /* if no width then */
205 args.rho = args.sigma; /* then width = height */
206 if ( args.rho < 1.0 ) /* if width too small */
207 args.rho = 3; /* then width = 3 */
208 if ( args.sigma < 1.0 ) /* if height too small */
209 args.sigma = args.rho; /* then height = width */
210 if ( (flags & XValue) == 0 ) /* center offset if not defined */
211 args.xi = (double)(((long)args.rho-1)/2);
212 if ( (flags & YValue) == 0 )
213 args.psi = (double)(((long)args.sigma-1)/2);
214 }
215
216 return(AcquireKernelBuiltIn((MagickKernelType)type, &args));
217 }
218
219 kernel=(MagickKernel *) AcquireMagickMemory(sizeof(*kernel));
220 if (kernel == (MagickKernel *)NULL)
221 return(kernel);
222 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
223 kernel->type = UserDefinedKernel;
224
225 /* Has a ':' in argument - New user kernel specification */
226 p = strchr(kernel_string, ':');
227 if ( p != (char *) NULL)
228 {
229#if 1
230 /* ParseGeometry() needs the geometry separated! -- Arrgghh */
231 memcpy(token, kernel_string, p-kernel_string);
232 token[p-kernel_string] = '\0';
233 flags = ParseGeometry(token, &args);
234#else
235 flags = ParseGeometry(kernel_string, &args);
236#endif
237
238 /* Size Handling and Checks */
239 if ( (flags & WidthValue) == 0 ) /* if no width then */
240 args.rho = args.sigma; /* then width = height */
241 if ( args.rho < 1.0 ) /* if width too small */
242 args.rho = 1.0; /* then width = 1 */
243 if ( args.sigma < 1.0 ) /* if height too small */
244 args.sigma = args.rho; /* then height = width */
245 kernel->width = (unsigned long)args.rho;
246 kernel->height = (unsigned long)args.sigma;
247
248 /* Offset Handling and Checks */
249 if ( args.xi < 0.0 || args.psi < 0.0 )
250 return(DestroyKernel(kernel));
251 kernel->offset_x = ((flags & XValue)!=0) ? (unsigned long)args.xi
252 : (kernel->width-1)/2;
253 kernel->offset_y = ((flags & YValue)!=0) ? (unsigned long)args.psi
254 : (kernel->height-1)/2;
255 if ( kernel->offset_x >= kernel->width ||
256 kernel->offset_y >= kernel->height )
257 return(DestroyKernel(kernel));
258
259 p++; /* advance beyond the ':' */
260 }
261 else
262 { /* ELSE - Old old kernel specification, forming odd-square kernel */
263 /* count up number of values given */
264 p=(const char *) kernel_string;
265 for (i=0; *p != '\0'; i++)
266 {
267 GetMagickToken(p,&p,token);
268 if (*token == ',')
269 GetMagickToken(p,&p,token);
270 }
271 /* set the size of the kernel - old sized square */
272 kernel->width = kernel->height= (unsigned long) sqrt((double) i+1.0);
273 kernel->offset_x = kernel->offset_y = (kernel->width-1)/2;
274 p=(const char *) kernel_string;
275 }
276
277 /* Read in the kernel values from rest of input string argument */
278 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
279 kernel->height*sizeof(double));
280 if (kernel->values == (double *) NULL)
281 return(DestroyKernel(kernel));
282
283 kernel->range_neg = kernel->range_pos = 0.0;
284 for (i=0; (i < kernel->width*kernel->height) && (*p != '\0'); i++)
285 {
286 GetMagickToken(p,&p,token);
287 if (*token == ',')
288 GetMagickToken(p,&p,token);
289 (( kernel->values[i] = StringToDouble(token) ) < 0)
290 ? ( kernel->range_neg += kernel->values[i] )
291 : ( kernel->range_pos += kernel->values[i] );
292 }
293 for ( ; i < kernel->width*kernel->height; i++)
294 kernel->values[i]=0.0;
295
296 return(kernel);
297}
298
299/*
300%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
301% %
302% %
303% %
304% A c q u i r e K e r n e l B u i l t I n %
305% %
306% %
307% %
308%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
309%
310% AcquireKernelBuiltIn() returned one of the 'named' built-in types of
311% kernels used for special purposes such as gaussian blurring, skeleton
312% pruning, and edge distance determination.
313%
314% They take a KernelType, and a set of geometry style arguments, which were
315% typically decoded from a user supplied string, or from a more complex
316% Morphology Method that was requested.
317%
318% The format of the AcquireKernalBuiltIn method is:
319%
320% MagickKernel *AcquireKernelBuiltIn(const MagickKernelType type,
321% const GeometryInfo args)
322%
323% A description of each parameter follows:
324%
325% o type: the pre-defined type of kernel wanted
326%
327% o args: arguments defining or modifying the kernel
328%
329% Convolution Kernels
330%
331% Gaussian "[{radius}]x{sigma}"
332% Generate a two-dimentional gaussian kernel, as used by -gaussian
333% A sigma is required, (with the 'x'), due to historical reasons.
334%
335% NOTE: that the 'radius' is optional, but if provided can limit (clip)
336% the final size of the resulting kernel to a square 2*radius+1 in size.
337% The radius should be at least 2 times that of the sigma value, or
338% sever clipping and aliasing may result. If not given or set to 0 the
339% radius will be determined so as to produce the best minimal error
340% result, which is usally much larger than is normally needed.
341%
342% Blur "[{radius}]x{sigma}[+angle]"
343% As per Gaussian, but generates a 1 dimensional or linear gaussian
344% blur, at the angle given (current restricted to orthogonal angles).
345% If a 'radius' is given the kernel is clipped to a width of 2*radius+1.
346%
347% NOTE that two such blurs perpendicular to each other is equivelent to
348% -blur and the previous gaussian, but is often 10 or more times faster.
349%
350% Comet "[{width}]x{sigma}[+angle]"
351% Blur in one direction only, mush like how a bright object leaves
352% a comet like trail. The Kernel is actually half a gaussian curve,
353% Adding two such blurs in oppiste directions produces a Linear Blur.
354%
355% NOTE: that the first argument is the width of the kernel and not the
356% radius of the kernel.
357%
358% # Still to be implemented...
359% #
360% # Laplacian "{radius}x{sigma}"
361% # Laplacian (a mexican hat like) Function
362% #
363% # LOG "{radius},{sigma1},{sigma2}
364% # Laplacian of Gaussian
365% #
366% # DOG "{radius},{sigma1},{sigma2}
367% # Difference of Gaussians
368%
369% Boolean Kernels
370%
371% Rectangle "{geometry}"
372% Simply generate a rectangle of 1's with the size given. You can also
373% specify the location of the 'control point', otherwise the closest
374% pixel to the center of the rectangle is selected.
375%
376% Properly centered and odd sized rectangles work the best.
377%
378% Diamond "[{radius}]"
379% Generate a diamond shaped kernal with given radius to the points.
380% Kernel size will again be radius*2+1 square and defaults to radius 1,
381% generating a 3x3 kernel that is slightly larger than a square.
382%
383% Square "[{radius}]"
384% Generate a square shaped kernel of size radius*2+1, and defaulting
385% to a 3x3 (radius 1).
386%
387% Note that using a larger radius for the "Square" or the "Diamond"
388% is also equivelent to iterating the basic morphological method
389% that many times. However However iterating with the smaller radius 1
390% default is actually faster than using a larger kernel radius.
391%
392% Disk "[{radius}]
393% Generate a binary disk of the radius given, radius may be a float.
394% Kernel size will be ceil(radius)*2+1 square.
395% NOTE: Here are some disk shapes of specific interest
396% "disk:1" => "diamond" or "cross:1"
397% "disk:1.5" => "square"
398% "disk:2" => "diamond:2"
399% "disk:2.5" => default - radius 2 disk shape
400% "disk:2.9" => "square:2"
401% "disk:3.5" => octagonal/disk shape of radius 3
402% "disk:4.2" => roughly octagonal shape of radius 4
403% "disk:4.3" => disk shape of radius 4
404% After this all the kernel shape becomes more and more circular.
405%
406% Because a "disk" is more circular when using a larger radius, using a
407% larger radius is preferred over iterating the morphological operation.
408%
409% Plus "[{radius}]"
410% Generate a kernel in the shape of a 'plus' sign. The length of each
411% arm is also the radius, which defaults to 2.
412%
413% This kernel is not a good general morphological kernel, but is used
414% more for highlighting and marking any single pixels in an image using,
415% a "Dilate" or "Erode" method as appropriate.
anthonyc94cdb02010-01-06 08:15:29 +0000416%
anthony602ab9b2010-01-05 08:06:50 +0000417% NOTE: "plus:1" is equivelent to a "Diamond" kernel.
418%
419% Note that unlike other kernels iterating a plus does not produce the
420% same result as using a larger radius for the cross.
421%
422% Distance Measuring Kernels
423%
424% Chebyshev "[{radius}][x{scale}]" largest x or y distance (default r=1)
425% Manhatten "[{radius}][x{scale}]" square grid distance (default r=1)
anthonyc94cdb02010-01-06 08:15:29 +0000426% Euclidean "[{radius}][x{scale}]" direct distance (default r=1)
anthony602ab9b2010-01-05 08:06:50 +0000427%
428% Different types of distance measuring methods, which are used with the
429% a 'Distance' morphology method for generating a gradient based on
430% distance from an edge of a binary shape, though there is a technique
431% for handling a anti-aliased shape.
432%
anthonyc94cdb02010-01-06 08:15:29 +0000433% Chebyshev Distance (also known as Tchebychev Distance) is a value of
434% one to any neighbour, orthogonal or diagonal. One why of thinking of
435% it is the number of squares a 'King' or 'Queen' in chess needs to
436% traverse reach any other position on a chess board. It results in a
437% 'square' like distance function, but one where diagonals are closer
438% than expected.
anthony602ab9b2010-01-05 08:06:50 +0000439%
anthonyc94cdb02010-01-06 08:15:29 +0000440% Manhatten Distance (also known as Rectilinear Distance, or the Taxi
441% Cab metric), is the distance needed when you can only travel in
442% orthogonal (horizontal or vertical) only. It is the distance a 'Rook'
443% in chess would travel. It results in a diamond like distances, where
444% diagonals are further than expected.
anthony602ab9b2010-01-05 08:06:50 +0000445%
anthonyc94cdb02010-01-06 08:15:29 +0000446% Euclidean Distance is the 'direct' or 'as the crow flys distance.
447% However by default the kernel size only has a radius of 1, which
448% limits the distance to 'Knight' like moves, with only orthogonal and
449% diagonal measurements being correct. As such for the default kernel
450% you will get octagonal like distance function, which is reasonally
451% accurate.
452%
453% However if you use a larger radius such as "Euclidean:4" you will
454% get a much smoother distance gradient from the edge of the shape.
455% Of course a larger kernel is slower to use, and generally not needed.
456%
457% To allow the use of fractional distances that you get with diagonals
458% the actual distance is scaled by a fixed value which the user can
459% provide. This is not actually nessary for either ""Chebyshev" or
460% "Manhatten" distance kernels, but is done for all three distance
461% kernels. If no scale is provided it is set to a value of 100,
462% allowing for a maximum distance measurement of 655 pixels using a Q16
463% version of IM, from any edge. However for small images this can
464% result in quite a dark gradient.
465%
466% See the 'Distance' Morphological Method, for information of how it is
467% applied.
anthony602ab9b2010-01-05 08:06:50 +0000468%
469*/
470
anthony602ab9b2010-01-05 08:06:50 +0000471MagickExport MagickKernel *AcquireKernelBuiltIn(const MagickKernelType type,
472 const GeometryInfo *args)
473{
474 MagickKernel
475 *kernel;
476
477 register unsigned long
478 i;
479
480 register long
481 u,
482 v;
483
484 double
485 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
486
487 kernel=(MagickKernel *) AcquireMagickMemory(sizeof(*kernel));
488 if (kernel == (MagickKernel *) NULL)
489 return(kernel);
490 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
anthonyc94cdb02010-01-06 08:15:29 +0000491 kernel->value_min = kernel->value_max = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000492 kernel->range_neg = kernel->range_pos = 0.0;
493 kernel->type = type;
494
495 switch(type) {
496 /* Convolution Kernels */
497 case GaussianKernel:
498 { double
499 sigma = fabs(args->sigma);
500
501 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
502
503 kernel->width = kernel->height =
504 GetOptimalKernelWidth2D(args->rho,sigma);
505 kernel->offset_x = kernel->offset_y = (kernel->width-1)/2;
506 kernel->range_neg = kernel->range_pos = 0.0;
507 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
508 kernel->height*sizeof(double));
509 if (kernel->values == (double *) NULL)
510 return(DestroyKernel(kernel));
511
512 sigma = 2.0*sigma*sigma; /* simplify the expression */
513 for ( i=0, v=-kernel->offset_y; v <= (long)kernel->offset_y; v++)
514 for ( u=-kernel->offset_x; u <= (long)kernel->offset_x; u++, i++)
515 kernel->range_pos += (
516 kernel->values[i] =
517 exp(-((double)(u*u+v*v))/sigma)
518 /* / (MagickPI*sigma) */ );
anthonyc94cdb02010-01-06 08:15:29 +0000519 kernel->value_min = 0;
520 kernel->value_max = kernel->values[
521 kernel->offset_y*kernel->width+kernel->offset_x ];
anthony602ab9b2010-01-05 08:06:50 +0000522
anthonyc94cdb02010-01-06 08:15:29 +0000523 KernelNormalize(kernel);
anthony602ab9b2010-01-05 08:06:50 +0000524
525 break;
526 }
527 case BlurKernel:
528 { double
529 sigma = fabs(args->sigma);
530
531 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
532
533 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
534 kernel->offset_x = (kernel->width-1)/2;
535 kernel->height = 1;
536 kernel->offset_y = 0;
537 kernel->range_neg = kernel->range_pos = 0.0;
538 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
539 kernel->height*sizeof(double));
540 if (kernel->values == (double *) NULL)
541 return(DestroyKernel(kernel));
542
543#if 1
544#define KernelRank 3
545 /* Formula derived from GetBlurKernel() in "effect.c" (plus bug fix).
546 ** It generates a gaussian 3 times the width, and compresses it into
547 ** the expected range. This produces a closer normalization of the
548 ** resulting kernel, especially for very low sigma values.
549 ** As such while wierd it is prefered.
550 **
551 ** I am told this method originally came from Photoshop.
552 */
553 sigma *= KernelRank; /* simplify expanded curve */
554 v = (kernel->width*KernelRank-1)/2; /* start/end points to fit range */
555 (void) ResetMagickMemory(kernel->values,0, (size_t)
556 kernel->width*sizeof(double));
557 for ( u=-v; u <= v; u++) {
558 kernel->values[(u+v)/KernelRank] +=
559 exp(-((double)(u*u))/(2.0*sigma*sigma))
560 /* / (MagickSQ2PI*sigma/KernelRank) */ ;
561 }
562 for (i=0; i < kernel->width; i++)
563 kernel->range_pos += kernel->values[i];
564#else
565 for ( i=0, u=-kernel->offset_x; i < kernel->width; i++, u++)
566 kernel->range_pos += (
567 kernel->values[i] =
568 exp(-((double)(u*u))/(2.0*sigma*sigma))
569 /* / (MagickSQ2PI*sigma) */ );
570#endif
anthonyc94cdb02010-01-06 08:15:29 +0000571 kernel->value_min = 0;
572 kernel->value_max = kernel->values[ kernel->offset_x ];
anthony602ab9b2010-01-05 08:06:50 +0000573 /* Note that both the above methods do not generate a normalized
574 ** kernel, though it gets close. The kernel may be 'clipped' by a user
575 ** defined radius, producing a smaller (darker) kernel. Also for very
576 ** small sigma's (> 0.1) the central value becomes larger than one,
anthonyc94cdb02010-01-06 08:15:29 +0000577 ** and thus producing a very bright kernel.
anthony602ab9b2010-01-05 08:06:50 +0000578 */
579#if 1
580 /* Normalize the 1D Gaussian Kernel
581 **
582 ** Because of this the divisor in the above kernel generator is
anthonyc94cdb02010-01-06 08:15:29 +0000583 ** not needed, so is not done above.
anthony602ab9b2010-01-05 08:06:50 +0000584 */
anthonyc94cdb02010-01-06 08:15:29 +0000585 KernelNormalize(kernel);
anthony602ab9b2010-01-05 08:06:50 +0000586#endif
587 /* rotate the kernel by given angle */
588 KernelRotate(kernel, args->xi);
589 break;
590 }
591 case CometKernel:
592 { double
593 sigma = fabs(args->sigma);
594
595 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
596
597 if ( args->rho < 1.0 )
598 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
599 else
600 kernel->width = (unsigned long)args->rho;
601 kernel->offset_x = kernel->offset_y = 0;
602 kernel->height = 1;
603 kernel->range_neg = kernel->range_pos = 0.0;
604 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
605 kernel->height*sizeof(double));
606 if (kernel->values == (double *) NULL)
607 return(DestroyKernel(kernel));
608
609 /* A comet blur is half a gaussian curve, so that the object is
610 ** blurred in one direction only. This may not be quite the right
611 ** curve so may change in the future. The function must be normalised.
612 */
613#if 1
614#define KernelRank 3
615 sigma *= KernelRank; /* simplify expanded curve */
616 v = kernel->width*KernelRank; /* start/end points to fit range */
617 (void) ResetMagickMemory(kernel->values,0, (size_t)
618 kernel->width*sizeof(double));
619 for ( u=0; u < v; u++) {
620 kernel->values[u/KernelRank] +=
621 exp(-((double)(u*u))/(2.0*sigma*sigma))
622 /* / (MagickSQ2PI*sigma/KernelRank) */ ;
623 }
624 for (i=0; i < kernel->width; i++)
625 kernel->range_pos += kernel->values[i];
626#else
627 for ( i=0; i < kernel->width; i++)
628 kernel->range_pos += (
629 kernel->values[i] =
630 exp(-((double)(i*i))/(2.0*sigma*sigma))
631 /* / (MagickSQ2PI*sigma) */ );
632#endif
anthonyc94cdb02010-01-06 08:15:29 +0000633 kernel->value_min = 0;
634 kernel->value_max = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000635
anthonyc94cdb02010-01-06 08:15:29 +0000636 KernelNormalize(kernel);
anthony602ab9b2010-01-05 08:06:50 +0000637 KernelRotate(kernel, args->xi);
638 break;
639 }
640 /* Boolean Kernels */
641 case RectangleKernel:
642 case SquareKernel:
643 {
644 if ( type == SquareKernel )
645 {
646 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000647 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000648 else
649 kernel->width = kernel->height = 2*(long)args->rho+1;
650 kernel->offset_x = kernel->offset_y = (kernel->width-1)/2;
651 }
652 else {
anthonyc94cdb02010-01-06 08:15:29 +0000653 /* NOTE: user defaults set in "AcquireKernelFromString()" */
anthony602ab9b2010-01-05 08:06:50 +0000654 if ( args->rho < 1.0 || args->sigma < 1.0 )
anthonyc94cdb02010-01-06 08:15:29 +0000655 return(DestroyKernel(kernel)); /* invalid args given */
anthony602ab9b2010-01-05 08:06:50 +0000656 kernel->width = (unsigned long)args->rho;
657 kernel->height = (unsigned long)args->sigma;
658 if ( args->xi < 0.0 || args->xi > (double)kernel->width ||
659 args->psi < 0.0 || args->psi > (double)kernel->height )
anthonyc94cdb02010-01-06 08:15:29 +0000660 return(DestroyKernel(kernel)); /* invalid args given */
anthony602ab9b2010-01-05 08:06:50 +0000661 kernel->offset_x = (unsigned long)args->xi;
662 kernel->offset_y = (unsigned long)args->psi;
663 }
664 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
665 kernel->height*sizeof(double));
666 if (kernel->values == (double *) NULL)
667 return(DestroyKernel(kernel));
668
669 u=kernel->width*kernel->height;
670 for ( i=0; i < (unsigned long)u; i++)
671 kernel->values[i] = 1.0;
672 break;
anthonyc94cdb02010-01-06 08:15:29 +0000673 kernel->value_min = kernel->value_max = 1.0; /* a flat kernel */
674 kernel->range_pos = (double) u;
anthony602ab9b2010-01-05 08:06:50 +0000675 }
676 case DiamondKernel:
677 {
678 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000679 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000680 else
681 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
682 kernel->offset_x = kernel->offset_y = (kernel->width-1)/2;
683
684 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
685 kernel->height*sizeof(double));
686 if (kernel->values == (double *) NULL)
687 return(DestroyKernel(kernel));
688
689 for ( i=0, v=-kernel->offset_y; v <= (long)kernel->offset_y; v++)
690 for ( u=-kernel->offset_x; u <= (long)kernel->offset_x; u++, i++)
691 if ((labs(u)+labs(v)) <= (long)kernel->offset_x)
692 kernel->range_pos += kernel->values[i] = 1.0;
693 else
694 kernel->values[i] = nan;
anthonyc94cdb02010-01-06 08:15:29 +0000695 kernel->value_min = kernel->value_max = 1.0; /* a flat kernel */
anthony602ab9b2010-01-05 08:06:50 +0000696 break;
697 }
698 case DiskKernel:
699 {
700 long
701 limit;
702
703 limit = (long)(args->rho*args->rho);
anthonyc94cdb02010-01-06 08:15:29 +0000704 if (args->rho < 1.0) /* default radius approx 2.5 */
anthony602ab9b2010-01-05 08:06:50 +0000705 kernel->width = kernel->height = 5L, limit = 5L;
706 else
707 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
708 kernel->offset_x = kernel->offset_y = (kernel->width-1)/2;
709
710 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
711 kernel->height*sizeof(double));
712 if (kernel->values == (double *) NULL)
713 return(DestroyKernel(kernel));
714
715 for ( i=0, v=-kernel->offset_y; v <= (long)kernel->offset_y; v++)
716 for ( u=-kernel->offset_x; u <= (long)kernel->offset_x; u++, i++)
717 if ((u*u+v*v) <= limit)
718 kernel->range_pos += kernel->values[i] = 1.0;
719 else
720 kernel->values[i] = nan;
anthonyc94cdb02010-01-06 08:15:29 +0000721 kernel->value_min = kernel->value_max = 1.0; /* a flat kernel */
anthony602ab9b2010-01-05 08:06:50 +0000722 break;
723 }
724 case PlusKernel:
725 {
726 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000727 kernel->width = kernel->height = 5; /* default radius 2 */
anthony602ab9b2010-01-05 08:06:50 +0000728 else
729 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
730 kernel->offset_x = kernel->offset_y = (kernel->width-1)/2;
731
732 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
733 kernel->height*sizeof(double));
734 if (kernel->values == (double *) NULL)
735 return(DestroyKernel(kernel));
736
737 for ( i=0, v=-kernel->offset_y; v <= (long)kernel->offset_y; v++)
738 for ( u=-kernel->offset_x; u <= (long)kernel->offset_x; u++, i++)
739 kernel->values[i] = (u == 0 || v == 0) ? 1.0 : nan;
anthonyc94cdb02010-01-06 08:15:29 +0000740 kernel->value_min = kernel->value_max = 1.0; /* a flat kernel */
anthony602ab9b2010-01-05 08:06:50 +0000741 kernel->range_pos = kernel->width*2.0 - 1.0;
742 break;
743 }
744 /* Distance Measuring Kernels */
745 case ChebyshevKernel:
746 {
747 double
748 scale;
749
750 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000751 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000752 else
753 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
754 kernel->offset_x = kernel->offset_y = (kernel->width-1)/2;
755
756 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
757 kernel->height*sizeof(double));
758 if (kernel->values == (double *) NULL)
759 return(DestroyKernel(kernel));
760
761 scale = (args->sigma < 1.0) ? 100.0 : args->sigma;
762 for ( i=0, v=-kernel->offset_y; v <= (long)kernel->offset_y; v++)
763 for ( u=-kernel->offset_x; u <= (long)kernel->offset_x; u++, i++)
764 kernel->range_pos += ( kernel->values[i] =
765 scale*((labs(u)>labs(v)) ? labs(u) : labs(v)) );
anthonyc94cdb02010-01-06 08:15:29 +0000766 kernel->value_max = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000767 break;
768 }
769 case ManhattenKernel:
770 {
771 double
772 scale;
773
774 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000775 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000776 else
777 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
778 kernel->offset_x = kernel->offset_y = (kernel->width-1)/2;
779
780 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
781 kernel->height*sizeof(double));
782 if (kernel->values == (double *) NULL)
783 return(DestroyKernel(kernel));
784
785 scale = (args->sigma < 1.0) ? 100.0 : args->sigma;
786 for ( i=0, v=-kernel->offset_y; v <= (long)kernel->offset_y; v++)
787 for ( u=-kernel->offset_x; u <= (long)kernel->offset_x; u++, i++)
788 kernel->range_pos += ( kernel->values[i] =
789 scale*(labs(u)+labs(v)) );
anthonyc94cdb02010-01-06 08:15:29 +0000790 kernel->value_max = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000791 break;
792 }
793 case EuclideanKernel:
794 {
795 double
796 scale;
797
798 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000799 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000800 else
801 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
802 kernel->offset_x = kernel->offset_y = (kernel->width-1)/2;
803
804 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
805 kernel->height*sizeof(double));
806 if (kernel->values == (double *) NULL)
807 return(DestroyKernel(kernel));
808
809 scale = (args->sigma < 1.0) ? 100.0 : args->sigma;
810 for ( i=0, v=-kernel->offset_y; v <= (long)kernel->offset_y; v++)
811 for ( u=-kernel->offset_x; u <= (long)kernel->offset_x; u++, i++)
812 kernel->range_pos += ( kernel->values[i] =
813 scale*sqrt((double)(u*u+v*v)) );
anthonyc94cdb02010-01-06 08:15:29 +0000814 kernel->value_max = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000815 break;
816 }
817 /* Undefined Kernels */
818 case LaplacianKernel:
819 case LOGKernel:
820 case DOGKernel:
821 assert("Kernel Type has not been defined yet");
822 /* FALL THRU */
823 default:
824 /* Generate a No-Op minimal kernel - 1x1 pixel */
825 kernel->values=(double *)AcquireQuantumMemory((size_t)1,sizeof(double));
826 if (kernel->values == (double *) NULL)
827 return(DestroyKernel(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000828 kernel->width = kernel->height = 1;
829 kernel->offset_x = kernel->offset_x = 0;
830 kernel->type = UndefinedKernel;
anthonyc94cdb02010-01-06 08:15:29 +0000831 kernel->value_max =
832 kernel->range_pos =
833 kernel->values[0] = 1.0; /* a flat single-point no-op kernel! */
anthony602ab9b2010-01-05 08:06:50 +0000834 break;
835 }
836
837 return(kernel);
838}
anthonyc94cdb02010-01-06 08:15:29 +0000839
anthony602ab9b2010-01-05 08:06:50 +0000840/*
841%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
842% %
843% %
844% %
845% D e s t r o y K e r n e l %
846% %
847% %
848% %
849%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
850%
851% DestroyKernel() frees the memory used by a Convolution/Morphology kernel.
852%
853% The format of the DestroyKernel method is:
854%
855% MagickKernel *DestroyKernel(MagickKernel *kernel)
856%
857% A description of each parameter follows:
858%
859% o kernel: the Morphology/Convolution kernel to be destroyed
860%
861*/
862
863MagickExport MagickKernel *DestroyKernel(MagickKernel *kernel)
864{
865 assert(kernel != (MagickKernel *) NULL);
866 kernel->values=(double *)RelinquishMagickMemory(kernel->values);
867 kernel=(MagickKernel *) RelinquishMagickMemory(kernel);
868 return(kernel);
869}
anthonyc94cdb02010-01-06 08:15:29 +0000870
871/*
872%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
873% %
874% %
875% %
876% K e r n e l N o r m a l i z e %
877% %
878% %
879% %
880%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
881%
882% KernelNormalize() normalize the kernel so its convolution output will
883% be over a unit range.
884%
885% The format of the KernelNormalize method is:
886%
887% void KernelRotate (MagickKernel *kernel)
888%
889% A description of each parameter follows:
890%
891% o kernel: the Morphology/Convolution kernel
892%
893*/
894MagickExport void KernelNormalize(MagickKernel *kernel)
895{
896 register unsigned long
897 i;
anthony602ab9b2010-01-05 08:06:50 +0000898
anthonyc94cdb02010-01-06 08:15:29 +0000899 for (i=0; i < kernel->width; i++)
900 kernel->values[i] /= (kernel->range_pos - kernel->range_neg);
901
902 kernel->range_pos /= (kernel->range_pos - kernel->range_neg);
903 kernel->range_neg /= (kernel->range_pos - kernel->range_neg);
904 kernel->value_max /= (kernel->range_pos - kernel->range_neg);
905 kernel->value_min /= (kernel->range_pos - kernel->range_neg);
906
907 return;
908}
909
910/*
911%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
912% %
913% %
914% %
915% K e r n e l P r i n t %
916% %
917% %
918% %
919%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
920%
921% KernelPrint() Print out the kernel details to standard error
922%
923% The format of the KernelNormalize method is:
924%
925% void KernelPrint (MagickKernel *kernel)
926%
927% A description of each parameter follows:
928%
929% o kernel: the Morphology/Convolution kernel
930%
931*/
932MagickExport void KernelPrint(MagickKernel *kernel)
933{
934 unsigned long
935 i, u, v;
936
937 fprintf(stderr,
938 "Kernel \"%s\" of size %lux%lu%+ld%+ld with value from %lg to %lg\n",
939 MagickOptionToMnemonic(MagickKernelOptions, kernel->type),
940 kernel->width, kernel->height,
941 kernel->offset_x, kernel->offset_y,
942 kernel->value_min, kernel->value_max);
943 fprintf(stderr, " Forming an output range from %lg to %lg%s\n",
944 kernel->range_neg, kernel->range_pos,
945 kernel->normalized == MagickTrue ? " (normalized)" : "" );
946 for (i=v=0; v < kernel->height; v++) {
947 fprintf(stderr,"%2ld: ",v);
948 for (u=0; u < kernel->width; u++, i++)
949 fprintf(stderr,"%5.3lf ",kernel->values[i]);
950 fprintf(stderr,"\n");
951 }
952}
953
954/*
955%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
956% %
957% %
958% %
959% K e r n e l R o t a t e %
960% %
961% %
962% %
963%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
964%
965% KernelRotate() rotates the kernel by the angle given. Currently it is
966% restricted to 90 degree angles, but this may be improved in the future.
967%
968% The format of the KernelRotate method is:
969%
970% void KernelRotate (MagickKernel *kernel, double angle)
971%
972% A description of each parameter follows:
973%
974% o kernel: the Morphology/Convolution kernel
975%
976% o angle: angle to rotate in degrees
977%
978*/
979MagickExport void KernelRotate(MagickKernel *kernel, double angle)
980{
981 /* WARNING: Currently assumes the kernel (rightly) is horizontally symetrical
982 **
983 ** TODO: expand beyond simple 90 degree rotates, flips and flops
984 */
985
986 /* Modulus the angle */
987 angle = fmod(angle, 360.0);
988 if ( angle < 0 )
989 angle += 360.0;
990
991 if ( 315.0 < angle || angle <= 45.0 )
992 return; /* no change! - At least at this time */
993
994 switch (kernel->type) {
995 /* These built-in kernels are cylindrical kernel, rotating is useless */
996 case GaussianKernel:
997 case LaplacianKernel:
998 case LOGKernel:
999 case DOGKernel:
1000 case DiskKernel:
1001 case ChebyshevKernel:
1002 case ManhattenKernel:
1003 case EuclideanKernel:
1004 return;
1005
1006 /* These may be rotatable at non-90 angles in the future */
1007 /* but simply rotating them 90 degrees is useless */
1008 case SquareKernel:
1009 case DiamondKernel:
1010 case PlusKernel:
1011 return;
1012
1013 /* These only allows a +/-90 degree rotation (transpose) */
1014 case BlurKernel:
1015 case RectangleKernel:
1016 if ( 135.0 < angle && angle <= 225.0 )
1017 return;
1018 if ( 225.0 < angle && angle <= 315.0 )
1019 angle -= 180;
1020 break;
1021
1022 /* these are freely rotatable in 90 degree units */
1023 case CometKernel:
1024 case UndefinedKernel:
1025 case UserDefinedKernel:
1026 break;
1027 }
1028
1029 if ( 135.0 < angle && angle <= 315.0 )
1030 {
1031 /* Do a flop, this assumes kernel is horizontally symetrical. */
1032 /* Each kernel data row need to be reversed! */
1033 unsigned long
1034 y;
1035 register unsigned long
1036 x,r;
1037 register double
1038 *k,t;
1039 for ( y=0, k=kernel->values; y < kernel->height; y++, k+=kernel->width) {
1040 for ( x=0, r=kernel->width-1; x<kernel->width/2; x++, r--)
1041 t=k[x], k[x]=k[r], k[r]=t;
1042 }
1043 kernel->offset_x = kernel->width - kernel->offset_x - 1;
1044 angle = fmod(angle+180.0, 360.0);
1045 }
1046 if ( 45.0 < angle && angle <= 135.0 )
1047 {
1048 /* Do a transpose, this assumes the kernel is orthoginally symetrical */
1049 /* The data is the same, just the size and offsets needs to be swapped. */
1050 unsigned long
1051 t;
1052 t = kernel->width;
1053 kernel->width = kernel->height;
1054 kernel->height = t;
1055 t = kernel->offset_x;
1056 kernel->offset_x = kernel->offset_y;
1057 kernel->offset_y = t;
1058 angle = fmod(450.0 - angle, 360.0);
1059 }
1060 /* at this point angle should be between +45 and -45 (315) degrees */
1061 return;
1062}
anthony602ab9b2010-01-05 08:06:50 +00001063
1064/*
1065%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1066% %
1067% %
1068% %
1069% M o r p h o l o g y I m a g e %
1070% %
1071% %
1072% %
1073%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1074%
1075% MorphologyImage() applies a user supplied kernel to the image according to
1076% the given mophology method.
1077%
1078% The given kernel is assumed to have been pre-scaled appropriatally, usally
1079% by the kernel generator.
1080%
1081% The format of the MorphologyImage method is:
1082%
1083% Image *MorphologyImage(const Image *image, const MorphologyMethod
1084% method, const long iterations, const ChannelType channel,
1085% const MagickKernel *kernel, ExceptionInfo *exception)
1086%
1087% A description of each parameter follows:
1088%
1089% o image: the image.
1090%
1091% o method: the morphology method to be applied.
1092%
1093% o iterations: apply the operation this many times (or no change).
1094% A value of -1 means loop until no change found.
1095% How this is applied may depend on the morphology method.
1096% Typically this is a value of 1.
1097%
1098% o channel: the channel type.
1099%
1100% o kernel: An array of double representing the morphology kernel.
anthonyc94cdb02010-01-06 08:15:29 +00001101% Warning: kernel may be normalized for a Convolve.
anthony602ab9b2010-01-05 08:06:50 +00001102%
1103% o exception: return any errors or warnings in this structure.
1104%
1105%
1106% TODO: bias and auto-scale handling of the kernel for convolution
1107% The given kernel is assumed to have been pre-scaled appropriatally, usally
1108% by the kernel generator.
1109%
1110*/
1111
1112static inline double MagickMin(const MagickRealType x,const MagickRealType y)
1113{
1114 return( x < y ? x : y);
1115}
1116static inline double MagickMax(const MagickRealType x,const MagickRealType y)
1117{
1118 return( x > y ? x : y);
1119}
1120#define Minimize(assign,value) assign=MagickMin(assign,value)
1121#define Maximize(assign,value) assign=MagickMax(assign,value)
1122
1123/* incr change if the value being assigned changed */
1124#define Assign(channel,value) \
1125 { q->channel = RoundToQuantum(value); \
1126 if ( p[r].channel != q->channel ) changed++; \
1127 }
1128#define AssignIndex(value) \
1129 { q_indexes[x] = RoundToQuantum(value); \
1130 if ( p_indexes[r] != q_indexes[x] ) changed++; \
1131 }
1132
1133/* Internal function
1134 * Apply the Morphology method with the given Kernel
1135 * And return the number of values changed.
1136 */
1137static unsigned long MorphologyApply(const Image *image, Image
1138 *result_image, const MorphologyMethod method, const ChannelType channel,
1139 const MagickKernel *kernel, ExceptionInfo *exception)
1140{
1141 #define MorphologyTag "Morphology/Image"
1142
1143 long
1144 progress,
1145 y;
1146
1147 unsigned long
1148 changed;
1149
1150 MagickBooleanType
1151 status;
1152
1153 MagickPixelPacket
1154 bias;
1155
1156 CacheView
1157 *p_view,
1158 *q_view;
1159
1160 /*
1161 Apply Morphology to Image.
1162 */
1163 status=MagickTrue;
1164 changed=0;
1165 progress=0;
1166
1167 GetMagickPixelPacket(image,&bias);
1168 SetMagickPixelPacketBias(image,&bias);
1169
1170 p_view=AcquireCacheView(image);
1171 q_view=AcquireCacheView(result_image);
1172#if defined(MAGICKCORE_OPENMP_SUPPORT)
1173 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1174#endif
1175 for (y=0; y < (long) image->rows; y++)
1176 {
1177 MagickBooleanType
1178 sync;
1179
1180 register const PixelPacket
1181 *restrict p;
1182
1183 register const IndexPacket
1184 *restrict p_indexes;
1185
1186 register PixelPacket
1187 *restrict q;
1188
1189 register IndexPacket
1190 *restrict q_indexes;
1191
1192 register long
1193 x;
1194
1195 long
1196 r;
1197
1198 if (status == MagickFalse)
1199 continue;
1200 p=GetCacheViewVirtualPixels(p_view, -kernel->offset_x, y-kernel->offset_y,
1201 image->columns+kernel->width, kernel->height, exception);
1202 q=GetCacheViewAuthenticPixels(q_view,0,y,result_image->columns,1,
1203 exception);
1204 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1205 {
1206 status=MagickFalse;
1207 continue;
1208 }
1209 p_indexes=GetCacheViewVirtualIndexQueue(p_view);
1210 q_indexes=GetCacheViewAuthenticIndexQueue(q_view);
1211 r = (image->columns+kernel->width)*kernel->offset_y+kernel->offset_x;
1212 for (x=0; x < (long) image->columns; x++)
1213 {
1214 long
1215 v;
1216
1217 register long
1218 u;
1219
1220 register const double
1221 *restrict k;
1222
1223 register const PixelPacket
1224 *restrict k_pixels;
1225
1226 register const IndexPacket
1227 *restrict k_indexes;
1228
1229 MagickPixelPacket
1230 result;
1231
1232 /* Copy input to ouput image - removes need for 'cloning' new images */
1233 *q = p[r];
1234 if (image->colorspace == CMYKColorspace)
1235 q_indexes[x] = p_indexes[r];
1236
cristybba804b2010-01-05 15:39:59 +00001237 result.index=0;
anthony602ab9b2010-01-05 08:06:50 +00001238 switch (method) {
1239 case ConvolveMorphology:
1240 result=bias;
1241 break; /* default result is the convolution bias */
1242 case DialateIntensityMorphology:
1243 case ErodeIntensityMorphology:
1244 /* result is the pixel as is */
1245 result.red = p[r].red;
1246 result.green = p[r].green;
1247 result.blue = p[r].blue;
1248 result.opacity = p[r].opacity;
1249 if ( image->colorspace == CMYKColorspace)
1250 result.index = p_indexes[r];
1251 break;
1252 default:
1253 /* most need to handle transparency as alpha */
1254 result.red = p[r].red;
1255 result.green = p[r].green;
1256 result.blue = p[r].blue;
1257 result.opacity = QuantumRange - p[r].opacity;
1258 if ( image->colorspace == CMYKColorspace)
1259 result.index = p_indexes[r];
1260 break;
1261 }
1262
1263 switch ( method ) {
1264 case ConvolveMorphology:
1265 /* Weighted Average of pixels */
1266 if (((channel & OpacityChannel) == 0) ||
1267 (image->matte == MagickFalse))
1268 {
1269 /* Kernel Weighted Convolution (no transparency) */
1270 k = kernel->values;
1271 k_pixels = p;
1272 k_indexes = p_indexes;
1273 for (v=0; v < (long) kernel->height; v++) {
1274 for (u=0; u < (long) kernel->width; u++, k++) {
1275 if ( IsNan(*k) ) continue;
1276 result.red += (*k)*k_pixels[u].red;
1277 result.green += (*k)*k_pixels[u].green;
1278 result.blue += (*k)*k_pixels[u].blue;
1279 /* result.opacity += no involvment */
1280 if ( image->colorspace == CMYKColorspace)
1281 result.index += (*k)*k_indexes[u];
1282 }
1283 k_pixels += image->columns+kernel->width;
1284 k_indexes += image->columns+kernel->width;
1285 }
1286 if ((channel & RedChannel) != 0)
1287 Assign(red,result.red);
1288 if ((channel & GreenChannel) != 0)
1289 Assign(green,result.green);
1290 if ((channel & BlueChannel) != 0)
1291 Assign(blue,result.blue);
1292 /* no transparency involved */
1293 if ((channel & IndexChannel) != 0
1294 && image->colorspace == CMYKColorspace)
1295 AssignIndex(result.index);
1296 }
1297 else
1298 { /* Kernel & Alpha weighted Convolution */
1299 MagickRealType
1300 alpha, /* alpha value * kernel weighting */
1301 gamma; /* weighting divisor */
1302
1303 gamma=0.0;
1304 k = kernel->values;
1305 k_pixels = p;
1306 k_indexes = p_indexes;
1307 for (v=0; v < (long) kernel->height; v++) {
1308 for (u=0; u < (long) kernel->width; u++, k++) {
1309 if ( IsNan(*k) ) continue;
1310 alpha=(*k)*(QuantumScale*(QuantumRange-
1311 k_pixels[u].opacity));
1312 gamma += alpha;
1313 result.red += alpha*k_pixels[u].red;
1314 result.green += alpha*k_pixels[u].green;
1315 result.blue += alpha*k_pixels[u].blue;
1316 result.opacity += (*k)*k_pixels[u].opacity;
1317 if ( image->colorspace == CMYKColorspace)
1318 result.index += alpha*k_indexes[u];
1319 }
1320 k_pixels += image->columns+kernel->width;
1321 k_indexes += image->columns+kernel->width;
1322 }
1323 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1324 if ((channel & RedChannel) != 0)
1325 Assign(red,gamma*result.red);
1326 if ((channel & GreenChannel) != 0)
1327 Assign(green,gamma*result.green);
1328 if ((channel & BlueChannel) != 0)
1329 Assign(blue,gamma*result.blue);
1330 if ((channel & OpacityChannel) != 0
1331 && image->matte == MagickTrue )
1332 Assign(opacity,result.opacity);
1333 if ((channel & IndexChannel) != 0
1334 && image->colorspace == CMYKColorspace)
1335 AssignIndex(gamma*result.index);
1336 }
1337 break;
1338
1339 case DialateMorphology:
1340 /* Maximize Value - Kernel should be boolean */
1341 k = kernel->values;
1342 k_pixels = p;
1343 k_indexes = p_indexes;
1344 for (v=0; v < (long) kernel->height; v++) {
1345 for (u=0; u < (long) kernel->width; u++, k++) {
1346 if ( IsNan(*k) || (*k) < 0.5 ) continue;
1347 Maximize(result.red, k_pixels[u].red);
1348 Maximize(result.green, k_pixels[u].green);
1349 Maximize(result.blue, k_pixels[u].blue);
1350 Maximize(result.opacity, QuantumRange-k_pixels[u].opacity);
1351 if ( image->colorspace == CMYKColorspace)
1352 Maximize(result.index, k_indexes[u]);
1353 }
1354 k_pixels += image->columns+kernel->width;
1355 k_indexes += image->columns+kernel->width;
1356 }
1357 if ((channel & RedChannel) != 0)
1358 Assign(red,result.red);
1359 if ((channel & GreenChannel) != 0)
1360 Assign(green,result.green);
1361 if ((channel & BlueChannel) != 0)
1362 Assign(blue,result.blue);
1363 if ((channel & OpacityChannel) != 0
1364 && image->matte == MagickTrue )
1365 Assign(opacity,QuantumRange-result.opacity);
1366 if ((channel & IndexChannel) != 0
1367 && image->colorspace == CMYKColorspace)
1368 AssignIndex(result.index);
1369 break;
1370
1371 case ErodeMorphology:
1372 /* Minimize Value - Kernel should be boolean */
1373 k = kernel->values;
1374 k_pixels = p;
1375 k_indexes = p_indexes;
1376 for (v=0; v < (long) kernel->height; v++) {
1377 for (u=0; u < (long) kernel->width; u++, k++) {
1378 if ( IsNan(*k) || (*k) < 0.5 ) continue;
1379 Minimize(result.red, k_pixels[u].red);
1380 Minimize(result.green, k_pixels[u].green);
1381 Minimize(result.blue, k_pixels[u].blue);
1382 Minimize(result.opacity, QuantumRange-k_pixels[u].opacity);
1383 if ( image->colorspace == CMYKColorspace)
1384 Minimize(result.index, k_indexes[u]);
1385 }
1386 k_pixels += image->columns+kernel->width;
1387 k_indexes += image->columns+kernel->width;
1388 }
1389 if ((channel & RedChannel) != 0)
1390 Assign(red,result.red);
1391 if ((channel & GreenChannel) != 0)
1392 Assign(green,result.green);
1393 if ((channel & BlueChannel) != 0)
1394 Assign(blue,result.blue);
1395 if ((channel & OpacityChannel) != 0
1396 && image->matte == MagickTrue )
1397 Assign(opacity,QuantumRange-result.opacity);
1398 if ((channel & IndexChannel) != 0
1399 && image->colorspace == CMYKColorspace)
1400 AssignIndex(result.index);
1401 break;
1402
1403 case DialateIntensityMorphology:
1404 /* Maximum Intensity Pixel - Kernel should be boolean */
1405 k = kernel->values;
1406 k_pixels = p;
1407 k_indexes = p_indexes;
1408 for (v=0; v < (long) kernel->height; v++) {
1409 for (u=0; u < (long) kernel->width; u++, k++) {
1410 if ( IsNan(*k) || (*k) < 0.5 ) continue;
1411 if ( PixelIntensity(&p[r]) >
1412 PixelIntensity(&(k_pixels[u])) ) continue;
1413 result.red = k_pixels[u].red;
1414 result.green = k_pixels[u].green;
1415 result.blue = k_pixels[u].blue;
1416 result.opacity = k_pixels[u].opacity;
1417 if ( image->colorspace == CMYKColorspace)
1418 result.index = k_indexes[u];
1419 }
1420 k_pixels += image->columns+kernel->width;
1421 k_indexes += image->columns+kernel->width;
1422 }
1423 if ((channel & RedChannel) != 0)
1424 Assign(red,result.red);
1425 if ((channel & GreenChannel) != 0)
1426 Assign(green,result.green);
1427 if ((channel & BlueChannel) != 0)
1428 Assign(blue,result.blue);
1429 if ((channel & OpacityChannel) != 0
1430 && image->matte == MagickTrue )
1431 Assign(opacity,result.opacity);
1432 if ((channel & IndexChannel) != 0
1433 && image->colorspace == CMYKColorspace)
1434 AssignIndex(result.index);
1435 break;
1436
1437 case ErodeIntensityMorphology:
1438 /* Minimum Intensity Pixel - Kernel should be boolean */
1439 k = kernel->values;
1440 k_pixels = p;
1441 k_indexes = p_indexes;
1442 for (v=0; v < (long) kernel->height; v++) {
1443 for (u=0; u < (long) kernel->width; u++, k++) {
1444 if ( IsNan(*k) || (*k) < 0.5 ) continue;
1445 if ( PixelIntensity(&p[r]) <
1446 PixelIntensity(&(k_pixels[u])) ) continue;
1447 result.red = k_pixels[u].red;
1448 result.green = k_pixels[u].green;
1449 result.blue = k_pixels[u].blue;
1450 result.opacity = k_pixels[u].opacity;
1451 if ( image->colorspace == CMYKColorspace)
1452 result.index = k_indexes[u];
1453 }
1454 k_pixels += image->columns+kernel->width;
1455 k_indexes += image->columns+kernel->width;
1456 }
1457 if ((channel & RedChannel) != 0)
1458 Assign(red,result.red);
1459 if ((channel & GreenChannel) != 0)
1460 Assign(green,result.green);
1461 if ((channel & BlueChannel) != 0)
1462 Assign(blue,result.blue);
1463 if ((channel & OpacityChannel) != 0
1464 && image->matte == MagickTrue )
1465 Assign(opacity,result.opacity);
1466 if ((channel & IndexChannel) != 0
1467 && image->colorspace == CMYKColorspace)
1468 AssignIndex(result.index);
1469 break;
1470
1471 case DistanceMorphology:
1472#if 0
1473 /* No need to do distance morphology if all values are zero */
1474 /* Unfortunatally I have not been able to get this right! */
1475 if ( ((channel & RedChannel) == 0 && p[r].red == 0)
1476 || ((channel & GreenChannel) == 0 && p[r].green == 0)
1477 || ((channel & BlueChannel) == 0 && p[r].blue == 0)
1478 || ((channel & OpacityChannel) == 0 && p[r].opacity == 0)
1479 || (( (channel & IndexChannel) == 0
1480 || image->colorspace != CMYKColorspace
1481 ) && p_indexes[x] ==0 )
1482 )
1483 break;
1484#endif
1485 k = kernel->values;
1486 k_pixels = p;
1487 k_indexes = p_indexes;
1488 for (v=0; v < (long) kernel->height; v++) {
1489 for (u=0; u < (long) kernel->width; u++, k++) {
1490 if ( IsNan(*k) ) continue;
1491 Minimize(result.red, (*k)+k_pixels[u].red);
1492 Minimize(result.green, (*k)+k_pixels[u].green);
1493 Minimize(result.blue, (*k)+k_pixels[u].blue);
1494 Minimize(result.opacity, (*k)+QuantumRange-k_pixels[u].opacity);
1495 if ( image->colorspace == CMYKColorspace)
1496 Minimize(result.index, (*k)+k_indexes[u]);
1497 }
1498 k_pixels += image->columns+kernel->width;
1499 k_indexes += image->columns+kernel->width;
1500 }
1501#if 1
1502 if ((channel & RedChannel) != 0)
1503 Assign(red,result.red);
1504 if ((channel & GreenChannel) != 0)
1505 Assign(green,result.green);
1506 if ((channel & BlueChannel) != 0)
1507 Assign(blue,result.blue);
1508 if ((channel & OpacityChannel) != 0
1509 && image->matte == MagickTrue )
1510 Assign(opacity,QuantumRange-result.opacity);
1511 if ((channel & IndexChannel) != 0
1512 && image->colorspace == CMYKColorspace)
1513 AssignIndex(result.index);
1514#else
1515 /* By returning the number of 'maximum' values still to process
1516 ** we can get the Distance iteration to finish faster.
1517 ** BUT this may cause an infinite loop on very large shapes,
1518 ** which may have a distance that reachs a maximum gradient.
1519 */
1520 if ((channel & RedChannel) != 0)
1521 { q->red = RoundToQuantum(result.red);
1522 if ( q->red == QuantumRange ) changed++; /* more to do */
1523 }
1524 if ((channel & GreenChannel) != 0)
1525 { q->green = RoundToQuantum(result.green);
1526 if ( q->green == QuantumRange ) changed++; /* more to do */
1527 }
1528 if ((channel & BlueChannel) != 0)
1529 { q->blue = RoundToQuantum(result.blue);
1530 if ( q->blue == QuantumRange ) changed++; /* more to do */
1531 }
1532 if ((channel & OpacityChannel) != 0)
1533 { q->opacity = RoundToQuantum(QuantumRange-result.opacity);
1534 if ( q->opacity == 0 ) changed++; /* more to do */
1535 }
1536 if (((channel & IndexChannel) != 0) &&
1537 (image->colorspace == CMYKColorspace))
1538 { q_indexes[x] = RoundToQuantum(result.index);
1539 if ( q_indexes[x] == QuantumRange ) changed++;
1540 }
1541#endif
1542 break;
1543
1544 case UndefinedMorphology:
1545 default:
1546 break; /* Do nothing */
1547 }
1548 p++;
1549 q++;
1550 }
1551 sync=SyncCacheViewAuthenticPixels(q_view,exception);
1552 if (sync == MagickFalse)
1553 status=MagickFalse;
1554 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1555 {
1556 MagickBooleanType
1557 proceed;
1558
1559#if defined(MAGICKCORE_OPENMP_SUPPORT)
1560 #pragma omp critical (MagickCore_MorphologyImage)
1561#endif
1562 proceed=SetImageProgress(image,MorphologyTag,progress++,image->rows);
1563 if (proceed == MagickFalse)
1564 status=MagickFalse;
1565 }
1566 }
1567 result_image->type=image->type;
1568 q_view=DestroyCacheView(q_view);
1569 p_view=DestroyCacheView(p_view);
1570 return(status ? changed : 0);
1571}
1572
1573
1574MagickExport Image *MorphologyImage(const Image *image, MorphologyMethod
1575 method, const long iterations, const ChannelType channel,
anthonyc94cdb02010-01-06 08:15:29 +00001576 MagickKernel *kernel, ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001577{
1578 unsigned long
1579 count,
1580 limit,
1581 changed;
1582
1583 Image
1584 *new_image,
1585 *old_image;
1586
1587 assert(image != (Image *) NULL);
1588 assert(image->signature == MagickSignature);
1589 assert(exception != (ExceptionInfo *) NULL);
1590 assert(exception->signature == MagickSignature);
1591
1592 if ( GetImageArtifact(image,"showkernel") != (const char *) NULL)
anthonyc94cdb02010-01-06 08:15:29 +00001593 KernelPrint(kernel);
anthony602ab9b2010-01-05 08:06:50 +00001594
1595 if ( iterations == 0 )
1596 return((Image *)NULL); /* null operation - nothing to do! */
1597
1598 /* kernel must be valid at this point
1599 * (except maybe for posible future morphology methods like "Prune"
1600 */
1601 assert(kernel != (MagickKernel *)NULL);
1602
1603 count = 0;
1604 limit = iterations;
1605 if ( iterations < 0 )
1606 limit = image->columns > image->rows ? image->columns : image->rows;
1607
1608 /* Special morphology cases */
cristybba804b2010-01-05 15:39:59 +00001609 changed=MagickFalse;
anthony602ab9b2010-01-05 08:06:50 +00001610 switch( method ) {
1611 case CloseMorphology:
1612 new_image = MorphologyImage(image, DialateMorphology, iterations, channel,
1613 kernel, exception);
1614 if (new_image == (Image *) NULL)
1615 return((Image *) NULL);
1616 method = ErodeMorphology;
1617 break;
1618 case OpenMorphology:
1619 new_image = MorphologyImage(image, ErodeMorphology, iterations, channel,
1620 kernel, exception);
1621 if (new_image == (Image *) NULL)
1622 return((Image *) NULL);
1623 method = DialateMorphology;
1624 break;
1625 case CloseIntensityMorphology:
1626 new_image = MorphologyImage(image, DialateIntensityMorphology,
1627 iterations, channel, kernel, exception);
1628 if (new_image == (Image *) NULL)
1629 return((Image *) NULL);
1630 method = ErodeIntensityMorphology;
1631 break;
1632 case OpenIntensityMorphology:
1633 new_image = MorphologyImage(image, ErodeIntensityMorphology,
1634 iterations, channel, kernel, exception);
1635 if (new_image == (Image *) NULL)
1636 return((Image *) NULL);
1637 method = DialateIntensityMorphology;
1638 break;
1639
anthonyc94cdb02010-01-06 08:15:29 +00001640 case ConvolveMorphology:
1641 KernelNormalize(kernel);
1642 /* FALL-THRU */
anthony602ab9b2010-01-05 08:06:50 +00001643 default:
anthonyc94cdb02010-01-06 08:15:29 +00001644 /* Do a morphology just once at this point!
anthony602ab9b2010-01-05 08:06:50 +00001645 This ensures a new_image has been generated, but allows us
anthonyc94cdb02010-01-06 08:15:29 +00001646 to skip the creation of 'old_image' if it isn't needed.
anthony602ab9b2010-01-05 08:06:50 +00001647 */
1648 new_image=CloneImage(image,0,0,MagickTrue,exception);
1649 if (new_image == (Image *) NULL)
1650 return((Image *) NULL);
1651 if (SetImageStorageClass(new_image,DirectClass) == MagickFalse)
1652 {
1653 InheritException(exception,&new_image->exception);
1654 new_image=DestroyImage(new_image);
1655 return((Image *) NULL);
1656 }
1657 changed = MorphologyApply(image,new_image,method,channel,kernel,
1658 exception);
1659 count++;
1660 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
1661 fprintf(stderr, "Morphology %s:%lu => Changed %lu\n",
1662 MagickOptionToMnemonic(MagickMorphologyOptions, method),
1663 count, changed);
1664 }
1665
1666 /* Repeat the interative morphology until count or no change */
1667 if ( count < limit && changed > 0 ) {
1668 old_image = CloneImage(new_image,0,0,MagickTrue,exception);
1669 if (old_image == (Image *) NULL)
1670 return(DestroyImage(new_image));
1671 if (SetImageStorageClass(old_image,DirectClass) == MagickFalse)
1672 {
1673 InheritException(exception,&old_image->exception);
1674 old_image=DestroyImage(old_image);
1675 return(DestroyImage(new_image));
1676 }
1677 while( count < limit && changed != 0 )
1678 {
1679 Image *tmp = old_image;
1680 old_image = new_image;
1681 new_image = tmp;
1682 changed = MorphologyApply(old_image,new_image,method,channel,kernel,
1683 exception);
1684 count++;
1685 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
1686 fprintf(stderr, "Morphology %s:%lu => Changed %lu\n",
1687 MagickOptionToMnemonic(MagickMorphologyOptions, method),
1688 count, changed);
1689 }
1690 DestroyImage(old_image);
1691 }
1692
1693 return(new_image);
1694}
1695