blob: b1c5ccab1a1ba69db073f1b76cda526b2999fcba [file] [log] [blame]
cristy701db312009-11-20 03:14:08 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% M M OOO RRRR PPPP H H OOO L OOO GGGG Y Y %
7% MM MM O O R R P P H H O O L O O G Y Y %
8% M M M O O RRRR PPPP HHHHH O O L O O G GGG Y %
9% M M O O R R P H H O O L O O G G Y %
10% M M OOO R R P H H OOO LLLLL OOO GGG Y %
11% %
12% %
13% MagickCore Morphology Methods %
14% %
15% Software Design %
16% Anthony Thyssen %
anthonyc94cdb02010-01-06 08:15:29 +000017% January 2010 %
cristy701db312009-11-20 03:14:08 +000018% %
19% %
cristy16af1cb2009-12-11 21:38:29 +000020% Copyright 1999-2010 ImageMagick Studio LLC, a non-profit organization %
cristy701db312009-11-20 03:14:08 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
anthony602ab9b2010-01-05 08:06:50 +000036% Morpology is the the application of various kernals, of any size and even
37% shape, to a image in various ways (typically binary, but not always).
cristy701db312009-11-20 03:14:08 +000038%
anthony602ab9b2010-01-05 08:06:50 +000039% Convolution (weighted sum or average) is just one specific type of
40% morphology. Just one that is very common for image bluring and sharpening
41% effects. Not only 2D Gaussian blurring, but also 2-pass 1D Blurring.
42%
43% This module provides not only a general morphology function, and the ability
44% to apply more advanced or iterative morphologies, but also functions for the
45% generation of many different types of kernel arrays from user supplied
46% arguments. Prehaps even the generation of a kernel from a small image.
cristy701db312009-11-20 03:14:08 +000047*/
48
49/*
50 Include declarations.
51*/
52#include "magick/studio.h"
anthony602ab9b2010-01-05 08:06:50 +000053#include "magick/artifact.h"
cristy701db312009-11-20 03:14:08 +000054#include "magick/cache-view.h"
55#include "magick/color-private.h"
56#include "magick/enhance.h"
57#include "magick/exception.h"
58#include "magick/exception-private.h"
anthony602ab9b2010-01-05 08:06:50 +000059#include "magick/gem.h"
cristy701db312009-11-20 03:14:08 +000060#include "magick/hashmap.h"
61#include "magick/image.h"
cristybba804b2010-01-05 15:39:59 +000062#include "magick/image-private.h"
cristy701db312009-11-20 03:14:08 +000063#include "magick/list.h"
anthony29188a82010-01-22 10:12:34 +000064#include "magick/magick.h"
cristy701db312009-11-20 03:14:08 +000065#include "magick/memory_.h"
66#include "magick/monitor-private.h"
67#include "magick/morphology.h"
anthony602ab9b2010-01-05 08:06:50 +000068#include "magick/option.h"
cristy701db312009-11-20 03:14:08 +000069#include "magick/pixel-private.h"
70#include "magick/prepress.h"
71#include "magick/quantize.h"
72#include "magick/registry.h"
73#include "magick/semaphore.h"
74#include "magick/splay-tree.h"
75#include "magick/statistic.h"
76#include "magick/string_.h"
anthony602ab9b2010-01-05 08:06:50 +000077#include "magick/string-private.h"
78#include "magick/token.h"
79
anthonyc94cdb02010-01-06 08:15:29 +000080
anthony602ab9b2010-01-05 08:06:50 +000081/*
anthonyc94cdb02010-01-06 08:15:29 +000082 * The following test is for special floating point numbers of value NaN (not
83 * a number), that may be used within a Kernel Definition. NaN's are defined
84 * as part of the IEEE standard for floating point number representation.
anthony602ab9b2010-01-05 08:06:50 +000085 *
anthonyc94cdb02010-01-06 08:15:29 +000086 * These are used a Kernel value of NaN means that that kernal position is not
87 * part of the normal convolution or morphology process, and thus allowing the
88 * use of 'shaped' kernels.
anthony602ab9b2010-01-05 08:06:50 +000089 *
anthonyc94cdb02010-01-06 08:15:29 +000090 * Special Properities Two NaN's are never equal, even if they are from the
91 * same variable That is the IsNaN() macro is only true if the value is NaN.
anthony602ab9b2010-01-05 08:06:50 +000092 */
93#define IsNan(a) ((a)!=(a))
94
anthony29188a82010-01-22 10:12:34 +000095/*
96 * Other global definitions used by module
97 */
98static inline double MagickMin(const double x,const double y)
99{
100 return( x < y ? x : y);
101}
102static inline double MagickMax(const double x,const double y)
103{
104 return( x > y ? x : y);
105}
106#define Minimize(assign,value) assign=MagickMin(assign,value)
107#define Maximize(assign,value) assign=MagickMax(assign,value)
108
anthony602ab9b2010-01-05 08:06:50 +0000109
110/*
111%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
112% %
113% %
114% %
115% A c q u i r e K e r n e l F r o m S t r i n g %
116% %
117% %
118% %
119%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
120%
cristy2be15382010-01-21 02:38:03 +0000121% AcquireKernelInfo() takes the given string (generally supplied by the
anthony602ab9b2010-01-05 08:06:50 +0000122% user) and converts it into a Morphology/Convolution Kernel. This allows
123% users to specify a kernel from a number of pre-defined kernels, or to fully
124% specify their own kernel for a specific Convolution or Morphology
125% Operation.
126%
127% The kernel so generated can be any rectangular array of floating point
128% values (doubles) with the 'control point' or 'pixel being affected'
129% anywhere within that array of values.
130%
131% ASIDE: Previously IM was restricted to a square of odd size using the exact
132% center.
133%
134% The floating point values in the kernel can also include a special value
135% known as 'NaN' or 'not a number' to indicate that this value is not part
136% of the kernel array. This allows you to specify a non-rectangular shaped
137% kernel, for use in Morphological operators, without the need for some type
138% of kernal mask.
139%
140% The returned kernel should be freed using the DestroyKernel() when you are
141% finished with it.
142%
143% Input kernel defintion strings can consist of any of three types.
144%
anthony29188a82010-01-22 10:12:34 +0000145% "name:args"
146% Select from one of the built in kernels, using the name and
147% geometry arguments supplied. See AcquireKernelBuiltIn()
anthony602ab9b2010-01-05 08:06:50 +0000148%
149% "WxH[+X+Y]:num, num, num ..."
150% a kernal of size W by H, with W*H floating point numbers following.
151% the 'center' can be optionally be defined at +X+Y (such that +0+0
anthony29188a82010-01-22 10:12:34 +0000152% is top left corner). If not defined the pixel in the center, for
153% odd sizes, or to the immediate top or left of center for even sizes
154% is automatically selected.
anthony602ab9b2010-01-05 08:06:50 +0000155%
anthony29188a82010-01-22 10:12:34 +0000156% "num, num, num, num, ..."
157% list of floating point numbers defining an 'old style' odd sized
158% square kernel. At least 9 values should be provided for a 3x3
159% square kernel, 25 for a 5x5 square kernel, 49 for 7x7, etc.
160% Values can be space or comma separated. This is not recommended.
anthony602ab9b2010-01-05 08:06:50 +0000161%
162% Note that 'name' kernels will start with an alphabetic character
163% while the new kernel specification has a ':' character in its
anthony29188a82010-01-22 10:12:34 +0000164% specification string. If neither is the case, it assumes it is the
165% old style of a simple list of numbers.
anthony602ab9b2010-01-05 08:06:50 +0000166%
167% The format of the AcquireKernal method is:
168%
cristy2be15382010-01-21 02:38:03 +0000169% KernelInfo *AcquireKernelInfo(const char *kernel_string)
anthony602ab9b2010-01-05 08:06:50 +0000170%
171% A description of each parameter follows:
172%
173% o kernel_string: the Morphology/Convolution kernel wanted.
174%
175*/
176
cristy2be15382010-01-21 02:38:03 +0000177MagickExport KernelInfo *AcquireKernelInfo(const char *kernel_string)
anthony602ab9b2010-01-05 08:06:50 +0000178{
cristy2be15382010-01-21 02:38:03 +0000179 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000180 *kernel;
181
182 char
183 token[MaxTextExtent];
184
185 register unsigned long
186 i;
187
188 const char
189 *p;
190
191 MagickStatusType
192 flags;
193
194 GeometryInfo
195 args;
196
anthony29188a82010-01-22 10:12:34 +0000197 double
198 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
199
anthony602ab9b2010-01-05 08:06:50 +0000200 assert(kernel_string != (const char *) NULL);
201 SetGeometryInfo(&args);
202
203 /* does it start with an alpha - Return a builtin kernel */
204 GetMagickToken(kernel_string,&p,token);
205 if ( isalpha((int)token[0]) )
206 {
207 long
208 type;
209
anthony29188a82010-01-22 10:12:34 +0000210 type=ParseMagickOption(MagickKernelOptions,MagickFalse,token);
anthony602ab9b2010-01-05 08:06:50 +0000211 if ( type < 0 || type == UserDefinedKernel )
cristy2be15382010-01-21 02:38:03 +0000212 return((KernelInfo *)NULL);
anthony602ab9b2010-01-05 08:06:50 +0000213
214 while (((isspace((int) ((unsigned char) *p)) != 0) ||
215 (*p == ',') || (*p == ':' )) && (*p != '\0'))
216 p++;
217 flags = ParseGeometry(p, &args);
218
219 /* special handling of missing values in input string */
220 if ( type == RectangleKernel ) {
221 if ( (flags & WidthValue) == 0 ) /* if no width then */
222 args.rho = args.sigma; /* then width = height */
223 if ( args.rho < 1.0 ) /* if width too small */
224 args.rho = 3; /* then width = 3 */
225 if ( args.sigma < 1.0 ) /* if height too small */
226 args.sigma = args.rho; /* then height = width */
227 if ( (flags & XValue) == 0 ) /* center offset if not defined */
228 args.xi = (double)(((long)args.rho-1)/2);
229 if ( (flags & YValue) == 0 )
230 args.psi = (double)(((long)args.sigma-1)/2);
231 }
232
cristy2be15382010-01-21 02:38:03 +0000233 return(AcquireKernelBuiltIn((KernelInfoType)type, &args));
anthony602ab9b2010-01-05 08:06:50 +0000234 }
235
cristy2be15382010-01-21 02:38:03 +0000236 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
237 if (kernel == (KernelInfo *)NULL)
anthony602ab9b2010-01-05 08:06:50 +0000238 return(kernel);
239 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
240 kernel->type = UserDefinedKernel;
cristyd43a46b2010-01-21 02:13:41 +0000241 kernel->signature = MagickSignature;
anthony602ab9b2010-01-05 08:06:50 +0000242
243 /* Has a ':' in argument - New user kernel specification */
244 p = strchr(kernel_string, ':');
245 if ( p != (char *) NULL)
246 {
anthony602ab9b2010-01-05 08:06:50 +0000247 /* ParseGeometry() needs the geometry separated! -- Arrgghh */
248 memcpy(token, kernel_string, p-kernel_string);
249 token[p-kernel_string] = '\0';
250 flags = ParseGeometry(token, &args);
anthony602ab9b2010-01-05 08:06:50 +0000251
anthony29188a82010-01-22 10:12:34 +0000252 /* Size handling and checks of geometry settings */
anthony602ab9b2010-01-05 08:06:50 +0000253 if ( (flags & WidthValue) == 0 ) /* if no width then */
254 args.rho = args.sigma; /* then width = height */
255 if ( args.rho < 1.0 ) /* if width too small */
256 args.rho = 1.0; /* then width = 1 */
257 if ( args.sigma < 1.0 ) /* if height too small */
258 args.sigma = args.rho; /* then height = width */
259 kernel->width = (unsigned long)args.rho;
260 kernel->height = (unsigned long)args.sigma;
261
262 /* Offset Handling and Checks */
263 if ( args.xi < 0.0 || args.psi < 0.0 )
264 return(DestroyKernel(kernel));
265 kernel->offset_x = ((flags & XValue)!=0) ? (unsigned long)args.xi
266 : (kernel->width-1)/2;
267 kernel->offset_y = ((flags & YValue)!=0) ? (unsigned long)args.psi
268 : (kernel->height-1)/2;
269 if ( kernel->offset_x >= kernel->width ||
270 kernel->offset_y >= kernel->height )
271 return(DestroyKernel(kernel));
272
273 p++; /* advance beyond the ':' */
274 }
275 else
276 { /* ELSE - Old old kernel specification, forming odd-square kernel */
277 /* count up number of values given */
278 p=(const char *) kernel_string;
cristya699b172010-01-06 16:48:49 +0000279 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
anthony29188a82010-01-22 10:12:34 +0000280 p++; /* ignore "'" chars for convolve filter usage - Cristy */
anthony602ab9b2010-01-05 08:06:50 +0000281 for (i=0; *p != '\0'; i++)
282 {
283 GetMagickToken(p,&p,token);
284 if (*token == ',')
285 GetMagickToken(p,&p,token);
286 }
287 /* set the size of the kernel - old sized square */
288 kernel->width = kernel->height= (unsigned long) sqrt((double) i+1.0);
289 kernel->offset_x = kernel->offset_y = (kernel->width-1)/2;
290 p=(const char *) kernel_string;
anthony29188a82010-01-22 10:12:34 +0000291 while ((isspace((int) ((unsigned char) *p)) != 0) || (*p == '\''))
292 p++; /* ignore "'" chars for convolve filter usage - Cristy */
anthony602ab9b2010-01-05 08:06:50 +0000293 }
294
295 /* Read in the kernel values from rest of input string argument */
296 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
297 kernel->height*sizeof(double));
298 if (kernel->values == (double *) NULL)
299 return(DestroyKernel(kernel));
300
anthony29188a82010-01-22 10:12:34 +0000301 kernel->value_min = +MagickHuge;
302 kernel->value_max = -MagickHuge;
anthony602ab9b2010-01-05 08:06:50 +0000303 kernel->range_neg = kernel->range_pos = 0.0;
304 for (i=0; (i < kernel->width*kernel->height) && (*p != '\0'); i++)
305 {
306 GetMagickToken(p,&p,token);
307 if (*token == ',')
308 GetMagickToken(p,&p,token);
anthony29188a82010-01-22 10:12:34 +0000309 if ( LocaleCompare("nan",token) == 0
310 || LocaleCompare("-",token) == 0 ) {
311 kernel->values[i] = nan; /* do not include this value in kernel */
312 }
313 else {
314 kernel->values[i] = StringToDouble(token);
315 ( kernel->values[i] < 0)
316 ? ( kernel->range_neg += kernel->values[i] )
317 : ( kernel->range_pos += kernel->values[i] );
318 Minimize(kernel->value_min, kernel->values[i]);
319 Maximize(kernel->value_max, kernel->values[i]);
320 }
anthony602ab9b2010-01-05 08:06:50 +0000321 }
anthony29188a82010-01-22 10:12:34 +0000322
323 /* This should not be needed for a fully defined defined kernel
324 * Perhaps an error should be reported instead!
325 */
326 if ( i < kernel->width*kernel->height ) {
327 Minimize(kernel->value_min, kernel->values[i]);
328 Maximize(kernel->value_max, kernel->values[i]);
329 for ( ; i < kernel->width*kernel->height; i++)
330 kernel->values[i]=0.0;
331 }
anthony602ab9b2010-01-05 08:06:50 +0000332
333 return(kernel);
334}
335
336/*
337%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
338% %
339% %
340% %
341% A c q u i r e K e r n e l B u i l t I n %
342% %
343% %
344% %
345%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
346%
347% AcquireKernelBuiltIn() returned one of the 'named' built-in types of
348% kernels used for special purposes such as gaussian blurring, skeleton
349% pruning, and edge distance determination.
350%
351% They take a KernelType, and a set of geometry style arguments, which were
352% typically decoded from a user supplied string, or from a more complex
353% Morphology Method that was requested.
354%
355% The format of the AcquireKernalBuiltIn method is:
356%
cristy2be15382010-01-21 02:38:03 +0000357% KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000358% const GeometryInfo args)
359%
360% A description of each parameter follows:
361%
362% o type: the pre-defined type of kernel wanted
363%
364% o args: arguments defining or modifying the kernel
365%
366% Convolution Kernels
367%
368% Gaussian "[{radius}]x{sigma}"
369% Generate a two-dimentional gaussian kernel, as used by -gaussian
370% A sigma is required, (with the 'x'), due to historical reasons.
371%
372% NOTE: that the 'radius' is optional, but if provided can limit (clip)
373% the final size of the resulting kernel to a square 2*radius+1 in size.
374% The radius should be at least 2 times that of the sigma value, or
375% sever clipping and aliasing may result. If not given or set to 0 the
376% radius will be determined so as to produce the best minimal error
377% result, which is usally much larger than is normally needed.
378%
379% Blur "[{radius}]x{sigma}[+angle]"
380% As per Gaussian, but generates a 1 dimensional or linear gaussian
381% blur, at the angle given (current restricted to orthogonal angles).
382% If a 'radius' is given the kernel is clipped to a width of 2*radius+1.
383%
384% NOTE that two such blurs perpendicular to each other is equivelent to
385% -blur and the previous gaussian, but is often 10 or more times faster.
386%
387% Comet "[{width}]x{sigma}[+angle]"
388% Blur in one direction only, mush like how a bright object leaves
389% a comet like trail. The Kernel is actually half a gaussian curve,
390% Adding two such blurs in oppiste directions produces a Linear Blur.
391%
392% NOTE: that the first argument is the width of the kernel and not the
393% radius of the kernel.
394%
395% # Still to be implemented...
396% #
397% # Laplacian "{radius}x{sigma}"
398% # Laplacian (a mexican hat like) Function
399% #
400% # LOG "{radius},{sigma1},{sigma2}
401% # Laplacian of Gaussian
402% #
403% # DOG "{radius},{sigma1},{sigma2}
404% # Difference of Gaussians
405%
406% Boolean Kernels
407%
408% Rectangle "{geometry}"
409% Simply generate a rectangle of 1's with the size given. You can also
410% specify the location of the 'control point', otherwise the closest
411% pixel to the center of the rectangle is selected.
412%
413% Properly centered and odd sized rectangles work the best.
414%
415% Diamond "[{radius}]"
416% Generate a diamond shaped kernal with given radius to the points.
417% Kernel size will again be radius*2+1 square and defaults to radius 1,
418% generating a 3x3 kernel that is slightly larger than a square.
419%
420% Square "[{radius}]"
421% Generate a square shaped kernel of size radius*2+1, and defaulting
422% to a 3x3 (radius 1).
423%
424% Note that using a larger radius for the "Square" or the "Diamond"
425% is also equivelent to iterating the basic morphological method
426% that many times. However However iterating with the smaller radius 1
427% default is actually faster than using a larger kernel radius.
428%
429% Disk "[{radius}]
430% Generate a binary disk of the radius given, radius may be a float.
431% Kernel size will be ceil(radius)*2+1 square.
432% NOTE: Here are some disk shapes of specific interest
433% "disk:1" => "diamond" or "cross:1"
434% "disk:1.5" => "square"
435% "disk:2" => "diamond:2"
436% "disk:2.5" => default - radius 2 disk shape
437% "disk:2.9" => "square:2"
438% "disk:3.5" => octagonal/disk shape of radius 3
439% "disk:4.2" => roughly octagonal shape of radius 4
440% "disk:4.3" => disk shape of radius 4
441% After this all the kernel shape becomes more and more circular.
442%
443% Because a "disk" is more circular when using a larger radius, using a
444% larger radius is preferred over iterating the morphological operation.
445%
446% Plus "[{radius}]"
447% Generate a kernel in the shape of a 'plus' sign. The length of each
448% arm is also the radius, which defaults to 2.
449%
450% This kernel is not a good general morphological kernel, but is used
451% more for highlighting and marking any single pixels in an image using,
452% a "Dilate" or "Erode" method as appropriate.
anthonyc94cdb02010-01-06 08:15:29 +0000453%
anthony602ab9b2010-01-05 08:06:50 +0000454% NOTE: "plus:1" is equivelent to a "Diamond" kernel.
455%
456% Note that unlike other kernels iterating a plus does not produce the
457% same result as using a larger radius for the cross.
458%
459% Distance Measuring Kernels
460%
461% Chebyshev "[{radius}][x{scale}]" largest x or y distance (default r=1)
462% Manhatten "[{radius}][x{scale}]" square grid distance (default r=1)
anthonyc94cdb02010-01-06 08:15:29 +0000463% Euclidean "[{radius}][x{scale}]" direct distance (default r=1)
anthony602ab9b2010-01-05 08:06:50 +0000464%
465% Different types of distance measuring methods, which are used with the
466% a 'Distance' morphology method for generating a gradient based on
467% distance from an edge of a binary shape, though there is a technique
468% for handling a anti-aliased shape.
469%
anthonyc94cdb02010-01-06 08:15:29 +0000470% Chebyshev Distance (also known as Tchebychev Distance) is a value of
471% one to any neighbour, orthogonal or diagonal. One why of thinking of
472% it is the number of squares a 'King' or 'Queen' in chess needs to
473% traverse reach any other position on a chess board. It results in a
474% 'square' like distance function, but one where diagonals are closer
475% than expected.
anthony602ab9b2010-01-05 08:06:50 +0000476%
anthonyc94cdb02010-01-06 08:15:29 +0000477% Manhatten Distance (also known as Rectilinear Distance, or the Taxi
478% Cab metric), is the distance needed when you can only travel in
479% orthogonal (horizontal or vertical) only. It is the distance a 'Rook'
480% in chess would travel. It results in a diamond like distances, where
481% diagonals are further than expected.
anthony602ab9b2010-01-05 08:06:50 +0000482%
anthonyc94cdb02010-01-06 08:15:29 +0000483% Euclidean Distance is the 'direct' or 'as the crow flys distance.
484% However by default the kernel size only has a radius of 1, which
485% limits the distance to 'Knight' like moves, with only orthogonal and
486% diagonal measurements being correct. As such for the default kernel
487% you will get octagonal like distance function, which is reasonally
488% accurate.
489%
490% However if you use a larger radius such as "Euclidean:4" you will
491% get a much smoother distance gradient from the edge of the shape.
492% Of course a larger kernel is slower to use, and generally not needed.
493%
494% To allow the use of fractional distances that you get with diagonals
495% the actual distance is scaled by a fixed value which the user can
496% provide. This is not actually nessary for either ""Chebyshev" or
497% "Manhatten" distance kernels, but is done for all three distance
498% kernels. If no scale is provided it is set to a value of 100,
499% allowing for a maximum distance measurement of 655 pixels using a Q16
500% version of IM, from any edge. However for small images this can
501% result in quite a dark gradient.
502%
503% See the 'Distance' Morphological Method, for information of how it is
504% applied.
anthony602ab9b2010-01-05 08:06:50 +0000505%
506*/
507
cristy2be15382010-01-21 02:38:03 +0000508MagickExport KernelInfo *AcquireKernelBuiltIn(const KernelInfoType type,
anthony602ab9b2010-01-05 08:06:50 +0000509 const GeometryInfo *args)
510{
cristy2be15382010-01-21 02:38:03 +0000511 KernelInfo
anthony602ab9b2010-01-05 08:06:50 +0000512 *kernel;
513
514 register unsigned long
515 i;
516
517 register long
518 u,
519 v;
520
521 double
522 nan = sqrt((double)-1.0); /* Special Value : Not A Number */
523
cristy2be15382010-01-21 02:38:03 +0000524 kernel=(KernelInfo *) AcquireMagickMemory(sizeof(*kernel));
525 if (kernel == (KernelInfo *) NULL)
anthony602ab9b2010-01-05 08:06:50 +0000526 return(kernel);
527 (void) ResetMagickMemory(kernel,0,sizeof(*kernel));
anthonyc94cdb02010-01-06 08:15:29 +0000528 kernel->value_min = kernel->value_max = 0.0;
anthony602ab9b2010-01-05 08:06:50 +0000529 kernel->range_neg = kernel->range_pos = 0.0;
530 kernel->type = type;
cristyd43a46b2010-01-21 02:13:41 +0000531 kernel->signature = MagickSignature;
anthony602ab9b2010-01-05 08:06:50 +0000532
533 switch(type) {
534 /* Convolution Kernels */
535 case GaussianKernel:
536 { double
537 sigma = fabs(args->sigma);
538
539 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
540
541 kernel->width = kernel->height =
542 GetOptimalKernelWidth2D(args->rho,sigma);
543 kernel->offset_x = kernel->offset_y = (kernel->width-1)/2;
544 kernel->range_neg = kernel->range_pos = 0.0;
545 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
546 kernel->height*sizeof(double));
547 if (kernel->values == (double *) NULL)
548 return(DestroyKernel(kernel));
549
550 sigma = 2.0*sigma*sigma; /* simplify the expression */
551 for ( i=0, v=-kernel->offset_y; v <= (long)kernel->offset_y; v++)
552 for ( u=-kernel->offset_x; u <= (long)kernel->offset_x; u++, i++)
553 kernel->range_pos += (
554 kernel->values[i] =
555 exp(-((double)(u*u+v*v))/sigma)
556 /* / (MagickPI*sigma) */ );
anthonyc94cdb02010-01-06 08:15:29 +0000557 kernel->value_min = 0;
558 kernel->value_max = kernel->values[
559 kernel->offset_y*kernel->width+kernel->offset_x ];
anthony602ab9b2010-01-05 08:06:50 +0000560
anthonyc94cdb02010-01-06 08:15:29 +0000561 KernelNormalize(kernel);
anthony602ab9b2010-01-05 08:06:50 +0000562
563 break;
564 }
565 case BlurKernel:
566 { double
567 sigma = fabs(args->sigma);
568
569 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
570
571 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
572 kernel->offset_x = (kernel->width-1)/2;
573 kernel->height = 1;
574 kernel->offset_y = 0;
575 kernel->range_neg = kernel->range_pos = 0.0;
576 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
577 kernel->height*sizeof(double));
578 if (kernel->values == (double *) NULL)
579 return(DestroyKernel(kernel));
580
581#if 1
582#define KernelRank 3
583 /* Formula derived from GetBlurKernel() in "effect.c" (plus bug fix).
584 ** It generates a gaussian 3 times the width, and compresses it into
585 ** the expected range. This produces a closer normalization of the
586 ** resulting kernel, especially for very low sigma values.
587 ** As such while wierd it is prefered.
588 **
589 ** I am told this method originally came from Photoshop.
590 */
591 sigma *= KernelRank; /* simplify expanded curve */
592 v = (kernel->width*KernelRank-1)/2; /* start/end points to fit range */
593 (void) ResetMagickMemory(kernel->values,0, (size_t)
594 kernel->width*sizeof(double));
595 for ( u=-v; u <= v; u++) {
596 kernel->values[(u+v)/KernelRank] +=
597 exp(-((double)(u*u))/(2.0*sigma*sigma))
598 /* / (MagickSQ2PI*sigma/KernelRank) */ ;
599 }
600 for (i=0; i < kernel->width; i++)
601 kernel->range_pos += kernel->values[i];
602#else
603 for ( i=0, u=-kernel->offset_x; i < kernel->width; i++, u++)
604 kernel->range_pos += (
605 kernel->values[i] =
606 exp(-((double)(u*u))/(2.0*sigma*sigma))
607 /* / (MagickSQ2PI*sigma) */ );
608#endif
anthonyc94cdb02010-01-06 08:15:29 +0000609 kernel->value_min = 0;
610 kernel->value_max = kernel->values[ kernel->offset_x ];
anthony602ab9b2010-01-05 08:06:50 +0000611 /* Note that both the above methods do not generate a normalized
612 ** kernel, though it gets close. The kernel may be 'clipped' by a user
613 ** defined radius, producing a smaller (darker) kernel. Also for very
614 ** small sigma's (> 0.1) the central value becomes larger than one,
anthonyc94cdb02010-01-06 08:15:29 +0000615 ** and thus producing a very bright kernel.
anthony602ab9b2010-01-05 08:06:50 +0000616 */
617#if 1
618 /* Normalize the 1D Gaussian Kernel
619 **
620 ** Because of this the divisor in the above kernel generator is
anthonyc94cdb02010-01-06 08:15:29 +0000621 ** not needed, so is not done above.
anthony602ab9b2010-01-05 08:06:50 +0000622 */
anthonyc94cdb02010-01-06 08:15:29 +0000623 KernelNormalize(kernel);
anthony602ab9b2010-01-05 08:06:50 +0000624#endif
625 /* rotate the kernel by given angle */
626 KernelRotate(kernel, args->xi);
627 break;
628 }
629 case CometKernel:
630 { double
631 sigma = fabs(args->sigma);
632
633 sigma = (sigma <= MagickEpsilon) ? 1.0 : sigma;
634
635 if ( args->rho < 1.0 )
636 kernel->width = GetOptimalKernelWidth1D(args->rho,sigma);
637 else
638 kernel->width = (unsigned long)args->rho;
639 kernel->offset_x = kernel->offset_y = 0;
640 kernel->height = 1;
641 kernel->range_neg = kernel->range_pos = 0.0;
642 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
643 kernel->height*sizeof(double));
644 if (kernel->values == (double *) NULL)
645 return(DestroyKernel(kernel));
646
647 /* A comet blur is half a gaussian curve, so that the object is
648 ** blurred in one direction only. This may not be quite the right
649 ** curve so may change in the future. The function must be normalised.
650 */
651#if 1
652#define KernelRank 3
653 sigma *= KernelRank; /* simplify expanded curve */
654 v = kernel->width*KernelRank; /* start/end points to fit range */
655 (void) ResetMagickMemory(kernel->values,0, (size_t)
656 kernel->width*sizeof(double));
657 for ( u=0; u < v; u++) {
658 kernel->values[u/KernelRank] +=
659 exp(-((double)(u*u))/(2.0*sigma*sigma))
660 /* / (MagickSQ2PI*sigma/KernelRank) */ ;
661 }
662 for (i=0; i < kernel->width; i++)
663 kernel->range_pos += kernel->values[i];
664#else
665 for ( i=0; i < kernel->width; i++)
666 kernel->range_pos += (
667 kernel->values[i] =
668 exp(-((double)(i*i))/(2.0*sigma*sigma))
669 /* / (MagickSQ2PI*sigma) */ );
670#endif
anthonyc94cdb02010-01-06 08:15:29 +0000671 kernel->value_min = 0;
672 kernel->value_max = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000673
anthonyc94cdb02010-01-06 08:15:29 +0000674 KernelNormalize(kernel);
anthony602ab9b2010-01-05 08:06:50 +0000675 KernelRotate(kernel, args->xi);
676 break;
677 }
678 /* Boolean Kernels */
679 case RectangleKernel:
680 case SquareKernel:
681 {
682 if ( type == SquareKernel )
683 {
684 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000685 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000686 else
687 kernel->width = kernel->height = 2*(long)args->rho+1;
688 kernel->offset_x = kernel->offset_y = (kernel->width-1)/2;
689 }
690 else {
cristy2be15382010-01-21 02:38:03 +0000691 /* NOTE: user defaults set in "AcquireKernelInfo()" */
anthony602ab9b2010-01-05 08:06:50 +0000692 if ( args->rho < 1.0 || args->sigma < 1.0 )
anthonyc94cdb02010-01-06 08:15:29 +0000693 return(DestroyKernel(kernel)); /* invalid args given */
anthony602ab9b2010-01-05 08:06:50 +0000694 kernel->width = (unsigned long)args->rho;
695 kernel->height = (unsigned long)args->sigma;
696 if ( args->xi < 0.0 || args->xi > (double)kernel->width ||
697 args->psi < 0.0 || args->psi > (double)kernel->height )
anthonyc94cdb02010-01-06 08:15:29 +0000698 return(DestroyKernel(kernel)); /* invalid args given */
anthony602ab9b2010-01-05 08:06:50 +0000699 kernel->offset_x = (unsigned long)args->xi;
700 kernel->offset_y = (unsigned long)args->psi;
701 }
702 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
703 kernel->height*sizeof(double));
704 if (kernel->values == (double *) NULL)
705 return(DestroyKernel(kernel));
706
707 u=kernel->width*kernel->height;
708 for ( i=0; i < (unsigned long)u; i++)
709 kernel->values[i] = 1.0;
710 break;
anthonyc94cdb02010-01-06 08:15:29 +0000711 kernel->value_min = kernel->value_max = 1.0; /* a flat kernel */
712 kernel->range_pos = (double) u;
anthony602ab9b2010-01-05 08:06:50 +0000713 }
714 case DiamondKernel:
715 {
716 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000717 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000718 else
719 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
720 kernel->offset_x = kernel->offset_y = (kernel->width-1)/2;
721
722 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
723 kernel->height*sizeof(double));
724 if (kernel->values == (double *) NULL)
725 return(DestroyKernel(kernel));
726
727 for ( i=0, v=-kernel->offset_y; v <= (long)kernel->offset_y; v++)
728 for ( u=-kernel->offset_x; u <= (long)kernel->offset_x; u++, i++)
729 if ((labs(u)+labs(v)) <= (long)kernel->offset_x)
730 kernel->range_pos += kernel->values[i] = 1.0;
731 else
732 kernel->values[i] = nan;
anthonyc94cdb02010-01-06 08:15:29 +0000733 kernel->value_min = kernel->value_max = 1.0; /* a flat kernel */
anthony602ab9b2010-01-05 08:06:50 +0000734 break;
735 }
736 case DiskKernel:
737 {
738 long
739 limit;
740
741 limit = (long)(args->rho*args->rho);
anthony29188a82010-01-22 10:12:34 +0000742 if (args->rho < 0.1) /* default radius approx 2.5 */
anthony602ab9b2010-01-05 08:06:50 +0000743 kernel->width = kernel->height = 5L, limit = 5L;
744 else
745 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
746 kernel->offset_x = kernel->offset_y = (kernel->width-1)/2;
747
748 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
749 kernel->height*sizeof(double));
750 if (kernel->values == (double *) NULL)
751 return(DestroyKernel(kernel));
752
753 for ( i=0, v=-kernel->offset_y; v <= (long)kernel->offset_y; v++)
754 for ( u=-kernel->offset_x; u <= (long)kernel->offset_x; u++, i++)
755 if ((u*u+v*v) <= limit)
756 kernel->range_pos += kernel->values[i] = 1.0;
757 else
758 kernel->values[i] = nan;
anthonyc94cdb02010-01-06 08:15:29 +0000759 kernel->value_min = kernel->value_max = 1.0; /* a flat kernel */
anthony602ab9b2010-01-05 08:06:50 +0000760 break;
761 }
762 case PlusKernel:
763 {
764 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000765 kernel->width = kernel->height = 5; /* default radius 2 */
anthony602ab9b2010-01-05 08:06:50 +0000766 else
767 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
768 kernel->offset_x = kernel->offset_y = (kernel->width-1)/2;
769
770 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
771 kernel->height*sizeof(double));
772 if (kernel->values == (double *) NULL)
773 return(DestroyKernel(kernel));
774
775 for ( i=0, v=-kernel->offset_y; v <= (long)kernel->offset_y; v++)
776 for ( u=-kernel->offset_x; u <= (long)kernel->offset_x; u++, i++)
777 kernel->values[i] = (u == 0 || v == 0) ? 1.0 : nan;
anthonyc94cdb02010-01-06 08:15:29 +0000778 kernel->value_min = kernel->value_max = 1.0; /* a flat kernel */
anthony602ab9b2010-01-05 08:06:50 +0000779 kernel->range_pos = kernel->width*2.0 - 1.0;
780 break;
781 }
782 /* Distance Measuring Kernels */
783 case ChebyshevKernel:
784 {
785 double
786 scale;
787
788 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000789 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000790 else
791 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
792 kernel->offset_x = kernel->offset_y = (kernel->width-1)/2;
793
794 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
795 kernel->height*sizeof(double));
796 if (kernel->values == (double *) NULL)
797 return(DestroyKernel(kernel));
798
799 scale = (args->sigma < 1.0) ? 100.0 : args->sigma;
800 for ( i=0, v=-kernel->offset_y; v <= (long)kernel->offset_y; v++)
801 for ( u=-kernel->offset_x; u <= (long)kernel->offset_x; u++, i++)
802 kernel->range_pos += ( kernel->values[i] =
803 scale*((labs(u)>labs(v)) ? labs(u) : labs(v)) );
anthonyc94cdb02010-01-06 08:15:29 +0000804 kernel->value_max = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000805 break;
806 }
807 case ManhattenKernel:
808 {
809 double
810 scale;
811
812 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000813 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000814 else
815 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
816 kernel->offset_x = kernel->offset_y = (kernel->width-1)/2;
817
818 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
819 kernel->height*sizeof(double));
820 if (kernel->values == (double *) NULL)
821 return(DestroyKernel(kernel));
822
823 scale = (args->sigma < 1.0) ? 100.0 : args->sigma;
824 for ( i=0, v=-kernel->offset_y; v <= (long)kernel->offset_y; v++)
825 for ( u=-kernel->offset_x; u <= (long)kernel->offset_x; u++, i++)
826 kernel->range_pos += ( kernel->values[i] =
827 scale*(labs(u)+labs(v)) );
anthonyc94cdb02010-01-06 08:15:29 +0000828 kernel->value_max = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000829 break;
830 }
831 case EuclideanKernel:
832 {
833 double
834 scale;
835
836 if (args->rho < 1.0)
anthonyc94cdb02010-01-06 08:15:29 +0000837 kernel->width = kernel->height = 3; /* default radius = 1 */
anthony602ab9b2010-01-05 08:06:50 +0000838 else
839 kernel->width = kernel->height = ((unsigned long)args->rho)*2+1;
840 kernel->offset_x = kernel->offset_y = (kernel->width-1)/2;
841
842 kernel->values=(double *) AcquireQuantumMemory(kernel->width,
843 kernel->height*sizeof(double));
844 if (kernel->values == (double *) NULL)
845 return(DestroyKernel(kernel));
846
847 scale = (args->sigma < 1.0) ? 100.0 : args->sigma;
848 for ( i=0, v=-kernel->offset_y; v <= (long)kernel->offset_y; v++)
849 for ( u=-kernel->offset_x; u <= (long)kernel->offset_x; u++, i++)
850 kernel->range_pos += ( kernel->values[i] =
851 scale*sqrt((double)(u*u+v*v)) );
anthonyc94cdb02010-01-06 08:15:29 +0000852 kernel->value_max = kernel->values[0];
anthony602ab9b2010-01-05 08:06:50 +0000853 break;
854 }
855 /* Undefined Kernels */
856 case LaplacianKernel:
857 case LOGKernel:
858 case DOGKernel:
859 assert("Kernel Type has not been defined yet");
860 /* FALL THRU */
861 default:
862 /* Generate a No-Op minimal kernel - 1x1 pixel */
863 kernel->values=(double *)AcquireQuantumMemory((size_t)1,sizeof(double));
864 if (kernel->values == (double *) NULL)
865 return(DestroyKernel(kernel));
anthony602ab9b2010-01-05 08:06:50 +0000866 kernel->width = kernel->height = 1;
867 kernel->offset_x = kernel->offset_x = 0;
868 kernel->type = UndefinedKernel;
anthonyc94cdb02010-01-06 08:15:29 +0000869 kernel->value_max =
870 kernel->range_pos =
871 kernel->values[0] = 1.0; /* a flat single-point no-op kernel! */
anthony602ab9b2010-01-05 08:06:50 +0000872 break;
873 }
874
875 return(kernel);
876}
anthonyc94cdb02010-01-06 08:15:29 +0000877
anthony602ab9b2010-01-05 08:06:50 +0000878/*
879%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
880% %
881% %
882% %
883% D e s t r o y K e r n e l %
884% %
885% %
886% %
887%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
888%
889% DestroyKernel() frees the memory used by a Convolution/Morphology kernel.
890%
891% The format of the DestroyKernel method is:
892%
cristy2be15382010-01-21 02:38:03 +0000893% KernelInfo *DestroyKernel(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +0000894%
895% A description of each parameter follows:
896%
897% o kernel: the Morphology/Convolution kernel to be destroyed
898%
899*/
900
cristy2be15382010-01-21 02:38:03 +0000901MagickExport KernelInfo *DestroyKernel(KernelInfo *kernel)
anthony602ab9b2010-01-05 08:06:50 +0000902{
cristy2be15382010-01-21 02:38:03 +0000903 assert(kernel != (KernelInfo *) NULL);
anthony602ab9b2010-01-05 08:06:50 +0000904 kernel->values=(double *)RelinquishMagickMemory(kernel->values);
cristy2be15382010-01-21 02:38:03 +0000905 kernel=(KernelInfo *) RelinquishMagickMemory(kernel);
anthony602ab9b2010-01-05 08:06:50 +0000906 return(kernel);
907}
anthonyc94cdb02010-01-06 08:15:29 +0000908
909/*
910%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
911% %
912% %
913% %
914% K e r n e l N o r m a l i z e %
915% %
916% %
917% %
918%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
919%
920% KernelNormalize() normalize the kernel so its convolution output will
921% be over a unit range.
922%
923% The format of the KernelNormalize method is:
924%
cristy2be15382010-01-21 02:38:03 +0000925% void KernelRotate (KernelInfo *kernel)
anthonyc94cdb02010-01-06 08:15:29 +0000926%
927% A description of each parameter follows:
928%
929% o kernel: the Morphology/Convolution kernel
930%
931*/
cristy2be15382010-01-21 02:38:03 +0000932MagickExport void KernelNormalize(KernelInfo *kernel)
anthonyc94cdb02010-01-06 08:15:29 +0000933{
934 register unsigned long
935 i;
anthony602ab9b2010-01-05 08:06:50 +0000936
anthony29188a82010-01-22 10:12:34 +0000937 for (i=0; i < kernel->width*kernel->height; i++)
anthonyc94cdb02010-01-06 08:15:29 +0000938 kernel->values[i] /= (kernel->range_pos - kernel->range_neg);
939
940 kernel->range_pos /= (kernel->range_pos - kernel->range_neg);
941 kernel->range_neg /= (kernel->range_pos - kernel->range_neg);
942 kernel->value_max /= (kernel->range_pos - kernel->range_neg);
943 kernel->value_min /= (kernel->range_pos - kernel->range_neg);
944
945 return;
946}
947
948/*
949%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
950% %
951% %
952% %
953% K e r n e l P r i n t %
954% %
955% %
956% %
957%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
958%
959% KernelPrint() Print out the kernel details to standard error
960%
961% The format of the KernelNormalize method is:
962%
cristy2be15382010-01-21 02:38:03 +0000963% void KernelPrint (KernelInfo *kernel)
anthonyc94cdb02010-01-06 08:15:29 +0000964%
965% A description of each parameter follows:
966%
967% o kernel: the Morphology/Convolution kernel
968%
969*/
cristy2be15382010-01-21 02:38:03 +0000970MagickExport void KernelPrint(KernelInfo *kernel)
anthonyc94cdb02010-01-06 08:15:29 +0000971{
972 unsigned long
973 i, u, v;
974
975 fprintf(stderr,
976 "Kernel \"%s\" of size %lux%lu%+ld%+ld with value from %lg to %lg\n",
anthony29188a82010-01-22 10:12:34 +0000977 MagickOptionToMnemonic(MagickKernelOptions, kernel->type),
anthonyc94cdb02010-01-06 08:15:29 +0000978 kernel->width, kernel->height,
979 kernel->offset_x, kernel->offset_y,
980 kernel->value_min, kernel->value_max);
anthony29188a82010-01-22 10:12:34 +0000981 fprintf(stderr, "Forming convolution output range from %lg to %lg%s\n",
anthonyc94cdb02010-01-06 08:15:29 +0000982 kernel->range_neg, kernel->range_pos,
983 kernel->normalized == MagickTrue ? " (normalized)" : "" );
984 for (i=v=0; v < kernel->height; v++) {
anthony29188a82010-01-22 10:12:34 +0000985 fprintf(stderr,"%2ld:",v);
anthonyc94cdb02010-01-06 08:15:29 +0000986 for (u=0; u < kernel->width; u++, i++)
anthony29188a82010-01-22 10:12:34 +0000987 if ( IsNan(kernel->values[i]) )
988 fprintf(stderr," %*s", GetMagickPrecision()+2, "nan");
989 else
990 fprintf(stderr," %.*lg", GetMagickPrecision(), kernel->values[i]);
anthonyc94cdb02010-01-06 08:15:29 +0000991 fprintf(stderr,"\n");
992 }
993}
994
995/*
996%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
997% %
998% %
999% %
1000% K e r n e l R o t a t e %
1001% %
1002% %
1003% %
1004%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1005%
1006% KernelRotate() rotates the kernel by the angle given. Currently it is
1007% restricted to 90 degree angles, but this may be improved in the future.
1008%
1009% The format of the KernelRotate method is:
1010%
cristy2be15382010-01-21 02:38:03 +00001011% void KernelRotate (KernelInfo *kernel, double angle)
anthonyc94cdb02010-01-06 08:15:29 +00001012%
1013% A description of each parameter follows:
1014%
1015% o kernel: the Morphology/Convolution kernel
1016%
1017% o angle: angle to rotate in degrees
1018%
1019*/
cristy2be15382010-01-21 02:38:03 +00001020MagickExport void KernelRotate(KernelInfo *kernel, double angle)
anthonyc94cdb02010-01-06 08:15:29 +00001021{
1022 /* WARNING: Currently assumes the kernel (rightly) is horizontally symetrical
1023 **
1024 ** TODO: expand beyond simple 90 degree rotates, flips and flops
1025 */
1026
1027 /* Modulus the angle */
1028 angle = fmod(angle, 360.0);
1029 if ( angle < 0 )
1030 angle += 360.0;
1031
1032 if ( 315.0 < angle || angle <= 45.0 )
1033 return; /* no change! - At least at this time */
1034
1035 switch (kernel->type) {
anthony29188a82010-01-22 10:12:34 +00001036 /* These built-in kernels are cylindrical kernels, rotating is useless */
anthonyc94cdb02010-01-06 08:15:29 +00001037 case GaussianKernel:
1038 case LaplacianKernel:
1039 case LOGKernel:
1040 case DOGKernel:
1041 case DiskKernel:
1042 case ChebyshevKernel:
1043 case ManhattenKernel:
1044 case EuclideanKernel:
1045 return;
1046
1047 /* These may be rotatable at non-90 angles in the future */
anthony29188a82010-01-22 10:12:34 +00001048 /* but simply rotating them in multiples of 90 degrees is useless */
anthonyc94cdb02010-01-06 08:15:29 +00001049 case SquareKernel:
1050 case DiamondKernel:
1051 case PlusKernel:
1052 return;
1053
anthony29188a82010-01-22 10:12:34 +00001054 /* These only allows a +/-90 degree rotation (by transpose) */
1055 /* A 180 degree rotation is useless */
anthonyc94cdb02010-01-06 08:15:29 +00001056 case BlurKernel:
1057 case RectangleKernel:
1058 if ( 135.0 < angle && angle <= 225.0 )
1059 return;
1060 if ( 225.0 < angle && angle <= 315.0 )
1061 angle -= 180;
1062 break;
1063
1064 /* these are freely rotatable in 90 degree units */
1065 case CometKernel:
1066 case UndefinedKernel:
1067 case UserDefinedKernel:
1068 break;
1069 }
anthony29188a82010-01-22 10:12:34 +00001070 if ( 135.0 < angle && angle <= 225.0 )
anthonyc94cdb02010-01-06 08:15:29 +00001071 {
anthony29188a82010-01-22 10:12:34 +00001072 /* for a 180 degree rotation - also know as a reflection */
1073 /* This is actually a very very common operation! */
1074 /* basically this is a reverse of the kernel data! */
anthonyc94cdb02010-01-06 08:15:29 +00001075 unsigned long
anthony29188a82010-01-22 10:12:34 +00001076 i,j;
anthonyc94cdb02010-01-06 08:15:29 +00001077 register double
1078 *k,t;
anthony29188a82010-01-22 10:12:34 +00001079
1080 k=kernel->values;
1081 for ( i=0, j=kernel->width*kernel->height-1; i<j; i++, j--)
1082 t=k[i], k[i]=k[j], k[j]=t;
1083
anthonyc94cdb02010-01-06 08:15:29 +00001084 kernel->offset_x = kernel->width - kernel->offset_x - 1;
anthony29188a82010-01-22 10:12:34 +00001085 kernel->offset_y = kernel->width - kernel->offset_y - 1;
anthonyc94cdb02010-01-06 08:15:29 +00001086 angle = fmod(angle+180.0, 360.0);
1087 }
1088 if ( 45.0 < angle && angle <= 135.0 )
anthony29188a82010-01-22 10:12:34 +00001089 { /* Do a transpose and a flop, of the image, which results in a 90
1090 * degree rotation using two mirror operations.
1091 *
1092 * WARNING: this assumes the original image was a 1 dimentional image
1093 * but currently that is the only built-ins it is applied to.
1094 */
anthonyc94cdb02010-01-06 08:15:29 +00001095 unsigned long
1096 t;
1097 t = kernel->width;
1098 kernel->width = kernel->height;
1099 kernel->height = t;
1100 t = kernel->offset_x;
1101 kernel->offset_x = kernel->offset_y;
1102 kernel->offset_y = t;
1103 angle = fmod(450.0 - angle, 360.0);
1104 }
anthony29188a82010-01-22 10:12:34 +00001105 /* at this point angle should be between -45 (315) and +45 degrees */
1106
1107#if 0
1108 { /* Do a flop, this assumes kernel is horizontally symetrical.
1109 * Each kernel data row need to be reversed!
1110 * This is currently not used, but by be used in the future.
1111 */
1112 unsigned long
1113 y;
1114 register unsigned long
1115 x,r;
1116 register double
1117 *k,t;
1118
1119 for ( y=0, k=kernel->values; y < kernel->height; y++, k+=kernel->width)
1120 for ( x=0, r=kernel->width-1; x<kernel->width/2; x++, r--)
1121 t=k[x], k[x]=k[r], k[r]=t;
1122
1123 kernel->offset_x = kernel->width - kernel->offset_x - 1;
1124 angle = fmod(angle+180.0, 360.0);
1125 }
1126#endif
anthonyc94cdb02010-01-06 08:15:29 +00001127 return;
1128}
anthony602ab9b2010-01-05 08:06:50 +00001129
1130/*
1131%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1132% %
1133% %
1134% %
anthony29188a82010-01-22 10:12:34 +00001135% 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 +00001136% %
1137% %
1138% %
1139%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1140%
anthony29188a82010-01-22 10:12:34 +00001141% MorphologyImageChannel() applies a user supplied kernel to the image
1142% according to the given mophology method.
anthony602ab9b2010-01-05 08:06:50 +00001143%
1144% The given kernel is assumed to have been pre-scaled appropriatally, usally
1145% by the kernel generator.
1146%
1147% The format of the MorphologyImage method is:
1148%
anthony29188a82010-01-22 10:12:34 +00001149% Image *MorphologyImage(const Image *image, MorphologyMethod method,
1150% const long iterations, KernelInfo *kernel, ExceptionInfo *exception)
1151% Image *MorphologyImageChannel(const Image *image, const ChannelType
1152% channel, MorphologyMethod method, const long iterations, KernelInfo
1153% *kernel, ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001154%
1155% A description of each parameter follows:
1156%
1157% o image: the image.
1158%
1159% o method: the morphology method to be applied.
1160%
1161% o iterations: apply the operation this many times (or no change).
1162% A value of -1 means loop until no change found.
1163% How this is applied may depend on the morphology method.
1164% Typically this is a value of 1.
1165%
1166% o channel: the channel type.
1167%
1168% o kernel: An array of double representing the morphology kernel.
anthony29188a82010-01-22 10:12:34 +00001169% Warning: kernel may be normalized for the Convolve method.
anthony602ab9b2010-01-05 08:06:50 +00001170%
1171% o exception: return any errors or warnings in this structure.
1172%
1173%
1174% TODO: bias and auto-scale handling of the kernel for convolution
1175% The given kernel is assumed to have been pre-scaled appropriatally, usally
1176% by the kernel generator.
1177%
1178*/
1179
anthony602ab9b2010-01-05 08:06:50 +00001180/* incr change if the value being assigned changed */
1181#define Assign(channel,value) \
cristyce70c172010-01-07 17:15:30 +00001182 { q->channel = ClampToQuantum(value); \
anthony602ab9b2010-01-05 08:06:50 +00001183 if ( p[r].channel != q->channel ) changed++; \
1184 }
1185#define AssignIndex(value) \
cristyce70c172010-01-07 17:15:30 +00001186 { q_indexes[x] = ClampToQuantum(value); \
anthony602ab9b2010-01-05 08:06:50 +00001187 if ( p_indexes[r] != q_indexes[x] ) changed++; \
1188 }
1189
1190/* Internal function
1191 * Apply the Morphology method with the given Kernel
1192 * And return the number of values changed.
1193 */
1194static unsigned long MorphologyApply(const Image *image, Image
1195 *result_image, const MorphologyMethod method, const ChannelType channel,
cristy2be15382010-01-21 02:38:03 +00001196 const KernelInfo *kernel, ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001197{
cristy2be15382010-01-21 02:38:03 +00001198#define MorphologyTag "Morphology/Image"
anthony602ab9b2010-01-05 08:06:50 +00001199
1200 long
anthony29188a82010-01-22 10:12:34 +00001201 progress;
anthony602ab9b2010-01-05 08:06:50 +00001202
1203 unsigned long
anthony29188a82010-01-22 10:12:34 +00001204 y, offx, offy,
anthony602ab9b2010-01-05 08:06:50 +00001205 changed;
1206
1207 MagickBooleanType
1208 status;
1209
1210 MagickPixelPacket
1211 bias;
1212
1213 CacheView
1214 *p_view,
1215 *q_view;
1216
1217 /*
1218 Apply Morphology to Image.
1219 */
1220 status=MagickTrue;
1221 changed=0;
1222 progress=0;
1223
1224 GetMagickPixelPacket(image,&bias);
1225 SetMagickPixelPacketBias(image,&bias);
1226
1227 p_view=AcquireCacheView(image);
1228 q_view=AcquireCacheView(result_image);
anthony29188a82010-01-22 10:12:34 +00001229
1230 /* some methods (including convolve) needs to apply a reflected kernel
1231 * the offset for getting the kernel view needs to be adjusted for this
1232 * situation.
1233 */
1234 offx = kernel->offset_x;
1235 offy = kernel->offset_y;
1236 switch(method) {
1237 case ErodeMorphology:
1238 case ErodeIntensityMorphology:
1239 /* kernel is not reflected */
1240 break;
1241 default:
1242 /* kernel needs to be reflected */
1243 offx = kernel->width-offx-1;
1244 offy = kernel->height-offy-1;
1245 break;
1246 }
1247
anthony602ab9b2010-01-05 08:06:50 +00001248#if defined(MAGICKCORE_OPENMP_SUPPORT)
1249 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1250#endif
anthony29188a82010-01-22 10:12:34 +00001251 for (y=0; y < image->rows; y++)
anthony602ab9b2010-01-05 08:06:50 +00001252 {
1253 MagickBooleanType
1254 sync;
1255
1256 register const PixelPacket
1257 *restrict p;
1258
1259 register const IndexPacket
1260 *restrict p_indexes;
1261
1262 register PixelPacket
1263 *restrict q;
1264
1265 register IndexPacket
1266 *restrict q_indexes;
1267
anthony29188a82010-01-22 10:12:34 +00001268 register unsigned long
anthony602ab9b2010-01-05 08:06:50 +00001269 x;
1270
anthony29188a82010-01-22 10:12:34 +00001271 unsigned long
anthony602ab9b2010-01-05 08:06:50 +00001272 r;
1273
1274 if (status == MagickFalse)
1275 continue;
anthony29188a82010-01-22 10:12:34 +00001276 p=GetCacheViewVirtualPixels(p_view, -offx, y-offy,
1277 image->columns+kernel->width, kernel->height, exception);
anthony602ab9b2010-01-05 08:06:50 +00001278 q=GetCacheViewAuthenticPixels(q_view,0,y,result_image->columns,1,
1279 exception);
1280 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1281 {
1282 status=MagickFalse;
1283 continue;
1284 }
1285 p_indexes=GetCacheViewVirtualIndexQueue(p_view);
1286 q_indexes=GetCacheViewAuthenticIndexQueue(q_view);
anthony29188a82010-01-22 10:12:34 +00001287 r = (image->columns+kernel->width)*offy+offx; /* constant */
1288
1289 for (x=0; x < image->columns; x++)
anthony602ab9b2010-01-05 08:06:50 +00001290 {
anthony29188a82010-01-22 10:12:34 +00001291 unsigned long
anthony602ab9b2010-01-05 08:06:50 +00001292 v;
1293
anthony29188a82010-01-22 10:12:34 +00001294 register unsigned long
anthony602ab9b2010-01-05 08:06:50 +00001295 u;
1296
1297 register const double
1298 *restrict k;
1299
1300 register const PixelPacket
1301 *restrict k_pixels;
1302
1303 register const IndexPacket
1304 *restrict k_indexes;
1305
1306 MagickPixelPacket
1307 result;
1308
anthony29188a82010-01-22 10:12:34 +00001309 /* Copy input to ouput image for unused channels
1310 * This removes need for 'cloning' new images
1311 */
anthony602ab9b2010-01-05 08:06:50 +00001312 *q = p[r];
1313 if (image->colorspace == CMYKColorspace)
1314 q_indexes[x] = p_indexes[r];
1315
anthony29188a82010-01-22 10:12:34 +00001316 result.index=0; /* stop compiler warnings */
cristy5386a692010-01-22 15:37:59 +00001317 result.green=0; /* stop compiler warnings */
1318 result.blue=0; /* stop compiler warnings */
1319 result.opacity=0; /* stop compiler warnings */
anthony602ab9b2010-01-05 08:06:50 +00001320 switch (method) {
1321 case ConvolveMorphology:
1322 result=bias;
1323 break; /* default result is the convolution bias */
anthony29188a82010-01-22 10:12:34 +00001324#if 0
1325removed as it caused problems with change
1326Really we want to set each to the first value found
1327but not up date change if that is the first value!
1328 case DialateMorphology:
1329 result.red =
1330 result.green =
1331 result.blue =
1332 result.opacity =
1333 result.index = -MagickHuge;
1334 break;
1335 case ErodeMorphology:
1336 result.red =
1337 result.green =
1338 result.blue =
1339 result.opacity =
1340 result.index = +MagickHuge;
1341 break;
1342#endif
anthony602ab9b2010-01-05 08:06:50 +00001343 case DialateIntensityMorphology:
1344 case ErodeIntensityMorphology:
anthony29188a82010-01-22 10:12:34 +00001345 result.red = 0.0; /* was initial pixel found ? */
anthony602ab9b2010-01-05 08:06:50 +00001346 break;
1347 default:
anthony29188a82010-01-22 10:12:34 +00001348 /* Otherwise just start with the original pixel value */
anthony602ab9b2010-01-05 08:06:50 +00001349 result.red = p[r].red;
1350 result.green = p[r].green;
1351 result.blue = p[r].blue;
1352 result.opacity = QuantumRange - p[r].opacity;
1353 if ( image->colorspace == CMYKColorspace)
1354 result.index = p_indexes[r];
1355 break;
1356 }
1357
1358 switch ( method ) {
1359 case ConvolveMorphology:
anthony29188a82010-01-22 10:12:34 +00001360 /* Weighted Average of pixels
1361 *
1362 * NOTE for correct working of this operation for asymetrical
1363 * kernels, the kernel needs to be applied in its reflected form.
1364 * That is its values needs to be reversed.
1365 */
anthony602ab9b2010-01-05 08:06:50 +00001366 if (((channel & OpacityChannel) == 0) ||
1367 (image->matte == MagickFalse))
1368 {
anthony29188a82010-01-22 10:12:34 +00001369 /* Convolution (no transparency) */
1370 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001371 k_pixels = p;
1372 k_indexes = p_indexes;
anthony29188a82010-01-22 10:12:34 +00001373 for (v=0; v < kernel->height; v++) {
1374 for (u=0; u < kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001375 if ( IsNan(*k) ) continue;
1376 result.red += (*k)*k_pixels[u].red;
1377 result.green += (*k)*k_pixels[u].green;
1378 result.blue += (*k)*k_pixels[u].blue;
1379 /* result.opacity += no involvment */
1380 if ( image->colorspace == CMYKColorspace)
1381 result.index += (*k)*k_indexes[u];
1382 }
1383 k_pixels += image->columns+kernel->width;
1384 k_indexes += image->columns+kernel->width;
1385 }
1386 if ((channel & RedChannel) != 0)
1387 Assign(red,result.red);
1388 if ((channel & GreenChannel) != 0)
1389 Assign(green,result.green);
1390 if ((channel & BlueChannel) != 0)
1391 Assign(blue,result.blue);
1392 /* no transparency involved */
1393 if ((channel & IndexChannel) != 0
1394 && image->colorspace == CMYKColorspace)
1395 AssignIndex(result.index);
1396 }
1397 else
1398 { /* Kernel & Alpha weighted Convolution */
1399 MagickRealType
1400 alpha, /* alpha value * kernel weighting */
1401 gamma; /* weighting divisor */
1402
1403 gamma=0.0;
anthony29188a82010-01-22 10:12:34 +00001404 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001405 k_pixels = p;
1406 k_indexes = p_indexes;
anthony29188a82010-01-22 10:12:34 +00001407 for (v=0; v < kernel->height; v++) {
1408 for (u=0; u < kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001409 if ( IsNan(*k) ) continue;
1410 alpha=(*k)*(QuantumScale*(QuantumRange-
1411 k_pixels[u].opacity));
1412 gamma += alpha;
1413 result.red += alpha*k_pixels[u].red;
1414 result.green += alpha*k_pixels[u].green;
1415 result.blue += alpha*k_pixels[u].blue;
1416 result.opacity += (*k)*k_pixels[u].opacity;
1417 if ( image->colorspace == CMYKColorspace)
1418 result.index += alpha*k_indexes[u];
1419 }
1420 k_pixels += image->columns+kernel->width;
1421 k_indexes += image->columns+kernel->width;
1422 }
1423 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1424 if ((channel & RedChannel) != 0)
1425 Assign(red,gamma*result.red);
1426 if ((channel & GreenChannel) != 0)
1427 Assign(green,gamma*result.green);
1428 if ((channel & BlueChannel) != 0)
1429 Assign(blue,gamma*result.blue);
1430 if ((channel & OpacityChannel) != 0
1431 && image->matte == MagickTrue )
1432 Assign(opacity,result.opacity);
1433 if ((channel & IndexChannel) != 0
1434 && image->colorspace == CMYKColorspace)
1435 AssignIndex(gamma*result.index);
1436 }
1437 break;
1438
1439 case DialateMorphology:
anthony29188a82010-01-22 10:12:34 +00001440 /* Maximize Value within kernel shape
1441 *
1442 * NOTE for correct working of this operation for asymetrical
1443 * kernels, the kernel needs to be applied in its reflected form.
1444 * That is its values needs to be reversed.
1445 *
1446 * NOTE: in normal Greyscale Morphology, the kernel value should
1447 * be added to the real value, this is currently not done, due to
1448 * the nature of the boolean kernels being used.
1449 *
1450 */
1451 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001452 k_pixels = p;
1453 k_indexes = p_indexes;
anthony29188a82010-01-22 10:12:34 +00001454 for (v=0; v < kernel->height; v++) {
1455 for (u=0; u < kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001456 if ( IsNan(*k) || (*k) < 0.5 ) continue;
1457 Maximize(result.red, k_pixels[u].red);
1458 Maximize(result.green, k_pixels[u].green);
1459 Maximize(result.blue, k_pixels[u].blue);
1460 Maximize(result.opacity, QuantumRange-k_pixels[u].opacity);
1461 if ( image->colorspace == CMYKColorspace)
1462 Maximize(result.index, k_indexes[u]);
1463 }
1464 k_pixels += image->columns+kernel->width;
1465 k_indexes += image->columns+kernel->width;
1466 }
1467 if ((channel & RedChannel) != 0)
1468 Assign(red,result.red);
1469 if ((channel & GreenChannel) != 0)
1470 Assign(green,result.green);
1471 if ((channel & BlueChannel) != 0)
1472 Assign(blue,result.blue);
1473 if ((channel & OpacityChannel) != 0
1474 && image->matte == MagickTrue )
1475 Assign(opacity,QuantumRange-result.opacity);
1476 if ((channel & IndexChannel) != 0
1477 && image->colorspace == CMYKColorspace)
1478 AssignIndex(result.index);
1479 break;
1480
1481 case ErodeMorphology:
anthony29188a82010-01-22 10:12:34 +00001482 /* Minimize Value within kernel shape
1483 *
1484 * NOTE that the kernel is not reflected for this operation!
1485 *
1486 * NOTE: in normal Greyscale Morphology, the kernel value should
1487 * be added to the real value, this is currently not done, due to
1488 * the nature of the boolean kernels being used.
1489 */
anthony602ab9b2010-01-05 08:06:50 +00001490 k = kernel->values;
1491 k_pixels = p;
1492 k_indexes = p_indexes;
anthony29188a82010-01-22 10:12:34 +00001493 for (v=0; v < kernel->height; v++) {
1494 for (u=0; u < kernel->width; u++, k++) {
anthony602ab9b2010-01-05 08:06:50 +00001495 if ( IsNan(*k) || (*k) < 0.5 ) continue;
1496 Minimize(result.red, k_pixels[u].red);
1497 Minimize(result.green, k_pixels[u].green);
1498 Minimize(result.blue, k_pixels[u].blue);
1499 Minimize(result.opacity, QuantumRange-k_pixels[u].opacity);
1500 if ( image->colorspace == CMYKColorspace)
1501 Minimize(result.index, k_indexes[u]);
1502 }
1503 k_pixels += image->columns+kernel->width;
1504 k_indexes += image->columns+kernel->width;
1505 }
1506 if ((channel & RedChannel) != 0)
1507 Assign(red,result.red);
1508 if ((channel & GreenChannel) != 0)
1509 Assign(green,result.green);
1510 if ((channel & BlueChannel) != 0)
1511 Assign(blue,result.blue);
1512 if ((channel & OpacityChannel) != 0
1513 && image->matte == MagickTrue )
1514 Assign(opacity,QuantumRange-result.opacity);
1515 if ((channel & IndexChannel) != 0
1516 && image->colorspace == CMYKColorspace)
1517 AssignIndex(result.index);
1518 break;
1519
1520 case DialateIntensityMorphology:
anthony29188a82010-01-22 10:12:34 +00001521 /* Select pixel with maximum intensity within kernel shape
1522 *
1523 * WARNING: the intensity test fails for CMYK and does not
1524 * take into account the moderating effect of teh alpha channel
1525 * on the intensity.
1526 *
1527 * NOTE for correct working of this operation for asymetrical
1528 * kernels, the kernel needs to be applied in its reflected form.
1529 * That is its values needs to be reversed.
1530 */
1531 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001532 k_pixels = p;
1533 k_indexes = p_indexes;
anthony29188a82010-01-22 10:12:34 +00001534 for (v=0; v < kernel->height; v++) {
1535 for (u=0; u < kernel->width; u++, k--) {
1536 if ( IsNan(*k) || (*k) < 0.5 ) continue; /* boolean kernel */
1537 if ( result.red == 0.0 ||
1538 PixelIntensity(&(k_pixels[u])) > PixelIntensity(q) ) {
1539 /* copy the whole pixel - no channel selection */
1540 *q = k_pixels[u];
1541 if ( result.red > 0.0 ) changed++;
1542 result.red = 1.0;
1543 }
anthony602ab9b2010-01-05 08:06:50 +00001544 }
1545 k_pixels += image->columns+kernel->width;
1546 k_indexes += image->columns+kernel->width;
1547 }
anthony602ab9b2010-01-05 08:06:50 +00001548 break;
1549
1550 case ErodeIntensityMorphology:
anthony29188a82010-01-22 10:12:34 +00001551 /* Select pixel with mimimum intensity within kernel shape
1552 *
1553 * WARNING: the intensity test fails for CMYK and does not
1554 * take into account the moderating effect of teh alpha channel
1555 * on the intensity.
1556 *
1557 * NOTE that the kernel is not reflected for this operation!
1558 */
anthony602ab9b2010-01-05 08:06:50 +00001559 k = kernel->values;
1560 k_pixels = p;
1561 k_indexes = p_indexes;
anthony29188a82010-01-22 10:12:34 +00001562 for (v=0; v < kernel->height; v++) {
1563 for (u=0; u < kernel->width; u++, k++) {
anthony602ab9b2010-01-05 08:06:50 +00001564 if ( IsNan(*k) || (*k) < 0.5 ) continue;
anthony29188a82010-01-22 10:12:34 +00001565 if ( result.red == 0.0 ||
1566 PixelIntensity(&(k_pixels[u])) < PixelIntensity(q) ) {
1567 /* copy the whole pixel - no channel selection */
1568 *q = k_pixels[u];
1569 if ( result.red > 0.0 ) changed++;
1570 result.red = 1.0;
1571 }
anthony602ab9b2010-01-05 08:06:50 +00001572 }
1573 k_pixels += image->columns+kernel->width;
1574 k_indexes += image->columns+kernel->width;
1575 }
anthony602ab9b2010-01-05 08:06:50 +00001576 break;
1577
1578 case DistanceMorphology:
anthony29188a82010-01-22 10:12:34 +00001579 /* Add kernel value and select the minimum value found.
1580 * The result is a iterative distance from edge function.
1581 *
1582 * All Distance Kernels are symetrical, but that may not always
1583 * be the case. For example how about a distance from left edges?
1584 * To make it work correctly for asymetrical kernels the reflected
1585 * kernel needs to be applied.
1586 */
anthony602ab9b2010-01-05 08:06:50 +00001587#if 0
1588 /* No need to do distance morphology if all values are zero */
1589 /* Unfortunatally I have not been able to get this right! */
1590 if ( ((channel & RedChannel) == 0 && p[r].red == 0)
1591 || ((channel & GreenChannel) == 0 && p[r].green == 0)
1592 || ((channel & BlueChannel) == 0 && p[r].blue == 0)
1593 || ((channel & OpacityChannel) == 0 && p[r].opacity == 0)
1594 || (( (channel & IndexChannel) == 0
1595 || image->colorspace != CMYKColorspace
1596 ) && p_indexes[x] ==0 )
1597 )
1598 break;
1599#endif
anthony29188a82010-01-22 10:12:34 +00001600 k = &kernel->values[ kernel->width*kernel->height-1 ];
anthony602ab9b2010-01-05 08:06:50 +00001601 k_pixels = p;
1602 k_indexes = p_indexes;
anthony29188a82010-01-22 10:12:34 +00001603 for (v=0; v < kernel->height; v++) {
1604 for (u=0; u < kernel->width; u++, k--) {
anthony602ab9b2010-01-05 08:06:50 +00001605 if ( IsNan(*k) ) continue;
1606 Minimize(result.red, (*k)+k_pixels[u].red);
1607 Minimize(result.green, (*k)+k_pixels[u].green);
1608 Minimize(result.blue, (*k)+k_pixels[u].blue);
1609 Minimize(result.opacity, (*k)+QuantumRange-k_pixels[u].opacity);
1610 if ( image->colorspace == CMYKColorspace)
1611 Minimize(result.index, (*k)+k_indexes[u]);
1612 }
1613 k_pixels += image->columns+kernel->width;
1614 k_indexes += image->columns+kernel->width;
1615 }
1616#if 1
1617 if ((channel & RedChannel) != 0)
1618 Assign(red,result.red);
1619 if ((channel & GreenChannel) != 0)
1620 Assign(green,result.green);
1621 if ((channel & BlueChannel) != 0)
1622 Assign(blue,result.blue);
1623 if ((channel & OpacityChannel) != 0
1624 && image->matte == MagickTrue )
1625 Assign(opacity,QuantumRange-result.opacity);
1626 if ((channel & IndexChannel) != 0
1627 && image->colorspace == CMYKColorspace)
1628 AssignIndex(result.index);
1629#else
1630 /* By returning the number of 'maximum' values still to process
1631 ** we can get the Distance iteration to finish faster.
1632 ** BUT this may cause an infinite loop on very large shapes,
anthony29188a82010-01-22 10:12:34 +00001633 ** which may have a distance gradient that reachs max value!
anthony602ab9b2010-01-05 08:06:50 +00001634 */
1635 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001636 { q->red = ClampToQuantum(result.red);
anthony602ab9b2010-01-05 08:06:50 +00001637 if ( q->red == QuantumRange ) changed++; /* more to do */
1638 }
1639 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001640 { q->green = ClampToQuantum(result.green);
anthony602ab9b2010-01-05 08:06:50 +00001641 if ( q->green == QuantumRange ) changed++; /* more to do */
1642 }
1643 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001644 { q->blue = ClampToQuantum(result.blue);
anthony602ab9b2010-01-05 08:06:50 +00001645 if ( q->blue == QuantumRange ) changed++; /* more to do */
1646 }
1647 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001648 { q->opacity = ClampToQuantum(QuantumRange-result.opacity);
anthony602ab9b2010-01-05 08:06:50 +00001649 if ( q->opacity == 0 ) changed++; /* more to do */
1650 }
1651 if (((channel & IndexChannel) != 0) &&
1652 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00001653 { q_indexes[x] = ClampToQuantum(result.index);
anthony602ab9b2010-01-05 08:06:50 +00001654 if ( q_indexes[x] == QuantumRange ) changed++;
1655 }
1656#endif
1657 break;
1658
1659 case UndefinedMorphology:
1660 default:
1661 break; /* Do nothing */
1662 }
1663 p++;
1664 q++;
1665 }
1666 sync=SyncCacheViewAuthenticPixels(q_view,exception);
1667 if (sync == MagickFalse)
1668 status=MagickFalse;
1669 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1670 {
1671 MagickBooleanType
1672 proceed;
1673
1674#if defined(MAGICKCORE_OPENMP_SUPPORT)
1675 #pragma omp critical (MagickCore_MorphologyImage)
1676#endif
1677 proceed=SetImageProgress(image,MorphologyTag,progress++,image->rows);
1678 if (proceed == MagickFalse)
1679 status=MagickFalse;
1680 }
1681 }
1682 result_image->type=image->type;
1683 q_view=DestroyCacheView(q_view);
1684 p_view=DestroyCacheView(p_view);
1685 return(status ? changed : 0);
1686}
1687
cristy2be15382010-01-21 02:38:03 +00001688MagickExport Image *MorphologyImage(const Image *image,MorphologyMethod method,
1689 const long iterations,KernelInfo *kernel, ExceptionInfo *exception)
1690{
1691 Image
1692 *morphology_image;
1693
1694 morphology_image=MorphologyImageChannel(image,DefaultChannels,method,
1695 iterations,kernel,exception);
1696 return(morphology_image);
1697}
1698
1699MagickExport Image *MorphologyImageChannel(const Image *image,
cristy672da672010-01-10 15:43:07 +00001700 const ChannelType channel, MorphologyMethod method, const long iterations,
cristy2be15382010-01-21 02:38:03 +00001701 KernelInfo *kernel, ExceptionInfo *exception)
anthony602ab9b2010-01-05 08:06:50 +00001702{
1703 unsigned long
1704 count,
1705 limit,
1706 changed;
1707
1708 Image
1709 *new_image,
1710 *old_image;
1711
1712 assert(image != (Image *) NULL);
1713 assert(image->signature == MagickSignature);
1714 assert(exception != (ExceptionInfo *) NULL);
1715 assert(exception->signature == MagickSignature);
1716
1717 if ( GetImageArtifact(image,"showkernel") != (const char *) NULL)
anthonyc94cdb02010-01-06 08:15:29 +00001718 KernelPrint(kernel);
anthony602ab9b2010-01-05 08:06:50 +00001719
1720 if ( iterations == 0 )
1721 return((Image *)NULL); /* null operation - nothing to do! */
1722
1723 /* kernel must be valid at this point
1724 * (except maybe for posible future morphology methods like "Prune"
1725 */
cristy2be15382010-01-21 02:38:03 +00001726 assert(kernel != (KernelInfo *)NULL);
anthony602ab9b2010-01-05 08:06:50 +00001727
1728 count = 0;
anthony29188a82010-01-22 10:12:34 +00001729 changed = 1;
anthony602ab9b2010-01-05 08:06:50 +00001730 limit = iterations;
1731 if ( iterations < 0 )
1732 limit = image->columns > image->rows ? image->columns : image->rows;
1733
1734 /* Special morphology cases */
1735 switch( method ) {
1736 case CloseMorphology:
anthony29188a82010-01-22 10:12:34 +00001737 new_image = MorphologyImageChannel(image, channel, DialateMorphology,
1738 iterations, kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001739 if (new_image == (Image *) NULL)
1740 return((Image *) NULL);
1741 method = ErodeMorphology;
1742 break;
1743 case OpenMorphology:
anthony29188a82010-01-22 10:12:34 +00001744 new_image = MorphologyImageChannel(image, channel, ErodeMorphology,
1745 iterations, kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001746 if (new_image == (Image *) NULL)
1747 return((Image *) NULL);
1748 method = DialateMorphology;
1749 break;
1750 case CloseIntensityMorphology:
anthony29188a82010-01-22 10:12:34 +00001751 new_image = MorphologyImageChannel(image, channel, DialateIntensityMorphology,
1752 iterations, kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001753 if (new_image == (Image *) NULL)
1754 return((Image *) NULL);
1755 method = ErodeIntensityMorphology;
1756 break;
1757 case OpenIntensityMorphology:
anthony29188a82010-01-22 10:12:34 +00001758 new_image = MorphologyImageChannel(image, channel, ErodeIntensityMorphology,
1759 iterations, kernel, exception);
anthony602ab9b2010-01-05 08:06:50 +00001760 if (new_image == (Image *) NULL)
1761 return((Image *) NULL);
1762 method = DialateIntensityMorphology;
1763 break;
1764
anthonyc94cdb02010-01-06 08:15:29 +00001765 case ConvolveMorphology:
1766 KernelNormalize(kernel);
1767 /* FALL-THRU */
anthony602ab9b2010-01-05 08:06:50 +00001768 default:
anthonyc94cdb02010-01-06 08:15:29 +00001769 /* Do a morphology just once at this point!
anthony602ab9b2010-01-05 08:06:50 +00001770 This ensures a new_image has been generated, but allows us
anthonyc94cdb02010-01-06 08:15:29 +00001771 to skip the creation of 'old_image' if it isn't needed.
anthony602ab9b2010-01-05 08:06:50 +00001772 */
1773 new_image=CloneImage(image,0,0,MagickTrue,exception);
1774 if (new_image == (Image *) NULL)
1775 return((Image *) NULL);
1776 if (SetImageStorageClass(new_image,DirectClass) == MagickFalse)
1777 {
1778 InheritException(exception,&new_image->exception);
1779 new_image=DestroyImage(new_image);
1780 return((Image *) NULL);
1781 }
1782 changed = MorphologyApply(image,new_image,method,channel,kernel,
1783 exception);
1784 count++;
1785 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
1786 fprintf(stderr, "Morphology %s:%lu => Changed %lu\n",
1787 MagickOptionToMnemonic(MagickMorphologyOptions, method),
1788 count, changed);
1789 }
1790
1791 /* Repeat the interative morphology until count or no change */
1792 if ( count < limit && changed > 0 ) {
1793 old_image = CloneImage(new_image,0,0,MagickTrue,exception);
1794 if (old_image == (Image *) NULL)
1795 return(DestroyImage(new_image));
1796 if (SetImageStorageClass(old_image,DirectClass) == MagickFalse)
1797 {
1798 InheritException(exception,&old_image->exception);
1799 old_image=DestroyImage(old_image);
1800 return(DestroyImage(new_image));
1801 }
1802 while( count < limit && changed != 0 )
1803 {
1804 Image *tmp = old_image;
1805 old_image = new_image;
1806 new_image = tmp;
1807 changed = MorphologyApply(old_image,new_image,method,channel,kernel,
1808 exception);
1809 count++;
1810 if ( GetImageArtifact(image,"verbose") != (const char *) NULL )
1811 fprintf(stderr, "Morphology %s:%lu => Changed %lu\n",
1812 MagickOptionToMnemonic(MagickMorphologyOptions, method),
1813 count, changed);
1814 }
1815 DestroyImage(old_image);
1816 }
1817
1818 return(new_image);
1819}
1820