blob: 82b43c8cb1d5605ed0a340ff5f96b749988d07f8 [file] [log] [blame]
cristy3ed852e2009-09-05 21:47:34 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% EEEEE FFFFF FFFFF EEEEE CCCC TTTTT %
7% E F F E C T %
8% EEE FFF FFF EEE C T %
9% E F F E C T %
10% EEEEE F F EEEEE CCCC T %
11% %
12% %
13% MagickCore Image Effects Methods %
14% %
15% Software Design %
16% John Cristy %
17% October 1996 %
18% %
19% %
cristy7e41fe82010-12-04 23:12:08 +000020% Copyright 1999-2011 ImageMagick Studio LLC, a non-profit organization %
cristy3ed852e2009-09-05 21:47:34 +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%
36%
37%
38*/
39
40/*
41 Include declarations.
42*/
cristy4c08aed2011-07-01 19:47:50 +000043#include "MagickCore/studio.h"
44#include "MagickCore/accelerate.h"
45#include "MagickCore/blob.h"
46#include "MagickCore/cache-view.h"
47#include "MagickCore/color.h"
48#include "MagickCore/color-private.h"
49#include "MagickCore/colorspace.h"
50#include "MagickCore/constitute.h"
51#include "MagickCore/decorate.h"
52#include "MagickCore/draw.h"
53#include "MagickCore/enhance.h"
54#include "MagickCore/exception.h"
55#include "MagickCore/exception-private.h"
56#include "MagickCore/effect.h"
57#include "MagickCore/fx.h"
58#include "MagickCore/gem.h"
59#include "MagickCore/geometry.h"
60#include "MagickCore/image-private.h"
61#include "MagickCore/list.h"
62#include "MagickCore/log.h"
63#include "MagickCore/memory_.h"
64#include "MagickCore/monitor.h"
65#include "MagickCore/monitor-private.h"
66#include "MagickCore/montage.h"
67#include "MagickCore/morphology.h"
68#include "MagickCore/paint.h"
69#include "MagickCore/pixel-accessor.h"
70#include "MagickCore/property.h"
71#include "MagickCore/quantize.h"
72#include "MagickCore/quantum.h"
73#include "MagickCore/quantum-private.h"
74#include "MagickCore/random_.h"
75#include "MagickCore/random-private.h"
76#include "MagickCore/resample.h"
77#include "MagickCore/resample-private.h"
78#include "MagickCore/resize.h"
79#include "MagickCore/resource_.h"
80#include "MagickCore/segment.h"
81#include "MagickCore/shear.h"
82#include "MagickCore/signature-private.h"
83#include "MagickCore/string_.h"
84#include "MagickCore/thread-private.h"
85#include "MagickCore/transform.h"
86#include "MagickCore/threshold.h"
cristy3ed852e2009-09-05 21:47:34 +000087
88/*
89%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
90% %
91% %
92% %
93% A d a p t i v e B l u r I m a g e %
94% %
95% %
96% %
97%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
98%
99% AdaptiveBlurImage() adaptively blurs the image by blurring less
100% intensely near image edges and more intensely far from edges. We blur the
101% image with a Gaussian operator of the given radius and standard deviation
102% (sigma). For reasonable results, radius should be larger than sigma. Use a
103% radius of 0 and AdaptiveBlurImage() selects a suitable radius for you.
104%
105% The format of the AdaptiveBlurImage method is:
106%
107% Image *AdaptiveBlurImage(const Image *image,const double radius,
108% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000109%
110% A description of each parameter follows:
111%
112% o image: the image.
113%
cristy3ed852e2009-09-05 21:47:34 +0000114% o radius: the radius of the Gaussian, in pixels, not counting the center
115% pixel.
116%
117% o sigma: the standard deviation of the Laplacian, in pixels.
118%
119% o exception: return any errors or warnings in this structure.
120%
121*/
122
cristyf89cb1d2011-07-07 01:24:37 +0000123MagickExport MagickBooleanType AdaptiveLevelImage(Image *image,
124 const char *levels)
125{
126 double
127 black_point,
128 gamma,
129 white_point;
130
131 GeometryInfo
132 geometry_info;
133
134 MagickBooleanType
135 status;
136
137 MagickStatusType
138 flags;
139
140 /*
141 Parse levels.
142 */
143 if (levels == (char *) NULL)
144 return(MagickFalse);
145 flags=ParseGeometry(levels,&geometry_info);
146 black_point=geometry_info.rho;
147 white_point=(double) QuantumRange;
148 if ((flags & SigmaValue) != 0)
149 white_point=geometry_info.sigma;
150 gamma=1.0;
151 if ((flags & XiValue) != 0)
152 gamma=geometry_info.xi;
153 if ((flags & PercentValue) != 0)
154 {
155 black_point*=(double) image->columns*image->rows/100.0;
156 white_point*=(double) image->columns*image->rows/100.0;
157 }
158 if ((flags & SigmaValue) == 0)
159 white_point=(double) QuantumRange-black_point;
160 if ((flags & AspectValue ) == 0)
161 status=LevelImage(image,black_point,white_point,gamma);
162 else
163 status=LevelizeImage(image,black_point,white_point,gamma);
164 return(status);
165}
166
cristyf4ad9df2011-07-08 16:49:03 +0000167MagickExport Image *AdaptiveBlurImage(const Image *image,
168 const double radius,const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000169{
170#define AdaptiveBlurImageTag "Convolve/Image"
171#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
172
cristyc4c8d132010-01-07 01:58:38 +0000173 CacheView
174 *blur_view,
175 *edge_view,
176 *image_view;
177
cristy3ed852e2009-09-05 21:47:34 +0000178 double
cristy47e00502009-12-17 19:19:57 +0000179 **kernel,
180 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000181
182 Image
183 *blur_image,
184 *edge_image,
185 *gaussian_image;
186
cristy3ed852e2009-09-05 21:47:34 +0000187 MagickBooleanType
188 status;
189
cristybb503372010-05-27 20:51:26 +0000190 MagickOffsetType
191 progress;
192
cristy4c08aed2011-07-01 19:47:50 +0000193 PixelInfo
cristyddd82202009-11-03 20:14:50 +0000194 bias;
cristy3ed852e2009-09-05 21:47:34 +0000195
cristybb503372010-05-27 20:51:26 +0000196 register ssize_t
cristy47e00502009-12-17 19:19:57 +0000197 i;
cristy3ed852e2009-09-05 21:47:34 +0000198
cristybb503372010-05-27 20:51:26 +0000199 size_t
cristy3ed852e2009-09-05 21:47:34 +0000200 width;
201
cristybb503372010-05-27 20:51:26 +0000202 ssize_t
203 j,
204 k,
205 u,
206 v,
207 y;
208
cristy3ed852e2009-09-05 21:47:34 +0000209 assert(image != (const Image *) NULL);
210 assert(image->signature == MagickSignature);
211 if (image->debug != MagickFalse)
212 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
213 assert(exception != (ExceptionInfo *) NULL);
214 assert(exception->signature == MagickSignature);
215 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
216 if (blur_image == (Image *) NULL)
217 return((Image *) NULL);
218 if (fabs(sigma) <= MagickEpsilon)
219 return(blur_image);
220 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
221 {
222 InheritException(exception,&blur_image->exception);
223 blur_image=DestroyImage(blur_image);
224 return((Image *) NULL);
225 }
226 /*
227 Edge detect the image brighness channel, level, blur, and level again.
228 */
229 edge_image=EdgeImage(image,radius,exception);
230 if (edge_image == (Image *) NULL)
231 {
232 blur_image=DestroyImage(blur_image);
233 return((Image *) NULL);
234 }
cristyf89cb1d2011-07-07 01:24:37 +0000235 (void) AdaptiveLevelImage(edge_image,"20%,95%");
cristy3ed852e2009-09-05 21:47:34 +0000236 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,exception);
237 if (gaussian_image != (Image *) NULL)
238 {
239 edge_image=DestroyImage(edge_image);
240 edge_image=gaussian_image;
241 }
cristyf89cb1d2011-07-07 01:24:37 +0000242 (void) AdaptiveLevelImage(edge_image,"10%,95%");
cristy3ed852e2009-09-05 21:47:34 +0000243 /*
244 Create a set of kernels from maximum (radius,sigma) to minimum.
245 */
246 width=GetOptimalKernelWidth2D(radius,sigma);
247 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
248 if (kernel == (double **) NULL)
249 {
250 edge_image=DestroyImage(edge_image);
251 blur_image=DestroyImage(blur_image);
252 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
253 }
254 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
cristybb503372010-05-27 20:51:26 +0000255 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000256 {
257 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
258 sizeof(**kernel));
259 if (kernel[i] == (double *) NULL)
260 break;
cristy47e00502009-12-17 19:19:57 +0000261 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000262 j=(ssize_t) (width-i)/2;
cristy47e00502009-12-17 19:19:57 +0000263 k=0;
264 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000265 {
cristy47e00502009-12-17 19:19:57 +0000266 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000267 {
cristy4205a3c2010-09-12 20:19:59 +0000268 kernel[i][k]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
269 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +0000270 normalize+=kernel[i][k];
271 k++;
cristy3ed852e2009-09-05 21:47:34 +0000272 }
273 }
cristy3ed852e2009-09-05 21:47:34 +0000274 if (fabs(normalize) <= MagickEpsilon)
275 normalize=1.0;
276 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000277 for (k=0; k < (j*j); k++)
278 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000279 }
cristybb503372010-05-27 20:51:26 +0000280 if (i < (ssize_t) width)
cristy3ed852e2009-09-05 21:47:34 +0000281 {
282 for (i-=2; i >= 0; i-=2)
283 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
284 kernel=(double **) RelinquishMagickMemory(kernel);
285 edge_image=DestroyImage(edge_image);
286 blur_image=DestroyImage(blur_image);
287 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
288 }
289 /*
290 Adaptively blur image.
291 */
292 status=MagickTrue;
293 progress=0;
cristy4c08aed2011-07-01 19:47:50 +0000294 GetPixelInfo(image,&bias);
295 SetPixelInfoBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000296 image_view=AcquireCacheView(image);
297 edge_view=AcquireCacheView(edge_image);
298 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000299#if defined(MAGICKCORE_OPENMP_SUPPORT)
300 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000301#endif
cristybb503372010-05-27 20:51:26 +0000302 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000303 {
cristy4c08aed2011-07-01 19:47:50 +0000304 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000305 *restrict p,
306 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000307
cristy4c08aed2011-07-01 19:47:50 +0000308 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000309 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000310
cristy117ff172010-08-15 21:35:32 +0000311 register ssize_t
312 x;
313
cristy3ed852e2009-09-05 21:47:34 +0000314 if (status == MagickFalse)
315 continue;
316 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
317 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
318 exception);
cristy4c08aed2011-07-01 19:47:50 +0000319 if ((r == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000320 {
321 status=MagickFalse;
322 continue;
323 }
cristybb503372010-05-27 20:51:26 +0000324 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000325 {
cristy4c08aed2011-07-01 19:47:50 +0000326 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000327 pixel;
328
329 MagickRealType
330 alpha,
331 gamma;
332
333 register const double
cristyc47d1f82009-11-26 01:44:43 +0000334 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000335
cristybb503372010-05-27 20:51:26 +0000336 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000337 i,
338 u,
339 v;
340
341 gamma=0.0;
cristy4c08aed2011-07-01 19:47:50 +0000342 i=(ssize_t) ceil((double) width*QuantumScale*
343 GetPixelIntensity(edge_image,r)-0.5);
cristy3ed852e2009-09-05 21:47:34 +0000344 if (i < 0)
345 i=0;
346 else
cristybb503372010-05-27 20:51:26 +0000347 if (i > (ssize_t) width)
348 i=(ssize_t) width;
cristy3ed852e2009-09-05 21:47:34 +0000349 if ((i & 0x01) != 0)
350 i--;
cristya21afde2010-07-02 00:45:40 +0000351 p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) (width-i)/2L),y-
352 (ssize_t) ((width-i)/2L),width-i,width-i,exception);
cristy4c08aed2011-07-01 19:47:50 +0000353 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000354 break;
cristyddd82202009-11-03 20:14:50 +0000355 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +0000356 k=kernel[i];
cristybb503372010-05-27 20:51:26 +0000357 for (v=0; v < (ssize_t) (width-i); v++)
cristy3ed852e2009-09-05 21:47:34 +0000358 {
cristybb503372010-05-27 20:51:26 +0000359 for (u=0; u < (ssize_t) (width-i); u++)
cristy3ed852e2009-09-05 21:47:34 +0000360 {
361 alpha=1.0;
cristyed231572011-07-14 02:18:59 +0000362 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000363 (image->matte != MagickFalse))
cristy4c08aed2011-07-01 19:47:50 +0000364 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,p));
cristyed231572011-07-14 02:18:59 +0000365 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000366 pixel.red+=(*k)*alpha*GetPixelRed(image,p);
cristyed231572011-07-14 02:18:59 +0000367 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000368 pixel.green+=(*k)*alpha*GetPixelGreen(image,p);
cristyed231572011-07-14 02:18:59 +0000369 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000370 pixel.blue+=(*k)*alpha*GetPixelBlue(image,p);
cristyed231572011-07-14 02:18:59 +0000371 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000372 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +0000373 pixel.black+=(*k)*alpha*GetPixelBlack(image,p);
cristyed231572011-07-14 02:18:59 +0000374 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000375 pixel.alpha+=(*k)*GetPixelAlpha(image,p);
cristy3ed852e2009-09-05 21:47:34 +0000376 gamma+=(*k)*alpha;
377 k++;
cristyed231572011-07-14 02:18:59 +0000378 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000379 }
380 }
381 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +0000382 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000383 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +0000384 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000385 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +0000386 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000387 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +0000388 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000389 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +0000390 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristyed231572011-07-14 02:18:59 +0000391 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000392 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristyed231572011-07-14 02:18:59 +0000393 q+=GetPixelChannels(blur_image);
394 r+=GetPixelChannels(edge_image);
cristy3ed852e2009-09-05 21:47:34 +0000395 }
396 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
397 status=MagickFalse;
398 if (image->progress_monitor != (MagickProgressMonitor) NULL)
399 {
400 MagickBooleanType
401 proceed;
402
cristyb5d5f722009-11-04 03:03:49 +0000403#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +0000404 #pragma omp critical (MagickCore_AdaptiveBlurImage)
cristy3ed852e2009-09-05 21:47:34 +0000405#endif
406 proceed=SetImageProgress(image,AdaptiveBlurImageTag,progress++,
407 image->rows);
408 if (proceed == MagickFalse)
409 status=MagickFalse;
410 }
411 }
412 blur_image->type=image->type;
413 blur_view=DestroyCacheView(blur_view);
414 edge_view=DestroyCacheView(edge_view);
415 image_view=DestroyCacheView(image_view);
416 edge_image=DestroyImage(edge_image);
cristybb503372010-05-27 20:51:26 +0000417 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000418 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
419 kernel=(double **) RelinquishMagickMemory(kernel);
420 if (status == MagickFalse)
421 blur_image=DestroyImage(blur_image);
422 return(blur_image);
423}
424
425/*
426%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
427% %
428% %
429% %
430% A d a p t i v e S h a r p e n I m a g e %
431% %
432% %
433% %
434%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
435%
436% AdaptiveSharpenImage() adaptively sharpens the image by sharpening more
437% intensely near image edges and less intensely far from edges. We sharpen the
438% image with a Gaussian operator of the given radius and standard deviation
439% (sigma). For reasonable results, radius should be larger than sigma. Use a
440% radius of 0 and AdaptiveSharpenImage() selects a suitable radius for you.
441%
442% The format of the AdaptiveSharpenImage method is:
443%
444% Image *AdaptiveSharpenImage(const Image *image,const double radius,
445% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000446%
447% A description of each parameter follows:
448%
449% o image: the image.
450%
cristy3ed852e2009-09-05 21:47:34 +0000451% o radius: the radius of the Gaussian, in pixels, not counting the center
452% pixel.
453%
454% o sigma: the standard deviation of the Laplacian, in pixels.
455%
456% o exception: return any errors or warnings in this structure.
457%
458*/
cristy3ed852e2009-09-05 21:47:34 +0000459MagickExport Image *AdaptiveSharpenImage(const Image *image,const double radius,
460 const double sigma,ExceptionInfo *exception)
461{
cristy3ed852e2009-09-05 21:47:34 +0000462#define AdaptiveSharpenImageTag "Convolve/Image"
463#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
464
cristyc4c8d132010-01-07 01:58:38 +0000465 CacheView
466 *sharp_view,
467 *edge_view,
468 *image_view;
469
cristy3ed852e2009-09-05 21:47:34 +0000470 double
cristy47e00502009-12-17 19:19:57 +0000471 **kernel,
472 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000473
474 Image
475 *sharp_image,
476 *edge_image,
477 *gaussian_image;
478
cristy3ed852e2009-09-05 21:47:34 +0000479 MagickBooleanType
480 status;
481
cristybb503372010-05-27 20:51:26 +0000482 MagickOffsetType
483 progress;
484
cristy4c08aed2011-07-01 19:47:50 +0000485 PixelInfo
cristyddd82202009-11-03 20:14:50 +0000486 bias;
cristy3ed852e2009-09-05 21:47:34 +0000487
cristybb503372010-05-27 20:51:26 +0000488 register ssize_t
cristy47e00502009-12-17 19:19:57 +0000489 i;
cristy3ed852e2009-09-05 21:47:34 +0000490
cristybb503372010-05-27 20:51:26 +0000491 size_t
cristy3ed852e2009-09-05 21:47:34 +0000492 width;
493
cristybb503372010-05-27 20:51:26 +0000494 ssize_t
495 j,
496 k,
497 u,
498 v,
499 y;
500
cristy3ed852e2009-09-05 21:47:34 +0000501 assert(image != (const Image *) NULL);
502 assert(image->signature == MagickSignature);
503 if (image->debug != MagickFalse)
504 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
505 assert(exception != (ExceptionInfo *) NULL);
506 assert(exception->signature == MagickSignature);
507 sharp_image=CloneImage(image,0,0,MagickTrue,exception);
508 if (sharp_image == (Image *) NULL)
509 return((Image *) NULL);
510 if (fabs(sigma) <= MagickEpsilon)
511 return(sharp_image);
512 if (SetImageStorageClass(sharp_image,DirectClass) == MagickFalse)
513 {
514 InheritException(exception,&sharp_image->exception);
515 sharp_image=DestroyImage(sharp_image);
516 return((Image *) NULL);
517 }
518 /*
519 Edge detect the image brighness channel, level, sharp, and level again.
520 */
521 edge_image=EdgeImage(image,radius,exception);
522 if (edge_image == (Image *) NULL)
523 {
524 sharp_image=DestroyImage(sharp_image);
525 return((Image *) NULL);
526 }
cristyf89cb1d2011-07-07 01:24:37 +0000527 (void) AdaptiveLevelImage(edge_image,"20%,95%");
cristy3ed852e2009-09-05 21:47:34 +0000528 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,exception);
529 if (gaussian_image != (Image *) NULL)
530 {
531 edge_image=DestroyImage(edge_image);
532 edge_image=gaussian_image;
533 }
cristyf89cb1d2011-07-07 01:24:37 +0000534 (void) AdaptiveLevelImage(edge_image,"10%,95%");
cristy3ed852e2009-09-05 21:47:34 +0000535 /*
536 Create a set of kernels from maximum (radius,sigma) to minimum.
537 */
538 width=GetOptimalKernelWidth2D(radius,sigma);
539 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
540 if (kernel == (double **) NULL)
541 {
542 edge_image=DestroyImage(edge_image);
543 sharp_image=DestroyImage(sharp_image);
544 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
545 }
546 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
cristybb503372010-05-27 20:51:26 +0000547 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000548 {
549 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
550 sizeof(**kernel));
551 if (kernel[i] == (double *) NULL)
552 break;
cristy47e00502009-12-17 19:19:57 +0000553 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000554 j=(ssize_t) (width-i)/2;
cristy47e00502009-12-17 19:19:57 +0000555 k=0;
556 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000557 {
cristy47e00502009-12-17 19:19:57 +0000558 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000559 {
cristy4205a3c2010-09-12 20:19:59 +0000560 kernel[i][k]=(double) (-exp(-((double) u*u+v*v)/(2.0*MagickSigma*
561 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +0000562 normalize+=kernel[i][k];
563 k++;
cristy3ed852e2009-09-05 21:47:34 +0000564 }
565 }
cristy3ed852e2009-09-05 21:47:34 +0000566 if (fabs(normalize) <= MagickEpsilon)
567 normalize=1.0;
568 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000569 for (k=0; k < (j*j); k++)
570 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000571 }
cristybb503372010-05-27 20:51:26 +0000572 if (i < (ssize_t) width)
cristy3ed852e2009-09-05 21:47:34 +0000573 {
574 for (i-=2; i >= 0; i-=2)
575 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
576 kernel=(double **) RelinquishMagickMemory(kernel);
577 edge_image=DestroyImage(edge_image);
578 sharp_image=DestroyImage(sharp_image);
579 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
580 }
581 /*
582 Adaptively sharpen image.
583 */
584 status=MagickTrue;
585 progress=0;
cristy4c08aed2011-07-01 19:47:50 +0000586 GetPixelInfo(image,&bias);
587 SetPixelInfoBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000588 image_view=AcquireCacheView(image);
589 edge_view=AcquireCacheView(edge_image);
590 sharp_view=AcquireCacheView(sharp_image);
cristyb5d5f722009-11-04 03:03:49 +0000591#if defined(MAGICKCORE_OPENMP_SUPPORT)
592 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000593#endif
cristybb503372010-05-27 20:51:26 +0000594 for (y=0; y < (ssize_t) sharp_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000595 {
cristy4c08aed2011-07-01 19:47:50 +0000596 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000597 *restrict p,
598 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000599
cristy4c08aed2011-07-01 19:47:50 +0000600 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000601 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000602
cristy117ff172010-08-15 21:35:32 +0000603 register ssize_t
604 x;
605
cristy3ed852e2009-09-05 21:47:34 +0000606 if (status == MagickFalse)
607 continue;
608 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
609 q=QueueCacheViewAuthenticPixels(sharp_view,0,y,sharp_image->columns,1,
610 exception);
cristy4c08aed2011-07-01 19:47:50 +0000611 if ((r == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000612 {
613 status=MagickFalse;
614 continue;
615 }
cristybb503372010-05-27 20:51:26 +0000616 for (x=0; x < (ssize_t) sharp_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000617 {
cristy4c08aed2011-07-01 19:47:50 +0000618 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000619 pixel;
620
621 MagickRealType
622 alpha,
623 gamma;
624
625 register const double
cristyc47d1f82009-11-26 01:44:43 +0000626 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000627
cristybb503372010-05-27 20:51:26 +0000628 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000629 i,
630 u,
631 v;
632
633 gamma=0.0;
cristy4c08aed2011-07-01 19:47:50 +0000634 i=(ssize_t) ceil((double) width*QuantumScale*
635 GetPixelIntensity(edge_image,r)-0.5);
cristy3ed852e2009-09-05 21:47:34 +0000636 if (i < 0)
637 i=0;
638 else
cristybb503372010-05-27 20:51:26 +0000639 if (i > (ssize_t) width)
640 i=(ssize_t) width;
cristy3ed852e2009-09-05 21:47:34 +0000641 if ((i & 0x01) != 0)
642 i--;
cristy117ff172010-08-15 21:35:32 +0000643 p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) (width-i)/2L),y-
644 (ssize_t) ((width-i)/2L),width-i,width-i,exception);
cristy4c08aed2011-07-01 19:47:50 +0000645 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000646 break;
cristy3ed852e2009-09-05 21:47:34 +0000647 k=kernel[i];
cristyddd82202009-11-03 20:14:50 +0000648 pixel=bias;
cristybb503372010-05-27 20:51:26 +0000649 for (v=0; v < (ssize_t) (width-i); v++)
cristy3ed852e2009-09-05 21:47:34 +0000650 {
cristybb503372010-05-27 20:51:26 +0000651 for (u=0; u < (ssize_t) (width-i); u++)
cristy3ed852e2009-09-05 21:47:34 +0000652 {
653 alpha=1.0;
cristyed231572011-07-14 02:18:59 +0000654 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000655 (image->matte != MagickFalse))
cristy4c08aed2011-07-01 19:47:50 +0000656 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,p));
cristyed231572011-07-14 02:18:59 +0000657 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000658 pixel.red+=(*k)*alpha*GetPixelRed(image,p);
cristyed231572011-07-14 02:18:59 +0000659 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000660 pixel.green+=(*k)*alpha*GetPixelGreen(image,p);
cristyed231572011-07-14 02:18:59 +0000661 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000662 pixel.blue+=(*k)*alpha*GetPixelBlue(image,p);
cristyed231572011-07-14 02:18:59 +0000663 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000664 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +0000665 pixel.black+=(*k)*alpha*GetPixelBlack(image,p);
cristyed231572011-07-14 02:18:59 +0000666 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000667 pixel.alpha+=(*k)*GetPixelAlpha(image,p);
cristy3ed852e2009-09-05 21:47:34 +0000668 gamma+=(*k)*alpha;
669 k++;
cristyed231572011-07-14 02:18:59 +0000670 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000671 }
672 }
673 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +0000674 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000675 SetPixelRed(sharp_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +0000676 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000677 SetPixelGreen(sharp_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +0000678 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000679 SetPixelBlue(sharp_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +0000680 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000681 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +0000682 SetPixelBlack(sharp_image,ClampToQuantum(gamma*pixel.black),q);
cristyed231572011-07-14 02:18:59 +0000683 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000684 SetPixelAlpha(sharp_image,ClampToQuantum(pixel.alpha),q);
cristyed231572011-07-14 02:18:59 +0000685 q+=GetPixelChannels(sharp_image);
686 r+=GetPixelChannels(edge_image);
cristy3ed852e2009-09-05 21:47:34 +0000687 }
688 if (SyncCacheViewAuthenticPixels(sharp_view,exception) == MagickFalse)
689 status=MagickFalse;
690 if (image->progress_monitor != (MagickProgressMonitor) NULL)
691 {
692 MagickBooleanType
693 proceed;
694
cristyb5d5f722009-11-04 03:03:49 +0000695#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +0000696 #pragma omp critical (MagickCore_AdaptiveSharpenImage)
cristy3ed852e2009-09-05 21:47:34 +0000697#endif
698 proceed=SetImageProgress(image,AdaptiveSharpenImageTag,progress++,
699 image->rows);
700 if (proceed == MagickFalse)
701 status=MagickFalse;
702 }
703 }
704 sharp_image->type=image->type;
705 sharp_view=DestroyCacheView(sharp_view);
706 edge_view=DestroyCacheView(edge_view);
707 image_view=DestroyCacheView(image_view);
708 edge_image=DestroyImage(edge_image);
cristybb503372010-05-27 20:51:26 +0000709 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000710 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
711 kernel=(double **) RelinquishMagickMemory(kernel);
712 if (status == MagickFalse)
713 sharp_image=DestroyImage(sharp_image);
714 return(sharp_image);
715}
716
717/*
718%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
719% %
720% %
721% %
722% B l u r I m a g e %
723% %
724% %
725% %
726%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
727%
728% BlurImage() blurs an image. We convolve the image with a Gaussian operator
729% of the given radius and standard deviation (sigma). For reasonable results,
730% the radius should be larger than sigma. Use a radius of 0 and BlurImage()
731% selects a suitable radius for you.
732%
733% BlurImage() differs from GaussianBlurImage() in that it uses a separable
734% kernel which is faster but mathematically equivalent to the non-separable
735% kernel.
736%
737% The format of the BlurImage method is:
738%
739% Image *BlurImage(const Image *image,const double radius,
740% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000741%
742% A description of each parameter follows:
743%
744% o image: the image.
745%
cristy3ed852e2009-09-05 21:47:34 +0000746% o radius: the radius of the Gaussian, in pixels, not counting the center
747% pixel.
748%
749% o sigma: the standard deviation of the Gaussian, in pixels.
750%
751% o exception: return any errors or warnings in this structure.
752%
753*/
754
cristybb503372010-05-27 20:51:26 +0000755static double *GetBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +0000756{
cristy3ed852e2009-09-05 21:47:34 +0000757 double
cristy47e00502009-12-17 19:19:57 +0000758 *kernel,
759 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000760
cristy117ff172010-08-15 21:35:32 +0000761 register ssize_t
762 i;
763
cristybb503372010-05-27 20:51:26 +0000764 ssize_t
cristy47e00502009-12-17 19:19:57 +0000765 j,
766 k;
cristy3ed852e2009-09-05 21:47:34 +0000767
cristy3ed852e2009-09-05 21:47:34 +0000768 /*
769 Generate a 1-D convolution kernel.
770 */
771 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
772 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
773 if (kernel == (double *) NULL)
774 return(0);
cristy3ed852e2009-09-05 21:47:34 +0000775 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000776 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +0000777 i=0;
778 for (k=(-j); k <= j; k++)
779 {
cristy4205a3c2010-09-12 20:19:59 +0000780 kernel[i]=(double) (exp(-((double) k*k)/(2.0*MagickSigma*MagickSigma))/
781 (MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +0000782 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +0000783 i++;
784 }
cristybb503372010-05-27 20:51:26 +0000785 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000786 kernel[i]/=normalize;
787 return(kernel);
788}
789
cristyf4ad9df2011-07-08 16:49:03 +0000790MagickExport Image *BlurImage(const Image *image,const double radius,
791 const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000792{
793#define BlurImageTag "Blur/Image"
794
cristyc4c8d132010-01-07 01:58:38 +0000795 CacheView
796 *blur_view,
797 *image_view;
798
cristy3ed852e2009-09-05 21:47:34 +0000799 double
800 *kernel;
801
802 Image
803 *blur_image;
804
cristy3ed852e2009-09-05 21:47:34 +0000805 MagickBooleanType
806 status;
807
cristybb503372010-05-27 20:51:26 +0000808 MagickOffsetType
809 progress;
810
cristy4c08aed2011-07-01 19:47:50 +0000811 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000812 bias;
813
cristybb503372010-05-27 20:51:26 +0000814 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000815 i;
816
cristybb503372010-05-27 20:51:26 +0000817 size_t
cristy3ed852e2009-09-05 21:47:34 +0000818 width;
819
cristybb503372010-05-27 20:51:26 +0000820 ssize_t
821 x,
822 y;
823
cristy3ed852e2009-09-05 21:47:34 +0000824 /*
825 Initialize blur image attributes.
826 */
827 assert(image != (Image *) NULL);
828 assert(image->signature == MagickSignature);
829 if (image->debug != MagickFalse)
830 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
831 assert(exception != (ExceptionInfo *) NULL);
832 assert(exception->signature == MagickSignature);
833 blur_image=CloneImage(image,0,0,MagickTrue,exception);
834 if (blur_image == (Image *) NULL)
835 return((Image *) NULL);
836 if (fabs(sigma) <= MagickEpsilon)
837 return(blur_image);
838 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
839 {
840 InheritException(exception,&blur_image->exception);
841 blur_image=DestroyImage(blur_image);
842 return((Image *) NULL);
843 }
844 width=GetOptimalKernelWidth1D(radius,sigma);
845 kernel=GetBlurKernel(width,sigma);
846 if (kernel == (double *) NULL)
847 {
848 blur_image=DestroyImage(blur_image);
849 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
850 }
851 if (image->debug != MagickFalse)
852 {
853 char
854 format[MaxTextExtent],
855 *message;
856
857 register const double
858 *k;
859
860 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +0000861 " BlurImage with %.20g kernel:",(double) width);
cristy3ed852e2009-09-05 21:47:34 +0000862 message=AcquireString("");
863 k=kernel;
cristybb503372010-05-27 20:51:26 +0000864 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000865 {
866 *message='\0';
cristyb51dff52011-05-19 16:55:47 +0000867 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) i);
cristy3ed852e2009-09-05 21:47:34 +0000868 (void) ConcatenateString(&message,format);
cristyb51dff52011-05-19 16:55:47 +0000869 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristy3ed852e2009-09-05 21:47:34 +0000870 (void) ConcatenateString(&message,format);
871 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
872 }
873 message=DestroyString(message);
874 }
875 /*
876 Blur rows.
877 */
878 status=MagickTrue;
879 progress=0;
cristy4c08aed2011-07-01 19:47:50 +0000880 GetPixelInfo(image,&bias);
881 SetPixelInfoBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000882 image_view=AcquireCacheView(image);
883 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000884#if defined(MAGICKCORE_OPENMP_SUPPORT)
885 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000886#endif
cristybb503372010-05-27 20:51:26 +0000887 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000888 {
cristy4c08aed2011-07-01 19:47:50 +0000889 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000890 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000891
cristy4c08aed2011-07-01 19:47:50 +0000892 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000893 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000894
cristy117ff172010-08-15 21:35:32 +0000895 register ssize_t
896 x;
897
cristy3ed852e2009-09-05 21:47:34 +0000898 if (status == MagickFalse)
899 continue;
cristy117ff172010-08-15 21:35:32 +0000900 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y,
901 image->columns+width,1,exception);
cristy3ed852e2009-09-05 21:47:34 +0000902 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
903 exception);
cristy4c08aed2011-07-01 19:47:50 +0000904 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000905 {
906 status=MagickFalse;
907 continue;
908 }
cristybb503372010-05-27 20:51:26 +0000909 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000910 {
cristy4c08aed2011-07-01 19:47:50 +0000911 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000912 pixel;
913
914 register const double
cristyc47d1f82009-11-26 01:44:43 +0000915 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000916
cristy4c08aed2011-07-01 19:47:50 +0000917 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000918 *restrict kernel_pixels;
cristy3ed852e2009-09-05 21:47:34 +0000919
cristybb503372010-05-27 20:51:26 +0000920 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000921 i;
922
cristyddd82202009-11-03 20:14:50 +0000923 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +0000924 k=kernel;
925 kernel_pixels=p;
cristyed231572011-07-14 02:18:59 +0000926 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) ||
cristyf4ad9df2011-07-08 16:49:03 +0000927 (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +0000928 {
cristybb503372010-05-27 20:51:26 +0000929 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000930 {
cristy4c08aed2011-07-01 19:47:50 +0000931 pixel.red+=(*k)*GetPixelRed(image,kernel_pixels);
932 pixel.green+=(*k)*GetPixelGreen(image,kernel_pixels);
933 pixel.blue+=(*k)*GetPixelBlue(image,kernel_pixels);
934 if (image->colorspace == CMYKColorspace)
935 pixel.black+=(*k)*GetPixelBlack(image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +0000936 k++;
cristyed231572011-07-14 02:18:59 +0000937 kernel_pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000938 }
cristyed231572011-07-14 02:18:59 +0000939 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000940 SetPixelRed(blur_image,ClampToQuantum(pixel.red),q);
cristyed231572011-07-14 02:18:59 +0000941 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000942 SetPixelGreen(blur_image,ClampToQuantum(pixel.green),q);
cristyed231572011-07-14 02:18:59 +0000943 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000944 SetPixelBlue(blur_image,ClampToQuantum(pixel.blue),q);
cristyed231572011-07-14 02:18:59 +0000945 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000946 (blur_image->colorspace == CMYKColorspace))
947 SetPixelBlack(blur_image,ClampToQuantum(pixel.black),q);
cristyed231572011-07-14 02:18:59 +0000948 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +0000949 {
950 k=kernel;
951 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +0000952 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000953 {
cristy4c08aed2011-07-01 19:47:50 +0000954 pixel.alpha+=(*k)*GetPixelAlpha(image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +0000955 k++;
cristyed231572011-07-14 02:18:59 +0000956 kernel_pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000957 }
cristy4c08aed2011-07-01 19:47:50 +0000958 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +0000959 }
960 }
961 else
962 {
963 MagickRealType
964 alpha,
965 gamma;
966
967 gamma=0.0;
cristybb503372010-05-27 20:51:26 +0000968 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000969 {
cristy4c08aed2011-07-01 19:47:50 +0000970 alpha=(MagickRealType) (QuantumScale*
971 GetPixelAlpha(image,kernel_pixels));
972 pixel.red+=(*k)*alpha*GetPixelRed(image,kernel_pixels);
973 pixel.green+=(*k)*alpha*GetPixelGreen(image,kernel_pixels);
974 pixel.blue+=(*k)*alpha*GetPixelBlue(image,kernel_pixels);
975 if (image->colorspace == CMYKColorspace)
976 pixel.black+=(*k)*alpha*GetPixelBlack(image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +0000977 gamma+=(*k)*alpha;
978 k++;
cristyed231572011-07-14 02:18:59 +0000979 kernel_pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000980 }
981 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +0000982 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000983 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +0000984 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000985 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +0000986 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000987 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +0000988 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000989 (blur_image->colorspace == CMYKColorspace))
990 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristyed231572011-07-14 02:18:59 +0000991 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +0000992 {
993 k=kernel;
994 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +0000995 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000996 {
cristy4c08aed2011-07-01 19:47:50 +0000997 pixel.alpha+=(*k)*GetPixelAlpha(image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +0000998 k++;
cristyed231572011-07-14 02:18:59 +0000999 kernel_pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001000 }
cristy4c08aed2011-07-01 19:47:50 +00001001 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00001002 }
1003 }
cristyed231572011-07-14 02:18:59 +00001004 p+=GetPixelChannels(image);
1005 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001006 }
1007 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1008 status=MagickFalse;
1009 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1010 {
1011 MagickBooleanType
1012 proceed;
1013
cristyb5d5f722009-11-04 03:03:49 +00001014#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00001015 #pragma omp critical (MagickCore_BlurImage)
cristy3ed852e2009-09-05 21:47:34 +00001016#endif
1017 proceed=SetImageProgress(image,BlurImageTag,progress++,blur_image->rows+
1018 blur_image->columns);
1019 if (proceed == MagickFalse)
1020 status=MagickFalse;
1021 }
1022 }
1023 blur_view=DestroyCacheView(blur_view);
1024 image_view=DestroyCacheView(image_view);
1025 /*
1026 Blur columns.
1027 */
1028 image_view=AcquireCacheView(blur_image);
1029 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00001030#if defined(MAGICKCORE_OPENMP_SUPPORT)
1031 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001032#endif
cristybb503372010-05-27 20:51:26 +00001033 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001034 {
cristy4c08aed2011-07-01 19:47:50 +00001035 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001036 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001037
cristy4c08aed2011-07-01 19:47:50 +00001038 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001039 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001040
cristy117ff172010-08-15 21:35:32 +00001041 register ssize_t
1042 y;
1043
cristy3ed852e2009-09-05 21:47:34 +00001044 if (status == MagickFalse)
1045 continue;
cristy117ff172010-08-15 21:35:32 +00001046 p=GetCacheViewVirtualPixels(image_view,x,-((ssize_t) width/2L),1,
1047 image->rows+width,exception);
cristy3ed852e2009-09-05 21:47:34 +00001048 q=GetCacheViewAuthenticPixels(blur_view,x,0,1,blur_image->rows,exception);
cristy4c08aed2011-07-01 19:47:50 +00001049 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001050 {
1051 status=MagickFalse;
1052 continue;
1053 }
cristybb503372010-05-27 20:51:26 +00001054 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001055 {
cristy4c08aed2011-07-01 19:47:50 +00001056 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001057 pixel;
1058
1059 register const double
cristyc47d1f82009-11-26 01:44:43 +00001060 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00001061
cristy4c08aed2011-07-01 19:47:50 +00001062 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001063 *restrict kernel_pixels;
cristy3ed852e2009-09-05 21:47:34 +00001064
cristybb503372010-05-27 20:51:26 +00001065 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001066 i;
1067
cristyddd82202009-11-03 20:14:50 +00001068 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00001069 k=kernel;
1070 kernel_pixels=p;
cristyed231572011-07-14 02:18:59 +00001071 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) ||
cristyf4ad9df2011-07-08 16:49:03 +00001072 (blur_image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00001073 {
cristybb503372010-05-27 20:51:26 +00001074 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001075 {
cristy4c08aed2011-07-01 19:47:50 +00001076 pixel.red+=(*k)*GetPixelRed(blur_image,kernel_pixels);
1077 pixel.green+=(*k)*GetPixelGreen(blur_image,kernel_pixels);
1078 pixel.blue+=(*k)*GetPixelBlue(blur_image,kernel_pixels);
1079 if (blur_image->colorspace == CMYKColorspace)
1080 pixel.black+=(*k)*GetPixelBlack(blur_image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001081 k++;
cristyed231572011-07-14 02:18:59 +00001082 kernel_pixels+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001083 }
cristyed231572011-07-14 02:18:59 +00001084 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001085 SetPixelRed(blur_image,ClampToQuantum(pixel.red),q);
cristyed231572011-07-14 02:18:59 +00001086 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001087 SetPixelGreen(blur_image,ClampToQuantum(pixel.green),q);
cristyed231572011-07-14 02:18:59 +00001088 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001089 SetPixelBlue(blur_image,ClampToQuantum(pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00001090 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001091 (blur_image->colorspace == CMYKColorspace))
1092 SetPixelBlack(blur_image,ClampToQuantum(pixel.black),q);
cristyed231572011-07-14 02:18:59 +00001093 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001094 {
1095 k=kernel;
1096 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001097 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001098 {
cristy4c08aed2011-07-01 19:47:50 +00001099 pixel.alpha+=(*k)*GetPixelAlpha(blur_image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001100 k++;
cristyed231572011-07-14 02:18:59 +00001101 kernel_pixels+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001102 }
cristy4c08aed2011-07-01 19:47:50 +00001103 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00001104 }
1105 }
1106 else
1107 {
1108 MagickRealType
1109 alpha,
1110 gamma;
1111
1112 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00001113 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001114 {
cristy46f08202010-01-10 04:04:21 +00001115 alpha=(MagickRealType) (QuantumScale*
cristy4c08aed2011-07-01 19:47:50 +00001116 GetPixelAlpha(blur_image,kernel_pixels));
1117 pixel.red+=(*k)*alpha*GetPixelRed(blur_image,kernel_pixels);
1118 pixel.green+=(*k)*alpha*GetPixelGreen(blur_image,kernel_pixels);
1119 pixel.blue+=(*k)*alpha*GetPixelBlue(blur_image,kernel_pixels);
1120 if (blur_image->colorspace == CMYKColorspace)
1121 pixel.black+=(*k)*alpha*GetPixelBlack(blur_image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001122 gamma+=(*k)*alpha;
1123 k++;
cristyed231572011-07-14 02:18:59 +00001124 kernel_pixels+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001125 }
1126 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00001127 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001128 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +00001129 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001130 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +00001131 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001132 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00001133 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001134 (blur_image->colorspace == CMYKColorspace))
1135 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristyed231572011-07-14 02:18:59 +00001136 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001137 {
1138 k=kernel;
1139 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001140 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001141 {
cristy4c08aed2011-07-01 19:47:50 +00001142 pixel.alpha+=(*k)*GetPixelAlpha(blur_image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001143 k++;
cristyed231572011-07-14 02:18:59 +00001144 kernel_pixels+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001145 }
cristy4c08aed2011-07-01 19:47:50 +00001146 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00001147 }
1148 }
cristyed231572011-07-14 02:18:59 +00001149 p+=GetPixelChannels(blur_image);
1150 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001151 }
1152 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1153 status=MagickFalse;
cristy4c08aed2011-07-01 19:47:50 +00001154 if (blur_image->progress_monitor != (MagickProgressMonitor) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001155 {
1156 MagickBooleanType
1157 proceed;
1158
cristyb5d5f722009-11-04 03:03:49 +00001159#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00001160 #pragma omp critical (MagickCore_BlurImage)
cristy3ed852e2009-09-05 21:47:34 +00001161#endif
cristy4c08aed2011-07-01 19:47:50 +00001162 proceed=SetImageProgress(blur_image,BlurImageTag,progress++,
1163 blur_image->rows+blur_image->columns);
cristy3ed852e2009-09-05 21:47:34 +00001164 if (proceed == MagickFalse)
1165 status=MagickFalse;
1166 }
1167 }
1168 blur_view=DestroyCacheView(blur_view);
1169 image_view=DestroyCacheView(image_view);
1170 kernel=(double *) RelinquishMagickMemory(kernel);
1171 if (status == MagickFalse)
1172 blur_image=DestroyImage(blur_image);
1173 blur_image->type=image->type;
1174 return(blur_image);
1175}
1176
1177/*
1178%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1179% %
1180% %
1181% %
cristyfccdab92009-11-30 16:43:57 +00001182% C o n v o l v e I m a g e %
1183% %
1184% %
1185% %
1186%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1187%
1188% ConvolveImage() applies a custom convolution kernel to the image.
1189%
1190% The format of the ConvolveImage method is:
1191%
cristy5e6be1e2011-07-16 01:23:39 +00001192% Image *ConvolveImage(const Image *image,const KernelInfo *kernel,
1193% ExceptionInfo *exception)
1194%
cristyfccdab92009-11-30 16:43:57 +00001195% A description of each parameter follows:
1196%
1197% o image: the image.
1198%
cristy5e6be1e2011-07-16 01:23:39 +00001199% o kernel: the filtering kernel.
cristyfccdab92009-11-30 16:43:57 +00001200%
1201% o exception: return any errors or warnings in this structure.
1202%
1203*/
cristy5e6be1e2011-07-16 01:23:39 +00001204MagickExport Image *ConvolveImage(const Image *image,
1205 const KernelInfo *kernel_info,ExceptionInfo *exception)
cristyfccdab92009-11-30 16:43:57 +00001206{
cristyfccdab92009-11-30 16:43:57 +00001207#define ConvolveImageTag "Convolve/Image"
1208
cristyc4c8d132010-01-07 01:58:38 +00001209 CacheView
1210 *convolve_view,
1211 *image_view;
1212
cristyfccdab92009-11-30 16:43:57 +00001213 Image
1214 *convolve_image;
1215
cristyfccdab92009-11-30 16:43:57 +00001216 MagickBooleanType
1217 status;
1218
cristybb503372010-05-27 20:51:26 +00001219 MagickOffsetType
1220 progress;
1221
cristybb503372010-05-27 20:51:26 +00001222 ssize_t
1223 y;
1224
cristyfccdab92009-11-30 16:43:57 +00001225 /*
1226 Initialize convolve image attributes.
1227 */
1228 assert(image != (Image *) NULL);
1229 assert(image->signature == MagickSignature);
1230 if (image->debug != MagickFalse)
1231 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1232 assert(exception != (ExceptionInfo *) NULL);
1233 assert(exception->signature == MagickSignature);
cristy5e6be1e2011-07-16 01:23:39 +00001234 if ((kernel_info->width % 2) == 0)
cristyfccdab92009-11-30 16:43:57 +00001235 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
cristy08429172011-07-14 17:18:16 +00001236 convolve_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1237 exception);
cristyfccdab92009-11-30 16:43:57 +00001238 if (convolve_image == (Image *) NULL)
1239 return((Image *) NULL);
1240 if (SetImageStorageClass(convolve_image,DirectClass) == MagickFalse)
1241 {
1242 InheritException(exception,&convolve_image->exception);
1243 convolve_image=DestroyImage(convolve_image);
1244 return((Image *) NULL);
1245 }
1246 if (image->debug != MagickFalse)
1247 {
1248 char
1249 format[MaxTextExtent],
1250 *message;
1251
cristy117ff172010-08-15 21:35:32 +00001252 register const double
1253 *k;
1254
cristy4e154852011-07-14 13:28:53 +00001255 register ssize_t
1256 u;
1257
cristybb503372010-05-27 20:51:26 +00001258 ssize_t
cristyfccdab92009-11-30 16:43:57 +00001259 v;
1260
cristyfccdab92009-11-30 16:43:57 +00001261 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristy5e6be1e2011-07-16 01:23:39 +00001262 " ConvolveImage with %.20gx%.20g kernel:",(double) kernel_info->width,
1263 (double) kernel_info->height);
cristyfccdab92009-11-30 16:43:57 +00001264 message=AcquireString("");
cristy5e6be1e2011-07-16 01:23:39 +00001265 k=kernel_info->values;
1266 for (v=0; v < (ssize_t) kernel_info->width; v++)
cristyfccdab92009-11-30 16:43:57 +00001267 {
1268 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00001269 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristyfccdab92009-11-30 16:43:57 +00001270 (void) ConcatenateString(&message,format);
cristy5e6be1e2011-07-16 01:23:39 +00001271 for (u=0; u < (ssize_t) kernel_info->height; u++)
cristyfccdab92009-11-30 16:43:57 +00001272 {
cristyb51dff52011-05-19 16:55:47 +00001273 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristyfccdab92009-11-30 16:43:57 +00001274 (void) ConcatenateString(&message,format);
1275 }
1276 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
1277 }
1278 message=DestroyString(message);
1279 }
1280 /*
cristyfccdab92009-11-30 16:43:57 +00001281 Convolve image.
1282 */
1283 status=MagickTrue;
1284 progress=0;
cristyfccdab92009-11-30 16:43:57 +00001285 image_view=AcquireCacheView(image);
1286 convolve_view=AcquireCacheView(convolve_image);
1287#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy175653e2011-07-10 23:13:34 +00001288 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristyfccdab92009-11-30 16:43:57 +00001289#endif
cristybb503372010-05-27 20:51:26 +00001290 for (y=0; y < (ssize_t) image->rows; y++)
cristyfccdab92009-11-30 16:43:57 +00001291 {
cristy4c08aed2011-07-01 19:47:50 +00001292 register const Quantum
cristyfccdab92009-11-30 16:43:57 +00001293 *restrict p;
1294
cristy4c08aed2011-07-01 19:47:50 +00001295 register Quantum
cristyfccdab92009-11-30 16:43:57 +00001296 *restrict q;
1297
cristy117ff172010-08-15 21:35:32 +00001298 register ssize_t
1299 x;
1300
cristy63852c12011-07-14 23:47:58 +00001301 size_t
cristyed231572011-07-14 02:18:59 +00001302 channels,
1303 convolve_channels;
1304
cristyfccdab92009-11-30 16:43:57 +00001305 if (status == MagickFalse)
1306 continue;
cristy5e6be1e2011-07-16 01:23:39 +00001307 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) kernel_info->width/2L),y-
1308 (ssize_t) (kernel_info->height/2L),image->columns+kernel_info->width,
1309 kernel_info->height,exception);
cristy08429172011-07-14 17:18:16 +00001310 q=QueueCacheViewAuthenticPixels(convolve_view,0,y,convolve_image->columns,1,
cristyfccdab92009-11-30 16:43:57 +00001311 exception);
cristy4c08aed2011-07-01 19:47:50 +00001312 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristyfccdab92009-11-30 16:43:57 +00001313 {
1314 status=MagickFalse;
1315 continue;
1316 }
cristyed231572011-07-14 02:18:59 +00001317 channels=GetPixelChannels(image);
1318 convolve_channels=GetPixelChannels(convolve_image);
cristybb503372010-05-27 20:51:26 +00001319 for (x=0; x < (ssize_t) image->columns; x++)
cristyfccdab92009-11-30 16:43:57 +00001320 {
cristybb503372010-05-27 20:51:26 +00001321 register ssize_t
cristyed231572011-07-14 02:18:59 +00001322 i;
cristyfccdab92009-11-30 16:43:57 +00001323
cristyed231572011-07-14 02:18:59 +00001324 for (i=0; i < (ssize_t) channels; i++)
1325 {
cristyed231572011-07-14 02:18:59 +00001326 MagickRealType
cristy4e154852011-07-14 13:28:53 +00001327 alpha,
1328 gamma,
cristyed231572011-07-14 02:18:59 +00001329 pixel;
1330
1331 PixelChannel
1332 channel;
1333
1334 PixelTrait
1335 convolve_traits,
1336 traits;
1337
1338 register const double
1339 *restrict k;
1340
1341 register const Quantum
1342 *restrict kernel_pixels;
1343
1344 register ssize_t
1345 u;
1346
1347 ssize_t
1348 v;
1349
cristyed231572011-07-14 02:18:59 +00001350 traits=GetPixelChannelMapTraits(image,i);
cristy4e154852011-07-14 13:28:53 +00001351 if (traits == UndefinedPixelTrait)
cristyed231572011-07-14 02:18:59 +00001352 continue;
cristy4e154852011-07-14 13:28:53 +00001353 channel=GetPixelChannelMapChannel(image,i);
1354 convolve_traits=GetPixelChannelMapTraits(convolve_image,channel);
1355 if (convolve_traits == UndefinedPixelTrait)
1356 continue;
1357 if ((convolve_traits & CopyPixelTrait) != 0)
1358 {
cristy39177402011-07-14 16:45:04 +00001359 size_t
cristyf8500872011-07-14 14:02:41 +00001360 center;
cristy4e154852011-07-14 13:28:53 +00001361
cristy5e6be1e2011-07-16 01:23:39 +00001362 center=((image->columns+kernel_info->width)*kernel_info->height/2)*
1363 channels+i;
cristyf8500872011-07-14 14:02:41 +00001364 SetPixelChannel(convolve_image,channel,p[center],q);
cristy4e154852011-07-14 13:28:53 +00001365 continue;
1366 }
cristy5e6be1e2011-07-16 01:23:39 +00001367 k=kernel_info->values;
cristyed231572011-07-14 02:18:59 +00001368 kernel_pixels=p;
cristy0a922382011-07-16 15:30:34 +00001369 pixel=kernel_info->bias;
cristy1ce96d02011-07-14 17:57:24 +00001370 if (((convolve_traits & BlendPixelTrait) == 0) ||
1371 (GetPixelAlphaTraits(image) == UndefinedPixelTrait) ||
cristyed231572011-07-14 02:18:59 +00001372 (image->matte == MagickFalse))
cristyfccdab92009-11-30 16:43:57 +00001373 {
cristyed231572011-07-14 02:18:59 +00001374 /*
cristy4e154852011-07-14 13:28:53 +00001375 No alpha blending.
cristyed231572011-07-14 02:18:59 +00001376 */
cristy5e6be1e2011-07-16 01:23:39 +00001377 for (v=0; v < (ssize_t) kernel_info->width; v++)
cristyfccdab92009-11-30 16:43:57 +00001378 {
cristy5e6be1e2011-07-16 01:23:39 +00001379 for (u=0; u < (ssize_t) kernel_info->height; u++)
cristy175653e2011-07-10 23:13:34 +00001380 {
cristyed231572011-07-14 02:18:59 +00001381 pixel+=(*k)*kernel_pixels[u*channels+i];
1382 k++;
cristy175653e2011-07-10 23:13:34 +00001383 }
cristy5e6be1e2011-07-16 01:23:39 +00001384 kernel_pixels+=(image->columns+kernel_info->width)*channels;
cristy175653e2011-07-10 23:13:34 +00001385 }
cristy4e154852011-07-14 13:28:53 +00001386 SetPixelChannel(convolve_image,channel,ClampToQuantum(pixel),q);
1387 continue;
cristyed231572011-07-14 02:18:59 +00001388 }
cristy4e154852011-07-14 13:28:53 +00001389 /*
1390 Alpha blending.
1391 */
1392 gamma=0.0;
cristy5e6be1e2011-07-16 01:23:39 +00001393 for (v=0; v < (ssize_t) kernel_info->width; v++)
cristy4e154852011-07-14 13:28:53 +00001394 {
cristy5e6be1e2011-07-16 01:23:39 +00001395 for (u=0; u < (ssize_t) kernel_info->height; u++)
cristy4e154852011-07-14 13:28:53 +00001396 {
1397 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,
1398 kernel_pixels+u*channels));
cristy1ce96d02011-07-14 17:57:24 +00001399 pixel+=(*k)*alpha*kernel_pixels[u*channels+i];
cristy4e154852011-07-14 13:28:53 +00001400 gamma+=(*k)*alpha;
1401 k++;
1402 }
cristy5e6be1e2011-07-16 01:23:39 +00001403 kernel_pixels+=(image->columns+kernel_info->width)*channels;
cristy4e154852011-07-14 13:28:53 +00001404 }
cristy1ce96d02011-07-14 17:57:24 +00001405 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1406 SetPixelChannel(convolve_image,channel,ClampToQuantum(gamma*pixel),q);
cristyed231572011-07-14 02:18:59 +00001407 }
1408 p+=channels;
1409 q+=convolve_channels;
cristyfccdab92009-11-30 16:43:57 +00001410 }
cristyed231572011-07-14 02:18:59 +00001411 if (SyncCacheViewAuthenticPixels(convolve_view,exception) == MagickFalse)
cristyfccdab92009-11-30 16:43:57 +00001412 status=MagickFalse;
1413 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1414 {
1415 MagickBooleanType
1416 proceed;
1417
1418#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00001419 #pragma omp critical (MagickCore_ConvolveImage)
cristyfccdab92009-11-30 16:43:57 +00001420#endif
1421 proceed=SetImageProgress(image,ConvolveImageTag,progress++,image->rows);
1422 if (proceed == MagickFalse)
1423 status=MagickFalse;
1424 }
1425 }
1426 convolve_image->type=image->type;
1427 convolve_view=DestroyCacheView(convolve_view);
1428 image_view=DestroyCacheView(image_view);
cristyfccdab92009-11-30 16:43:57 +00001429 if (status == MagickFalse)
1430 convolve_image=DestroyImage(convolve_image);
1431 return(convolve_image);
1432}
1433
1434/*
1435%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1436% %
1437% %
1438% %
cristy3ed852e2009-09-05 21:47:34 +00001439% D e s p e c k l e I m a g e %
1440% %
1441% %
1442% %
1443%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1444%
1445% DespeckleImage() reduces the speckle noise in an image while perserving the
1446% edges of the original image.
1447%
1448% The format of the DespeckleImage method is:
1449%
1450% Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1451%
1452% A description of each parameter follows:
1453%
1454% o image: the image.
1455%
1456% o exception: return any errors or warnings in this structure.
1457%
1458*/
1459
cristybb503372010-05-27 20:51:26 +00001460static void Hull(const ssize_t x_offset,const ssize_t y_offset,
1461 const size_t columns,const size_t rows,Quantum *f,Quantum *g,
cristy3ed852e2009-09-05 21:47:34 +00001462 const int polarity)
1463{
cristy3ed852e2009-09-05 21:47:34 +00001464 MagickRealType
1465 v;
1466
cristy3ed852e2009-09-05 21:47:34 +00001467 register Quantum
1468 *p,
1469 *q,
1470 *r,
1471 *s;
1472
cristy117ff172010-08-15 21:35:32 +00001473 register ssize_t
1474 x;
1475
1476 ssize_t
1477 y;
1478
cristy3ed852e2009-09-05 21:47:34 +00001479 assert(f != (Quantum *) NULL);
1480 assert(g != (Quantum *) NULL);
1481 p=f+(columns+2);
1482 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001483 r=p+(y_offset*((ssize_t) columns+2)+x_offset);
1484 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001485 {
1486 p++;
1487 q++;
1488 r++;
1489 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001490 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001491 {
1492 v=(MagickRealType) (*p);
1493 if ((MagickRealType) *r >= (v+(MagickRealType) ScaleCharToQuantum(2)))
1494 v+=ScaleCharToQuantum(1);
1495 *q=(Quantum) v;
1496 p++;
1497 q++;
1498 r++;
1499 }
1500 else
cristybb503372010-05-27 20:51:26 +00001501 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001502 {
1503 v=(MagickRealType) (*p);
1504 if ((MagickRealType) *r <= (v-(MagickRealType) ScaleCharToQuantum(2)))
cristybb503372010-05-27 20:51:26 +00001505 v-=(ssize_t) ScaleCharToQuantum(1);
cristy3ed852e2009-09-05 21:47:34 +00001506 *q=(Quantum) v;
1507 p++;
1508 q++;
1509 r++;
1510 }
1511 p++;
1512 q++;
1513 r++;
1514 }
1515 p=f+(columns+2);
1516 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001517 r=q+(y_offset*((ssize_t) columns+2)+x_offset);
1518 s=q-(y_offset*((ssize_t) columns+2)+x_offset);
1519 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001520 {
1521 p++;
1522 q++;
1523 r++;
1524 s++;
1525 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001526 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001527 {
1528 v=(MagickRealType) (*q);
1529 if (((MagickRealType) *s >=
1530 (v+(MagickRealType) ScaleCharToQuantum(2))) &&
1531 ((MagickRealType) *r > v))
1532 v+=ScaleCharToQuantum(1);
1533 *p=(Quantum) v;
1534 p++;
1535 q++;
1536 r++;
1537 s++;
1538 }
1539 else
cristybb503372010-05-27 20:51:26 +00001540 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001541 {
1542 v=(MagickRealType) (*q);
1543 if (((MagickRealType) *s <=
1544 (v-(MagickRealType) ScaleCharToQuantum(2))) &&
1545 ((MagickRealType) *r < v))
1546 v-=(MagickRealType) ScaleCharToQuantum(1);
1547 *p=(Quantum) v;
1548 p++;
1549 q++;
1550 r++;
1551 s++;
1552 }
1553 p++;
1554 q++;
1555 r++;
1556 s++;
1557 }
1558}
1559
1560MagickExport Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1561{
1562#define DespeckleImageTag "Despeckle/Image"
1563
cristy2407fc22009-09-11 00:55:25 +00001564 CacheView
1565 *despeckle_view,
1566 *image_view;
1567
cristy3ed852e2009-09-05 21:47:34 +00001568 Image
1569 *despeckle_image;
1570
cristy3ed852e2009-09-05 21:47:34 +00001571 MagickBooleanType
1572 status;
1573
cristya58c3172011-02-19 19:23:11 +00001574 register ssize_t
1575 i;
1576
cristy3ed852e2009-09-05 21:47:34 +00001577 Quantum
cristy65b9f392011-02-22 14:22:54 +00001578 *restrict buffers,
1579 *restrict pixels;
cristy3ed852e2009-09-05 21:47:34 +00001580
1581 size_t
cristya58c3172011-02-19 19:23:11 +00001582 length,
1583 number_channels;
cristy117ff172010-08-15 21:35:32 +00001584
cristybb503372010-05-27 20:51:26 +00001585 static const ssize_t
cristy691a29e2009-09-11 00:44:10 +00001586 X[4] = {0, 1, 1,-1},
1587 Y[4] = {1, 0, 1, 1};
cristy3ed852e2009-09-05 21:47:34 +00001588
cristy3ed852e2009-09-05 21:47:34 +00001589 /*
1590 Allocate despeckled image.
1591 */
1592 assert(image != (const Image *) NULL);
1593 assert(image->signature == MagickSignature);
1594 if (image->debug != MagickFalse)
1595 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1596 assert(exception != (ExceptionInfo *) NULL);
1597 assert(exception->signature == MagickSignature);
1598 despeckle_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1599 exception);
1600 if (despeckle_image == (Image *) NULL)
1601 return((Image *) NULL);
1602 if (SetImageStorageClass(despeckle_image,DirectClass) == MagickFalse)
1603 {
1604 InheritException(exception,&despeckle_image->exception);
1605 despeckle_image=DestroyImage(despeckle_image);
1606 return((Image *) NULL);
1607 }
1608 /*
1609 Allocate image buffers.
1610 */
1611 length=(size_t) ((image->columns+2)*(image->rows+2));
cristy65b9f392011-02-22 14:22:54 +00001612 pixels=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1613 buffers=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1614 if ((pixels == (Quantum *) NULL) || (buffers == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001615 {
cristy65b9f392011-02-22 14:22:54 +00001616 if (buffers != (Quantum *) NULL)
1617 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1618 if (pixels != (Quantum *) NULL)
1619 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001620 despeckle_image=DestroyImage(despeckle_image);
1621 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1622 }
1623 /*
1624 Reduce speckle in the image.
1625 */
1626 status=MagickTrue;
cristy109695a2011-02-19 19:38:14 +00001627 number_channels=(size_t) (image->colorspace == CMYKColorspace ? 5 : 4);
cristy3ed852e2009-09-05 21:47:34 +00001628 image_view=AcquireCacheView(image);
1629 despeckle_view=AcquireCacheView(despeckle_image);
cristy8df3d002011-02-19 19:40:59 +00001630 for (i=0; i < (ssize_t) number_channels; i++)
cristy3ed852e2009-09-05 21:47:34 +00001631 {
cristy3ed852e2009-09-05 21:47:34 +00001632 register Quantum
1633 *buffer,
1634 *pixel;
1635
cristyc1488b52011-02-19 18:54:15 +00001636 register ssize_t
cristya58c3172011-02-19 19:23:11 +00001637 k,
cristyc1488b52011-02-19 18:54:15 +00001638 x;
1639
cristy117ff172010-08-15 21:35:32 +00001640 ssize_t
1641 j,
1642 y;
1643
cristy3ed852e2009-09-05 21:47:34 +00001644 if (status == MagickFalse)
1645 continue;
cristy65b9f392011-02-22 14:22:54 +00001646 pixel=pixels;
cristy3ed852e2009-09-05 21:47:34 +00001647 (void) ResetMagickMemory(pixel,0,length*sizeof(*pixel));
cristy65b9f392011-02-22 14:22:54 +00001648 buffer=buffers;
cristybb503372010-05-27 20:51:26 +00001649 j=(ssize_t) image->columns+2;
1650 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001651 {
cristy4c08aed2011-07-01 19:47:50 +00001652 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001653 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001654
1655 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001656 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001657 break;
1658 j++;
cristybb503372010-05-27 20:51:26 +00001659 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001660 {
cristya58c3172011-02-19 19:23:11 +00001661 switch (i)
cristy3ed852e2009-09-05 21:47:34 +00001662 {
cristy4c08aed2011-07-01 19:47:50 +00001663 case 0: pixel[j]=GetPixelRed(image,p); break;
1664 case 1: pixel[j]=GetPixelGreen(image,p); break;
1665 case 2: pixel[j]=GetPixelBlue(image,p); break;
1666 case 3: pixel[j]=GetPixelAlpha(image,p); break;
1667 case 4: pixel[j]=GetPixelBlack(image,p); break;
cristy3ed852e2009-09-05 21:47:34 +00001668 default: break;
1669 }
cristyed231572011-07-14 02:18:59 +00001670 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001671 j++;
1672 }
1673 j++;
1674 }
cristy3ed852e2009-09-05 21:47:34 +00001675 (void) ResetMagickMemory(buffer,0,length*sizeof(*buffer));
cristya58c3172011-02-19 19:23:11 +00001676 for (k=0; k < 4; k++)
cristy3ed852e2009-09-05 21:47:34 +00001677 {
cristya58c3172011-02-19 19:23:11 +00001678 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,1);
1679 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,1);
1680 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,-1);
1681 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,-1);
cristy3ed852e2009-09-05 21:47:34 +00001682 }
cristybb503372010-05-27 20:51:26 +00001683 j=(ssize_t) image->columns+2;
1684 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001685 {
1686 MagickBooleanType
1687 sync;
1688
cristy4c08aed2011-07-01 19:47:50 +00001689 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001690 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001691
1692 q=GetCacheViewAuthenticPixels(despeckle_view,0,y,despeckle_image->columns,
1693 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001694 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001695 break;
1696 j++;
cristybb503372010-05-27 20:51:26 +00001697 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001698 {
cristya58c3172011-02-19 19:23:11 +00001699 switch (i)
cristy3ed852e2009-09-05 21:47:34 +00001700 {
cristy4c08aed2011-07-01 19:47:50 +00001701 case 0: SetPixelRed(despeckle_image,pixel[j],q); break;
1702 case 1: SetPixelGreen(despeckle_image,pixel[j],q); break;
1703 case 2: SetPixelBlue(despeckle_image,pixel[j],q); break;
1704 case 3: SetPixelAlpha(despeckle_image,pixel[j],q); break;
1705 case 4: SetPixelBlack(despeckle_image,pixel[j],q); break;
cristy3ed852e2009-09-05 21:47:34 +00001706 default: break;
1707 }
cristyed231572011-07-14 02:18:59 +00001708 q+=GetPixelChannels(despeckle_image);
cristy3ed852e2009-09-05 21:47:34 +00001709 j++;
1710 }
1711 sync=SyncCacheViewAuthenticPixels(despeckle_view,exception);
1712 if (sync == MagickFalse)
1713 {
1714 status=MagickFalse;
1715 break;
1716 }
1717 j++;
1718 }
1719 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1720 {
1721 MagickBooleanType
1722 proceed;
1723
cristya58c3172011-02-19 19:23:11 +00001724 proceed=SetImageProgress(image,DespeckleImageTag,(MagickOffsetType) i,
1725 number_channels);
cristy3ed852e2009-09-05 21:47:34 +00001726 if (proceed == MagickFalse)
1727 status=MagickFalse;
1728 }
1729 }
1730 despeckle_view=DestroyCacheView(despeckle_view);
1731 image_view=DestroyCacheView(image_view);
cristy65b9f392011-02-22 14:22:54 +00001732 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1733 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001734 despeckle_image->type=image->type;
1735 if (status == MagickFalse)
1736 despeckle_image=DestroyImage(despeckle_image);
1737 return(despeckle_image);
1738}
1739
1740/*
1741%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1742% %
1743% %
1744% %
1745% E d g e I m a g e %
1746% %
1747% %
1748% %
1749%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1750%
1751% EdgeImage() finds edges in an image. Radius defines the radius of the
1752% convolution filter. Use a radius of 0 and EdgeImage() selects a suitable
1753% radius for you.
1754%
1755% The format of the EdgeImage method is:
1756%
1757% Image *EdgeImage(const Image *image,const double radius,
1758% ExceptionInfo *exception)
1759%
1760% A description of each parameter follows:
1761%
1762% o image: the image.
1763%
1764% o radius: the radius of the pixel neighborhood.
1765%
1766% o exception: return any errors or warnings in this structure.
1767%
1768*/
1769MagickExport Image *EdgeImage(const Image *image,const double radius,
1770 ExceptionInfo *exception)
1771{
1772 Image
1773 *edge_image;
1774
cristy41cbe682011-07-15 19:12:37 +00001775 KernelInfo
1776 *kernel_info;
cristy3ed852e2009-09-05 21:47:34 +00001777
cristybb503372010-05-27 20:51:26 +00001778 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001779 i;
1780
cristybb503372010-05-27 20:51:26 +00001781 size_t
cristy3ed852e2009-09-05 21:47:34 +00001782 width;
1783
cristy41cbe682011-07-15 19:12:37 +00001784 ssize_t
1785 j,
1786 u,
1787 v;
1788
cristy3ed852e2009-09-05 21:47:34 +00001789 assert(image != (const Image *) NULL);
1790 assert(image->signature == MagickSignature);
1791 if (image->debug != MagickFalse)
1792 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1793 assert(exception != (ExceptionInfo *) NULL);
1794 assert(exception->signature == MagickSignature);
1795 width=GetOptimalKernelWidth1D(radius,0.5);
cristy5e6be1e2011-07-16 01:23:39 +00001796 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00001797 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001798 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00001799 kernel_info->width=width;
1800 kernel_info->height=width;
cristy41cbe682011-07-15 19:12:37 +00001801 kernel_info->values=(double *) AcquireAlignedMemory(kernel_info->width,
1802 kernel_info->width*sizeof(*kernel_info->values));
1803 if (kernel_info->values == (double *) NULL)
1804 {
1805 kernel_info=DestroyKernelInfo(kernel_info);
1806 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1807 }
1808 j=(ssize_t) kernel_info->width/2;
1809 i=0;
1810 for (v=(-j); v <= j; v++)
1811 {
1812 for (u=(-j); u <= j; u++)
1813 {
1814 kernel_info->values[i]=(-1.0);
1815 i++;
1816 }
1817 }
1818 kernel_info->values[i/2]=(double) (width*width-1.0);
cristy0a922382011-07-16 15:30:34 +00001819 kernel_info->bias=image->bias;
cristy5e6be1e2011-07-16 01:23:39 +00001820 edge_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00001821 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00001822 return(edge_image);
1823}
1824
1825/*
1826%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1827% %
1828% %
1829% %
1830% E m b o s s I m a g e %
1831% %
1832% %
1833% %
1834%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1835%
1836% EmbossImage() returns a grayscale image with a three-dimensional effect.
1837% We convolve the image with a Gaussian operator of the given radius and
1838% standard deviation (sigma). For reasonable results, radius should be
1839% larger than sigma. Use a radius of 0 and Emboss() selects a suitable
1840% radius for you.
1841%
1842% The format of the EmbossImage method is:
1843%
1844% Image *EmbossImage(const Image *image,const double radius,
1845% const double sigma,ExceptionInfo *exception)
1846%
1847% A description of each parameter follows:
1848%
1849% o image: the image.
1850%
1851% o radius: the radius of the pixel neighborhood.
1852%
1853% o sigma: the standard deviation of the Gaussian, in pixels.
1854%
1855% o exception: return any errors or warnings in this structure.
1856%
1857*/
1858MagickExport Image *EmbossImage(const Image *image,const double radius,
1859 const double sigma,ExceptionInfo *exception)
1860{
cristy3ed852e2009-09-05 21:47:34 +00001861 Image
1862 *emboss_image;
1863
cristy41cbe682011-07-15 19:12:37 +00001864 KernelInfo
1865 *kernel_info;
1866
cristybb503372010-05-27 20:51:26 +00001867 register ssize_t
cristy47e00502009-12-17 19:19:57 +00001868 i;
1869
cristybb503372010-05-27 20:51:26 +00001870 size_t
cristy3ed852e2009-09-05 21:47:34 +00001871 width;
1872
cristy117ff172010-08-15 21:35:32 +00001873 ssize_t
1874 j,
1875 k,
1876 u,
1877 v;
1878
cristy41cbe682011-07-15 19:12:37 +00001879 assert(image != (const Image *) NULL);
cristy3ed852e2009-09-05 21:47:34 +00001880 assert(image->signature == MagickSignature);
1881 if (image->debug != MagickFalse)
1882 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1883 assert(exception != (ExceptionInfo *) NULL);
1884 assert(exception->signature == MagickSignature);
1885 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00001886 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00001887 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001888 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00001889 kernel_info->width=width;
1890 kernel_info->height=width;
cristy41cbe682011-07-15 19:12:37 +00001891 kernel_info->values=(double *) AcquireAlignedMemory(kernel_info->width,
1892 kernel_info->width*sizeof(*kernel_info->values));
1893 if (kernel_info->values == (double *) NULL)
1894 {
1895 kernel_info=DestroyKernelInfo(kernel_info);
1896 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1897 }
1898 j=(ssize_t) kernel_info->width/2;
cristy47e00502009-12-17 19:19:57 +00001899 k=j;
1900 i=0;
1901 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00001902 {
cristy47e00502009-12-17 19:19:57 +00001903 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00001904 {
cristy41cbe682011-07-15 19:12:37 +00001905 kernel_info->values[i]=(double) (((u < 0) || (v < 0) ? -8.0 : 8.0)*
cristy47e00502009-12-17 19:19:57 +00001906 exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
cristy4205a3c2010-09-12 20:19:59 +00001907 (2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +00001908 if (u != k)
cristy41cbe682011-07-15 19:12:37 +00001909 kernel_info->values[i]=0.0;
cristy3ed852e2009-09-05 21:47:34 +00001910 i++;
1911 }
cristy47e00502009-12-17 19:19:57 +00001912 k--;
cristy3ed852e2009-09-05 21:47:34 +00001913 }
cristy0a922382011-07-16 15:30:34 +00001914 kernel_info->bias=image->bias;
cristy5e6be1e2011-07-16 01:23:39 +00001915 emboss_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00001916 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00001917 if (emboss_image != (Image *) NULL)
1918 (void) EqualizeImage(emboss_image);
cristy3ed852e2009-09-05 21:47:34 +00001919 return(emboss_image);
1920}
1921
1922/*
1923%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1924% %
1925% %
1926% %
1927% G a u s s i a n B l u r I m a g e %
1928% %
1929% %
1930% %
1931%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1932%
1933% GaussianBlurImage() blurs an image. We convolve the image with a
1934% Gaussian operator of the given radius and standard deviation (sigma).
1935% For reasonable results, the radius should be larger than sigma. Use a
1936% radius of 0 and GaussianBlurImage() selects a suitable radius for you
1937%
1938% The format of the GaussianBlurImage method is:
1939%
1940% Image *GaussianBlurImage(const Image *image,onst double radius,
1941% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001942%
1943% A description of each parameter follows:
1944%
1945% o image: the image.
1946%
cristy3ed852e2009-09-05 21:47:34 +00001947% o radius: the radius of the Gaussian, in pixels, not counting the center
1948% pixel.
1949%
1950% o sigma: the standard deviation of the Gaussian, in pixels.
1951%
1952% o exception: return any errors or warnings in this structure.
1953%
1954*/
cristy41cbe682011-07-15 19:12:37 +00001955MagickExport Image *GaussianBlurImage(const Image *image,const double radius,
1956 const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001957{
cristy3ed852e2009-09-05 21:47:34 +00001958 Image
1959 *blur_image;
1960
cristy41cbe682011-07-15 19:12:37 +00001961 KernelInfo
1962 *kernel_info;
1963
cristybb503372010-05-27 20:51:26 +00001964 register ssize_t
cristy47e00502009-12-17 19:19:57 +00001965 i;
1966
cristybb503372010-05-27 20:51:26 +00001967 size_t
cristy3ed852e2009-09-05 21:47:34 +00001968 width;
1969
cristy117ff172010-08-15 21:35:32 +00001970 ssize_t
1971 j,
1972 u,
1973 v;
1974
cristy3ed852e2009-09-05 21:47:34 +00001975 assert(image != (const Image *) NULL);
1976 assert(image->signature == MagickSignature);
1977 if (image->debug != MagickFalse)
1978 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1979 assert(exception != (ExceptionInfo *) NULL);
1980 assert(exception->signature == MagickSignature);
1981 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00001982 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00001983 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001984 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00001985 (void) ResetMagickMemory(kernel_info,0,sizeof(*kernel_info));
1986 kernel_info->width=width;
1987 kernel_info->height=width;
1988 kernel_info->signature=MagickSignature;
1989 kernel_info->values=(double *) AcquireAlignedMemory(kernel_info->width,
1990 kernel_info->width*sizeof(*kernel_info->values));
1991 if (kernel_info->values == (double *) NULL)
1992 {
1993 kernel_info=DestroyKernelInfo(kernel_info);
1994 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1995 }
1996 j=(ssize_t) kernel_info->width/2;
cristy3ed852e2009-09-05 21:47:34 +00001997 i=0;
cristy47e00502009-12-17 19:19:57 +00001998 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00001999 {
cristy47e00502009-12-17 19:19:57 +00002000 for (u=(-j); u <= j; u++)
cristy41cbe682011-07-15 19:12:37 +00002001 {
2002 kernel_info->values[i]=(double) (exp(-((double) u*u+v*v)/(2.0*
2003 MagickSigma*MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
2004 i++;
2005 }
cristy3ed852e2009-09-05 21:47:34 +00002006 }
cristy0a922382011-07-16 15:30:34 +00002007 kernel_info->bias=image->bias;
cristy5e6be1e2011-07-16 01:23:39 +00002008 blur_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00002009 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00002010 return(blur_image);
2011}
2012
2013/*
2014%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2015% %
2016% %
2017% %
cristy3ed852e2009-09-05 21:47:34 +00002018% M o t i o n B l u r I m a g e %
2019% %
2020% %
2021% %
2022%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2023%
2024% MotionBlurImage() simulates motion blur. We convolve the image with a
2025% Gaussian operator of the given radius and standard deviation (sigma).
2026% For reasonable results, radius should be larger than sigma. Use a
2027% radius of 0 and MotionBlurImage() selects a suitable radius for you.
2028% Angle gives the angle of the blurring motion.
2029%
2030% Andrew Protano contributed this effect.
2031%
2032% The format of the MotionBlurImage method is:
2033%
2034% Image *MotionBlurImage(const Image *image,const double radius,
2035% const double sigma,const double angle,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002036%
2037% A description of each parameter follows:
2038%
2039% o image: the image.
2040%
cristy3ed852e2009-09-05 21:47:34 +00002041% o radius: the radius of the Gaussian, in pixels, not counting
2042% the center pixel.
2043%
2044% o sigma: the standard deviation of the Gaussian, in pixels.
2045%
cristycee97112010-05-28 00:44:52 +00002046% o angle: Apply the effect along this angle.
cristy3ed852e2009-09-05 21:47:34 +00002047%
2048% o exception: return any errors or warnings in this structure.
2049%
2050*/
2051
cristybb503372010-05-27 20:51:26 +00002052static double *GetMotionBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +00002053{
cristy3ed852e2009-09-05 21:47:34 +00002054 double
cristy47e00502009-12-17 19:19:57 +00002055 *kernel,
cristy3ed852e2009-09-05 21:47:34 +00002056 normalize;
2057
cristybb503372010-05-27 20:51:26 +00002058 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002059 i;
2060
2061 /*
cristy47e00502009-12-17 19:19:57 +00002062 Generate a 1-D convolution kernel.
cristy3ed852e2009-09-05 21:47:34 +00002063 */
2064 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
2065 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
2066 if (kernel == (double *) NULL)
2067 return(kernel);
cristy3ed852e2009-09-05 21:47:34 +00002068 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00002069 for (i=0; i < (ssize_t) width; i++)
cristy47e00502009-12-17 19:19:57 +00002070 {
cristy4205a3c2010-09-12 20:19:59 +00002071 kernel[i]=(double) (exp((-((double) i*i)/(double) (2.0*MagickSigma*
2072 MagickSigma)))/(MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00002073 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +00002074 }
cristybb503372010-05-27 20:51:26 +00002075 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002076 kernel[i]/=normalize;
2077 return(kernel);
2078}
2079
cristyf4ad9df2011-07-08 16:49:03 +00002080MagickExport Image *MotionBlurImage(const Image *image,
2081 const double radius,const double sigma,const double angle,
2082 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002083{
cristyc4c8d132010-01-07 01:58:38 +00002084 CacheView
2085 *blur_view,
2086 *image_view;
2087
cristy3ed852e2009-09-05 21:47:34 +00002088 double
2089 *kernel;
2090
2091 Image
2092 *blur_image;
2093
cristy3ed852e2009-09-05 21:47:34 +00002094 MagickBooleanType
2095 status;
2096
cristybb503372010-05-27 20:51:26 +00002097 MagickOffsetType
2098 progress;
2099
cristy4c08aed2011-07-01 19:47:50 +00002100 PixelInfo
cristyddd82202009-11-03 20:14:50 +00002101 bias;
cristy3ed852e2009-09-05 21:47:34 +00002102
2103 OffsetInfo
2104 *offset;
2105
2106 PointInfo
2107 point;
2108
cristybb503372010-05-27 20:51:26 +00002109 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002110 i;
2111
cristybb503372010-05-27 20:51:26 +00002112 size_t
cristy3ed852e2009-09-05 21:47:34 +00002113 width;
2114
cristybb503372010-05-27 20:51:26 +00002115 ssize_t
2116 y;
2117
cristy3ed852e2009-09-05 21:47:34 +00002118 assert(image != (Image *) NULL);
2119 assert(image->signature == MagickSignature);
2120 if (image->debug != MagickFalse)
2121 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2122 assert(exception != (ExceptionInfo *) NULL);
2123 width=GetOptimalKernelWidth1D(radius,sigma);
2124 kernel=GetMotionBlurKernel(width,sigma);
2125 if (kernel == (double *) NULL)
2126 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2127 offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset));
2128 if (offset == (OffsetInfo *) NULL)
2129 {
2130 kernel=(double *) RelinquishMagickMemory(kernel);
2131 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2132 }
2133 blur_image=CloneImage(image,0,0,MagickTrue,exception);
2134 if (blur_image == (Image *) NULL)
2135 {
2136 kernel=(double *) RelinquishMagickMemory(kernel);
2137 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2138 return((Image *) NULL);
2139 }
2140 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
2141 {
2142 kernel=(double *) RelinquishMagickMemory(kernel);
2143 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2144 InheritException(exception,&blur_image->exception);
2145 blur_image=DestroyImage(blur_image);
2146 return((Image *) NULL);
2147 }
2148 point.x=(double) width*sin(DegreesToRadians(angle));
2149 point.y=(double) width*cos(DegreesToRadians(angle));
cristybb503372010-05-27 20:51:26 +00002150 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002151 {
cristybb503372010-05-27 20:51:26 +00002152 offset[i].x=(ssize_t) ceil((double) (i*point.y)/hypot(point.x,point.y)-0.5);
2153 offset[i].y=(ssize_t) ceil((double) (i*point.x)/hypot(point.x,point.y)-0.5);
cristy3ed852e2009-09-05 21:47:34 +00002154 }
2155 /*
2156 Motion blur image.
2157 */
2158 status=MagickTrue;
2159 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00002160 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00002161 image_view=AcquireCacheView(image);
2162 blur_view=AcquireCacheView(blur_image);
cristyb557a152011-02-22 12:14:30 +00002163#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00002164 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00002165#endif
cristybb503372010-05-27 20:51:26 +00002166 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002167 {
cristy4c08aed2011-07-01 19:47:50 +00002168 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002169 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002170
cristy117ff172010-08-15 21:35:32 +00002171 register ssize_t
2172 x;
2173
cristy3ed852e2009-09-05 21:47:34 +00002174 if (status == MagickFalse)
2175 continue;
2176 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
2177 exception);
cristy4c08aed2011-07-01 19:47:50 +00002178 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002179 {
2180 status=MagickFalse;
2181 continue;
2182 }
cristybb503372010-05-27 20:51:26 +00002183 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002184 {
cristy4c08aed2011-07-01 19:47:50 +00002185 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002186 qixel;
2187
2188 PixelPacket
2189 pixel;
2190
2191 register double
cristyc47d1f82009-11-26 01:44:43 +00002192 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00002193
cristybb503372010-05-27 20:51:26 +00002194 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002195 i;
2196
cristy3ed852e2009-09-05 21:47:34 +00002197 k=kernel;
cristyddd82202009-11-03 20:14:50 +00002198 qixel=bias;
cristyed231572011-07-14 02:18:59 +00002199 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00002200 {
cristybb503372010-05-27 20:51:26 +00002201 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002202 {
2203 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
2204 offset[i].y,&pixel,exception);
2205 qixel.red+=(*k)*pixel.red;
2206 qixel.green+=(*k)*pixel.green;
2207 qixel.blue+=(*k)*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00002208 qixel.alpha+=(*k)*pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00002209 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00002210 qixel.black+=(*k)*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00002211 k++;
2212 }
cristyed231572011-07-14 02:18:59 +00002213 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002214 SetPixelRed(blur_image,
2215 ClampToQuantum(qixel.red),q);
cristyed231572011-07-14 02:18:59 +00002216 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002217 SetPixelGreen(blur_image,
2218 ClampToQuantum(qixel.green),q);
cristyed231572011-07-14 02:18:59 +00002219 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002220 SetPixelBlue(blur_image,
2221 ClampToQuantum(qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00002222 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002223 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002224 SetPixelBlack(blur_image,
2225 ClampToQuantum(qixel.black),q);
cristyed231572011-07-14 02:18:59 +00002226 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002227 SetPixelAlpha(blur_image,
2228 ClampToQuantum(qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00002229 }
2230 else
2231 {
2232 MagickRealType
2233 alpha,
2234 gamma;
2235
2236 alpha=0.0;
2237 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00002238 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002239 {
2240 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
2241 offset[i].y,&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00002242 alpha=(MagickRealType) (QuantumScale*pixel.alpha);
cristy3ed852e2009-09-05 21:47:34 +00002243 qixel.red+=(*k)*alpha*pixel.red;
2244 qixel.green+=(*k)*alpha*pixel.green;
2245 qixel.blue+=(*k)*alpha*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00002246 qixel.alpha+=(*k)*pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00002247 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00002248 qixel.black+=(*k)*alpha*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00002249 gamma+=(*k)*alpha;
2250 k++;
2251 }
2252 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00002253 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002254 SetPixelRed(blur_image,
2255 ClampToQuantum(gamma*qixel.red),q);
cristyed231572011-07-14 02:18:59 +00002256 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002257 SetPixelGreen(blur_image,
2258 ClampToQuantum(gamma*qixel.green),q);
cristyed231572011-07-14 02:18:59 +00002259 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002260 SetPixelBlue(blur_image,
2261 ClampToQuantum(gamma*qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00002262 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002263 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002264 SetPixelBlack(blur_image,
2265 ClampToQuantum(gamma*qixel.black),q);
cristyed231572011-07-14 02:18:59 +00002266 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002267 SetPixelAlpha(blur_image,
2268 ClampToQuantum(qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00002269 }
cristyed231572011-07-14 02:18:59 +00002270 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00002271 }
2272 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
2273 status=MagickFalse;
2274 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2275 {
2276 MagickBooleanType
2277 proceed;
2278
cristyb557a152011-02-22 12:14:30 +00002279#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00002280 #pragma omp critical (MagickCore_MotionBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00002281#endif
2282 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
2283 if (proceed == MagickFalse)
2284 status=MagickFalse;
2285 }
2286 }
2287 blur_view=DestroyCacheView(blur_view);
2288 image_view=DestroyCacheView(image_view);
2289 kernel=(double *) RelinquishMagickMemory(kernel);
2290 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2291 if (status == MagickFalse)
2292 blur_image=DestroyImage(blur_image);
2293 return(blur_image);
2294}
2295
2296/*
2297%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2298% %
2299% %
2300% %
2301% P r e v i e w I m a g e %
2302% %
2303% %
2304% %
2305%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2306%
2307% PreviewImage() tiles 9 thumbnails of the specified image with an image
2308% processing operation applied with varying parameters. This may be helpful
2309% pin-pointing an appropriate parameter for a particular image processing
2310% operation.
2311%
2312% The format of the PreviewImages method is:
2313%
2314% Image *PreviewImages(const Image *image,const PreviewType preview,
2315% ExceptionInfo *exception)
2316%
2317% A description of each parameter follows:
2318%
2319% o image: the image.
2320%
2321% o preview: the image processing operation.
2322%
2323% o exception: return any errors or warnings in this structure.
2324%
2325*/
2326MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
2327 ExceptionInfo *exception)
2328{
2329#define NumberTiles 9
2330#define PreviewImageTag "Preview/Image"
2331#define DefaultPreviewGeometry "204x204+10+10"
2332
2333 char
2334 factor[MaxTextExtent],
2335 label[MaxTextExtent];
2336
2337 double
2338 degrees,
2339 gamma,
2340 percentage,
2341 radius,
2342 sigma,
2343 threshold;
2344
2345 Image
2346 *images,
2347 *montage_image,
2348 *preview_image,
2349 *thumbnail;
2350
2351 ImageInfo
2352 *preview_info;
2353
cristy3ed852e2009-09-05 21:47:34 +00002354 MagickBooleanType
2355 proceed;
2356
2357 MontageInfo
2358 *montage_info;
2359
2360 QuantizeInfo
2361 quantize_info;
2362
2363 RectangleInfo
2364 geometry;
2365
cristybb503372010-05-27 20:51:26 +00002366 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002367 i,
2368 x;
2369
cristybb503372010-05-27 20:51:26 +00002370 size_t
cristy3ed852e2009-09-05 21:47:34 +00002371 colors;
2372
cristy117ff172010-08-15 21:35:32 +00002373 ssize_t
2374 y;
2375
cristy3ed852e2009-09-05 21:47:34 +00002376 /*
2377 Open output image file.
2378 */
2379 assert(image != (Image *) NULL);
2380 assert(image->signature == MagickSignature);
2381 if (image->debug != MagickFalse)
2382 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2383 colors=2;
2384 degrees=0.0;
2385 gamma=(-0.2f);
2386 preview_info=AcquireImageInfo();
2387 SetGeometry(image,&geometry);
2388 (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
2389 &geometry.width,&geometry.height);
2390 images=NewImageList();
2391 percentage=12.5;
2392 GetQuantizeInfo(&quantize_info);
2393 radius=0.0;
2394 sigma=1.0;
2395 threshold=0.0;
2396 x=0;
2397 y=0;
2398 for (i=0; i < NumberTiles; i++)
2399 {
2400 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
2401 if (thumbnail == (Image *) NULL)
2402 break;
2403 (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
2404 (void *) NULL);
2405 (void) SetImageProperty(thumbnail,"label",DefaultTileLabel);
2406 if (i == (NumberTiles/2))
2407 {
2408 (void) QueryColorDatabase("#dfdfdf",&thumbnail->matte_color,exception);
2409 AppendImageToList(&images,thumbnail);
2410 continue;
2411 }
2412 switch (preview)
2413 {
2414 case RotatePreview:
2415 {
2416 degrees+=45.0;
2417 preview_image=RotateImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002418 (void) FormatLocaleString(label,MaxTextExtent,"rotate %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002419 break;
2420 }
2421 case ShearPreview:
2422 {
2423 degrees+=5.0;
2424 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002425 (void) FormatLocaleString(label,MaxTextExtent,"shear %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002426 degrees,2.0*degrees);
2427 break;
2428 }
2429 case RollPreview:
2430 {
cristybb503372010-05-27 20:51:26 +00002431 x=(ssize_t) ((i+1)*thumbnail->columns)/NumberTiles;
2432 y=(ssize_t) ((i+1)*thumbnail->rows)/NumberTiles;
cristy3ed852e2009-09-05 21:47:34 +00002433 preview_image=RollImage(thumbnail,x,y,exception);
cristyb51dff52011-05-19 16:55:47 +00002434 (void) FormatLocaleString(label,MaxTextExtent,"roll %+.20gx%+.20g",
cristye8c25f92010-06-03 00:53:06 +00002435 (double) x,(double) y);
cristy3ed852e2009-09-05 21:47:34 +00002436 break;
2437 }
2438 case HuePreview:
2439 {
2440 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2441 if (preview_image == (Image *) NULL)
2442 break;
cristyb51dff52011-05-19 16:55:47 +00002443 (void) FormatLocaleString(factor,MaxTextExtent,"100,100,%g",
cristy3ed852e2009-09-05 21:47:34 +00002444 2.0*percentage);
2445 (void) ModulateImage(preview_image,factor);
cristyb51dff52011-05-19 16:55:47 +00002446 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002447 break;
2448 }
2449 case SaturationPreview:
2450 {
2451 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2452 if (preview_image == (Image *) NULL)
2453 break;
cristyb51dff52011-05-19 16:55:47 +00002454 (void) FormatLocaleString(factor,MaxTextExtent,"100,%g",
cristy8cd5b312010-01-07 01:10:24 +00002455 2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00002456 (void) ModulateImage(preview_image,factor);
cristyb51dff52011-05-19 16:55:47 +00002457 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002458 break;
2459 }
2460 case BrightnessPreview:
2461 {
2462 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2463 if (preview_image == (Image *) NULL)
2464 break;
cristyb51dff52011-05-19 16:55:47 +00002465 (void) FormatLocaleString(factor,MaxTextExtent,"%g",2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00002466 (void) ModulateImage(preview_image,factor);
cristyb51dff52011-05-19 16:55:47 +00002467 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002468 break;
2469 }
2470 case GammaPreview:
2471 default:
2472 {
2473 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2474 if (preview_image == (Image *) NULL)
2475 break;
2476 gamma+=0.4f;
cristy50fbc382011-07-07 02:19:17 +00002477 (void) GammaImage(preview_image,gamma);
cristyb51dff52011-05-19 16:55:47 +00002478 (void) FormatLocaleString(label,MaxTextExtent,"gamma %g",gamma);
cristy3ed852e2009-09-05 21:47:34 +00002479 break;
2480 }
2481 case SpiffPreview:
2482 {
2483 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2484 if (preview_image != (Image *) NULL)
2485 for (x=0; x < i; x++)
2486 (void) ContrastImage(preview_image,MagickTrue);
cristyb51dff52011-05-19 16:55:47 +00002487 (void) FormatLocaleString(label,MaxTextExtent,"contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002488 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002489 break;
2490 }
2491 case DullPreview:
2492 {
2493 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2494 if (preview_image == (Image *) NULL)
2495 break;
2496 for (x=0; x < i; x++)
2497 (void) ContrastImage(preview_image,MagickFalse);
cristyb51dff52011-05-19 16:55:47 +00002498 (void) FormatLocaleString(label,MaxTextExtent,"+contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002499 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002500 break;
2501 }
2502 case GrayscalePreview:
2503 {
2504 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2505 if (preview_image == (Image *) NULL)
2506 break;
2507 colors<<=1;
2508 quantize_info.number_colors=colors;
2509 quantize_info.colorspace=GRAYColorspace;
2510 (void) QuantizeImage(&quantize_info,preview_image);
cristyb51dff52011-05-19 16:55:47 +00002511 (void) FormatLocaleString(label,MaxTextExtent,
cristye8c25f92010-06-03 00:53:06 +00002512 "-colorspace gray -colors %.20g",(double) colors);
cristy3ed852e2009-09-05 21:47:34 +00002513 break;
2514 }
2515 case QuantizePreview:
2516 {
2517 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2518 if (preview_image == (Image *) NULL)
2519 break;
2520 colors<<=1;
2521 quantize_info.number_colors=colors;
2522 (void) QuantizeImage(&quantize_info,preview_image);
cristyb51dff52011-05-19 16:55:47 +00002523 (void) FormatLocaleString(label,MaxTextExtent,"colors %.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00002524 colors);
cristy3ed852e2009-09-05 21:47:34 +00002525 break;
2526 }
2527 case DespecklePreview:
2528 {
2529 for (x=0; x < (i-1); x++)
2530 {
2531 preview_image=DespeckleImage(thumbnail,exception);
2532 if (preview_image == (Image *) NULL)
2533 break;
2534 thumbnail=DestroyImage(thumbnail);
2535 thumbnail=preview_image;
2536 }
2537 preview_image=DespeckleImage(thumbnail,exception);
2538 if (preview_image == (Image *) NULL)
2539 break;
cristyb51dff52011-05-19 16:55:47 +00002540 (void) FormatLocaleString(label,MaxTextExtent,"despeckle (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002541 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002542 break;
2543 }
2544 case ReduceNoisePreview:
2545 {
cristy95c38342011-03-18 22:39:51 +00002546 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) radius,
2547 (size_t) radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002548 (void) FormatLocaleString(label,MaxTextExtent,"noise %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002549 break;
2550 }
2551 case AddNoisePreview:
2552 {
2553 switch ((int) i)
2554 {
2555 case 0:
2556 {
2557 (void) CopyMagickString(factor,"uniform",MaxTextExtent);
2558 break;
2559 }
2560 case 1:
2561 {
2562 (void) CopyMagickString(factor,"gaussian",MaxTextExtent);
2563 break;
2564 }
2565 case 2:
2566 {
2567 (void) CopyMagickString(factor,"multiplicative",MaxTextExtent);
2568 break;
2569 }
2570 case 3:
2571 {
2572 (void) CopyMagickString(factor,"impulse",MaxTextExtent);
2573 break;
2574 }
2575 case 4:
2576 {
2577 (void) CopyMagickString(factor,"laplacian",MaxTextExtent);
2578 break;
2579 }
2580 case 5:
2581 {
2582 (void) CopyMagickString(factor,"Poisson",MaxTextExtent);
2583 break;
2584 }
2585 default:
2586 {
2587 (void) CopyMagickString(thumbnail->magick,"NULL",MaxTextExtent);
2588 break;
2589 }
2590 }
cristyd76c51e2011-03-26 00:21:26 +00002591 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) i,
2592 (size_t) i,exception);
cristyb51dff52011-05-19 16:55:47 +00002593 (void) FormatLocaleString(label,MaxTextExtent,"+noise %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002594 break;
2595 }
2596 case SharpenPreview:
2597 {
2598 preview_image=SharpenImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00002599 (void) FormatLocaleString(label,MaxTextExtent,"sharpen %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002600 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002601 break;
2602 }
2603 case BlurPreview:
2604 {
2605 preview_image=BlurImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00002606 (void) FormatLocaleString(label,MaxTextExtent,"blur %gx%g",radius,
cristy3ed852e2009-09-05 21:47:34 +00002607 sigma);
2608 break;
2609 }
2610 case ThresholdPreview:
2611 {
2612 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2613 if (preview_image == (Image *) NULL)
2614 break;
2615 (void) BilevelImage(thumbnail,
2616 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
cristyb51dff52011-05-19 16:55:47 +00002617 (void) FormatLocaleString(label,MaxTextExtent,"threshold %g",
cristy3ed852e2009-09-05 21:47:34 +00002618 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
2619 break;
2620 }
2621 case EdgeDetectPreview:
2622 {
2623 preview_image=EdgeImage(thumbnail,radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002624 (void) FormatLocaleString(label,MaxTextExtent,"edge %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002625 break;
2626 }
2627 case SpreadPreview:
2628 {
2629 preview_image=SpreadImage(thumbnail,radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002630 (void) FormatLocaleString(label,MaxTextExtent,"spread %g",
cristy8cd5b312010-01-07 01:10:24 +00002631 radius+0.5);
cristy3ed852e2009-09-05 21:47:34 +00002632 break;
2633 }
2634 case SolarizePreview:
2635 {
2636 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2637 if (preview_image == (Image *) NULL)
2638 break;
2639 (void) SolarizeImage(preview_image,(double) QuantumRange*
2640 percentage/100.0);
cristyb51dff52011-05-19 16:55:47 +00002641 (void) FormatLocaleString(label,MaxTextExtent,"solarize %g",
cristy3ed852e2009-09-05 21:47:34 +00002642 (QuantumRange*percentage)/100.0);
2643 break;
2644 }
2645 case ShadePreview:
2646 {
2647 degrees+=10.0;
2648 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
2649 exception);
cristyb51dff52011-05-19 16:55:47 +00002650 (void) FormatLocaleString(label,MaxTextExtent,"shade %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002651 degrees,degrees);
cristy3ed852e2009-09-05 21:47:34 +00002652 break;
2653 }
2654 case RaisePreview:
2655 {
2656 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2657 if (preview_image == (Image *) NULL)
2658 break;
cristybb503372010-05-27 20:51:26 +00002659 geometry.width=(size_t) (2*i+2);
2660 geometry.height=(size_t) (2*i+2);
cristy3ed852e2009-09-05 21:47:34 +00002661 geometry.x=i/2;
2662 geometry.y=i/2;
2663 (void) RaiseImage(preview_image,&geometry,MagickTrue);
cristyb51dff52011-05-19 16:55:47 +00002664 (void) FormatLocaleString(label,MaxTextExtent,
cristy6d8abba2010-06-03 01:10:47 +00002665 "raise %.20gx%.20g%+.20g%+.20g",(double) geometry.width,(double)
cristye8c25f92010-06-03 00:53:06 +00002666 geometry.height,(double) geometry.x,(double) geometry.y);
cristy3ed852e2009-09-05 21:47:34 +00002667 break;
2668 }
2669 case SegmentPreview:
2670 {
2671 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2672 if (preview_image == (Image *) NULL)
2673 break;
2674 threshold+=0.4f;
2675 (void) SegmentImage(preview_image,RGBColorspace,MagickFalse,threshold,
2676 threshold);
cristyb51dff52011-05-19 16:55:47 +00002677 (void) FormatLocaleString(label,MaxTextExtent,"segment %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002678 threshold,threshold);
2679 break;
2680 }
2681 case SwirlPreview:
2682 {
2683 preview_image=SwirlImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002684 (void) FormatLocaleString(label,MaxTextExtent,"swirl %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002685 degrees+=45.0;
2686 break;
2687 }
2688 case ImplodePreview:
2689 {
2690 degrees+=0.1f;
2691 preview_image=ImplodeImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002692 (void) FormatLocaleString(label,MaxTextExtent,"implode %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002693 break;
2694 }
2695 case WavePreview:
2696 {
2697 degrees+=5.0f;
2698 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002699 (void) FormatLocaleString(label,MaxTextExtent,"wave %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002700 0.5*degrees,2.0*degrees);
cristy3ed852e2009-09-05 21:47:34 +00002701 break;
2702 }
2703 case OilPaintPreview:
2704 {
2705 preview_image=OilPaintImage(thumbnail,(double) radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002706 (void) FormatLocaleString(label,MaxTextExtent,"paint %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002707 break;
2708 }
2709 case CharcoalDrawingPreview:
2710 {
2711 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
2712 exception);
cristyb51dff52011-05-19 16:55:47 +00002713 (void) FormatLocaleString(label,MaxTextExtent,"charcoal %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002714 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002715 break;
2716 }
2717 case JPEGPreview:
2718 {
2719 char
2720 filename[MaxTextExtent];
2721
2722 int
2723 file;
2724
2725 MagickBooleanType
2726 status;
2727
2728 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2729 if (preview_image == (Image *) NULL)
2730 break;
cristybb503372010-05-27 20:51:26 +00002731 preview_info->quality=(size_t) percentage;
cristyb51dff52011-05-19 16:55:47 +00002732 (void) FormatLocaleString(factor,MaxTextExtent,"%.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00002733 preview_info->quality);
cristy3ed852e2009-09-05 21:47:34 +00002734 file=AcquireUniqueFileResource(filename);
2735 if (file != -1)
2736 file=close(file)-1;
cristyb51dff52011-05-19 16:55:47 +00002737 (void) FormatLocaleString(preview_image->filename,MaxTextExtent,
cristy3ed852e2009-09-05 21:47:34 +00002738 "jpeg:%s",filename);
2739 status=WriteImage(preview_info,preview_image);
2740 if (status != MagickFalse)
2741 {
2742 Image
2743 *quality_image;
2744
2745 (void) CopyMagickString(preview_info->filename,
2746 preview_image->filename,MaxTextExtent);
2747 quality_image=ReadImage(preview_info,exception);
2748 if (quality_image != (Image *) NULL)
2749 {
2750 preview_image=DestroyImage(preview_image);
2751 preview_image=quality_image;
2752 }
2753 }
2754 (void) RelinquishUniqueFileResource(preview_image->filename);
2755 if ((GetBlobSize(preview_image)/1024) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00002756 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%gmb ",
cristy3ed852e2009-09-05 21:47:34 +00002757 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
2758 1024.0/1024.0);
2759 else
2760 if (GetBlobSize(preview_image) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00002761 (void) FormatLocaleString(label,MaxTextExtent,
cristye7f51092010-01-17 00:39:37 +00002762 "quality %s\n%gkb ",factor,(double) ((MagickOffsetType)
cristy8cd5b312010-01-07 01:10:24 +00002763 GetBlobSize(preview_image))/1024.0);
cristy3ed852e2009-09-05 21:47:34 +00002764 else
cristyb51dff52011-05-19 16:55:47 +00002765 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%.20gb ",
cristy54ea5732011-06-10 12:39:53 +00002766 factor,(double) ((MagickOffsetType) GetBlobSize(thumbnail)));
cristy3ed852e2009-09-05 21:47:34 +00002767 break;
2768 }
2769 }
2770 thumbnail=DestroyImage(thumbnail);
2771 percentage+=12.5;
2772 radius+=0.5;
2773 sigma+=0.25;
2774 if (preview_image == (Image *) NULL)
2775 break;
2776 (void) DeleteImageProperty(preview_image,"label");
2777 (void) SetImageProperty(preview_image,"label",label);
2778 AppendImageToList(&images,preview_image);
cristybb503372010-05-27 20:51:26 +00002779 proceed=SetImageProgress(image,PreviewImageTag,(MagickOffsetType) i,
2780 NumberTiles);
cristy3ed852e2009-09-05 21:47:34 +00002781 if (proceed == MagickFalse)
2782 break;
2783 }
2784 if (images == (Image *) NULL)
2785 {
2786 preview_info=DestroyImageInfo(preview_info);
2787 return((Image *) NULL);
2788 }
2789 /*
2790 Create the montage.
2791 */
2792 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
2793 (void) CopyMagickString(montage_info->filename,image->filename,MaxTextExtent);
2794 montage_info->shadow=MagickTrue;
2795 (void) CloneString(&montage_info->tile,"3x3");
2796 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
2797 (void) CloneString(&montage_info->frame,DefaultTileFrame);
2798 montage_image=MontageImages(images,montage_info,exception);
2799 montage_info=DestroyMontageInfo(montage_info);
2800 images=DestroyImageList(images);
2801 if (montage_image == (Image *) NULL)
2802 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2803 if (montage_image->montage != (char *) NULL)
2804 {
2805 /*
2806 Free image directory.
2807 */
2808 montage_image->montage=(char *) RelinquishMagickMemory(
2809 montage_image->montage);
2810 if (image->directory != (char *) NULL)
2811 montage_image->directory=(char *) RelinquishMagickMemory(
2812 montage_image->directory);
2813 }
2814 preview_info=DestroyImageInfo(preview_info);
2815 return(montage_image);
2816}
2817
2818/*
2819%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2820% %
2821% %
2822% %
2823% R a d i a l B l u r I m a g e %
2824% %
2825% %
2826% %
2827%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2828%
2829% RadialBlurImage() applies a radial blur to the image.
2830%
2831% Andrew Protano contributed this effect.
2832%
2833% The format of the RadialBlurImage method is:
2834%
2835% Image *RadialBlurImage(const Image *image,const double angle,
2836% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002837%
2838% A description of each parameter follows:
2839%
2840% o image: the image.
2841%
cristy3ed852e2009-09-05 21:47:34 +00002842% o angle: the angle of the radial blur.
2843%
2844% o exception: return any errors or warnings in this structure.
2845%
2846*/
cristyf4ad9df2011-07-08 16:49:03 +00002847MagickExport Image *RadialBlurImage(const Image *image,
2848 const double angle,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002849{
cristyc4c8d132010-01-07 01:58:38 +00002850 CacheView
2851 *blur_view,
2852 *image_view;
2853
cristy3ed852e2009-09-05 21:47:34 +00002854 Image
2855 *blur_image;
2856
cristy3ed852e2009-09-05 21:47:34 +00002857 MagickBooleanType
2858 status;
2859
cristybb503372010-05-27 20:51:26 +00002860 MagickOffsetType
2861 progress;
2862
cristy4c08aed2011-07-01 19:47:50 +00002863 PixelInfo
cristyddd82202009-11-03 20:14:50 +00002864 bias;
cristy3ed852e2009-09-05 21:47:34 +00002865
2866 MagickRealType
2867 blur_radius,
2868 *cos_theta,
2869 offset,
2870 *sin_theta,
2871 theta;
2872
2873 PointInfo
2874 blur_center;
2875
cristybb503372010-05-27 20:51:26 +00002876 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002877 i;
2878
cristybb503372010-05-27 20:51:26 +00002879 size_t
cristy3ed852e2009-09-05 21:47:34 +00002880 n;
2881
cristybb503372010-05-27 20:51:26 +00002882 ssize_t
2883 y;
2884
cristy3ed852e2009-09-05 21:47:34 +00002885 /*
2886 Allocate blur image.
2887 */
2888 assert(image != (Image *) NULL);
2889 assert(image->signature == MagickSignature);
2890 if (image->debug != MagickFalse)
2891 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2892 assert(exception != (ExceptionInfo *) NULL);
2893 assert(exception->signature == MagickSignature);
2894 blur_image=CloneImage(image,0,0,MagickTrue,exception);
2895 if (blur_image == (Image *) NULL)
2896 return((Image *) NULL);
2897 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
2898 {
2899 InheritException(exception,&blur_image->exception);
2900 blur_image=DestroyImage(blur_image);
2901 return((Image *) NULL);
2902 }
2903 blur_center.x=(double) image->columns/2.0;
2904 blur_center.y=(double) image->rows/2.0;
2905 blur_radius=hypot(blur_center.x,blur_center.y);
cristy117ff172010-08-15 21:35:32 +00002906 n=(size_t) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+2UL);
cristy3ed852e2009-09-05 21:47:34 +00002907 theta=DegreesToRadians(angle)/(MagickRealType) (n-1);
2908 cos_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
2909 sizeof(*cos_theta));
2910 sin_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
2911 sizeof(*sin_theta));
2912 if ((cos_theta == (MagickRealType *) NULL) ||
2913 (sin_theta == (MagickRealType *) NULL))
2914 {
2915 blur_image=DestroyImage(blur_image);
2916 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2917 }
2918 offset=theta*(MagickRealType) (n-1)/2.0;
cristybb503372010-05-27 20:51:26 +00002919 for (i=0; i < (ssize_t) n; i++)
cristy3ed852e2009-09-05 21:47:34 +00002920 {
2921 cos_theta[i]=cos((double) (theta*i-offset));
2922 sin_theta[i]=sin((double) (theta*i-offset));
2923 }
2924 /*
2925 Radial blur image.
2926 */
2927 status=MagickTrue;
2928 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00002929 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00002930 image_view=AcquireCacheView(image);
2931 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00002932#if defined(MAGICKCORE_OPENMP_SUPPORT)
2933 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002934#endif
cristybb503372010-05-27 20:51:26 +00002935 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002936 {
cristy4c08aed2011-07-01 19:47:50 +00002937 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002938 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002939
cristy117ff172010-08-15 21:35:32 +00002940 register ssize_t
2941 x;
2942
cristy3ed852e2009-09-05 21:47:34 +00002943 if (status == MagickFalse)
2944 continue;
2945 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
2946 exception);
cristy4c08aed2011-07-01 19:47:50 +00002947 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002948 {
2949 status=MagickFalse;
2950 continue;
2951 }
cristybb503372010-05-27 20:51:26 +00002952 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002953 {
cristy4c08aed2011-07-01 19:47:50 +00002954 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002955 qixel;
2956
2957 MagickRealType
2958 normalize,
2959 radius;
2960
2961 PixelPacket
2962 pixel;
2963
2964 PointInfo
2965 center;
2966
cristybb503372010-05-27 20:51:26 +00002967 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002968 i;
2969
cristybb503372010-05-27 20:51:26 +00002970 size_t
cristy3ed852e2009-09-05 21:47:34 +00002971 step;
2972
2973 center.x=(double) x-blur_center.x;
2974 center.y=(double) y-blur_center.y;
2975 radius=hypot((double) center.x,center.y);
2976 if (radius == 0)
2977 step=1;
2978 else
2979 {
cristybb503372010-05-27 20:51:26 +00002980 step=(size_t) (blur_radius/radius);
cristy3ed852e2009-09-05 21:47:34 +00002981 if (step == 0)
2982 step=1;
2983 else
2984 if (step >= n)
2985 step=n-1;
2986 }
2987 normalize=0.0;
cristyddd82202009-11-03 20:14:50 +00002988 qixel=bias;
cristyed231572011-07-14 02:18:59 +00002989 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00002990 {
cristyeaedf062010-05-29 22:36:02 +00002991 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00002992 {
cristyeaedf062010-05-29 22:36:02 +00002993 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
2994 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
2995 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
2996 cos_theta[i]+0.5),&pixel,exception);
cristy3ed852e2009-09-05 21:47:34 +00002997 qixel.red+=pixel.red;
2998 qixel.green+=pixel.green;
2999 qixel.blue+=pixel.blue;
cristy3ed852e2009-09-05 21:47:34 +00003000 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00003001 qixel.black+=pixel.black;
3002 qixel.alpha+=pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00003003 normalize+=1.0;
3004 }
3005 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
3006 normalize);
cristyed231572011-07-14 02:18:59 +00003007 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003008 SetPixelRed(blur_image,
3009 ClampToQuantum(normalize*qixel.red),q);
cristyed231572011-07-14 02:18:59 +00003010 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003011 SetPixelGreen(blur_image,
3012 ClampToQuantum(normalize*qixel.green),q);
cristyed231572011-07-14 02:18:59 +00003013 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003014 SetPixelBlue(blur_image,
3015 ClampToQuantum(normalize*qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003016 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003017 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003018 SetPixelBlack(blur_image,
3019 ClampToQuantum(normalize*qixel.black),q);
cristyed231572011-07-14 02:18:59 +00003020 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003021 SetPixelAlpha(blur_image,
3022 ClampToQuantum(normalize*qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003023 }
3024 else
3025 {
3026 MagickRealType
3027 alpha,
3028 gamma;
3029
3030 alpha=1.0;
3031 gamma=0.0;
cristyeaedf062010-05-29 22:36:02 +00003032 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00003033 {
cristyeaedf062010-05-29 22:36:02 +00003034 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
3035 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
3036 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
3037 cos_theta[i]+0.5),&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00003038 alpha=(MagickRealType) (QuantumScale*pixel.alpha);
cristy3ed852e2009-09-05 21:47:34 +00003039 qixel.red+=alpha*pixel.red;
3040 qixel.green+=alpha*pixel.green;
3041 qixel.blue+=alpha*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00003042 qixel.alpha+=pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00003043 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00003044 qixel.black+=alpha*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00003045 gamma+=alpha;
3046 normalize+=1.0;
3047 }
3048 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3049 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
3050 normalize);
cristyed231572011-07-14 02:18:59 +00003051 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003052 SetPixelRed(blur_image,
3053 ClampToQuantum(gamma*qixel.red),q);
cristyed231572011-07-14 02:18:59 +00003054 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003055 SetPixelGreen(blur_image,
3056 ClampToQuantum(gamma*qixel.green),q);
cristyed231572011-07-14 02:18:59 +00003057 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003058 SetPixelBlue(blur_image,
3059 ClampToQuantum(gamma*qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003060 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003061 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003062 SetPixelBlack(blur_image,
3063 ClampToQuantum(gamma*qixel.black),q);
cristyed231572011-07-14 02:18:59 +00003064 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003065 SetPixelAlpha(blur_image,
3066 ClampToQuantum(normalize*qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003067 }
cristyed231572011-07-14 02:18:59 +00003068 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003069 }
3070 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
3071 status=MagickFalse;
3072 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3073 {
3074 MagickBooleanType
3075 proceed;
3076
cristyb5d5f722009-11-04 03:03:49 +00003077#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00003078 #pragma omp critical (MagickCore_RadialBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00003079#endif
3080 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
3081 if (proceed == MagickFalse)
3082 status=MagickFalse;
3083 }
3084 }
3085 blur_view=DestroyCacheView(blur_view);
3086 image_view=DestroyCacheView(image_view);
3087 cos_theta=(MagickRealType *) RelinquishMagickMemory(cos_theta);
3088 sin_theta=(MagickRealType *) RelinquishMagickMemory(sin_theta);
3089 if (status == MagickFalse)
3090 blur_image=DestroyImage(blur_image);
3091 return(blur_image);
3092}
3093
3094/*
3095%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3096% %
3097% %
3098% %
cristy3ed852e2009-09-05 21:47:34 +00003099% S e l e c t i v e B l u r I m a g e %
3100% %
3101% %
3102% %
3103%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3104%
3105% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
3106% It is similar to the unsharpen mask that sharpens everything with contrast
3107% above a certain threshold.
3108%
3109% The format of the SelectiveBlurImage method is:
3110%
3111% Image *SelectiveBlurImage(const Image *image,const double radius,
3112% const double sigma,const double threshold,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003113%
3114% A description of each parameter follows:
3115%
3116% o image: the image.
3117%
cristy3ed852e2009-09-05 21:47:34 +00003118% o radius: the radius of the Gaussian, in pixels, not counting the center
3119% pixel.
3120%
3121% o sigma: the standard deviation of the Gaussian, in pixels.
3122%
3123% o threshold: only pixels within this contrast threshold are included
3124% in the blur operation.
3125%
3126% o exception: return any errors or warnings in this structure.
3127%
3128*/
cristyf4ad9df2011-07-08 16:49:03 +00003129MagickExport Image *SelectiveBlurImage(const Image *image,
3130 const double radius,const double sigma,const double threshold,
3131 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003132{
3133#define SelectiveBlurImageTag "SelectiveBlur/Image"
3134
cristy47e00502009-12-17 19:19:57 +00003135 CacheView
3136 *blur_view,
3137 *image_view;
3138
cristy3ed852e2009-09-05 21:47:34 +00003139 double
cristy3ed852e2009-09-05 21:47:34 +00003140 *kernel;
3141
3142 Image
3143 *blur_image;
3144
cristy3ed852e2009-09-05 21:47:34 +00003145 MagickBooleanType
3146 status;
3147
cristybb503372010-05-27 20:51:26 +00003148 MagickOffsetType
3149 progress;
3150
cristy4c08aed2011-07-01 19:47:50 +00003151 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003152 bias;
3153
cristybb503372010-05-27 20:51:26 +00003154 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003155 i;
cristy3ed852e2009-09-05 21:47:34 +00003156
cristybb503372010-05-27 20:51:26 +00003157 size_t
cristy3ed852e2009-09-05 21:47:34 +00003158 width;
3159
cristybb503372010-05-27 20:51:26 +00003160 ssize_t
3161 j,
3162 u,
3163 v,
3164 y;
3165
cristy3ed852e2009-09-05 21:47:34 +00003166 /*
3167 Initialize blur image attributes.
3168 */
3169 assert(image != (Image *) NULL);
3170 assert(image->signature == MagickSignature);
3171 if (image->debug != MagickFalse)
3172 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3173 assert(exception != (ExceptionInfo *) NULL);
3174 assert(exception->signature == MagickSignature);
3175 width=GetOptimalKernelWidth1D(radius,sigma);
3176 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
3177 if (kernel == (double *) NULL)
3178 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00003179 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00003180 i=0;
cristy47e00502009-12-17 19:19:57 +00003181 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003182 {
cristy47e00502009-12-17 19:19:57 +00003183 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00003184 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
3185 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00003186 }
3187 if (image->debug != MagickFalse)
3188 {
3189 char
3190 format[MaxTextExtent],
3191 *message;
3192
cristy117ff172010-08-15 21:35:32 +00003193 register const double
3194 *k;
3195
cristybb503372010-05-27 20:51:26 +00003196 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003197 u,
3198 v;
3199
cristy3ed852e2009-09-05 21:47:34 +00003200 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00003201 " SelectiveBlurImage with %.20gx%.20g kernel:",(double) width,(double)
3202 width);
cristy3ed852e2009-09-05 21:47:34 +00003203 message=AcquireString("");
3204 k=kernel;
cristybb503372010-05-27 20:51:26 +00003205 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003206 {
3207 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00003208 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy3ed852e2009-09-05 21:47:34 +00003209 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00003210 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003211 {
cristyb51dff52011-05-19 16:55:47 +00003212 (void) FormatLocaleString(format,MaxTextExtent,"%+f ",*k++);
cristy3ed852e2009-09-05 21:47:34 +00003213 (void) ConcatenateString(&message,format);
3214 }
3215 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
3216 }
3217 message=DestroyString(message);
3218 }
3219 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3220 if (blur_image == (Image *) NULL)
3221 return((Image *) NULL);
3222 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3223 {
3224 InheritException(exception,&blur_image->exception);
3225 blur_image=DestroyImage(blur_image);
3226 return((Image *) NULL);
3227 }
3228 /*
3229 Threshold blur image.
3230 */
3231 status=MagickTrue;
3232 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00003233 GetPixelInfo(image,&bias);
3234 SetPixelInfoBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003235 image_view=AcquireCacheView(image);
3236 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003237#if defined(MAGICKCORE_OPENMP_SUPPORT)
3238 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003239#endif
cristybb503372010-05-27 20:51:26 +00003240 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003241 {
cristy4c08aed2011-07-01 19:47:50 +00003242 double
3243 contrast;
3244
cristy3ed852e2009-09-05 21:47:34 +00003245 MagickBooleanType
3246 sync;
3247
3248 MagickRealType
3249 gamma;
3250
cristy4c08aed2011-07-01 19:47:50 +00003251 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003252 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003253
cristy4c08aed2011-07-01 19:47:50 +00003254 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003255 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003256
cristy117ff172010-08-15 21:35:32 +00003257 register ssize_t
3258 x;
3259
cristy3ed852e2009-09-05 21:47:34 +00003260 if (status == MagickFalse)
3261 continue;
cristy117ff172010-08-15 21:35:32 +00003262 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
3263 (width/2L),image->columns+width,width,exception);
cristy3ed852e2009-09-05 21:47:34 +00003264 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3265 exception);
cristy4c08aed2011-07-01 19:47:50 +00003266 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003267 {
3268 status=MagickFalse;
3269 continue;
3270 }
cristybb503372010-05-27 20:51:26 +00003271 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003272 {
cristy4c08aed2011-07-01 19:47:50 +00003273 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003274 pixel;
3275
3276 register const double
cristyc47d1f82009-11-26 01:44:43 +00003277 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00003278
cristybb503372010-05-27 20:51:26 +00003279 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003280 u;
3281
cristy117ff172010-08-15 21:35:32 +00003282 ssize_t
3283 j,
3284 v;
3285
cristyddd82202009-11-03 20:14:50 +00003286 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00003287 k=kernel;
3288 gamma=0.0;
3289 j=0;
cristyed231572011-07-14 02:18:59 +00003290 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00003291 {
cristybb503372010-05-27 20:51:26 +00003292 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003293 {
cristybb503372010-05-27 20:51:26 +00003294 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003295 {
cristyed231572011-07-14 02:18:59 +00003296 contrast=GetPixelIntensity(image,p+(u+j)*GetPixelChannels(image))-
cristy4c08aed2011-07-01 19:47:50 +00003297 (double) GetPixelIntensity(blur_image,q);
3298 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003299 {
cristy4c08aed2011-07-01 19:47:50 +00003300 pixel.red+=(*k)*
cristyed231572011-07-14 02:18:59 +00003301 GetPixelRed(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003302 pixel.green+=(*k)*
cristyed231572011-07-14 02:18:59 +00003303 GetPixelGreen(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003304 pixel.blue+=(*k)*
cristyed231572011-07-14 02:18:59 +00003305 GetPixelBlue(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003306 if (image->colorspace == CMYKColorspace)
3307 pixel.black+=(*k)*
cristyed231572011-07-14 02:18:59 +00003308 GetPixelBlack(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003309 gamma+=(*k);
3310 k++;
3311 }
3312 }
cristyd99b0962010-05-29 23:14:26 +00003313 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003314 }
3315 if (gamma != 0.0)
3316 {
3317 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00003318 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003319 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +00003320 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003321 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +00003322 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003323 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003324 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00003325 (image->colorspace == CMYKColorspace))
3326 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00003327 }
cristyed231572011-07-14 02:18:59 +00003328 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003329 {
3330 gamma=0.0;
3331 j=0;
cristybb503372010-05-27 20:51:26 +00003332 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003333 {
cristybb503372010-05-27 20:51:26 +00003334 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003335 {
cristy4c08aed2011-07-01 19:47:50 +00003336 contrast=GetPixelIntensity(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003337 GetPixelChannels(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003338 GetPixelIntensity(blur_image,q);
3339 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003340 {
cristy4c08aed2011-07-01 19:47:50 +00003341 pixel.alpha+=(*k)*
cristyed231572011-07-14 02:18:59 +00003342 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003343 gamma+=(*k);
3344 k++;
3345 }
3346 }
cristyeaedf062010-05-29 22:36:02 +00003347 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003348 }
3349 if (gamma != 0.0)
3350 {
3351 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
3352 gamma);
cristy4c08aed2011-07-01 19:47:50 +00003353 SetPixelAlpha(blur_image,ClampToQuantum(gamma*pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003354 }
3355 }
3356 }
3357 else
3358 {
3359 MagickRealType
3360 alpha;
3361
cristybb503372010-05-27 20:51:26 +00003362 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003363 {
cristybb503372010-05-27 20:51:26 +00003364 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003365 {
cristy4c08aed2011-07-01 19:47:50 +00003366 contrast=GetPixelIntensity(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003367 GetPixelChannels(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003368 GetPixelIntensity(blur_image,q);
3369 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003370 {
cristy4c08aed2011-07-01 19:47:50 +00003371 alpha=(MagickRealType) (QuantumScale*
cristyed231572011-07-14 02:18:59 +00003372 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image)));
cristy4c08aed2011-07-01 19:47:50 +00003373 pixel.red+=(*k)*alpha*
cristyed231572011-07-14 02:18:59 +00003374 GetPixelRed(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003375 pixel.green+=(*k)*alpha*GetPixelGreen(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003376 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003377 pixel.blue+=(*k)*alpha*GetPixelBlue(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003378 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003379 pixel.alpha+=(*k)*GetPixelAlpha(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003380 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003381 if (image->colorspace == CMYKColorspace)
3382 pixel.black+=(*k)*GetPixelBlack(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003383 GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003384 gamma+=(*k)*alpha;
3385 k++;
3386 }
3387 }
cristyeaedf062010-05-29 22:36:02 +00003388 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003389 }
3390 if (gamma != 0.0)
3391 {
3392 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00003393 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003394 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +00003395 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003396 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +00003397 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003398 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003399 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00003400 (image->colorspace == CMYKColorspace))
3401 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00003402 }
cristyed231572011-07-14 02:18:59 +00003403 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003404 {
3405 gamma=0.0;
3406 j=0;
cristybb503372010-05-27 20:51:26 +00003407 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003408 {
cristybb503372010-05-27 20:51:26 +00003409 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003410 {
cristy4c08aed2011-07-01 19:47:50 +00003411 contrast=GetPixelIntensity(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003412 GetPixelChannels(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003413 GetPixelIntensity(blur_image,q);
3414 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003415 {
cristy4c08aed2011-07-01 19:47:50 +00003416 pixel.alpha+=(*k)*
cristyed231572011-07-14 02:18:59 +00003417 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003418 gamma+=(*k);
3419 k++;
3420 }
3421 }
cristyeaedf062010-05-29 22:36:02 +00003422 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003423 }
3424 if (gamma != 0.0)
3425 {
3426 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
3427 gamma);
cristy4c08aed2011-07-01 19:47:50 +00003428 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003429 }
3430 }
3431 }
cristyed231572011-07-14 02:18:59 +00003432 p+=GetPixelChannels(image);
3433 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003434 }
3435 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
3436 if (sync == MagickFalse)
3437 status=MagickFalse;
3438 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3439 {
3440 MagickBooleanType
3441 proceed;
3442
cristyb5d5f722009-11-04 03:03:49 +00003443#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00003444 #pragma omp critical (MagickCore_SelectiveBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00003445#endif
3446 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress++,
3447 image->rows);
3448 if (proceed == MagickFalse)
3449 status=MagickFalse;
3450 }
3451 }
3452 blur_image->type=image->type;
3453 blur_view=DestroyCacheView(blur_view);
3454 image_view=DestroyCacheView(image_view);
3455 kernel=(double *) RelinquishMagickMemory(kernel);
3456 if (status == MagickFalse)
3457 blur_image=DestroyImage(blur_image);
3458 return(blur_image);
3459}
3460
3461/*
3462%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3463% %
3464% %
3465% %
3466% S h a d e I m a g e %
3467% %
3468% %
3469% %
3470%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3471%
3472% ShadeImage() shines a distant light on an image to create a
3473% three-dimensional effect. You control the positioning of the light with
3474% azimuth and elevation; azimuth is measured in degrees off the x axis
3475% and elevation is measured in pixels above the Z axis.
3476%
3477% The format of the ShadeImage method is:
3478%
3479% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3480% const double azimuth,const double elevation,ExceptionInfo *exception)
3481%
3482% A description of each parameter follows:
3483%
3484% o image: the image.
3485%
3486% o gray: A value other than zero shades the intensity of each pixel.
3487%
3488% o azimuth, elevation: Define the light source direction.
3489%
3490% o exception: return any errors or warnings in this structure.
3491%
3492*/
3493MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3494 const double azimuth,const double elevation,ExceptionInfo *exception)
3495{
3496#define ShadeImageTag "Shade/Image"
3497
cristyc4c8d132010-01-07 01:58:38 +00003498 CacheView
3499 *image_view,
3500 *shade_view;
3501
cristy3ed852e2009-09-05 21:47:34 +00003502 Image
3503 *shade_image;
3504
cristy3ed852e2009-09-05 21:47:34 +00003505 MagickBooleanType
3506 status;
3507
cristybb503372010-05-27 20:51:26 +00003508 MagickOffsetType
3509 progress;
3510
cristy3ed852e2009-09-05 21:47:34 +00003511 PrimaryInfo
3512 light;
3513
cristybb503372010-05-27 20:51:26 +00003514 ssize_t
3515 y;
3516
cristy3ed852e2009-09-05 21:47:34 +00003517 /*
3518 Initialize shaded image attributes.
3519 */
3520 assert(image != (const Image *) NULL);
3521 assert(image->signature == MagickSignature);
3522 if (image->debug != MagickFalse)
3523 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3524 assert(exception != (ExceptionInfo *) NULL);
3525 assert(exception->signature == MagickSignature);
3526 shade_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
3527 if (shade_image == (Image *) NULL)
3528 return((Image *) NULL);
3529 if (SetImageStorageClass(shade_image,DirectClass) == MagickFalse)
3530 {
3531 InheritException(exception,&shade_image->exception);
3532 shade_image=DestroyImage(shade_image);
3533 return((Image *) NULL);
3534 }
3535 /*
3536 Compute the light vector.
3537 */
3538 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
3539 cos(DegreesToRadians(elevation));
3540 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
3541 cos(DegreesToRadians(elevation));
3542 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
3543 /*
3544 Shade image.
3545 */
3546 status=MagickTrue;
3547 progress=0;
3548 image_view=AcquireCacheView(image);
3549 shade_view=AcquireCacheView(shade_image);
cristyb5d5f722009-11-04 03:03:49 +00003550#if defined(MAGICKCORE_OPENMP_SUPPORT)
3551 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003552#endif
cristybb503372010-05-27 20:51:26 +00003553 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003554 {
3555 MagickRealType
3556 distance,
3557 normal_distance,
3558 shade;
3559
3560 PrimaryInfo
3561 normal;
3562
cristy4c08aed2011-07-01 19:47:50 +00003563 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003564 *restrict p,
3565 *restrict s0,
3566 *restrict s1,
3567 *restrict s2;
cristy3ed852e2009-09-05 21:47:34 +00003568
cristy4c08aed2011-07-01 19:47:50 +00003569 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003570 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003571
cristy117ff172010-08-15 21:35:32 +00003572 register ssize_t
3573 x;
3574
cristy3ed852e2009-09-05 21:47:34 +00003575 if (status == MagickFalse)
3576 continue;
3577 p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception);
3578 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
3579 exception);
cristy4c08aed2011-07-01 19:47:50 +00003580 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003581 {
3582 status=MagickFalse;
3583 continue;
3584 }
3585 /*
3586 Shade this row of pixels.
3587 */
3588 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
cristyed231572011-07-14 02:18:59 +00003589 s0=p+GetPixelChannels(image);
3590 s1=s0+(image->columns+2)*GetPixelChannels(image);
3591 s2=s1+(image->columns+2)*GetPixelChannels(image);
cristybb503372010-05-27 20:51:26 +00003592 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003593 {
3594 /*
3595 Determine the surface normal and compute shading.
3596 */
cristyed231572011-07-14 02:18:59 +00003597 normal.x=(double) (GetPixelIntensity(image,s0-GetPixelChannels(image))+
3598 GetPixelIntensity(image,s1-GetPixelChannels(image))+
3599 GetPixelIntensity(image,s2-GetPixelChannels(image))-
3600 GetPixelIntensity(image,s0+GetPixelChannels(image))-
3601 GetPixelIntensity(image,s1+GetPixelChannels(image))-
3602 GetPixelIntensity(image,s2+GetPixelChannels(image)));
3603 normal.y=(double) (GetPixelIntensity(image,s2-GetPixelChannels(image))+
cristy4c08aed2011-07-01 19:47:50 +00003604 GetPixelIntensity(image,s2)+
cristyed231572011-07-14 02:18:59 +00003605 GetPixelIntensity(image,s2+GetPixelChannels(image))-
3606 GetPixelIntensity(image,s0-GetPixelChannels(image))-
cristy4c08aed2011-07-01 19:47:50 +00003607 GetPixelIntensity(image,s0)-
cristyed231572011-07-14 02:18:59 +00003608 GetPixelIntensity(image,s0+GetPixelChannels(image)));
cristy3ed852e2009-09-05 21:47:34 +00003609 if ((normal.x == 0.0) && (normal.y == 0.0))
3610 shade=light.z;
3611 else
3612 {
3613 shade=0.0;
3614 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
3615 if (distance > MagickEpsilon)
3616 {
3617 normal_distance=
3618 normal.x*normal.x+normal.y*normal.y+normal.z*normal.z;
3619 if (normal_distance > (MagickEpsilon*MagickEpsilon))
3620 shade=distance/sqrt((double) normal_distance);
3621 }
3622 }
3623 if (gray != MagickFalse)
3624 {
cristy4c08aed2011-07-01 19:47:50 +00003625 SetPixelRed(shade_image,ClampToQuantum(shade),q);
3626 SetPixelGreen(shade_image,ClampToQuantum(shade),q);
3627 SetPixelBlue(shade_image,ClampToQuantum(shade),q);
cristy3ed852e2009-09-05 21:47:34 +00003628 }
3629 else
3630 {
cristy4c08aed2011-07-01 19:47:50 +00003631 SetPixelRed(shade_image,ClampToQuantum(QuantumScale*shade*
3632 GetPixelRed(image,s1)),q);
3633 SetPixelGreen(shade_image,ClampToQuantum(QuantumScale*shade*
3634 GetPixelGreen(image,s1)),q);
3635 SetPixelBlue(shade_image,ClampToQuantum(QuantumScale*shade*
3636 GetPixelBlue(image,s1)),q);
cristy3ed852e2009-09-05 21:47:34 +00003637 }
cristy4c08aed2011-07-01 19:47:50 +00003638 SetPixelAlpha(shade_image,GetPixelAlpha(image,s1),q);
cristyed231572011-07-14 02:18:59 +00003639 s0+=GetPixelChannels(image);
3640 s1+=GetPixelChannels(image);
3641 s2+=GetPixelChannels(image);
3642 q+=GetPixelChannels(shade_image);
cristy3ed852e2009-09-05 21:47:34 +00003643 }
3644 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
3645 status=MagickFalse;
3646 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3647 {
3648 MagickBooleanType
3649 proceed;
3650
cristyb5d5f722009-11-04 03:03:49 +00003651#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003652 #pragma omp critical (MagickCore_ShadeImage)
3653#endif
3654 proceed=SetImageProgress(image,ShadeImageTag,progress++,image->rows);
3655 if (proceed == MagickFalse)
3656 status=MagickFalse;
3657 }
3658 }
3659 shade_view=DestroyCacheView(shade_view);
3660 image_view=DestroyCacheView(image_view);
3661 if (status == MagickFalse)
3662 shade_image=DestroyImage(shade_image);
3663 return(shade_image);
3664}
3665
3666/*
3667%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3668% %
3669% %
3670% %
3671% S h a r p e n I m a g e %
3672% %
3673% %
3674% %
3675%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3676%
3677% SharpenImage() sharpens the image. We convolve the image with a Gaussian
3678% operator of the given radius and standard deviation (sigma). For
3679% reasonable results, radius should be larger than sigma. Use a radius of 0
3680% and SharpenImage() selects a suitable radius for you.
3681%
3682% Using a separable kernel would be faster, but the negative weights cancel
3683% out on the corners of the kernel producing often undesirable ringing in the
3684% filtered result; this can be avoided by using a 2D gaussian shaped image
3685% sharpening kernel instead.
3686%
3687% The format of the SharpenImage method is:
3688%
3689% Image *SharpenImage(const Image *image,const double radius,
3690% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003691%
3692% A description of each parameter follows:
3693%
3694% o image: the image.
3695%
cristy3ed852e2009-09-05 21:47:34 +00003696% o radius: the radius of the Gaussian, in pixels, not counting the center
3697% pixel.
3698%
3699% o sigma: the standard deviation of the Laplacian, in pixels.
3700%
3701% o exception: return any errors or warnings in this structure.
3702%
3703*/
cristy3ed852e2009-09-05 21:47:34 +00003704MagickExport Image *SharpenImage(const Image *image,const double radius,
3705 const double sigma,ExceptionInfo *exception)
3706{
cristy3ed852e2009-09-05 21:47:34 +00003707 double
cristy47e00502009-12-17 19:19:57 +00003708 normalize;
cristy3ed852e2009-09-05 21:47:34 +00003709
3710 Image
3711 *sharp_image;
3712
cristy41cbe682011-07-15 19:12:37 +00003713 KernelInfo
3714 *kernel_info;
3715
cristybb503372010-05-27 20:51:26 +00003716 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003717 i;
3718
cristybb503372010-05-27 20:51:26 +00003719 size_t
cristy3ed852e2009-09-05 21:47:34 +00003720 width;
3721
cristy117ff172010-08-15 21:35:32 +00003722 ssize_t
3723 j,
3724 u,
3725 v;
3726
cristy3ed852e2009-09-05 21:47:34 +00003727 assert(image != (const Image *) NULL);
3728 assert(image->signature == MagickSignature);
3729 if (image->debug != MagickFalse)
3730 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3731 assert(exception != (ExceptionInfo *) NULL);
3732 assert(exception->signature == MagickSignature);
3733 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00003734 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00003735 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003736 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00003737 (void) ResetMagickMemory(kernel_info,0,sizeof(*kernel_info));
3738 kernel_info->width=width;
3739 kernel_info->height=width;
3740 kernel_info->signature=MagickSignature;
3741 kernel_info->values=(double *) AcquireAlignedMemory(kernel_info->width,
3742 kernel_info->width*sizeof(*kernel_info->values));
3743 if (kernel_info->values == (double *) NULL)
3744 {
3745 kernel_info=DestroyKernelInfo(kernel_info);
3746 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3747 }
cristy3ed852e2009-09-05 21:47:34 +00003748 normalize=0.0;
cristy41cbe682011-07-15 19:12:37 +00003749 j=(ssize_t) kernel_info->width/2;
cristy47e00502009-12-17 19:19:57 +00003750 i=0;
3751 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003752 {
cristy47e00502009-12-17 19:19:57 +00003753 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00003754 {
cristy41cbe682011-07-15 19:12:37 +00003755 kernel_info->values[i]=(double) (-exp(-((double) u*u+v*v)/(2.0*
3756 MagickSigma*MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
3757 normalize+=kernel_info->values[i];
cristy3ed852e2009-09-05 21:47:34 +00003758 i++;
3759 }
3760 }
cristy41cbe682011-07-15 19:12:37 +00003761 kernel_info->values[i/2]=(double) ((-2.0)*normalize);
cristy0a922382011-07-16 15:30:34 +00003762 kernel_info->bias=image->bias;
cristy5e6be1e2011-07-16 01:23:39 +00003763 sharp_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00003764 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00003765 return(sharp_image);
3766}
3767
3768/*
3769%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3770% %
3771% %
3772% %
3773% S p r e a d I m a g e %
3774% %
3775% %
3776% %
3777%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3778%
3779% SpreadImage() is a special effects method that randomly displaces each
3780% pixel in a block defined by the radius parameter.
3781%
3782% The format of the SpreadImage method is:
3783%
3784% Image *SpreadImage(const Image *image,const double radius,
3785% ExceptionInfo *exception)
3786%
3787% A description of each parameter follows:
3788%
3789% o image: the image.
3790%
3791% o radius: Choose a random pixel in a neighborhood of this extent.
3792%
3793% o exception: return any errors or warnings in this structure.
3794%
3795*/
3796MagickExport Image *SpreadImage(const Image *image,const double radius,
3797 ExceptionInfo *exception)
3798{
3799#define SpreadImageTag "Spread/Image"
3800
cristyfa112112010-01-04 17:48:07 +00003801 CacheView
cristy9f7e7cb2011-03-26 00:49:57 +00003802 *image_view,
3803 *spread_view;
cristyfa112112010-01-04 17:48:07 +00003804
cristy3ed852e2009-09-05 21:47:34 +00003805 Image
3806 *spread_image;
3807
cristy3ed852e2009-09-05 21:47:34 +00003808 MagickBooleanType
3809 status;
3810
cristybb503372010-05-27 20:51:26 +00003811 MagickOffsetType
3812 progress;
3813
cristy4c08aed2011-07-01 19:47:50 +00003814 PixelInfo
cristyddd82202009-11-03 20:14:50 +00003815 bias;
cristy3ed852e2009-09-05 21:47:34 +00003816
3817 RandomInfo
cristyfa112112010-01-04 17:48:07 +00003818 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00003819
cristybb503372010-05-27 20:51:26 +00003820 size_t
cristy3ed852e2009-09-05 21:47:34 +00003821 width;
3822
cristybb503372010-05-27 20:51:26 +00003823 ssize_t
3824 y;
3825
cristy3ed852e2009-09-05 21:47:34 +00003826 /*
3827 Initialize spread image attributes.
3828 */
3829 assert(image != (Image *) NULL);
3830 assert(image->signature == MagickSignature);
3831 if (image->debug != MagickFalse)
3832 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3833 assert(exception != (ExceptionInfo *) NULL);
3834 assert(exception->signature == MagickSignature);
3835 spread_image=CloneImage(image,image->columns,image->rows,MagickTrue,
3836 exception);
3837 if (spread_image == (Image *) NULL)
3838 return((Image *) NULL);
3839 if (SetImageStorageClass(spread_image,DirectClass) == MagickFalse)
3840 {
3841 InheritException(exception,&spread_image->exception);
3842 spread_image=DestroyImage(spread_image);
3843 return((Image *) NULL);
3844 }
3845 /*
3846 Spread image.
3847 */
3848 status=MagickTrue;
3849 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00003850 GetPixelInfo(spread_image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003851 width=GetOptimalKernelWidth1D(radius,0.5);
cristy3ed852e2009-09-05 21:47:34 +00003852 random_info=AcquireRandomInfoThreadSet();
cristy9f7e7cb2011-03-26 00:49:57 +00003853 image_view=AcquireCacheView(image);
3854 spread_view=AcquireCacheView(spread_image);
cristyb557a152011-02-22 12:14:30 +00003855#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00003856 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00003857#endif
cristybb503372010-05-27 20:51:26 +00003858 for (y=0; y < (ssize_t) spread_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003859 {
cristy5c9e6f22010-09-17 17:31:01 +00003860 const int
3861 id = GetOpenMPThreadId();
cristy6ebe97c2010-07-03 01:17:28 +00003862
cristy4c08aed2011-07-01 19:47:50 +00003863 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003864 pixel;
3865
cristy4c08aed2011-07-01 19:47:50 +00003866 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003867 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003868
cristy117ff172010-08-15 21:35:32 +00003869 register ssize_t
3870 x;
3871
cristy3ed852e2009-09-05 21:47:34 +00003872 if (status == MagickFalse)
3873 continue;
cristy9f7e7cb2011-03-26 00:49:57 +00003874 q=QueueCacheViewAuthenticPixels(spread_view,0,y,spread_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +00003875 exception);
cristy4c08aed2011-07-01 19:47:50 +00003876 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003877 {
3878 status=MagickFalse;
3879 continue;
3880 }
cristyddd82202009-11-03 20:14:50 +00003881 pixel=bias;
cristybb503372010-05-27 20:51:26 +00003882 for (x=0; x < (ssize_t) spread_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003883 {
cristy4c08aed2011-07-01 19:47:50 +00003884 (void) InterpolatePixelInfo(image,image_view,
cristy8a7c3e82011-03-26 02:10:53 +00003885 UndefinedInterpolatePixel,(double) x+width*(GetPseudoRandomValue(
3886 random_info[id])-0.5),(double) y+width*(GetPseudoRandomValue(
3887 random_info[id])-0.5),&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00003888 SetPixelPixelInfo(spread_image,&pixel,q);
cristyed231572011-07-14 02:18:59 +00003889 q+=GetPixelChannels(spread_image);
cristy3ed852e2009-09-05 21:47:34 +00003890 }
cristy9f7e7cb2011-03-26 00:49:57 +00003891 if (SyncCacheViewAuthenticPixels(spread_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003892 status=MagickFalse;
3893 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3894 {
3895 MagickBooleanType
3896 proceed;
3897
cristyb557a152011-02-22 12:14:30 +00003898#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003899 #pragma omp critical (MagickCore_SpreadImage)
3900#endif
3901 proceed=SetImageProgress(image,SpreadImageTag,progress++,image->rows);
3902 if (proceed == MagickFalse)
3903 status=MagickFalse;
3904 }
3905 }
cristy9f7e7cb2011-03-26 00:49:57 +00003906 spread_view=DestroyCacheView(spread_view);
cristy3ed852e2009-09-05 21:47:34 +00003907 image_view=DestroyCacheView(image_view);
3908 random_info=DestroyRandomInfoThreadSet(random_info);
cristy3ed852e2009-09-05 21:47:34 +00003909 return(spread_image);
3910}
3911
3912/*
3913%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3914% %
3915% %
3916% %
cristy0834d642011-03-18 18:26:08 +00003917% S t a t i s t i c I m a g e %
3918% %
3919% %
3920% %
3921%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3922%
3923% StatisticImage() makes each pixel the min / max / median / mode / etc. of
cristy8d752042011-03-19 01:00:36 +00003924% the neighborhood of the specified width and height.
cristy0834d642011-03-18 18:26:08 +00003925%
3926% The format of the StatisticImage method is:
3927%
3928% Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00003929% const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00003930%
3931% A description of each parameter follows:
3932%
3933% o image: the image.
3934%
cristy0834d642011-03-18 18:26:08 +00003935% o type: the statistic type (median, mode, etc.).
3936%
cristy95c38342011-03-18 22:39:51 +00003937% o width: the width of the pixel neighborhood.
3938%
3939% o height: the height of the pixel neighborhood.
cristy0834d642011-03-18 18:26:08 +00003940%
3941% o exception: return any errors or warnings in this structure.
3942%
3943*/
3944
cristy733678d2011-03-18 21:29:28 +00003945#define ListChannels 5
3946
3947typedef struct _ListNode
3948{
3949 size_t
3950 next[9],
3951 count,
3952 signature;
3953} ListNode;
3954
3955typedef struct _SkipList
3956{
3957 ssize_t
3958 level;
3959
3960 ListNode
3961 *nodes;
3962} SkipList;
3963
3964typedef struct _PixelList
3965{
3966 size_t
cristy6fc86bb2011-03-18 23:45:16 +00003967 length,
cristy733678d2011-03-18 21:29:28 +00003968 seed,
3969 signature;
3970
3971 SkipList
3972 lists[ListChannels];
3973} PixelList;
3974
3975static PixelList *DestroyPixelList(PixelList *pixel_list)
3976{
3977 register ssize_t
3978 i;
3979
3980 if (pixel_list == (PixelList *) NULL)
3981 return((PixelList *) NULL);
3982 for (i=0; i < ListChannels; i++)
3983 if (pixel_list->lists[i].nodes != (ListNode *) NULL)
3984 pixel_list->lists[i].nodes=(ListNode *) RelinquishMagickMemory(
3985 pixel_list->lists[i].nodes);
3986 pixel_list=(PixelList *) RelinquishMagickMemory(pixel_list);
3987 return(pixel_list);
3988}
3989
3990static PixelList **DestroyPixelListThreadSet(PixelList **pixel_list)
3991{
3992 register ssize_t
3993 i;
3994
3995 assert(pixel_list != (PixelList **) NULL);
3996 for (i=0; i < (ssize_t) GetOpenMPMaximumThreads(); i++)
3997 if (pixel_list[i] != (PixelList *) NULL)
3998 pixel_list[i]=DestroyPixelList(pixel_list[i]);
3999 pixel_list=(PixelList **) RelinquishMagickMemory(pixel_list);
4000 return(pixel_list);
4001}
4002
cristy6fc86bb2011-03-18 23:45:16 +00004003static PixelList *AcquirePixelList(const size_t width,const size_t height)
cristy733678d2011-03-18 21:29:28 +00004004{
4005 PixelList
4006 *pixel_list;
4007
4008 register ssize_t
4009 i;
4010
4011 pixel_list=(PixelList *) AcquireMagickMemory(sizeof(*pixel_list));
4012 if (pixel_list == (PixelList *) NULL)
4013 return(pixel_list);
4014 (void) ResetMagickMemory((void *) pixel_list,0,sizeof(*pixel_list));
cristy6fc86bb2011-03-18 23:45:16 +00004015 pixel_list->length=width*height;
cristy733678d2011-03-18 21:29:28 +00004016 for (i=0; i < ListChannels; i++)
4017 {
4018 pixel_list->lists[i].nodes=(ListNode *) AcquireQuantumMemory(65537UL,
4019 sizeof(*pixel_list->lists[i].nodes));
4020 if (pixel_list->lists[i].nodes == (ListNode *) NULL)
4021 return(DestroyPixelList(pixel_list));
4022 (void) ResetMagickMemory(pixel_list->lists[i].nodes,0,65537UL*
4023 sizeof(*pixel_list->lists[i].nodes));
4024 }
4025 pixel_list->signature=MagickSignature;
4026 return(pixel_list);
4027}
4028
cristy6fc86bb2011-03-18 23:45:16 +00004029static PixelList **AcquirePixelListThreadSet(const size_t width,
4030 const size_t height)
cristy733678d2011-03-18 21:29:28 +00004031{
4032 PixelList
4033 **pixel_list;
4034
4035 register ssize_t
4036 i;
4037
4038 size_t
4039 number_threads;
4040
4041 number_threads=GetOpenMPMaximumThreads();
4042 pixel_list=(PixelList **) AcquireQuantumMemory(number_threads,
4043 sizeof(*pixel_list));
4044 if (pixel_list == (PixelList **) NULL)
4045 return((PixelList **) NULL);
4046 (void) ResetMagickMemory(pixel_list,0,number_threads*sizeof(*pixel_list));
4047 for (i=0; i < (ssize_t) number_threads; i++)
4048 {
cristy6fc86bb2011-03-18 23:45:16 +00004049 pixel_list[i]=AcquirePixelList(width,height);
cristy733678d2011-03-18 21:29:28 +00004050 if (pixel_list[i] == (PixelList *) NULL)
4051 return(DestroyPixelListThreadSet(pixel_list));
4052 }
4053 return(pixel_list);
4054}
4055
4056static void AddNodePixelList(PixelList *pixel_list,const ssize_t channel,
4057 const size_t color)
4058{
4059 register SkipList
4060 *list;
4061
4062 register ssize_t
4063 level;
4064
4065 size_t
4066 search,
4067 update[9];
4068
4069 /*
4070 Initialize the node.
4071 */
4072 list=pixel_list->lists+channel;
4073 list->nodes[color].signature=pixel_list->signature;
4074 list->nodes[color].count=1;
4075 /*
4076 Determine where it belongs in the list.
4077 */
4078 search=65536UL;
4079 for (level=list->level; level >= 0; level--)
4080 {
4081 while (list->nodes[search].next[level] < color)
4082 search=list->nodes[search].next[level];
4083 update[level]=search;
4084 }
4085 /*
4086 Generate a pseudo-random level for this node.
4087 */
4088 for (level=0; ; level++)
4089 {
4090 pixel_list->seed=(pixel_list->seed*42893621L)+1L;
4091 if ((pixel_list->seed & 0x300) != 0x300)
4092 break;
4093 }
4094 if (level > 8)
4095 level=8;
4096 if (level > (list->level+2))
4097 level=list->level+2;
4098 /*
4099 If we're raising the list's level, link back to the root node.
4100 */
4101 while (level > list->level)
4102 {
4103 list->level++;
4104 update[list->level]=65536UL;
4105 }
4106 /*
4107 Link the node into the skip-list.
4108 */
4109 do
4110 {
4111 list->nodes[color].next[level]=list->nodes[update[level]].next[level];
4112 list->nodes[update[level]].next[level]=color;
cristy3cba8ca2011-03-19 01:29:12 +00004113 } while (level-- > 0);
cristy733678d2011-03-18 21:29:28 +00004114}
4115
cristy4c08aed2011-07-01 19:47:50 +00004116static PixelInfo GetMaximumPixelList(PixelList *pixel_list)
cristy6fc86bb2011-03-18 23:45:16 +00004117{
cristy4c08aed2011-07-01 19:47:50 +00004118 PixelInfo
cristy6fc86bb2011-03-18 23:45:16 +00004119 pixel;
4120
4121 register SkipList
4122 *list;
4123
4124 register ssize_t
4125 channel;
4126
4127 size_t
cristyd76c51e2011-03-26 00:21:26 +00004128 color,
4129 maximum;
cristy49f37242011-03-22 18:18:23 +00004130
4131 ssize_t
cristy6fc86bb2011-03-18 23:45:16 +00004132 count;
4133
4134 unsigned short
cristyd76c51e2011-03-26 00:21:26 +00004135 channels[ListChannels];
cristy6fc86bb2011-03-18 23:45:16 +00004136
4137 /*
4138 Find the maximum value for each of the color.
4139 */
4140 for (channel=0; channel < 5; channel++)
4141 {
4142 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004143 color=65536L;
cristy6fc86bb2011-03-18 23:45:16 +00004144 count=0;
cristy49f37242011-03-22 18:18:23 +00004145 maximum=list->nodes[color].next[0];
cristy6fc86bb2011-03-18 23:45:16 +00004146 do
4147 {
4148 color=list->nodes[color].next[0];
cristy49f37242011-03-22 18:18:23 +00004149 if (color > maximum)
4150 maximum=color;
cristy6fc86bb2011-03-18 23:45:16 +00004151 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004152 } while (count < (ssize_t) pixel_list->length);
cristy49f37242011-03-22 18:18:23 +00004153 channels[channel]=(unsigned short) maximum;
4154 }
cristy4c08aed2011-07-01 19:47:50 +00004155 GetPixelInfo((const Image *) NULL,&pixel);
cristy49f37242011-03-22 18:18:23 +00004156 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4157 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4158 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004159 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4160 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy49f37242011-03-22 18:18:23 +00004161 return(pixel);
4162}
4163
cristy4c08aed2011-07-01 19:47:50 +00004164static PixelInfo GetMeanPixelList(PixelList *pixel_list)
cristy49f37242011-03-22 18:18:23 +00004165{
cristy4c08aed2011-07-01 19:47:50 +00004166 PixelInfo
cristy49f37242011-03-22 18:18:23 +00004167 pixel;
4168
cristy80a99a32011-03-30 01:30:23 +00004169 MagickRealType
4170 sum;
4171
cristy49f37242011-03-22 18:18:23 +00004172 register SkipList
4173 *list;
4174
4175 register ssize_t
4176 channel;
4177
4178 size_t
cristy80a99a32011-03-30 01:30:23 +00004179 color;
cristy49f37242011-03-22 18:18:23 +00004180
4181 ssize_t
4182 count;
4183
4184 unsigned short
4185 channels[ListChannels];
4186
4187 /*
4188 Find the mean value for each of the color.
4189 */
4190 for (channel=0; channel < 5; channel++)
4191 {
4192 list=pixel_list->lists+channel;
4193 color=65536L;
4194 count=0;
cristy80a99a32011-03-30 01:30:23 +00004195 sum=0.0;
cristy49f37242011-03-22 18:18:23 +00004196 do
4197 {
4198 color=list->nodes[color].next[0];
cristy80a99a32011-03-30 01:30:23 +00004199 sum+=(MagickRealType) list->nodes[color].count*color;
cristy49f37242011-03-22 18:18:23 +00004200 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004201 } while (count < (ssize_t) pixel_list->length);
cristy80a99a32011-03-30 01:30:23 +00004202 sum/=pixel_list->length;
4203 channels[channel]=(unsigned short) sum;
cristy6fc86bb2011-03-18 23:45:16 +00004204 }
cristy4c08aed2011-07-01 19:47:50 +00004205 GetPixelInfo((const Image *) NULL,&pixel);
cristy6fc86bb2011-03-18 23:45:16 +00004206 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4207 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4208 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004209 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4210 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy6fc86bb2011-03-18 23:45:16 +00004211 return(pixel);
4212}
4213
cristy4c08aed2011-07-01 19:47:50 +00004214static PixelInfo GetMedianPixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004215{
cristy4c08aed2011-07-01 19:47:50 +00004216 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004217 pixel;
4218
4219 register SkipList
4220 *list;
4221
4222 register ssize_t
4223 channel;
4224
4225 size_t
cristy49f37242011-03-22 18:18:23 +00004226 color;
4227
4228 ssize_t
cristy733678d2011-03-18 21:29:28 +00004229 count;
4230
4231 unsigned short
4232 channels[ListChannels];
4233
4234 /*
4235 Find the median value for each of the color.
4236 */
cristy733678d2011-03-18 21:29:28 +00004237 for (channel=0; channel < 5; channel++)
4238 {
4239 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004240 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004241 count=0;
4242 do
4243 {
4244 color=list->nodes[color].next[0];
4245 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004246 } while (count <= (ssize_t) (pixel_list->length >> 1));
cristy6fc86bb2011-03-18 23:45:16 +00004247 channels[channel]=(unsigned short) color;
4248 }
cristy4c08aed2011-07-01 19:47:50 +00004249 GetPixelInfo((const Image *) NULL,&pixel);
cristy6fc86bb2011-03-18 23:45:16 +00004250 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4251 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4252 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004253 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4254 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy6fc86bb2011-03-18 23:45:16 +00004255 return(pixel);
4256}
4257
cristy4c08aed2011-07-01 19:47:50 +00004258static PixelInfo GetMinimumPixelList(PixelList *pixel_list)
cristy6fc86bb2011-03-18 23:45:16 +00004259{
cristy4c08aed2011-07-01 19:47:50 +00004260 PixelInfo
cristy6fc86bb2011-03-18 23:45:16 +00004261 pixel;
4262
4263 register SkipList
4264 *list;
4265
4266 register ssize_t
4267 channel;
4268
4269 size_t
cristyd76c51e2011-03-26 00:21:26 +00004270 color,
4271 minimum;
cristy6fc86bb2011-03-18 23:45:16 +00004272
cristy49f37242011-03-22 18:18:23 +00004273 ssize_t
4274 count;
4275
cristy6fc86bb2011-03-18 23:45:16 +00004276 unsigned short
cristyd76c51e2011-03-26 00:21:26 +00004277 channels[ListChannels];
cristy6fc86bb2011-03-18 23:45:16 +00004278
4279 /*
4280 Find the minimum value for each of the color.
4281 */
4282 for (channel=0; channel < 5; channel++)
4283 {
4284 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004285 count=0;
cristy6fc86bb2011-03-18 23:45:16 +00004286 color=65536UL;
cristy49f37242011-03-22 18:18:23 +00004287 minimum=list->nodes[color].next[0];
4288 do
4289 {
4290 color=list->nodes[color].next[0];
4291 if (color < minimum)
4292 minimum=color;
4293 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004294 } while (count < (ssize_t) pixel_list->length);
cristy49f37242011-03-22 18:18:23 +00004295 channels[channel]=(unsigned short) minimum;
cristy733678d2011-03-18 21:29:28 +00004296 }
cristy4c08aed2011-07-01 19:47:50 +00004297 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004298 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4299 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4300 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004301 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4302 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy733678d2011-03-18 21:29:28 +00004303 return(pixel);
4304}
4305
cristy4c08aed2011-07-01 19:47:50 +00004306static PixelInfo GetModePixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004307{
cristy4c08aed2011-07-01 19:47:50 +00004308 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004309 pixel;
4310
4311 register SkipList
4312 *list;
4313
4314 register ssize_t
4315 channel;
4316
4317 size_t
4318 color,
cristy733678d2011-03-18 21:29:28 +00004319 max_count,
cristy6fc86bb2011-03-18 23:45:16 +00004320 mode;
cristy733678d2011-03-18 21:29:28 +00004321
cristy49f37242011-03-22 18:18:23 +00004322 ssize_t
4323 count;
4324
cristy733678d2011-03-18 21:29:28 +00004325 unsigned short
4326 channels[5];
4327
4328 /*
glennrp30d2dc62011-06-25 03:17:16 +00004329 Make each pixel the 'predominant color' of the specified neighborhood.
cristy733678d2011-03-18 21:29:28 +00004330 */
cristy733678d2011-03-18 21:29:28 +00004331 for (channel=0; channel < 5; channel++)
4332 {
4333 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004334 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004335 mode=color;
4336 max_count=list->nodes[mode].count;
4337 count=0;
4338 do
4339 {
4340 color=list->nodes[color].next[0];
4341 if (list->nodes[color].count > max_count)
4342 {
4343 mode=color;
4344 max_count=list->nodes[mode].count;
4345 }
4346 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004347 } while (count < (ssize_t) pixel_list->length);
cristy733678d2011-03-18 21:29:28 +00004348 channels[channel]=(unsigned short) mode;
4349 }
cristy4c08aed2011-07-01 19:47:50 +00004350 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004351 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4352 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4353 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004354 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4355 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy733678d2011-03-18 21:29:28 +00004356 return(pixel);
4357}
4358
cristy4c08aed2011-07-01 19:47:50 +00004359static PixelInfo GetNonpeakPixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004360{
cristy4c08aed2011-07-01 19:47:50 +00004361 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004362 pixel;
4363
4364 register SkipList
4365 *list;
4366
4367 register ssize_t
4368 channel;
4369
4370 size_t
cristy733678d2011-03-18 21:29:28 +00004371 color,
cristy733678d2011-03-18 21:29:28 +00004372 next,
4373 previous;
4374
cristy49f37242011-03-22 18:18:23 +00004375 ssize_t
4376 count;
4377
cristy733678d2011-03-18 21:29:28 +00004378 unsigned short
4379 channels[5];
4380
4381 /*
cristy49f37242011-03-22 18:18:23 +00004382 Finds the non peak value for each of the colors.
cristy733678d2011-03-18 21:29:28 +00004383 */
cristy733678d2011-03-18 21:29:28 +00004384 for (channel=0; channel < 5; channel++)
4385 {
4386 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004387 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004388 next=list->nodes[color].next[0];
4389 count=0;
4390 do
4391 {
4392 previous=color;
4393 color=next;
4394 next=list->nodes[color].next[0];
4395 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004396 } while (count <= (ssize_t) (pixel_list->length >> 1));
cristy733678d2011-03-18 21:29:28 +00004397 if ((previous == 65536UL) && (next != 65536UL))
4398 color=next;
4399 else
4400 if ((previous != 65536UL) && (next == 65536UL))
4401 color=previous;
4402 channels[channel]=(unsigned short) color;
4403 }
cristy4c08aed2011-07-01 19:47:50 +00004404 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004405 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4406 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4407 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004408 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4409 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy733678d2011-03-18 21:29:28 +00004410 return(pixel);
4411}
4412
cristy4c08aed2011-07-01 19:47:50 +00004413static PixelInfo GetStandardDeviationPixelList(PixelList *pixel_list)
cristy9a68cbb2011-03-29 00:51:23 +00004414{
cristy4c08aed2011-07-01 19:47:50 +00004415 PixelInfo
cristy9a68cbb2011-03-29 00:51:23 +00004416 pixel;
4417
cristy80a99a32011-03-30 01:30:23 +00004418 MagickRealType
4419 sum,
4420 sum_squared;
4421
cristy9a68cbb2011-03-29 00:51:23 +00004422 register SkipList
4423 *list;
4424
4425 register ssize_t
4426 channel;
4427
4428 size_t
cristy80a99a32011-03-30 01:30:23 +00004429 color;
cristy9a68cbb2011-03-29 00:51:23 +00004430
4431 ssize_t
4432 count;
4433
4434 unsigned short
4435 channels[ListChannels];
4436
4437 /*
cristy80a99a32011-03-30 01:30:23 +00004438 Find the standard-deviation value for each of the color.
cristy9a68cbb2011-03-29 00:51:23 +00004439 */
4440 for (channel=0; channel < 5; channel++)
4441 {
4442 list=pixel_list->lists+channel;
4443 color=65536L;
4444 count=0;
cristy80a99a32011-03-30 01:30:23 +00004445 sum=0.0;
4446 sum_squared=0.0;
cristy9a68cbb2011-03-29 00:51:23 +00004447 do
4448 {
cristy80a99a32011-03-30 01:30:23 +00004449 register ssize_t
4450 i;
4451
cristy9a68cbb2011-03-29 00:51:23 +00004452 color=list->nodes[color].next[0];
cristy80a99a32011-03-30 01:30:23 +00004453 sum+=(MagickRealType) list->nodes[color].count*color;
4454 for (i=0; i < (ssize_t) list->nodes[color].count; i++)
4455 sum_squared+=((MagickRealType) color)*((MagickRealType) color);
cristy9a68cbb2011-03-29 00:51:23 +00004456 count+=list->nodes[color].count;
4457 } while (count < (ssize_t) pixel_list->length);
cristy80a99a32011-03-30 01:30:23 +00004458 sum/=pixel_list->length;
4459 sum_squared/=pixel_list->length;
4460 channels[channel]=(unsigned short) sqrt(sum_squared-(sum*sum));
cristy9a68cbb2011-03-29 00:51:23 +00004461 }
cristy4c08aed2011-07-01 19:47:50 +00004462 GetPixelInfo((const Image *) NULL,&pixel);
cristy9a68cbb2011-03-29 00:51:23 +00004463 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4464 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4465 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004466 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4467 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy9a68cbb2011-03-29 00:51:23 +00004468 return(pixel);
4469}
4470
cristy4c08aed2011-07-01 19:47:50 +00004471static inline void InsertPixelList(const Image *image,const Quantum *pixel,
4472 PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004473{
4474 size_t
4475 signature;
4476
4477 unsigned short
4478 index;
4479
cristy4c08aed2011-07-01 19:47:50 +00004480 index=ScaleQuantumToShort(GetPixelRed(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004481 signature=pixel_list->lists[0].nodes[index].signature;
4482 if (signature == pixel_list->signature)
4483 pixel_list->lists[0].nodes[index].count++;
4484 else
4485 AddNodePixelList(pixel_list,0,index);
cristy4c08aed2011-07-01 19:47:50 +00004486 index=ScaleQuantumToShort(GetPixelGreen(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004487 signature=pixel_list->lists[1].nodes[index].signature;
4488 if (signature == pixel_list->signature)
4489 pixel_list->lists[1].nodes[index].count++;
4490 else
4491 AddNodePixelList(pixel_list,1,index);
cristy4c08aed2011-07-01 19:47:50 +00004492 index=ScaleQuantumToShort(GetPixelBlue(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004493 signature=pixel_list->lists[2].nodes[index].signature;
4494 if (signature == pixel_list->signature)
4495 pixel_list->lists[2].nodes[index].count++;
4496 else
4497 AddNodePixelList(pixel_list,2,index);
cristy4c08aed2011-07-01 19:47:50 +00004498 index=ScaleQuantumToShort(GetPixelAlpha(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004499 signature=pixel_list->lists[3].nodes[index].signature;
4500 if (signature == pixel_list->signature)
4501 pixel_list->lists[3].nodes[index].count++;
4502 else
4503 AddNodePixelList(pixel_list,3,index);
4504 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00004505 index=ScaleQuantumToShort(GetPixelBlack(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004506 signature=pixel_list->lists[4].nodes[index].signature;
4507 if (signature == pixel_list->signature)
4508 pixel_list->lists[4].nodes[index].count++;
4509 else
4510 AddNodePixelList(pixel_list,4,index);
4511}
4512
cristy80c99742011-04-04 14:46:39 +00004513static inline MagickRealType MagickAbsoluteValue(const MagickRealType x)
4514{
4515 if (x < 0)
4516 return(-x);
4517 return(x);
4518}
4519
cristy733678d2011-03-18 21:29:28 +00004520static void ResetPixelList(PixelList *pixel_list)
4521{
4522 int
4523 level;
4524
4525 register ListNode
4526 *root;
4527
4528 register SkipList
4529 *list;
4530
4531 register ssize_t
4532 channel;
4533
4534 /*
4535 Reset the skip-list.
4536 */
4537 for (channel=0; channel < 5; channel++)
4538 {
4539 list=pixel_list->lists+channel;
4540 root=list->nodes+65536UL;
4541 list->level=0;
4542 for (level=0; level < 9; level++)
4543 root->next[level]=65536UL;
4544 }
4545 pixel_list->seed=pixel_list->signature++;
4546}
4547
cristy0834d642011-03-18 18:26:08 +00004548MagickExport Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00004549 const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00004550{
cristy3cba8ca2011-03-19 01:29:12 +00004551#define StatisticWidth \
cristyd76c51e2011-03-26 00:21:26 +00004552 (width == 0 ? GetOptimalKernelWidth2D((double) width,0.5) : width)
cristy3cba8ca2011-03-19 01:29:12 +00004553#define StatisticHeight \
cristyd76c51e2011-03-26 00:21:26 +00004554 (height == 0 ? GetOptimalKernelWidth2D((double) height,0.5) : height)
cristy0834d642011-03-18 18:26:08 +00004555#define StatisticImageTag "Statistic/Image"
4556
4557 CacheView
4558 *image_view,
4559 *statistic_view;
4560
4561 Image
4562 *statistic_image;
4563
4564 MagickBooleanType
4565 status;
4566
4567 MagickOffsetType
4568 progress;
4569
4570 PixelList
4571 **restrict pixel_list;
4572
cristy0834d642011-03-18 18:26:08 +00004573 ssize_t
4574 y;
4575
4576 /*
4577 Initialize statistics image attributes.
4578 */
4579 assert(image != (Image *) NULL);
4580 assert(image->signature == MagickSignature);
4581 if (image->debug != MagickFalse)
4582 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4583 assert(exception != (ExceptionInfo *) NULL);
4584 assert(exception->signature == MagickSignature);
cristy0834d642011-03-18 18:26:08 +00004585 statistic_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4586 exception);
4587 if (statistic_image == (Image *) NULL)
4588 return((Image *) NULL);
4589 if (SetImageStorageClass(statistic_image,DirectClass) == MagickFalse)
4590 {
4591 InheritException(exception,&statistic_image->exception);
4592 statistic_image=DestroyImage(statistic_image);
4593 return((Image *) NULL);
4594 }
cristy6fc86bb2011-03-18 23:45:16 +00004595 pixel_list=AcquirePixelListThreadSet(StatisticWidth,StatisticHeight);
cristy0834d642011-03-18 18:26:08 +00004596 if (pixel_list == (PixelList **) NULL)
4597 {
4598 statistic_image=DestroyImage(statistic_image);
4599 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
4600 }
4601 /*
cristy8d752042011-03-19 01:00:36 +00004602 Make each pixel the min / max / median / mode / etc. of the neighborhood.
cristy0834d642011-03-18 18:26:08 +00004603 */
4604 status=MagickTrue;
4605 progress=0;
4606 image_view=AcquireCacheView(image);
4607 statistic_view=AcquireCacheView(statistic_image);
4608#if defined(MAGICKCORE_OPENMP_SUPPORT)
4609 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
4610#endif
4611 for (y=0; y < (ssize_t) statistic_image->rows; y++)
4612 {
4613 const int
4614 id = GetOpenMPThreadId();
4615
cristy4c08aed2011-07-01 19:47:50 +00004616 register const Quantum
cristy0834d642011-03-18 18:26:08 +00004617 *restrict p;
4618
cristy4c08aed2011-07-01 19:47:50 +00004619 register Quantum
cristy0834d642011-03-18 18:26:08 +00004620 *restrict q;
4621
4622 register ssize_t
4623 x;
4624
4625 if (status == MagickFalse)
4626 continue;
cristy6fc86bb2011-03-18 23:45:16 +00004627 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) StatisticWidth/2L),y-
4628 (ssize_t) (StatisticHeight/2L),image->columns+StatisticWidth,
4629 StatisticHeight,exception);
cristy3cba8ca2011-03-19 01:29:12 +00004630 q=QueueCacheViewAuthenticPixels(statistic_view,0,y,statistic_image->columns, 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00004631 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy0834d642011-03-18 18:26:08 +00004632 {
4633 status=MagickFalse;
4634 continue;
4635 }
cristy0834d642011-03-18 18:26:08 +00004636 for (x=0; x < (ssize_t) statistic_image->columns; x++)
4637 {
cristy4c08aed2011-07-01 19:47:50 +00004638 PixelInfo
cristy0834d642011-03-18 18:26:08 +00004639 pixel;
4640
cristy4c08aed2011-07-01 19:47:50 +00004641 register const Quantum
cristy6e3026a2011-03-19 00:54:38 +00004642 *restrict r;
4643
cristy0834d642011-03-18 18:26:08 +00004644 register ssize_t
4645 u,
4646 v;
4647
4648 r=p;
cristy0834d642011-03-18 18:26:08 +00004649 ResetPixelList(pixel_list[id]);
cristy6e4c3292011-03-19 00:53:55 +00004650 for (v=0; v < (ssize_t) StatisticHeight; v++)
cristy0834d642011-03-18 18:26:08 +00004651 {
cristy6e4c3292011-03-19 00:53:55 +00004652 for (u=0; u < (ssize_t) StatisticWidth; u++)
cristyed231572011-07-14 02:18:59 +00004653 InsertPixelList(image,r+u*GetPixelChannels(image),pixel_list[id]);
4654 r+=(image->columns+StatisticWidth)*GetPixelChannels(image);
cristy0834d642011-03-18 18:26:08 +00004655 }
cristy4c08aed2011-07-01 19:47:50 +00004656 GetPixelInfo(image,&pixel);
cristy490408a2011-07-07 14:42:05 +00004657 SetPixelInfo(image,p+(StatisticWidth*StatisticHeight/2)*
cristyed231572011-07-14 02:18:59 +00004658 GetPixelChannels(image),&pixel);
cristy0834d642011-03-18 18:26:08 +00004659 switch (type)
4660 {
cristy80c99742011-04-04 14:46:39 +00004661 case GradientStatistic:
4662 {
cristy4c08aed2011-07-01 19:47:50 +00004663 PixelInfo
cristy80c99742011-04-04 14:46:39 +00004664 maximum,
4665 minimum;
4666
4667 minimum=GetMinimumPixelList(pixel_list[id]);
4668 maximum=GetMaximumPixelList(pixel_list[id]);
4669 pixel.red=MagickAbsoluteValue(maximum.red-minimum.red);
4670 pixel.green=MagickAbsoluteValue(maximum.green-minimum.green);
4671 pixel.blue=MagickAbsoluteValue(maximum.blue-minimum.blue);
cristy4c08aed2011-07-01 19:47:50 +00004672 pixel.alpha=MagickAbsoluteValue(maximum.alpha-minimum.alpha);
cristy80c99742011-04-04 14:46:39 +00004673 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00004674 pixel.black=MagickAbsoluteValue(maximum.black-minimum.black);
cristy80c99742011-04-04 14:46:39 +00004675 break;
4676 }
cristy6fc86bb2011-03-18 23:45:16 +00004677 case MaximumStatistic:
4678 {
4679 pixel=GetMaximumPixelList(pixel_list[id]);
4680 break;
4681 }
cristy49f37242011-03-22 18:18:23 +00004682 case MeanStatistic:
4683 {
4684 pixel=GetMeanPixelList(pixel_list[id]);
4685 break;
4686 }
cristyf2ad14a2011-03-18 18:57:25 +00004687 case MedianStatistic:
cristy6fc86bb2011-03-18 23:45:16 +00004688 default:
cristyf2ad14a2011-03-18 18:57:25 +00004689 {
4690 pixel=GetMedianPixelList(pixel_list[id]);
4691 break;
4692 }
cristy6fc86bb2011-03-18 23:45:16 +00004693 case MinimumStatistic:
4694 {
4695 pixel=GetMinimumPixelList(pixel_list[id]);
4696 break;
4697 }
cristyf2ad14a2011-03-18 18:57:25 +00004698 case ModeStatistic:
4699 {
4700 pixel=GetModePixelList(pixel_list[id]);
4701 break;
4702 }
4703 case NonpeakStatistic:
4704 {
4705 pixel=GetNonpeakPixelList(pixel_list[id]);
4706 break;
4707 }
cristy9a68cbb2011-03-29 00:51:23 +00004708 case StandardDeviationStatistic:
4709 {
4710 pixel=GetStandardDeviationPixelList(pixel_list[id]);
4711 break;
4712 }
cristy0834d642011-03-18 18:26:08 +00004713 }
cristyed231572011-07-14 02:18:59 +00004714 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00004715 SetPixelRed(statistic_image,ClampToQuantum(pixel.red),q);
cristyed231572011-07-14 02:18:59 +00004716 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00004717 SetPixelGreen(statistic_image,ClampToQuantum(pixel.green),q);
cristyed231572011-07-14 02:18:59 +00004718 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00004719 SetPixelBlue(statistic_image,ClampToQuantum(pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00004720 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy0834d642011-03-18 18:26:08 +00004721 (image->colorspace == CMYKColorspace))
cristy490408a2011-07-07 14:42:05 +00004722 SetPixelBlack(statistic_image,ClampToQuantum(pixel.black),q);
cristyed231572011-07-14 02:18:59 +00004723 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00004724 (image->matte != MagickFalse))
cristy490408a2011-07-07 14:42:05 +00004725 SetPixelAlpha(statistic_image,ClampToQuantum(pixel.alpha),q);
cristyed231572011-07-14 02:18:59 +00004726 p+=GetPixelChannels(image);
4727 q+=GetPixelChannels(statistic_image);
cristy0834d642011-03-18 18:26:08 +00004728 }
4729 if (SyncCacheViewAuthenticPixels(statistic_view,exception) == MagickFalse)
4730 status=MagickFalse;
4731 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4732 {
4733 MagickBooleanType
4734 proceed;
4735
4736#if defined(MAGICKCORE_OPENMP_SUPPORT)
4737 #pragma omp critical (MagickCore_StatisticImage)
4738#endif
4739 proceed=SetImageProgress(image,StatisticImageTag,progress++,
4740 image->rows);
4741 if (proceed == MagickFalse)
4742 status=MagickFalse;
4743 }
4744 }
4745 statistic_view=DestroyCacheView(statistic_view);
4746 image_view=DestroyCacheView(image_view);
4747 pixel_list=DestroyPixelListThreadSet(pixel_list);
4748 return(statistic_image);
4749}
4750
4751/*
4752%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4753% %
4754% %
4755% %
cristy3ed852e2009-09-05 21:47:34 +00004756% U n s h a r p M a s k I m a g e %
4757% %
4758% %
4759% %
4760%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4761%
4762% UnsharpMaskImage() sharpens one or more image channels. We convolve the
4763% image with a Gaussian operator of the given radius and standard deviation
4764% (sigma). For reasonable results, radius should be larger than sigma. Use a
4765% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
4766%
4767% The format of the UnsharpMaskImage method is:
4768%
4769% Image *UnsharpMaskImage(const Image *image,const double radius,
4770% const double sigma,const double amount,const double threshold,
4771% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00004772%
4773% A description of each parameter follows:
4774%
4775% o image: the image.
4776%
cristy3ed852e2009-09-05 21:47:34 +00004777% o radius: the radius of the Gaussian, in pixels, not counting the center
4778% pixel.
4779%
4780% o sigma: the standard deviation of the Gaussian, in pixels.
4781%
4782% o amount: the percentage of the difference between the original and the
4783% blur image that is added back into the original.
4784%
4785% o threshold: the threshold in pixels needed to apply the diffence amount.
4786%
4787% o exception: return any errors or warnings in this structure.
4788%
4789*/
cristyf4ad9df2011-07-08 16:49:03 +00004790MagickExport Image *UnsharpMaskImage(const Image *image,
4791 const double radius,const double sigma,const double amount,
4792 const double threshold,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00004793{
4794#define SharpenImageTag "Sharpen/Image"
4795
cristyc4c8d132010-01-07 01:58:38 +00004796 CacheView
4797 *image_view,
4798 *unsharp_view;
4799
cristy3ed852e2009-09-05 21:47:34 +00004800 Image
4801 *unsharp_image;
4802
cristy3ed852e2009-09-05 21:47:34 +00004803 MagickBooleanType
4804 status;
4805
cristybb503372010-05-27 20:51:26 +00004806 MagickOffsetType
4807 progress;
4808
cristy4c08aed2011-07-01 19:47:50 +00004809 PixelInfo
cristyddd82202009-11-03 20:14:50 +00004810 bias;
cristy3ed852e2009-09-05 21:47:34 +00004811
4812 MagickRealType
4813 quantum_threshold;
4814
cristybb503372010-05-27 20:51:26 +00004815 ssize_t
4816 y;
4817
cristy3ed852e2009-09-05 21:47:34 +00004818 assert(image != (const Image *) NULL);
4819 assert(image->signature == MagickSignature);
4820 if (image->debug != MagickFalse)
4821 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4822 assert(exception != (ExceptionInfo *) NULL);
cristyf4ad9df2011-07-08 16:49:03 +00004823 unsharp_image=BlurImage(image,radius,sigma,exception);
cristy3ed852e2009-09-05 21:47:34 +00004824 if (unsharp_image == (Image *) NULL)
4825 return((Image *) NULL);
4826 quantum_threshold=(MagickRealType) QuantumRange*threshold;
4827 /*
4828 Unsharp-mask image.
4829 */
4830 status=MagickTrue;
4831 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00004832 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00004833 image_view=AcquireCacheView(image);
4834 unsharp_view=AcquireCacheView(unsharp_image);
cristyb5d5f722009-11-04 03:03:49 +00004835#if defined(MAGICKCORE_OPENMP_SUPPORT)
4836 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004837#endif
cristybb503372010-05-27 20:51:26 +00004838 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00004839 {
cristy4c08aed2011-07-01 19:47:50 +00004840 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00004841 pixel;
4842
cristy4c08aed2011-07-01 19:47:50 +00004843 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00004844 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00004845
cristy4c08aed2011-07-01 19:47:50 +00004846 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00004847 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004848
cristy117ff172010-08-15 21:35:32 +00004849 register ssize_t
4850 x;
4851
cristy3ed852e2009-09-05 21:47:34 +00004852 if (status == MagickFalse)
4853 continue;
4854 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
4855 q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
4856 exception);
cristy4c08aed2011-07-01 19:47:50 +00004857 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00004858 {
4859 status=MagickFalse;
4860 continue;
4861 }
cristyddd82202009-11-03 20:14:50 +00004862 pixel=bias;
cristybb503372010-05-27 20:51:26 +00004863 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004864 {
cristyed231572011-07-14 02:18:59 +00004865 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00004866 {
cristy4c08aed2011-07-01 19:47:50 +00004867 pixel.red=GetPixelRed(image,p)-(MagickRealType) GetPixelRed(image,q);
cristy3ed852e2009-09-05 21:47:34 +00004868 if (fabs(2.0*pixel.red) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00004869 pixel.red=(MagickRealType) GetPixelRed(image,p);
cristy3ed852e2009-09-05 21:47:34 +00004870 else
cristy4c08aed2011-07-01 19:47:50 +00004871 pixel.red=(MagickRealType) GetPixelRed(image,p)+(pixel.red*amount);
4872 SetPixelRed(unsharp_image,ClampToQuantum(pixel.red),q);
cristy3ed852e2009-09-05 21:47:34 +00004873 }
cristyed231572011-07-14 02:18:59 +00004874 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00004875 {
cristy4c08aed2011-07-01 19:47:50 +00004876 pixel.green=GetPixelGreen(image,p)-
4877 (MagickRealType) GetPixelGreen(image,q);
cristy3ed852e2009-09-05 21:47:34 +00004878 if (fabs(2.0*pixel.green) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00004879 pixel.green=(MagickRealType)
4880 GetPixelGreen(image,p);
cristy3ed852e2009-09-05 21:47:34 +00004881 else
cristy4c08aed2011-07-01 19:47:50 +00004882 pixel.green=(MagickRealType)
4883 GetPixelGreen(image,p)+
4884 (pixel.green*amount);
4885 SetPixelGreen(unsharp_image,
4886 ClampToQuantum(pixel.green),q);
cristy3ed852e2009-09-05 21:47:34 +00004887 }
cristyed231572011-07-14 02:18:59 +00004888 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00004889 {
cristy4c08aed2011-07-01 19:47:50 +00004890 pixel.blue=GetPixelBlue(image,p)-
4891 (MagickRealType) GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00004892 if (fabs(2.0*pixel.blue) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00004893 pixel.blue=(MagickRealType)
4894 GetPixelBlue(image,p);
cristy3ed852e2009-09-05 21:47:34 +00004895 else
cristy4c08aed2011-07-01 19:47:50 +00004896 pixel.blue=(MagickRealType)
4897 GetPixelBlue(image,p)+(pixel.blue*amount);
4898 SetPixelBlue(unsharp_image,
4899 ClampToQuantum(pixel.blue),q);
cristy3ed852e2009-09-05 21:47:34 +00004900 }
cristyed231572011-07-14 02:18:59 +00004901 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00004902 (image->colorspace == CMYKColorspace))
4903 {
cristy4c08aed2011-07-01 19:47:50 +00004904 pixel.black=GetPixelBlack(image,p)-
4905 (MagickRealType) GetPixelBlack(image,q);
4906 if (fabs(2.0*pixel.black) < quantum_threshold)
4907 pixel.black=(MagickRealType)
4908 GetPixelBlack(image,p);
cristy3ed852e2009-09-05 21:47:34 +00004909 else
cristy4c08aed2011-07-01 19:47:50 +00004910 pixel.black=(MagickRealType)
4911 GetPixelBlack(image,p)+(pixel.black*
4912 amount);
4913 SetPixelBlack(unsharp_image,
4914 ClampToQuantum(pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00004915 }
cristyed231572011-07-14 02:18:59 +00004916 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00004917 {
4918 pixel.alpha=GetPixelAlpha(image,p)-
4919 (MagickRealType) GetPixelAlpha(image,q);
4920 if (fabs(2.0*pixel.alpha) < quantum_threshold)
4921 pixel.alpha=(MagickRealType)
4922 GetPixelAlpha(image,p);
4923 else
4924 pixel.alpha=GetPixelAlpha(image,p)+
4925 (pixel.alpha*amount);
4926 SetPixelAlpha(unsharp_image,
4927 ClampToQuantum(pixel.alpha),q);
4928 }
cristyed231572011-07-14 02:18:59 +00004929 p+=GetPixelChannels(image);
4930 q+=GetPixelChannels(unsharp_image);
cristy3ed852e2009-09-05 21:47:34 +00004931 }
4932 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
4933 status=MagickFalse;
4934 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4935 {
4936 MagickBooleanType
4937 proceed;
4938
cristyb5d5f722009-11-04 03:03:49 +00004939#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00004940 #pragma omp critical (MagickCore_UnsharpMaskImage)
cristy3ed852e2009-09-05 21:47:34 +00004941#endif
4942 proceed=SetImageProgress(image,SharpenImageTag,progress++,image->rows);
4943 if (proceed == MagickFalse)
4944 status=MagickFalse;
4945 }
4946 }
4947 unsharp_image->type=image->type;
4948 unsharp_view=DestroyCacheView(unsharp_view);
4949 image_view=DestroyCacheView(image_view);
4950 if (status == MagickFalse)
4951 unsharp_image=DestroyImage(unsharp_image);
4952 return(unsharp_image);
4953}