blob: cb791b523520e47859e5bc835d8099407d0c369a [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,
cristyeb52cde2011-07-17 01:52:52 +00001211 *image_view,
1212 *sliding_window_view;
cristyc4c8d132010-01-07 01:58:38 +00001213
cristyfccdab92009-11-30 16:43:57 +00001214 Image
1215 *convolve_image;
1216
cristyfccdab92009-11-30 16:43:57 +00001217 MagickBooleanType
1218 status;
1219
cristybb503372010-05-27 20:51:26 +00001220 MagickOffsetType
1221 progress;
1222
cristybb503372010-05-27 20:51:26 +00001223 ssize_t
1224 y;
1225
cristyfccdab92009-11-30 16:43:57 +00001226 /*
1227 Initialize convolve image attributes.
1228 */
1229 assert(image != (Image *) NULL);
1230 assert(image->signature == MagickSignature);
1231 if (image->debug != MagickFalse)
1232 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1233 assert(exception != (ExceptionInfo *) NULL);
1234 assert(exception->signature == MagickSignature);
cristy5e6be1e2011-07-16 01:23:39 +00001235 if ((kernel_info->width % 2) == 0)
cristyfccdab92009-11-30 16:43:57 +00001236 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
cristy08429172011-07-14 17:18:16 +00001237 convolve_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1238 exception);
cristyfccdab92009-11-30 16:43:57 +00001239 if (convolve_image == (Image *) NULL)
1240 return((Image *) NULL);
1241 if (SetImageStorageClass(convolve_image,DirectClass) == MagickFalse)
1242 {
1243 InheritException(exception,&convolve_image->exception);
1244 convolve_image=DestroyImage(convolve_image);
1245 return((Image *) NULL);
1246 }
1247 if (image->debug != MagickFalse)
1248 {
1249 char
1250 format[MaxTextExtent],
1251 *message;
1252
cristy117ff172010-08-15 21:35:32 +00001253 register const double
1254 *k;
1255
cristy4e154852011-07-14 13:28:53 +00001256 register ssize_t
1257 u;
1258
cristybb503372010-05-27 20:51:26 +00001259 ssize_t
cristyfccdab92009-11-30 16:43:57 +00001260 v;
1261
cristyfccdab92009-11-30 16:43:57 +00001262 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristy5e6be1e2011-07-16 01:23:39 +00001263 " ConvolveImage with %.20gx%.20g kernel:",(double) kernel_info->width,
1264 (double) kernel_info->height);
cristyfccdab92009-11-30 16:43:57 +00001265 message=AcquireString("");
cristy5e6be1e2011-07-16 01:23:39 +00001266 k=kernel_info->values;
1267 for (v=0; v < (ssize_t) kernel_info->width; v++)
cristyfccdab92009-11-30 16:43:57 +00001268 {
1269 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00001270 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristyfccdab92009-11-30 16:43:57 +00001271 (void) ConcatenateString(&message,format);
cristy5e6be1e2011-07-16 01:23:39 +00001272 for (u=0; u < (ssize_t) kernel_info->height; u++)
cristyfccdab92009-11-30 16:43:57 +00001273 {
cristyb51dff52011-05-19 16:55:47 +00001274 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristyfccdab92009-11-30 16:43:57 +00001275 (void) ConcatenateString(&message,format);
1276 }
1277 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
1278 }
1279 message=DestroyString(message);
1280 }
1281 /*
cristyfccdab92009-11-30 16:43:57 +00001282 Convolve image.
1283 */
1284 status=MagickTrue;
1285 progress=0;
cristyfccdab92009-11-30 16:43:57 +00001286 image_view=AcquireCacheView(image);
cristyeb52cde2011-07-17 01:52:52 +00001287 sliding_window_view=AcquireCacheView(image);
cristyfccdab92009-11-30 16:43:57 +00001288 convolve_view=AcquireCacheView(convolve_image);
1289#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy175653e2011-07-10 23:13:34 +00001290 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristyfccdab92009-11-30 16:43:57 +00001291#endif
cristybb503372010-05-27 20:51:26 +00001292 for (y=0; y < (ssize_t) image->rows; y++)
cristyfccdab92009-11-30 16:43:57 +00001293 {
cristy4c08aed2011-07-01 19:47:50 +00001294 register const Quantum
cristyeb52cde2011-07-17 01:52:52 +00001295 *restrict p,
1296 *restrict sliding_window;
cristyfccdab92009-11-30 16:43:57 +00001297
cristy4c08aed2011-07-01 19:47:50 +00001298 register Quantum
cristyfccdab92009-11-30 16:43:57 +00001299 *restrict q;
1300
cristy117ff172010-08-15 21:35:32 +00001301 register ssize_t
1302 x;
1303
cristy63852c12011-07-14 23:47:58 +00001304 size_t
cristyed231572011-07-14 02:18:59 +00001305 channels,
1306 convolve_channels;
1307
cristyfccdab92009-11-30 16:43:57 +00001308 if (status == MagickFalse)
1309 continue;
cristyeb52cde2011-07-17 01:52:52 +00001310 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1311 sliding_window=GetCacheViewVirtualPixels(image_view,-((ssize_t)
1312 kernel_info->width/2L),y-(ssize_t) (kernel_info->height/2L),
1313 image->columns+kernel_info->width,kernel_info->height,exception);
cristy08429172011-07-14 17:18:16 +00001314 q=QueueCacheViewAuthenticPixels(convolve_view,0,y,convolve_image->columns,1,
cristyfccdab92009-11-30 16:43:57 +00001315 exception);
cristyeb52cde2011-07-17 01:52:52 +00001316 if ((p == (const Quantum *) NULL) ||
1317 (sliding_window == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristyfccdab92009-11-30 16:43:57 +00001318 {
1319 status=MagickFalse;
1320 continue;
1321 }
cristyed231572011-07-14 02:18:59 +00001322 channels=GetPixelChannels(image);
1323 convolve_channels=GetPixelChannels(convolve_image);
cristybb503372010-05-27 20:51:26 +00001324 for (x=0; x < (ssize_t) image->columns; x++)
cristyfccdab92009-11-30 16:43:57 +00001325 {
cristybb503372010-05-27 20:51:26 +00001326 register ssize_t
cristyed231572011-07-14 02:18:59 +00001327 i;
cristyfccdab92009-11-30 16:43:57 +00001328
cristyed231572011-07-14 02:18:59 +00001329 for (i=0; i < (ssize_t) channels; i++)
1330 {
cristyed231572011-07-14 02:18:59 +00001331 MagickRealType
cristy4e154852011-07-14 13:28:53 +00001332 alpha,
1333 gamma,
cristyed231572011-07-14 02:18:59 +00001334 pixel;
1335
1336 PixelChannel
1337 channel;
1338
1339 PixelTrait
1340 convolve_traits,
1341 traits;
1342
1343 register const double
1344 *restrict k;
1345
1346 register const Quantum
cristyeb52cde2011-07-17 01:52:52 +00001347 *restrict pixels;
cristyed231572011-07-14 02:18:59 +00001348
1349 register ssize_t
1350 u;
1351
1352 ssize_t
1353 v;
1354
cristyed231572011-07-14 02:18:59 +00001355 traits=GetPixelChannelMapTraits(image,i);
cristy4e154852011-07-14 13:28:53 +00001356 if (traits == UndefinedPixelTrait)
cristyed231572011-07-14 02:18:59 +00001357 continue;
cristy4e154852011-07-14 13:28:53 +00001358 channel=GetPixelChannelMapChannel(image,i);
1359 convolve_traits=GetPixelChannelMapTraits(convolve_image,channel);
1360 if (convolve_traits == UndefinedPixelTrait)
1361 continue;
1362 if ((convolve_traits & CopyPixelTrait) != 0)
1363 {
cristyeb52cde2011-07-17 01:52:52 +00001364 SetPixelChannel(convolve_image,channel,p[i],q);
cristy4e154852011-07-14 13:28:53 +00001365 continue;
1366 }
cristy5e6be1e2011-07-16 01:23:39 +00001367 k=kernel_info->values;
cristyeb52cde2011-07-17 01:52:52 +00001368 pixels=sliding_window;
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 */
cristyeb52cde2011-07-17 01:52:52 +00001377 for (v=0; v < (ssize_t) kernel_info->height; v++)
cristyfccdab92009-11-30 16:43:57 +00001378 {
cristyeb52cde2011-07-17 01:52:52 +00001379 for (u=0; u < (ssize_t) kernel_info->width; u++)
cristy175653e2011-07-10 23:13:34 +00001380 {
cristyeb52cde2011-07-17 01:52:52 +00001381 pixel+=(*k)*pixels[i];
cristyed231572011-07-14 02:18:59 +00001382 k++;
cristyeb52cde2011-07-17 01:52:52 +00001383 pixels+=channels;
cristy175653e2011-07-10 23:13:34 +00001384 }
cristyeb52cde2011-07-17 01:52:52 +00001385 pixels+=image->columns*channels;
cristy175653e2011-07-10 23:13:34 +00001386 }
cristy4e154852011-07-14 13:28:53 +00001387 SetPixelChannel(convolve_image,channel,ClampToQuantum(pixel),q);
1388 continue;
cristyed231572011-07-14 02:18:59 +00001389 }
cristy4e154852011-07-14 13:28:53 +00001390 /*
1391 Alpha blending.
1392 */
1393 gamma=0.0;
cristyeb52cde2011-07-17 01:52:52 +00001394 for (v=0; v < (ssize_t) kernel_info->height; v++)
cristy4e154852011-07-14 13:28:53 +00001395 {
cristyeb52cde2011-07-17 01:52:52 +00001396 for (u=0; u < (ssize_t) kernel_info->width; u++)
cristy4e154852011-07-14 13:28:53 +00001397 {
cristyeb52cde2011-07-17 01:52:52 +00001398 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,pixels));
1399 pixel+=(*k)*alpha*pixels[i];
cristy4e154852011-07-14 13:28:53 +00001400 gamma+=(*k)*alpha;
1401 k++;
cristyeb52cde2011-07-17 01:52:52 +00001402 pixels+=channels;
cristy4e154852011-07-14 13:28:53 +00001403 }
cristyeb52cde2011-07-17 01:52:52 +00001404 pixels+=image->columns*channels;
cristy4e154852011-07-14 13:28:53 +00001405 }
cristy1ce96d02011-07-14 17:57:24 +00001406 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1407 SetPixelChannel(convolve_image,channel,ClampToQuantum(gamma*pixel),q);
cristyed231572011-07-14 02:18:59 +00001408 }
1409 p+=channels;
cristyeb52cde2011-07-17 01:52:52 +00001410 sliding_window+=channels;
cristyed231572011-07-14 02:18:59 +00001411 q+=convolve_channels;
cristyfccdab92009-11-30 16:43:57 +00001412 }
cristyed231572011-07-14 02:18:59 +00001413 if (SyncCacheViewAuthenticPixels(convolve_view,exception) == MagickFalse)
cristyfccdab92009-11-30 16:43:57 +00001414 status=MagickFalse;
1415 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1416 {
1417 MagickBooleanType
1418 proceed;
1419
1420#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00001421 #pragma omp critical (MagickCore_ConvolveImage)
cristyfccdab92009-11-30 16:43:57 +00001422#endif
1423 proceed=SetImageProgress(image,ConvolveImageTag,progress++,image->rows);
1424 if (proceed == MagickFalse)
1425 status=MagickFalse;
1426 }
1427 }
1428 convolve_image->type=image->type;
1429 convolve_view=DestroyCacheView(convolve_view);
cristyeb52cde2011-07-17 01:52:52 +00001430 sliding_window_view=DestroyCacheView(sliding_window_view);
cristyfccdab92009-11-30 16:43:57 +00001431 image_view=DestroyCacheView(image_view);
cristyfccdab92009-11-30 16:43:57 +00001432 if (status == MagickFalse)
1433 convolve_image=DestroyImage(convolve_image);
1434 return(convolve_image);
1435}
1436
1437/*
1438%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1439% %
1440% %
1441% %
cristy3ed852e2009-09-05 21:47:34 +00001442% D e s p e c k l e I m a g e %
1443% %
1444% %
1445% %
1446%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1447%
1448% DespeckleImage() reduces the speckle noise in an image while perserving the
1449% edges of the original image.
1450%
1451% The format of the DespeckleImage method is:
1452%
1453% Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1454%
1455% A description of each parameter follows:
1456%
1457% o image: the image.
1458%
1459% o exception: return any errors or warnings in this structure.
1460%
1461*/
1462
cristybb503372010-05-27 20:51:26 +00001463static void Hull(const ssize_t x_offset,const ssize_t y_offset,
1464 const size_t columns,const size_t rows,Quantum *f,Quantum *g,
cristy3ed852e2009-09-05 21:47:34 +00001465 const int polarity)
1466{
cristy3ed852e2009-09-05 21:47:34 +00001467 MagickRealType
1468 v;
1469
cristy3ed852e2009-09-05 21:47:34 +00001470 register Quantum
1471 *p,
1472 *q,
1473 *r,
1474 *s;
1475
cristy117ff172010-08-15 21:35:32 +00001476 register ssize_t
1477 x;
1478
1479 ssize_t
1480 y;
1481
cristy3ed852e2009-09-05 21:47:34 +00001482 assert(f != (Quantum *) NULL);
1483 assert(g != (Quantum *) NULL);
1484 p=f+(columns+2);
1485 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001486 r=p+(y_offset*((ssize_t) columns+2)+x_offset);
1487 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001488 {
1489 p++;
1490 q++;
1491 r++;
1492 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001493 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001494 {
1495 v=(MagickRealType) (*p);
1496 if ((MagickRealType) *r >= (v+(MagickRealType) ScaleCharToQuantum(2)))
1497 v+=ScaleCharToQuantum(1);
1498 *q=(Quantum) v;
1499 p++;
1500 q++;
1501 r++;
1502 }
1503 else
cristybb503372010-05-27 20:51:26 +00001504 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001505 {
1506 v=(MagickRealType) (*p);
1507 if ((MagickRealType) *r <= (v-(MagickRealType) ScaleCharToQuantum(2)))
cristybb503372010-05-27 20:51:26 +00001508 v-=(ssize_t) ScaleCharToQuantum(1);
cristy3ed852e2009-09-05 21:47:34 +00001509 *q=(Quantum) v;
1510 p++;
1511 q++;
1512 r++;
1513 }
1514 p++;
1515 q++;
1516 r++;
1517 }
1518 p=f+(columns+2);
1519 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001520 r=q+(y_offset*((ssize_t) columns+2)+x_offset);
1521 s=q-(y_offset*((ssize_t) columns+2)+x_offset);
1522 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001523 {
1524 p++;
1525 q++;
1526 r++;
1527 s++;
1528 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001529 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001530 {
1531 v=(MagickRealType) (*q);
1532 if (((MagickRealType) *s >=
1533 (v+(MagickRealType) ScaleCharToQuantum(2))) &&
1534 ((MagickRealType) *r > v))
1535 v+=ScaleCharToQuantum(1);
1536 *p=(Quantum) v;
1537 p++;
1538 q++;
1539 r++;
1540 s++;
1541 }
1542 else
cristybb503372010-05-27 20:51:26 +00001543 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001544 {
1545 v=(MagickRealType) (*q);
1546 if (((MagickRealType) *s <=
1547 (v-(MagickRealType) ScaleCharToQuantum(2))) &&
1548 ((MagickRealType) *r < v))
1549 v-=(MagickRealType) ScaleCharToQuantum(1);
1550 *p=(Quantum) v;
1551 p++;
1552 q++;
1553 r++;
1554 s++;
1555 }
1556 p++;
1557 q++;
1558 r++;
1559 s++;
1560 }
1561}
1562
1563MagickExport Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1564{
1565#define DespeckleImageTag "Despeckle/Image"
1566
cristy2407fc22009-09-11 00:55:25 +00001567 CacheView
1568 *despeckle_view,
1569 *image_view;
1570
cristy3ed852e2009-09-05 21:47:34 +00001571 Image
1572 *despeckle_image;
1573
cristy3ed852e2009-09-05 21:47:34 +00001574 MagickBooleanType
1575 status;
1576
cristya58c3172011-02-19 19:23:11 +00001577 register ssize_t
1578 i;
1579
cristy3ed852e2009-09-05 21:47:34 +00001580 Quantum
cristy65b9f392011-02-22 14:22:54 +00001581 *restrict buffers,
1582 *restrict pixels;
cristy3ed852e2009-09-05 21:47:34 +00001583
1584 size_t
cristya58c3172011-02-19 19:23:11 +00001585 length,
1586 number_channels;
cristy117ff172010-08-15 21:35:32 +00001587
cristybb503372010-05-27 20:51:26 +00001588 static const ssize_t
cristy691a29e2009-09-11 00:44:10 +00001589 X[4] = {0, 1, 1,-1},
1590 Y[4] = {1, 0, 1, 1};
cristy3ed852e2009-09-05 21:47:34 +00001591
cristy3ed852e2009-09-05 21:47:34 +00001592 /*
1593 Allocate despeckled image.
1594 */
1595 assert(image != (const Image *) NULL);
1596 assert(image->signature == MagickSignature);
1597 if (image->debug != MagickFalse)
1598 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1599 assert(exception != (ExceptionInfo *) NULL);
1600 assert(exception->signature == MagickSignature);
1601 despeckle_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1602 exception);
1603 if (despeckle_image == (Image *) NULL)
1604 return((Image *) NULL);
1605 if (SetImageStorageClass(despeckle_image,DirectClass) == MagickFalse)
1606 {
1607 InheritException(exception,&despeckle_image->exception);
1608 despeckle_image=DestroyImage(despeckle_image);
1609 return((Image *) NULL);
1610 }
1611 /*
1612 Allocate image buffers.
1613 */
1614 length=(size_t) ((image->columns+2)*(image->rows+2));
cristy65b9f392011-02-22 14:22:54 +00001615 pixels=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1616 buffers=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1617 if ((pixels == (Quantum *) NULL) || (buffers == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001618 {
cristy65b9f392011-02-22 14:22:54 +00001619 if (buffers != (Quantum *) NULL)
1620 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1621 if (pixels != (Quantum *) NULL)
1622 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001623 despeckle_image=DestroyImage(despeckle_image);
1624 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1625 }
1626 /*
1627 Reduce speckle in the image.
1628 */
1629 status=MagickTrue;
cristy109695a2011-02-19 19:38:14 +00001630 number_channels=(size_t) (image->colorspace == CMYKColorspace ? 5 : 4);
cristy3ed852e2009-09-05 21:47:34 +00001631 image_view=AcquireCacheView(image);
1632 despeckle_view=AcquireCacheView(despeckle_image);
cristy8df3d002011-02-19 19:40:59 +00001633 for (i=0; i < (ssize_t) number_channels; i++)
cristy3ed852e2009-09-05 21:47:34 +00001634 {
cristy3ed852e2009-09-05 21:47:34 +00001635 register Quantum
1636 *buffer,
1637 *pixel;
1638
cristyc1488b52011-02-19 18:54:15 +00001639 register ssize_t
cristya58c3172011-02-19 19:23:11 +00001640 k,
cristyc1488b52011-02-19 18:54:15 +00001641 x;
1642
cristy117ff172010-08-15 21:35:32 +00001643 ssize_t
1644 j,
1645 y;
1646
cristy3ed852e2009-09-05 21:47:34 +00001647 if (status == MagickFalse)
1648 continue;
cristy65b9f392011-02-22 14:22:54 +00001649 pixel=pixels;
cristy3ed852e2009-09-05 21:47:34 +00001650 (void) ResetMagickMemory(pixel,0,length*sizeof(*pixel));
cristy65b9f392011-02-22 14:22:54 +00001651 buffer=buffers;
cristybb503372010-05-27 20:51:26 +00001652 j=(ssize_t) image->columns+2;
1653 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001654 {
cristy4c08aed2011-07-01 19:47:50 +00001655 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001656 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001657
1658 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001659 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001660 break;
1661 j++;
cristybb503372010-05-27 20:51:26 +00001662 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001663 {
cristya58c3172011-02-19 19:23:11 +00001664 switch (i)
cristy3ed852e2009-09-05 21:47:34 +00001665 {
cristy4c08aed2011-07-01 19:47:50 +00001666 case 0: pixel[j]=GetPixelRed(image,p); break;
1667 case 1: pixel[j]=GetPixelGreen(image,p); break;
1668 case 2: pixel[j]=GetPixelBlue(image,p); break;
1669 case 3: pixel[j]=GetPixelAlpha(image,p); break;
1670 case 4: pixel[j]=GetPixelBlack(image,p); break;
cristy3ed852e2009-09-05 21:47:34 +00001671 default: break;
1672 }
cristyed231572011-07-14 02:18:59 +00001673 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001674 j++;
1675 }
1676 j++;
1677 }
cristy3ed852e2009-09-05 21:47:34 +00001678 (void) ResetMagickMemory(buffer,0,length*sizeof(*buffer));
cristya58c3172011-02-19 19:23:11 +00001679 for (k=0; k < 4; k++)
cristy3ed852e2009-09-05 21:47:34 +00001680 {
cristya58c3172011-02-19 19:23:11 +00001681 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,1);
1682 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,1);
1683 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,-1);
1684 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,-1);
cristy3ed852e2009-09-05 21:47:34 +00001685 }
cristybb503372010-05-27 20:51:26 +00001686 j=(ssize_t) image->columns+2;
1687 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001688 {
1689 MagickBooleanType
1690 sync;
1691
cristy4c08aed2011-07-01 19:47:50 +00001692 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001693 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001694
1695 q=GetCacheViewAuthenticPixels(despeckle_view,0,y,despeckle_image->columns,
1696 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001697 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001698 break;
1699 j++;
cristybb503372010-05-27 20:51:26 +00001700 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001701 {
cristya58c3172011-02-19 19:23:11 +00001702 switch (i)
cristy3ed852e2009-09-05 21:47:34 +00001703 {
cristy4c08aed2011-07-01 19:47:50 +00001704 case 0: SetPixelRed(despeckle_image,pixel[j],q); break;
1705 case 1: SetPixelGreen(despeckle_image,pixel[j],q); break;
1706 case 2: SetPixelBlue(despeckle_image,pixel[j],q); break;
1707 case 3: SetPixelAlpha(despeckle_image,pixel[j],q); break;
1708 case 4: SetPixelBlack(despeckle_image,pixel[j],q); break;
cristy3ed852e2009-09-05 21:47:34 +00001709 default: break;
1710 }
cristyed231572011-07-14 02:18:59 +00001711 q+=GetPixelChannels(despeckle_image);
cristy3ed852e2009-09-05 21:47:34 +00001712 j++;
1713 }
1714 sync=SyncCacheViewAuthenticPixels(despeckle_view,exception);
1715 if (sync == MagickFalse)
1716 {
1717 status=MagickFalse;
1718 break;
1719 }
1720 j++;
1721 }
1722 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1723 {
1724 MagickBooleanType
1725 proceed;
1726
cristya58c3172011-02-19 19:23:11 +00001727 proceed=SetImageProgress(image,DespeckleImageTag,(MagickOffsetType) i,
1728 number_channels);
cristy3ed852e2009-09-05 21:47:34 +00001729 if (proceed == MagickFalse)
1730 status=MagickFalse;
1731 }
1732 }
1733 despeckle_view=DestroyCacheView(despeckle_view);
1734 image_view=DestroyCacheView(image_view);
cristy65b9f392011-02-22 14:22:54 +00001735 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1736 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001737 despeckle_image->type=image->type;
1738 if (status == MagickFalse)
1739 despeckle_image=DestroyImage(despeckle_image);
1740 return(despeckle_image);
1741}
1742
1743/*
1744%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1745% %
1746% %
1747% %
1748% E d g e I m a g e %
1749% %
1750% %
1751% %
1752%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1753%
1754% EdgeImage() finds edges in an image. Radius defines the radius of the
1755% convolution filter. Use a radius of 0 and EdgeImage() selects a suitable
1756% radius for you.
1757%
1758% The format of the EdgeImage method is:
1759%
1760% Image *EdgeImage(const Image *image,const double radius,
1761% ExceptionInfo *exception)
1762%
1763% A description of each parameter follows:
1764%
1765% o image: the image.
1766%
1767% o radius: the radius of the pixel neighborhood.
1768%
1769% o exception: return any errors or warnings in this structure.
1770%
1771*/
1772MagickExport Image *EdgeImage(const Image *image,const double radius,
1773 ExceptionInfo *exception)
1774{
1775 Image
1776 *edge_image;
1777
cristy41cbe682011-07-15 19:12:37 +00001778 KernelInfo
1779 *kernel_info;
cristy3ed852e2009-09-05 21:47:34 +00001780
cristybb503372010-05-27 20:51:26 +00001781 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001782 i;
1783
cristybb503372010-05-27 20:51:26 +00001784 size_t
cristy3ed852e2009-09-05 21:47:34 +00001785 width;
1786
cristy41cbe682011-07-15 19:12:37 +00001787 ssize_t
1788 j,
1789 u,
1790 v;
1791
cristy3ed852e2009-09-05 21:47:34 +00001792 assert(image != (const Image *) NULL);
1793 assert(image->signature == MagickSignature);
1794 if (image->debug != MagickFalse)
1795 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1796 assert(exception != (ExceptionInfo *) NULL);
1797 assert(exception->signature == MagickSignature);
1798 width=GetOptimalKernelWidth1D(radius,0.5);
cristy5e6be1e2011-07-16 01:23:39 +00001799 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00001800 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001801 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00001802 kernel_info->width=width;
1803 kernel_info->height=width;
cristy41cbe682011-07-15 19:12:37 +00001804 kernel_info->values=(double *) AcquireAlignedMemory(kernel_info->width,
1805 kernel_info->width*sizeof(*kernel_info->values));
1806 if (kernel_info->values == (double *) NULL)
1807 {
1808 kernel_info=DestroyKernelInfo(kernel_info);
1809 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1810 }
1811 j=(ssize_t) kernel_info->width/2;
1812 i=0;
1813 for (v=(-j); v <= j; v++)
1814 {
1815 for (u=(-j); u <= j; u++)
1816 {
1817 kernel_info->values[i]=(-1.0);
1818 i++;
1819 }
1820 }
1821 kernel_info->values[i/2]=(double) (width*width-1.0);
cristy0a922382011-07-16 15:30:34 +00001822 kernel_info->bias=image->bias;
cristy5e6be1e2011-07-16 01:23:39 +00001823 edge_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00001824 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00001825 return(edge_image);
1826}
1827
1828/*
1829%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1830% %
1831% %
1832% %
1833% E m b o s s I m a g e %
1834% %
1835% %
1836% %
1837%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1838%
1839% EmbossImage() returns a grayscale image with a three-dimensional effect.
1840% We convolve the image with a Gaussian operator of the given radius and
1841% standard deviation (sigma). For reasonable results, radius should be
1842% larger than sigma. Use a radius of 0 and Emboss() selects a suitable
1843% radius for you.
1844%
1845% The format of the EmbossImage method is:
1846%
1847% Image *EmbossImage(const Image *image,const double radius,
1848% const double sigma,ExceptionInfo *exception)
1849%
1850% A description of each parameter follows:
1851%
1852% o image: the image.
1853%
1854% o radius: the radius of the pixel neighborhood.
1855%
1856% o sigma: the standard deviation of the Gaussian, in pixels.
1857%
1858% o exception: return any errors or warnings in this structure.
1859%
1860*/
1861MagickExport Image *EmbossImage(const Image *image,const double radius,
1862 const double sigma,ExceptionInfo *exception)
1863{
cristy3ed852e2009-09-05 21:47:34 +00001864 Image
1865 *emboss_image;
1866
cristy41cbe682011-07-15 19:12:37 +00001867 KernelInfo
1868 *kernel_info;
1869
cristybb503372010-05-27 20:51:26 +00001870 register ssize_t
cristy47e00502009-12-17 19:19:57 +00001871 i;
1872
cristybb503372010-05-27 20:51:26 +00001873 size_t
cristy3ed852e2009-09-05 21:47:34 +00001874 width;
1875
cristy117ff172010-08-15 21:35:32 +00001876 ssize_t
1877 j,
1878 k,
1879 u,
1880 v;
1881
cristy41cbe682011-07-15 19:12:37 +00001882 assert(image != (const Image *) NULL);
cristy3ed852e2009-09-05 21:47:34 +00001883 assert(image->signature == MagickSignature);
1884 if (image->debug != MagickFalse)
1885 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1886 assert(exception != (ExceptionInfo *) NULL);
1887 assert(exception->signature == MagickSignature);
1888 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00001889 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00001890 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001891 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00001892 kernel_info->width=width;
1893 kernel_info->height=width;
cristy41cbe682011-07-15 19:12:37 +00001894 kernel_info->values=(double *) AcquireAlignedMemory(kernel_info->width,
1895 kernel_info->width*sizeof(*kernel_info->values));
1896 if (kernel_info->values == (double *) NULL)
1897 {
1898 kernel_info=DestroyKernelInfo(kernel_info);
1899 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1900 }
1901 j=(ssize_t) kernel_info->width/2;
cristy47e00502009-12-17 19:19:57 +00001902 k=j;
1903 i=0;
1904 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00001905 {
cristy47e00502009-12-17 19:19:57 +00001906 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00001907 {
cristy41cbe682011-07-15 19:12:37 +00001908 kernel_info->values[i]=(double) (((u < 0) || (v < 0) ? -8.0 : 8.0)*
cristy47e00502009-12-17 19:19:57 +00001909 exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
cristy4205a3c2010-09-12 20:19:59 +00001910 (2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +00001911 if (u != k)
cristy41cbe682011-07-15 19:12:37 +00001912 kernel_info->values[i]=0.0;
cristy3ed852e2009-09-05 21:47:34 +00001913 i++;
1914 }
cristy47e00502009-12-17 19:19:57 +00001915 k--;
cristy3ed852e2009-09-05 21:47:34 +00001916 }
cristy0a922382011-07-16 15:30:34 +00001917 kernel_info->bias=image->bias;
cristy5e6be1e2011-07-16 01:23:39 +00001918 emboss_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00001919 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00001920 if (emboss_image != (Image *) NULL)
1921 (void) EqualizeImage(emboss_image);
cristy3ed852e2009-09-05 21:47:34 +00001922 return(emboss_image);
1923}
1924
1925/*
1926%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1927% %
1928% %
1929% %
1930% G a u s s i a n B l u r I m a g e %
1931% %
1932% %
1933% %
1934%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1935%
1936% GaussianBlurImage() blurs an image. We convolve the image with a
1937% Gaussian operator of the given radius and standard deviation (sigma).
1938% For reasonable results, the radius should be larger than sigma. Use a
1939% radius of 0 and GaussianBlurImage() selects a suitable radius for you
1940%
1941% The format of the GaussianBlurImage method is:
1942%
1943% Image *GaussianBlurImage(const Image *image,onst double radius,
1944% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001945%
1946% A description of each parameter follows:
1947%
1948% o image: the image.
1949%
cristy3ed852e2009-09-05 21:47:34 +00001950% o radius: the radius of the Gaussian, in pixels, not counting the center
1951% pixel.
1952%
1953% o sigma: the standard deviation of the Gaussian, in pixels.
1954%
1955% o exception: return any errors or warnings in this structure.
1956%
1957*/
cristy41cbe682011-07-15 19:12:37 +00001958MagickExport Image *GaussianBlurImage(const Image *image,const double radius,
1959 const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001960{
cristy3ed852e2009-09-05 21:47:34 +00001961 Image
1962 *blur_image;
1963
cristy41cbe682011-07-15 19:12:37 +00001964 KernelInfo
1965 *kernel_info;
1966
cristybb503372010-05-27 20:51:26 +00001967 register ssize_t
cristy47e00502009-12-17 19:19:57 +00001968 i;
1969
cristybb503372010-05-27 20:51:26 +00001970 size_t
cristy3ed852e2009-09-05 21:47:34 +00001971 width;
1972
cristy117ff172010-08-15 21:35:32 +00001973 ssize_t
1974 j,
1975 u,
1976 v;
1977
cristy3ed852e2009-09-05 21:47:34 +00001978 assert(image != (const Image *) NULL);
1979 assert(image->signature == MagickSignature);
1980 if (image->debug != MagickFalse)
1981 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1982 assert(exception != (ExceptionInfo *) NULL);
1983 assert(exception->signature == MagickSignature);
1984 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00001985 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00001986 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001987 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00001988 (void) ResetMagickMemory(kernel_info,0,sizeof(*kernel_info));
1989 kernel_info->width=width;
1990 kernel_info->height=width;
1991 kernel_info->signature=MagickSignature;
1992 kernel_info->values=(double *) AcquireAlignedMemory(kernel_info->width,
1993 kernel_info->width*sizeof(*kernel_info->values));
1994 if (kernel_info->values == (double *) NULL)
1995 {
1996 kernel_info=DestroyKernelInfo(kernel_info);
1997 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1998 }
1999 j=(ssize_t) kernel_info->width/2;
cristy3ed852e2009-09-05 21:47:34 +00002000 i=0;
cristy47e00502009-12-17 19:19:57 +00002001 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002002 {
cristy47e00502009-12-17 19:19:57 +00002003 for (u=(-j); u <= j; u++)
cristy41cbe682011-07-15 19:12:37 +00002004 {
2005 kernel_info->values[i]=(double) (exp(-((double) u*u+v*v)/(2.0*
2006 MagickSigma*MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
2007 i++;
2008 }
cristy3ed852e2009-09-05 21:47:34 +00002009 }
cristy0a922382011-07-16 15:30:34 +00002010 kernel_info->bias=image->bias;
cristy5e6be1e2011-07-16 01:23:39 +00002011 blur_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00002012 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00002013 return(blur_image);
2014}
2015
2016/*
2017%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2018% %
2019% %
2020% %
cristy3ed852e2009-09-05 21:47:34 +00002021% M o t i o n B l u r I m a g e %
2022% %
2023% %
2024% %
2025%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2026%
2027% MotionBlurImage() simulates motion blur. We convolve the image with a
2028% Gaussian operator of the given radius and standard deviation (sigma).
2029% For reasonable results, radius should be larger than sigma. Use a
2030% radius of 0 and MotionBlurImage() selects a suitable radius for you.
2031% Angle gives the angle of the blurring motion.
2032%
2033% Andrew Protano contributed this effect.
2034%
2035% The format of the MotionBlurImage method is:
2036%
2037% Image *MotionBlurImage(const Image *image,const double radius,
2038% const double sigma,const double angle,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002039%
2040% A description of each parameter follows:
2041%
2042% o image: the image.
2043%
cristy3ed852e2009-09-05 21:47:34 +00002044% o radius: the radius of the Gaussian, in pixels, not counting
2045% the center pixel.
2046%
2047% o sigma: the standard deviation of the Gaussian, in pixels.
2048%
cristycee97112010-05-28 00:44:52 +00002049% o angle: Apply the effect along this angle.
cristy3ed852e2009-09-05 21:47:34 +00002050%
2051% o exception: return any errors or warnings in this structure.
2052%
2053*/
2054
cristybb503372010-05-27 20:51:26 +00002055static double *GetMotionBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +00002056{
cristy3ed852e2009-09-05 21:47:34 +00002057 double
cristy47e00502009-12-17 19:19:57 +00002058 *kernel,
cristy3ed852e2009-09-05 21:47:34 +00002059 normalize;
2060
cristybb503372010-05-27 20:51:26 +00002061 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002062 i;
2063
2064 /*
cristy47e00502009-12-17 19:19:57 +00002065 Generate a 1-D convolution kernel.
cristy3ed852e2009-09-05 21:47:34 +00002066 */
2067 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
2068 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
2069 if (kernel == (double *) NULL)
2070 return(kernel);
cristy3ed852e2009-09-05 21:47:34 +00002071 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00002072 for (i=0; i < (ssize_t) width; i++)
cristy47e00502009-12-17 19:19:57 +00002073 {
cristy4205a3c2010-09-12 20:19:59 +00002074 kernel[i]=(double) (exp((-((double) i*i)/(double) (2.0*MagickSigma*
2075 MagickSigma)))/(MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00002076 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +00002077 }
cristybb503372010-05-27 20:51:26 +00002078 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002079 kernel[i]/=normalize;
2080 return(kernel);
2081}
2082
cristyf4ad9df2011-07-08 16:49:03 +00002083MagickExport Image *MotionBlurImage(const Image *image,
2084 const double radius,const double sigma,const double angle,
2085 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002086{
cristyc4c8d132010-01-07 01:58:38 +00002087 CacheView
2088 *blur_view,
2089 *image_view;
2090
cristy3ed852e2009-09-05 21:47:34 +00002091 double
2092 *kernel;
2093
2094 Image
2095 *blur_image;
2096
cristy3ed852e2009-09-05 21:47:34 +00002097 MagickBooleanType
2098 status;
2099
cristybb503372010-05-27 20:51:26 +00002100 MagickOffsetType
2101 progress;
2102
cristy4c08aed2011-07-01 19:47:50 +00002103 PixelInfo
cristyddd82202009-11-03 20:14:50 +00002104 bias;
cristy3ed852e2009-09-05 21:47:34 +00002105
2106 OffsetInfo
2107 *offset;
2108
2109 PointInfo
2110 point;
2111
cristybb503372010-05-27 20:51:26 +00002112 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002113 i;
2114
cristybb503372010-05-27 20:51:26 +00002115 size_t
cristy3ed852e2009-09-05 21:47:34 +00002116 width;
2117
cristybb503372010-05-27 20:51:26 +00002118 ssize_t
2119 y;
2120
cristy3ed852e2009-09-05 21:47:34 +00002121 assert(image != (Image *) NULL);
2122 assert(image->signature == MagickSignature);
2123 if (image->debug != MagickFalse)
2124 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2125 assert(exception != (ExceptionInfo *) NULL);
2126 width=GetOptimalKernelWidth1D(radius,sigma);
2127 kernel=GetMotionBlurKernel(width,sigma);
2128 if (kernel == (double *) NULL)
2129 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2130 offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset));
2131 if (offset == (OffsetInfo *) NULL)
2132 {
2133 kernel=(double *) RelinquishMagickMemory(kernel);
2134 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2135 }
2136 blur_image=CloneImage(image,0,0,MagickTrue,exception);
2137 if (blur_image == (Image *) NULL)
2138 {
2139 kernel=(double *) RelinquishMagickMemory(kernel);
2140 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2141 return((Image *) NULL);
2142 }
2143 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
2144 {
2145 kernel=(double *) RelinquishMagickMemory(kernel);
2146 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2147 InheritException(exception,&blur_image->exception);
2148 blur_image=DestroyImage(blur_image);
2149 return((Image *) NULL);
2150 }
2151 point.x=(double) width*sin(DegreesToRadians(angle));
2152 point.y=(double) width*cos(DegreesToRadians(angle));
cristybb503372010-05-27 20:51:26 +00002153 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002154 {
cristybb503372010-05-27 20:51:26 +00002155 offset[i].x=(ssize_t) ceil((double) (i*point.y)/hypot(point.x,point.y)-0.5);
2156 offset[i].y=(ssize_t) ceil((double) (i*point.x)/hypot(point.x,point.y)-0.5);
cristy3ed852e2009-09-05 21:47:34 +00002157 }
2158 /*
2159 Motion blur image.
2160 */
2161 status=MagickTrue;
2162 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00002163 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00002164 image_view=AcquireCacheView(image);
2165 blur_view=AcquireCacheView(blur_image);
cristyb557a152011-02-22 12:14:30 +00002166#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00002167 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00002168#endif
cristybb503372010-05-27 20:51:26 +00002169 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002170 {
cristy4c08aed2011-07-01 19:47:50 +00002171 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002172 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002173
cristy117ff172010-08-15 21:35:32 +00002174 register ssize_t
2175 x;
2176
cristy3ed852e2009-09-05 21:47:34 +00002177 if (status == MagickFalse)
2178 continue;
2179 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
2180 exception);
cristy4c08aed2011-07-01 19:47:50 +00002181 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002182 {
2183 status=MagickFalse;
2184 continue;
2185 }
cristybb503372010-05-27 20:51:26 +00002186 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002187 {
cristy4c08aed2011-07-01 19:47:50 +00002188 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002189 qixel;
2190
2191 PixelPacket
2192 pixel;
2193
2194 register double
cristyc47d1f82009-11-26 01:44:43 +00002195 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00002196
cristybb503372010-05-27 20:51:26 +00002197 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002198 i;
2199
cristy3ed852e2009-09-05 21:47:34 +00002200 k=kernel;
cristyddd82202009-11-03 20:14:50 +00002201 qixel=bias;
cristyed231572011-07-14 02:18:59 +00002202 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00002203 {
cristybb503372010-05-27 20:51:26 +00002204 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002205 {
2206 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
2207 offset[i].y,&pixel,exception);
2208 qixel.red+=(*k)*pixel.red;
2209 qixel.green+=(*k)*pixel.green;
2210 qixel.blue+=(*k)*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00002211 qixel.alpha+=(*k)*pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00002212 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00002213 qixel.black+=(*k)*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00002214 k++;
2215 }
cristyed231572011-07-14 02:18:59 +00002216 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002217 SetPixelRed(blur_image,
2218 ClampToQuantum(qixel.red),q);
cristyed231572011-07-14 02:18:59 +00002219 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002220 SetPixelGreen(blur_image,
2221 ClampToQuantum(qixel.green),q);
cristyed231572011-07-14 02:18:59 +00002222 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002223 SetPixelBlue(blur_image,
2224 ClampToQuantum(qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00002225 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002226 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002227 SetPixelBlack(blur_image,
2228 ClampToQuantum(qixel.black),q);
cristyed231572011-07-14 02:18:59 +00002229 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002230 SetPixelAlpha(blur_image,
2231 ClampToQuantum(qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00002232 }
2233 else
2234 {
2235 MagickRealType
2236 alpha,
2237 gamma;
2238
2239 alpha=0.0;
2240 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00002241 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002242 {
2243 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
2244 offset[i].y,&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00002245 alpha=(MagickRealType) (QuantumScale*pixel.alpha);
cristy3ed852e2009-09-05 21:47:34 +00002246 qixel.red+=(*k)*alpha*pixel.red;
2247 qixel.green+=(*k)*alpha*pixel.green;
2248 qixel.blue+=(*k)*alpha*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00002249 qixel.alpha+=(*k)*pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00002250 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00002251 qixel.black+=(*k)*alpha*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00002252 gamma+=(*k)*alpha;
2253 k++;
2254 }
2255 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00002256 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002257 SetPixelRed(blur_image,
2258 ClampToQuantum(gamma*qixel.red),q);
cristyed231572011-07-14 02:18:59 +00002259 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002260 SetPixelGreen(blur_image,
2261 ClampToQuantum(gamma*qixel.green),q);
cristyed231572011-07-14 02:18:59 +00002262 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002263 SetPixelBlue(blur_image,
2264 ClampToQuantum(gamma*qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00002265 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002266 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002267 SetPixelBlack(blur_image,
2268 ClampToQuantum(gamma*qixel.black),q);
cristyed231572011-07-14 02:18:59 +00002269 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002270 SetPixelAlpha(blur_image,
2271 ClampToQuantum(qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00002272 }
cristyed231572011-07-14 02:18:59 +00002273 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00002274 }
2275 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
2276 status=MagickFalse;
2277 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2278 {
2279 MagickBooleanType
2280 proceed;
2281
cristyb557a152011-02-22 12:14:30 +00002282#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00002283 #pragma omp critical (MagickCore_MotionBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00002284#endif
2285 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
2286 if (proceed == MagickFalse)
2287 status=MagickFalse;
2288 }
2289 }
2290 blur_view=DestroyCacheView(blur_view);
2291 image_view=DestroyCacheView(image_view);
2292 kernel=(double *) RelinquishMagickMemory(kernel);
2293 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2294 if (status == MagickFalse)
2295 blur_image=DestroyImage(blur_image);
2296 return(blur_image);
2297}
2298
2299/*
2300%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2301% %
2302% %
2303% %
2304% P r e v i e w I m a g e %
2305% %
2306% %
2307% %
2308%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2309%
2310% PreviewImage() tiles 9 thumbnails of the specified image with an image
2311% processing operation applied with varying parameters. This may be helpful
2312% pin-pointing an appropriate parameter for a particular image processing
2313% operation.
2314%
2315% The format of the PreviewImages method is:
2316%
2317% Image *PreviewImages(const Image *image,const PreviewType preview,
2318% ExceptionInfo *exception)
2319%
2320% A description of each parameter follows:
2321%
2322% o image: the image.
2323%
2324% o preview: the image processing operation.
2325%
2326% o exception: return any errors or warnings in this structure.
2327%
2328*/
2329MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
2330 ExceptionInfo *exception)
2331{
2332#define NumberTiles 9
2333#define PreviewImageTag "Preview/Image"
2334#define DefaultPreviewGeometry "204x204+10+10"
2335
2336 char
2337 factor[MaxTextExtent],
2338 label[MaxTextExtent];
2339
2340 double
2341 degrees,
2342 gamma,
2343 percentage,
2344 radius,
2345 sigma,
2346 threshold;
2347
2348 Image
2349 *images,
2350 *montage_image,
2351 *preview_image,
2352 *thumbnail;
2353
2354 ImageInfo
2355 *preview_info;
2356
cristy3ed852e2009-09-05 21:47:34 +00002357 MagickBooleanType
2358 proceed;
2359
2360 MontageInfo
2361 *montage_info;
2362
2363 QuantizeInfo
2364 quantize_info;
2365
2366 RectangleInfo
2367 geometry;
2368
cristybb503372010-05-27 20:51:26 +00002369 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002370 i,
2371 x;
2372
cristybb503372010-05-27 20:51:26 +00002373 size_t
cristy3ed852e2009-09-05 21:47:34 +00002374 colors;
2375
cristy117ff172010-08-15 21:35:32 +00002376 ssize_t
2377 y;
2378
cristy3ed852e2009-09-05 21:47:34 +00002379 /*
2380 Open output image file.
2381 */
2382 assert(image != (Image *) NULL);
2383 assert(image->signature == MagickSignature);
2384 if (image->debug != MagickFalse)
2385 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2386 colors=2;
2387 degrees=0.0;
2388 gamma=(-0.2f);
2389 preview_info=AcquireImageInfo();
2390 SetGeometry(image,&geometry);
2391 (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
2392 &geometry.width,&geometry.height);
2393 images=NewImageList();
2394 percentage=12.5;
2395 GetQuantizeInfo(&quantize_info);
2396 radius=0.0;
2397 sigma=1.0;
2398 threshold=0.0;
2399 x=0;
2400 y=0;
2401 for (i=0; i < NumberTiles; i++)
2402 {
2403 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
2404 if (thumbnail == (Image *) NULL)
2405 break;
2406 (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
2407 (void *) NULL);
2408 (void) SetImageProperty(thumbnail,"label",DefaultTileLabel);
2409 if (i == (NumberTiles/2))
2410 {
2411 (void) QueryColorDatabase("#dfdfdf",&thumbnail->matte_color,exception);
2412 AppendImageToList(&images,thumbnail);
2413 continue;
2414 }
2415 switch (preview)
2416 {
2417 case RotatePreview:
2418 {
2419 degrees+=45.0;
2420 preview_image=RotateImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002421 (void) FormatLocaleString(label,MaxTextExtent,"rotate %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002422 break;
2423 }
2424 case ShearPreview:
2425 {
2426 degrees+=5.0;
2427 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002428 (void) FormatLocaleString(label,MaxTextExtent,"shear %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002429 degrees,2.0*degrees);
2430 break;
2431 }
2432 case RollPreview:
2433 {
cristybb503372010-05-27 20:51:26 +00002434 x=(ssize_t) ((i+1)*thumbnail->columns)/NumberTiles;
2435 y=(ssize_t) ((i+1)*thumbnail->rows)/NumberTiles;
cristy3ed852e2009-09-05 21:47:34 +00002436 preview_image=RollImage(thumbnail,x,y,exception);
cristyb51dff52011-05-19 16:55:47 +00002437 (void) FormatLocaleString(label,MaxTextExtent,"roll %+.20gx%+.20g",
cristye8c25f92010-06-03 00:53:06 +00002438 (double) x,(double) y);
cristy3ed852e2009-09-05 21:47:34 +00002439 break;
2440 }
2441 case HuePreview:
2442 {
2443 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2444 if (preview_image == (Image *) NULL)
2445 break;
cristyb51dff52011-05-19 16:55:47 +00002446 (void) FormatLocaleString(factor,MaxTextExtent,"100,100,%g",
cristy3ed852e2009-09-05 21:47:34 +00002447 2.0*percentage);
2448 (void) ModulateImage(preview_image,factor);
cristyb51dff52011-05-19 16:55:47 +00002449 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002450 break;
2451 }
2452 case SaturationPreview:
2453 {
2454 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2455 if (preview_image == (Image *) NULL)
2456 break;
cristyb51dff52011-05-19 16:55:47 +00002457 (void) FormatLocaleString(factor,MaxTextExtent,"100,%g",
cristy8cd5b312010-01-07 01:10:24 +00002458 2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00002459 (void) ModulateImage(preview_image,factor);
cristyb51dff52011-05-19 16:55:47 +00002460 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002461 break;
2462 }
2463 case BrightnessPreview:
2464 {
2465 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2466 if (preview_image == (Image *) NULL)
2467 break;
cristyb51dff52011-05-19 16:55:47 +00002468 (void) FormatLocaleString(factor,MaxTextExtent,"%g",2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00002469 (void) ModulateImage(preview_image,factor);
cristyb51dff52011-05-19 16:55:47 +00002470 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002471 break;
2472 }
2473 case GammaPreview:
2474 default:
2475 {
2476 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2477 if (preview_image == (Image *) NULL)
2478 break;
2479 gamma+=0.4f;
cristy50fbc382011-07-07 02:19:17 +00002480 (void) GammaImage(preview_image,gamma);
cristyb51dff52011-05-19 16:55:47 +00002481 (void) FormatLocaleString(label,MaxTextExtent,"gamma %g",gamma);
cristy3ed852e2009-09-05 21:47:34 +00002482 break;
2483 }
2484 case SpiffPreview:
2485 {
2486 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2487 if (preview_image != (Image *) NULL)
2488 for (x=0; x < i; x++)
2489 (void) ContrastImage(preview_image,MagickTrue);
cristyb51dff52011-05-19 16:55:47 +00002490 (void) FormatLocaleString(label,MaxTextExtent,"contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002491 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002492 break;
2493 }
2494 case DullPreview:
2495 {
2496 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2497 if (preview_image == (Image *) NULL)
2498 break;
2499 for (x=0; x < i; x++)
2500 (void) ContrastImage(preview_image,MagickFalse);
cristyb51dff52011-05-19 16:55:47 +00002501 (void) FormatLocaleString(label,MaxTextExtent,"+contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002502 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002503 break;
2504 }
2505 case GrayscalePreview:
2506 {
2507 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2508 if (preview_image == (Image *) NULL)
2509 break;
2510 colors<<=1;
2511 quantize_info.number_colors=colors;
2512 quantize_info.colorspace=GRAYColorspace;
2513 (void) QuantizeImage(&quantize_info,preview_image);
cristyb51dff52011-05-19 16:55:47 +00002514 (void) FormatLocaleString(label,MaxTextExtent,
cristye8c25f92010-06-03 00:53:06 +00002515 "-colorspace gray -colors %.20g",(double) colors);
cristy3ed852e2009-09-05 21:47:34 +00002516 break;
2517 }
2518 case QuantizePreview:
2519 {
2520 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2521 if (preview_image == (Image *) NULL)
2522 break;
2523 colors<<=1;
2524 quantize_info.number_colors=colors;
2525 (void) QuantizeImage(&quantize_info,preview_image);
cristyb51dff52011-05-19 16:55:47 +00002526 (void) FormatLocaleString(label,MaxTextExtent,"colors %.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00002527 colors);
cristy3ed852e2009-09-05 21:47:34 +00002528 break;
2529 }
2530 case DespecklePreview:
2531 {
2532 for (x=0; x < (i-1); x++)
2533 {
2534 preview_image=DespeckleImage(thumbnail,exception);
2535 if (preview_image == (Image *) NULL)
2536 break;
2537 thumbnail=DestroyImage(thumbnail);
2538 thumbnail=preview_image;
2539 }
2540 preview_image=DespeckleImage(thumbnail,exception);
2541 if (preview_image == (Image *) NULL)
2542 break;
cristyb51dff52011-05-19 16:55:47 +00002543 (void) FormatLocaleString(label,MaxTextExtent,"despeckle (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002544 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002545 break;
2546 }
2547 case ReduceNoisePreview:
2548 {
cristy95c38342011-03-18 22:39:51 +00002549 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) radius,
2550 (size_t) radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002551 (void) FormatLocaleString(label,MaxTextExtent,"noise %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002552 break;
2553 }
2554 case AddNoisePreview:
2555 {
2556 switch ((int) i)
2557 {
2558 case 0:
2559 {
2560 (void) CopyMagickString(factor,"uniform",MaxTextExtent);
2561 break;
2562 }
2563 case 1:
2564 {
2565 (void) CopyMagickString(factor,"gaussian",MaxTextExtent);
2566 break;
2567 }
2568 case 2:
2569 {
2570 (void) CopyMagickString(factor,"multiplicative",MaxTextExtent);
2571 break;
2572 }
2573 case 3:
2574 {
2575 (void) CopyMagickString(factor,"impulse",MaxTextExtent);
2576 break;
2577 }
2578 case 4:
2579 {
2580 (void) CopyMagickString(factor,"laplacian",MaxTextExtent);
2581 break;
2582 }
2583 case 5:
2584 {
2585 (void) CopyMagickString(factor,"Poisson",MaxTextExtent);
2586 break;
2587 }
2588 default:
2589 {
2590 (void) CopyMagickString(thumbnail->magick,"NULL",MaxTextExtent);
2591 break;
2592 }
2593 }
cristyd76c51e2011-03-26 00:21:26 +00002594 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) i,
2595 (size_t) i,exception);
cristyb51dff52011-05-19 16:55:47 +00002596 (void) FormatLocaleString(label,MaxTextExtent,"+noise %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002597 break;
2598 }
2599 case SharpenPreview:
2600 {
2601 preview_image=SharpenImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00002602 (void) FormatLocaleString(label,MaxTextExtent,"sharpen %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002603 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002604 break;
2605 }
2606 case BlurPreview:
2607 {
2608 preview_image=BlurImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00002609 (void) FormatLocaleString(label,MaxTextExtent,"blur %gx%g",radius,
cristy3ed852e2009-09-05 21:47:34 +00002610 sigma);
2611 break;
2612 }
2613 case ThresholdPreview:
2614 {
2615 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2616 if (preview_image == (Image *) NULL)
2617 break;
2618 (void) BilevelImage(thumbnail,
2619 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
cristyb51dff52011-05-19 16:55:47 +00002620 (void) FormatLocaleString(label,MaxTextExtent,"threshold %g",
cristy3ed852e2009-09-05 21:47:34 +00002621 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
2622 break;
2623 }
2624 case EdgeDetectPreview:
2625 {
2626 preview_image=EdgeImage(thumbnail,radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002627 (void) FormatLocaleString(label,MaxTextExtent,"edge %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002628 break;
2629 }
2630 case SpreadPreview:
2631 {
2632 preview_image=SpreadImage(thumbnail,radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002633 (void) FormatLocaleString(label,MaxTextExtent,"spread %g",
cristy8cd5b312010-01-07 01:10:24 +00002634 radius+0.5);
cristy3ed852e2009-09-05 21:47:34 +00002635 break;
2636 }
2637 case SolarizePreview:
2638 {
2639 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2640 if (preview_image == (Image *) NULL)
2641 break;
2642 (void) SolarizeImage(preview_image,(double) QuantumRange*
2643 percentage/100.0);
cristyb51dff52011-05-19 16:55:47 +00002644 (void) FormatLocaleString(label,MaxTextExtent,"solarize %g",
cristy3ed852e2009-09-05 21:47:34 +00002645 (QuantumRange*percentage)/100.0);
2646 break;
2647 }
2648 case ShadePreview:
2649 {
2650 degrees+=10.0;
2651 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
2652 exception);
cristyb51dff52011-05-19 16:55:47 +00002653 (void) FormatLocaleString(label,MaxTextExtent,"shade %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002654 degrees,degrees);
cristy3ed852e2009-09-05 21:47:34 +00002655 break;
2656 }
2657 case RaisePreview:
2658 {
2659 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2660 if (preview_image == (Image *) NULL)
2661 break;
cristybb503372010-05-27 20:51:26 +00002662 geometry.width=(size_t) (2*i+2);
2663 geometry.height=(size_t) (2*i+2);
cristy3ed852e2009-09-05 21:47:34 +00002664 geometry.x=i/2;
2665 geometry.y=i/2;
2666 (void) RaiseImage(preview_image,&geometry,MagickTrue);
cristyb51dff52011-05-19 16:55:47 +00002667 (void) FormatLocaleString(label,MaxTextExtent,
cristy6d8abba2010-06-03 01:10:47 +00002668 "raise %.20gx%.20g%+.20g%+.20g",(double) geometry.width,(double)
cristye8c25f92010-06-03 00:53:06 +00002669 geometry.height,(double) geometry.x,(double) geometry.y);
cristy3ed852e2009-09-05 21:47:34 +00002670 break;
2671 }
2672 case SegmentPreview:
2673 {
2674 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2675 if (preview_image == (Image *) NULL)
2676 break;
2677 threshold+=0.4f;
2678 (void) SegmentImage(preview_image,RGBColorspace,MagickFalse,threshold,
2679 threshold);
cristyb51dff52011-05-19 16:55:47 +00002680 (void) FormatLocaleString(label,MaxTextExtent,"segment %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002681 threshold,threshold);
2682 break;
2683 }
2684 case SwirlPreview:
2685 {
2686 preview_image=SwirlImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002687 (void) FormatLocaleString(label,MaxTextExtent,"swirl %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002688 degrees+=45.0;
2689 break;
2690 }
2691 case ImplodePreview:
2692 {
2693 degrees+=0.1f;
2694 preview_image=ImplodeImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002695 (void) FormatLocaleString(label,MaxTextExtent,"implode %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002696 break;
2697 }
2698 case WavePreview:
2699 {
2700 degrees+=5.0f;
2701 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002702 (void) FormatLocaleString(label,MaxTextExtent,"wave %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002703 0.5*degrees,2.0*degrees);
cristy3ed852e2009-09-05 21:47:34 +00002704 break;
2705 }
2706 case OilPaintPreview:
2707 {
2708 preview_image=OilPaintImage(thumbnail,(double) radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002709 (void) FormatLocaleString(label,MaxTextExtent,"paint %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002710 break;
2711 }
2712 case CharcoalDrawingPreview:
2713 {
2714 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
2715 exception);
cristyb51dff52011-05-19 16:55:47 +00002716 (void) FormatLocaleString(label,MaxTextExtent,"charcoal %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002717 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002718 break;
2719 }
2720 case JPEGPreview:
2721 {
2722 char
2723 filename[MaxTextExtent];
2724
2725 int
2726 file;
2727
2728 MagickBooleanType
2729 status;
2730
2731 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2732 if (preview_image == (Image *) NULL)
2733 break;
cristybb503372010-05-27 20:51:26 +00002734 preview_info->quality=(size_t) percentage;
cristyb51dff52011-05-19 16:55:47 +00002735 (void) FormatLocaleString(factor,MaxTextExtent,"%.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00002736 preview_info->quality);
cristy3ed852e2009-09-05 21:47:34 +00002737 file=AcquireUniqueFileResource(filename);
2738 if (file != -1)
2739 file=close(file)-1;
cristyb51dff52011-05-19 16:55:47 +00002740 (void) FormatLocaleString(preview_image->filename,MaxTextExtent,
cristy3ed852e2009-09-05 21:47:34 +00002741 "jpeg:%s",filename);
2742 status=WriteImage(preview_info,preview_image);
2743 if (status != MagickFalse)
2744 {
2745 Image
2746 *quality_image;
2747
2748 (void) CopyMagickString(preview_info->filename,
2749 preview_image->filename,MaxTextExtent);
2750 quality_image=ReadImage(preview_info,exception);
2751 if (quality_image != (Image *) NULL)
2752 {
2753 preview_image=DestroyImage(preview_image);
2754 preview_image=quality_image;
2755 }
2756 }
2757 (void) RelinquishUniqueFileResource(preview_image->filename);
2758 if ((GetBlobSize(preview_image)/1024) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00002759 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%gmb ",
cristy3ed852e2009-09-05 21:47:34 +00002760 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
2761 1024.0/1024.0);
2762 else
2763 if (GetBlobSize(preview_image) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00002764 (void) FormatLocaleString(label,MaxTextExtent,
cristye7f51092010-01-17 00:39:37 +00002765 "quality %s\n%gkb ",factor,(double) ((MagickOffsetType)
cristy8cd5b312010-01-07 01:10:24 +00002766 GetBlobSize(preview_image))/1024.0);
cristy3ed852e2009-09-05 21:47:34 +00002767 else
cristyb51dff52011-05-19 16:55:47 +00002768 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%.20gb ",
cristy54ea5732011-06-10 12:39:53 +00002769 factor,(double) ((MagickOffsetType) GetBlobSize(thumbnail)));
cristy3ed852e2009-09-05 21:47:34 +00002770 break;
2771 }
2772 }
2773 thumbnail=DestroyImage(thumbnail);
2774 percentage+=12.5;
2775 radius+=0.5;
2776 sigma+=0.25;
2777 if (preview_image == (Image *) NULL)
2778 break;
2779 (void) DeleteImageProperty(preview_image,"label");
2780 (void) SetImageProperty(preview_image,"label",label);
2781 AppendImageToList(&images,preview_image);
cristybb503372010-05-27 20:51:26 +00002782 proceed=SetImageProgress(image,PreviewImageTag,(MagickOffsetType) i,
2783 NumberTiles);
cristy3ed852e2009-09-05 21:47:34 +00002784 if (proceed == MagickFalse)
2785 break;
2786 }
2787 if (images == (Image *) NULL)
2788 {
2789 preview_info=DestroyImageInfo(preview_info);
2790 return((Image *) NULL);
2791 }
2792 /*
2793 Create the montage.
2794 */
2795 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
2796 (void) CopyMagickString(montage_info->filename,image->filename,MaxTextExtent);
2797 montage_info->shadow=MagickTrue;
2798 (void) CloneString(&montage_info->tile,"3x3");
2799 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
2800 (void) CloneString(&montage_info->frame,DefaultTileFrame);
2801 montage_image=MontageImages(images,montage_info,exception);
2802 montage_info=DestroyMontageInfo(montage_info);
2803 images=DestroyImageList(images);
2804 if (montage_image == (Image *) NULL)
2805 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2806 if (montage_image->montage != (char *) NULL)
2807 {
2808 /*
2809 Free image directory.
2810 */
2811 montage_image->montage=(char *) RelinquishMagickMemory(
2812 montage_image->montage);
2813 if (image->directory != (char *) NULL)
2814 montage_image->directory=(char *) RelinquishMagickMemory(
2815 montage_image->directory);
2816 }
2817 preview_info=DestroyImageInfo(preview_info);
2818 return(montage_image);
2819}
2820
2821/*
2822%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2823% %
2824% %
2825% %
2826% R a d i a l B l u r I m a g e %
2827% %
2828% %
2829% %
2830%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2831%
2832% RadialBlurImage() applies a radial blur to the image.
2833%
2834% Andrew Protano contributed this effect.
2835%
2836% The format of the RadialBlurImage method is:
2837%
2838% Image *RadialBlurImage(const Image *image,const double angle,
2839% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002840%
2841% A description of each parameter follows:
2842%
2843% o image: the image.
2844%
cristy3ed852e2009-09-05 21:47:34 +00002845% o angle: the angle of the radial blur.
2846%
2847% o exception: return any errors or warnings in this structure.
2848%
2849*/
cristyf4ad9df2011-07-08 16:49:03 +00002850MagickExport Image *RadialBlurImage(const Image *image,
2851 const double angle,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002852{
cristyc4c8d132010-01-07 01:58:38 +00002853 CacheView
2854 *blur_view,
2855 *image_view;
2856
cristy3ed852e2009-09-05 21:47:34 +00002857 Image
2858 *blur_image;
2859
cristy3ed852e2009-09-05 21:47:34 +00002860 MagickBooleanType
2861 status;
2862
cristybb503372010-05-27 20:51:26 +00002863 MagickOffsetType
2864 progress;
2865
cristy4c08aed2011-07-01 19:47:50 +00002866 PixelInfo
cristyddd82202009-11-03 20:14:50 +00002867 bias;
cristy3ed852e2009-09-05 21:47:34 +00002868
2869 MagickRealType
2870 blur_radius,
2871 *cos_theta,
2872 offset,
2873 *sin_theta,
2874 theta;
2875
2876 PointInfo
2877 blur_center;
2878
cristybb503372010-05-27 20:51:26 +00002879 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002880 i;
2881
cristybb503372010-05-27 20:51:26 +00002882 size_t
cristy3ed852e2009-09-05 21:47:34 +00002883 n;
2884
cristybb503372010-05-27 20:51:26 +00002885 ssize_t
2886 y;
2887
cristy3ed852e2009-09-05 21:47:34 +00002888 /*
2889 Allocate blur image.
2890 */
2891 assert(image != (Image *) NULL);
2892 assert(image->signature == MagickSignature);
2893 if (image->debug != MagickFalse)
2894 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2895 assert(exception != (ExceptionInfo *) NULL);
2896 assert(exception->signature == MagickSignature);
2897 blur_image=CloneImage(image,0,0,MagickTrue,exception);
2898 if (blur_image == (Image *) NULL)
2899 return((Image *) NULL);
2900 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
2901 {
2902 InheritException(exception,&blur_image->exception);
2903 blur_image=DestroyImage(blur_image);
2904 return((Image *) NULL);
2905 }
2906 blur_center.x=(double) image->columns/2.0;
2907 blur_center.y=(double) image->rows/2.0;
2908 blur_radius=hypot(blur_center.x,blur_center.y);
cristy117ff172010-08-15 21:35:32 +00002909 n=(size_t) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+2UL);
cristy3ed852e2009-09-05 21:47:34 +00002910 theta=DegreesToRadians(angle)/(MagickRealType) (n-1);
2911 cos_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
2912 sizeof(*cos_theta));
2913 sin_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
2914 sizeof(*sin_theta));
2915 if ((cos_theta == (MagickRealType *) NULL) ||
2916 (sin_theta == (MagickRealType *) NULL))
2917 {
2918 blur_image=DestroyImage(blur_image);
2919 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2920 }
2921 offset=theta*(MagickRealType) (n-1)/2.0;
cristybb503372010-05-27 20:51:26 +00002922 for (i=0; i < (ssize_t) n; i++)
cristy3ed852e2009-09-05 21:47:34 +00002923 {
2924 cos_theta[i]=cos((double) (theta*i-offset));
2925 sin_theta[i]=sin((double) (theta*i-offset));
2926 }
2927 /*
2928 Radial blur image.
2929 */
2930 status=MagickTrue;
2931 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00002932 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00002933 image_view=AcquireCacheView(image);
2934 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00002935#if defined(MAGICKCORE_OPENMP_SUPPORT)
2936 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002937#endif
cristybb503372010-05-27 20:51:26 +00002938 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002939 {
cristy4c08aed2011-07-01 19:47:50 +00002940 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002941 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002942
cristy117ff172010-08-15 21:35:32 +00002943 register ssize_t
2944 x;
2945
cristy3ed852e2009-09-05 21:47:34 +00002946 if (status == MagickFalse)
2947 continue;
2948 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
2949 exception);
cristy4c08aed2011-07-01 19:47:50 +00002950 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002951 {
2952 status=MagickFalse;
2953 continue;
2954 }
cristybb503372010-05-27 20:51:26 +00002955 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002956 {
cristy4c08aed2011-07-01 19:47:50 +00002957 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002958 qixel;
2959
2960 MagickRealType
2961 normalize,
2962 radius;
2963
2964 PixelPacket
2965 pixel;
2966
2967 PointInfo
2968 center;
2969
cristybb503372010-05-27 20:51:26 +00002970 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002971 i;
2972
cristybb503372010-05-27 20:51:26 +00002973 size_t
cristy3ed852e2009-09-05 21:47:34 +00002974 step;
2975
2976 center.x=(double) x-blur_center.x;
2977 center.y=(double) y-blur_center.y;
2978 radius=hypot((double) center.x,center.y);
2979 if (radius == 0)
2980 step=1;
2981 else
2982 {
cristybb503372010-05-27 20:51:26 +00002983 step=(size_t) (blur_radius/radius);
cristy3ed852e2009-09-05 21:47:34 +00002984 if (step == 0)
2985 step=1;
2986 else
2987 if (step >= n)
2988 step=n-1;
2989 }
2990 normalize=0.0;
cristyddd82202009-11-03 20:14:50 +00002991 qixel=bias;
cristyed231572011-07-14 02:18:59 +00002992 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00002993 {
cristyeaedf062010-05-29 22:36:02 +00002994 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00002995 {
cristyeaedf062010-05-29 22:36:02 +00002996 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
2997 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
2998 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
2999 cos_theta[i]+0.5),&pixel,exception);
cristy3ed852e2009-09-05 21:47:34 +00003000 qixel.red+=pixel.red;
3001 qixel.green+=pixel.green;
3002 qixel.blue+=pixel.blue;
cristy3ed852e2009-09-05 21:47:34 +00003003 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00003004 qixel.black+=pixel.black;
3005 qixel.alpha+=pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00003006 normalize+=1.0;
3007 }
3008 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
3009 normalize);
cristyed231572011-07-14 02:18:59 +00003010 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003011 SetPixelRed(blur_image,
3012 ClampToQuantum(normalize*qixel.red),q);
cristyed231572011-07-14 02:18:59 +00003013 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003014 SetPixelGreen(blur_image,
3015 ClampToQuantum(normalize*qixel.green),q);
cristyed231572011-07-14 02:18:59 +00003016 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003017 SetPixelBlue(blur_image,
3018 ClampToQuantum(normalize*qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003019 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003020 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003021 SetPixelBlack(blur_image,
3022 ClampToQuantum(normalize*qixel.black),q);
cristyed231572011-07-14 02:18:59 +00003023 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003024 SetPixelAlpha(blur_image,
3025 ClampToQuantum(normalize*qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003026 }
3027 else
3028 {
3029 MagickRealType
3030 alpha,
3031 gamma;
3032
3033 alpha=1.0;
3034 gamma=0.0;
cristyeaedf062010-05-29 22:36:02 +00003035 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00003036 {
cristyeaedf062010-05-29 22:36:02 +00003037 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
3038 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
3039 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
3040 cos_theta[i]+0.5),&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00003041 alpha=(MagickRealType) (QuantumScale*pixel.alpha);
cristy3ed852e2009-09-05 21:47:34 +00003042 qixel.red+=alpha*pixel.red;
3043 qixel.green+=alpha*pixel.green;
3044 qixel.blue+=alpha*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00003045 qixel.alpha+=pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00003046 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00003047 qixel.black+=alpha*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00003048 gamma+=alpha;
3049 normalize+=1.0;
3050 }
3051 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3052 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
3053 normalize);
cristyed231572011-07-14 02:18:59 +00003054 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003055 SetPixelRed(blur_image,
3056 ClampToQuantum(gamma*qixel.red),q);
cristyed231572011-07-14 02:18:59 +00003057 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003058 SetPixelGreen(blur_image,
3059 ClampToQuantum(gamma*qixel.green),q);
cristyed231572011-07-14 02:18:59 +00003060 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003061 SetPixelBlue(blur_image,
3062 ClampToQuantum(gamma*qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003063 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003064 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003065 SetPixelBlack(blur_image,
3066 ClampToQuantum(gamma*qixel.black),q);
cristyed231572011-07-14 02:18:59 +00003067 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003068 SetPixelAlpha(blur_image,
3069 ClampToQuantum(normalize*qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003070 }
cristyed231572011-07-14 02:18:59 +00003071 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003072 }
3073 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
3074 status=MagickFalse;
3075 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3076 {
3077 MagickBooleanType
3078 proceed;
3079
cristyb5d5f722009-11-04 03:03:49 +00003080#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00003081 #pragma omp critical (MagickCore_RadialBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00003082#endif
3083 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
3084 if (proceed == MagickFalse)
3085 status=MagickFalse;
3086 }
3087 }
3088 blur_view=DestroyCacheView(blur_view);
3089 image_view=DestroyCacheView(image_view);
3090 cos_theta=(MagickRealType *) RelinquishMagickMemory(cos_theta);
3091 sin_theta=(MagickRealType *) RelinquishMagickMemory(sin_theta);
3092 if (status == MagickFalse)
3093 blur_image=DestroyImage(blur_image);
3094 return(blur_image);
3095}
3096
3097/*
3098%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3099% %
3100% %
3101% %
cristy3ed852e2009-09-05 21:47:34 +00003102% S e l e c t i v e B l u r I m a g e %
3103% %
3104% %
3105% %
3106%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3107%
3108% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
3109% It is similar to the unsharpen mask that sharpens everything with contrast
3110% above a certain threshold.
3111%
3112% The format of the SelectiveBlurImage method is:
3113%
3114% Image *SelectiveBlurImage(const Image *image,const double radius,
3115% const double sigma,const double threshold,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003116%
3117% A description of each parameter follows:
3118%
3119% o image: the image.
3120%
cristy3ed852e2009-09-05 21:47:34 +00003121% o radius: the radius of the Gaussian, in pixels, not counting the center
3122% pixel.
3123%
3124% o sigma: the standard deviation of the Gaussian, in pixels.
3125%
3126% o threshold: only pixels within this contrast threshold are included
3127% in the blur operation.
3128%
3129% o exception: return any errors or warnings in this structure.
3130%
3131*/
cristyf4ad9df2011-07-08 16:49:03 +00003132MagickExport Image *SelectiveBlurImage(const Image *image,
3133 const double radius,const double sigma,const double threshold,
3134 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003135{
3136#define SelectiveBlurImageTag "SelectiveBlur/Image"
3137
cristy47e00502009-12-17 19:19:57 +00003138 CacheView
3139 *blur_view,
3140 *image_view;
3141
cristy3ed852e2009-09-05 21:47:34 +00003142 double
cristy3ed852e2009-09-05 21:47:34 +00003143 *kernel;
3144
3145 Image
3146 *blur_image;
3147
cristy3ed852e2009-09-05 21:47:34 +00003148 MagickBooleanType
3149 status;
3150
cristybb503372010-05-27 20:51:26 +00003151 MagickOffsetType
3152 progress;
3153
cristy4c08aed2011-07-01 19:47:50 +00003154 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003155 bias;
3156
cristybb503372010-05-27 20:51:26 +00003157 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003158 i;
cristy3ed852e2009-09-05 21:47:34 +00003159
cristybb503372010-05-27 20:51:26 +00003160 size_t
cristy3ed852e2009-09-05 21:47:34 +00003161 width;
3162
cristybb503372010-05-27 20:51:26 +00003163 ssize_t
3164 j,
3165 u,
3166 v,
3167 y;
3168
cristy3ed852e2009-09-05 21:47:34 +00003169 /*
3170 Initialize blur image attributes.
3171 */
3172 assert(image != (Image *) NULL);
3173 assert(image->signature == MagickSignature);
3174 if (image->debug != MagickFalse)
3175 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3176 assert(exception != (ExceptionInfo *) NULL);
3177 assert(exception->signature == MagickSignature);
3178 width=GetOptimalKernelWidth1D(radius,sigma);
3179 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
3180 if (kernel == (double *) NULL)
3181 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00003182 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00003183 i=0;
cristy47e00502009-12-17 19:19:57 +00003184 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003185 {
cristy47e00502009-12-17 19:19:57 +00003186 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00003187 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
3188 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00003189 }
3190 if (image->debug != MagickFalse)
3191 {
3192 char
3193 format[MaxTextExtent],
3194 *message;
3195
cristy117ff172010-08-15 21:35:32 +00003196 register const double
3197 *k;
3198
cristybb503372010-05-27 20:51:26 +00003199 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003200 u,
3201 v;
3202
cristy3ed852e2009-09-05 21:47:34 +00003203 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00003204 " SelectiveBlurImage with %.20gx%.20g kernel:",(double) width,(double)
3205 width);
cristy3ed852e2009-09-05 21:47:34 +00003206 message=AcquireString("");
3207 k=kernel;
cristybb503372010-05-27 20:51:26 +00003208 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003209 {
3210 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00003211 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy3ed852e2009-09-05 21:47:34 +00003212 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00003213 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003214 {
cristyb51dff52011-05-19 16:55:47 +00003215 (void) FormatLocaleString(format,MaxTextExtent,"%+f ",*k++);
cristy3ed852e2009-09-05 21:47:34 +00003216 (void) ConcatenateString(&message,format);
3217 }
3218 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
3219 }
3220 message=DestroyString(message);
3221 }
3222 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3223 if (blur_image == (Image *) NULL)
3224 return((Image *) NULL);
3225 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3226 {
3227 InheritException(exception,&blur_image->exception);
3228 blur_image=DestroyImage(blur_image);
3229 return((Image *) NULL);
3230 }
3231 /*
3232 Threshold blur image.
3233 */
3234 status=MagickTrue;
3235 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00003236 GetPixelInfo(image,&bias);
3237 SetPixelInfoBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003238 image_view=AcquireCacheView(image);
3239 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003240#if defined(MAGICKCORE_OPENMP_SUPPORT)
3241 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003242#endif
cristybb503372010-05-27 20:51:26 +00003243 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003244 {
cristy4c08aed2011-07-01 19:47:50 +00003245 double
3246 contrast;
3247
cristy3ed852e2009-09-05 21:47:34 +00003248 MagickBooleanType
3249 sync;
3250
3251 MagickRealType
3252 gamma;
3253
cristy4c08aed2011-07-01 19:47:50 +00003254 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003255 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003256
cristy4c08aed2011-07-01 19:47:50 +00003257 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003258 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003259
cristy117ff172010-08-15 21:35:32 +00003260 register ssize_t
3261 x;
3262
cristy3ed852e2009-09-05 21:47:34 +00003263 if (status == MagickFalse)
3264 continue;
cristy117ff172010-08-15 21:35:32 +00003265 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
3266 (width/2L),image->columns+width,width,exception);
cristy3ed852e2009-09-05 21:47:34 +00003267 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3268 exception);
cristy4c08aed2011-07-01 19:47:50 +00003269 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003270 {
3271 status=MagickFalse;
3272 continue;
3273 }
cristybb503372010-05-27 20:51:26 +00003274 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003275 {
cristy4c08aed2011-07-01 19:47:50 +00003276 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003277 pixel;
3278
3279 register const double
cristyc47d1f82009-11-26 01:44:43 +00003280 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00003281
cristybb503372010-05-27 20:51:26 +00003282 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003283 u;
3284
cristy117ff172010-08-15 21:35:32 +00003285 ssize_t
3286 j,
3287 v;
3288
cristyddd82202009-11-03 20:14:50 +00003289 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00003290 k=kernel;
3291 gamma=0.0;
3292 j=0;
cristyed231572011-07-14 02:18:59 +00003293 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00003294 {
cristybb503372010-05-27 20:51:26 +00003295 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003296 {
cristybb503372010-05-27 20:51:26 +00003297 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003298 {
cristyed231572011-07-14 02:18:59 +00003299 contrast=GetPixelIntensity(image,p+(u+j)*GetPixelChannels(image))-
cristy4c08aed2011-07-01 19:47:50 +00003300 (double) GetPixelIntensity(blur_image,q);
3301 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003302 {
cristy4c08aed2011-07-01 19:47:50 +00003303 pixel.red+=(*k)*
cristyed231572011-07-14 02:18:59 +00003304 GetPixelRed(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003305 pixel.green+=(*k)*
cristyed231572011-07-14 02:18:59 +00003306 GetPixelGreen(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003307 pixel.blue+=(*k)*
cristyed231572011-07-14 02:18:59 +00003308 GetPixelBlue(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003309 if (image->colorspace == CMYKColorspace)
3310 pixel.black+=(*k)*
cristyed231572011-07-14 02:18:59 +00003311 GetPixelBlack(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003312 gamma+=(*k);
3313 k++;
3314 }
3315 }
cristyd99b0962010-05-29 23:14:26 +00003316 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003317 }
3318 if (gamma != 0.0)
3319 {
3320 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00003321 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003322 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +00003323 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003324 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +00003325 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003326 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003327 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00003328 (image->colorspace == CMYKColorspace))
3329 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00003330 }
cristyed231572011-07-14 02:18:59 +00003331 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003332 {
3333 gamma=0.0;
3334 j=0;
cristybb503372010-05-27 20:51:26 +00003335 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003336 {
cristybb503372010-05-27 20:51:26 +00003337 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003338 {
cristy4c08aed2011-07-01 19:47:50 +00003339 contrast=GetPixelIntensity(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003340 GetPixelChannels(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003341 GetPixelIntensity(blur_image,q);
3342 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003343 {
cristy4c08aed2011-07-01 19:47:50 +00003344 pixel.alpha+=(*k)*
cristyed231572011-07-14 02:18:59 +00003345 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003346 gamma+=(*k);
3347 k++;
3348 }
3349 }
cristyeaedf062010-05-29 22:36:02 +00003350 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003351 }
3352 if (gamma != 0.0)
3353 {
3354 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
3355 gamma);
cristy4c08aed2011-07-01 19:47:50 +00003356 SetPixelAlpha(blur_image,ClampToQuantum(gamma*pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003357 }
3358 }
3359 }
3360 else
3361 {
3362 MagickRealType
3363 alpha;
3364
cristybb503372010-05-27 20:51:26 +00003365 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003366 {
cristybb503372010-05-27 20:51:26 +00003367 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003368 {
cristy4c08aed2011-07-01 19:47:50 +00003369 contrast=GetPixelIntensity(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003370 GetPixelChannels(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003371 GetPixelIntensity(blur_image,q);
3372 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003373 {
cristy4c08aed2011-07-01 19:47:50 +00003374 alpha=(MagickRealType) (QuantumScale*
cristyed231572011-07-14 02:18:59 +00003375 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image)));
cristy4c08aed2011-07-01 19:47:50 +00003376 pixel.red+=(*k)*alpha*
cristyed231572011-07-14 02:18:59 +00003377 GetPixelRed(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003378 pixel.green+=(*k)*alpha*GetPixelGreen(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003379 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003380 pixel.blue+=(*k)*alpha*GetPixelBlue(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003381 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003382 pixel.alpha+=(*k)*GetPixelAlpha(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003383 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003384 if (image->colorspace == CMYKColorspace)
3385 pixel.black+=(*k)*GetPixelBlack(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003386 GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003387 gamma+=(*k)*alpha;
3388 k++;
3389 }
3390 }
cristyeaedf062010-05-29 22:36:02 +00003391 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003392 }
3393 if (gamma != 0.0)
3394 {
3395 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00003396 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003397 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +00003398 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003399 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +00003400 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003401 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003402 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00003403 (image->colorspace == CMYKColorspace))
3404 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00003405 }
cristyed231572011-07-14 02:18:59 +00003406 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003407 {
3408 gamma=0.0;
3409 j=0;
cristybb503372010-05-27 20:51:26 +00003410 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003411 {
cristybb503372010-05-27 20:51:26 +00003412 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003413 {
cristy4c08aed2011-07-01 19:47:50 +00003414 contrast=GetPixelIntensity(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003415 GetPixelChannels(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003416 GetPixelIntensity(blur_image,q);
3417 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003418 {
cristy4c08aed2011-07-01 19:47:50 +00003419 pixel.alpha+=(*k)*
cristyed231572011-07-14 02:18:59 +00003420 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003421 gamma+=(*k);
3422 k++;
3423 }
3424 }
cristyeaedf062010-05-29 22:36:02 +00003425 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003426 }
3427 if (gamma != 0.0)
3428 {
3429 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
3430 gamma);
cristy4c08aed2011-07-01 19:47:50 +00003431 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003432 }
3433 }
3434 }
cristyed231572011-07-14 02:18:59 +00003435 p+=GetPixelChannels(image);
3436 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003437 }
3438 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
3439 if (sync == MagickFalse)
3440 status=MagickFalse;
3441 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3442 {
3443 MagickBooleanType
3444 proceed;
3445
cristyb5d5f722009-11-04 03:03:49 +00003446#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00003447 #pragma omp critical (MagickCore_SelectiveBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00003448#endif
3449 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress++,
3450 image->rows);
3451 if (proceed == MagickFalse)
3452 status=MagickFalse;
3453 }
3454 }
3455 blur_image->type=image->type;
3456 blur_view=DestroyCacheView(blur_view);
3457 image_view=DestroyCacheView(image_view);
3458 kernel=(double *) RelinquishMagickMemory(kernel);
3459 if (status == MagickFalse)
3460 blur_image=DestroyImage(blur_image);
3461 return(blur_image);
3462}
3463
3464/*
3465%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3466% %
3467% %
3468% %
3469% S h a d e I m a g e %
3470% %
3471% %
3472% %
3473%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3474%
3475% ShadeImage() shines a distant light on an image to create a
3476% three-dimensional effect. You control the positioning of the light with
3477% azimuth and elevation; azimuth is measured in degrees off the x axis
3478% and elevation is measured in pixels above the Z axis.
3479%
3480% The format of the ShadeImage method is:
3481%
3482% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3483% const double azimuth,const double elevation,ExceptionInfo *exception)
3484%
3485% A description of each parameter follows:
3486%
3487% o image: the image.
3488%
3489% o gray: A value other than zero shades the intensity of each pixel.
3490%
3491% o azimuth, elevation: Define the light source direction.
3492%
3493% o exception: return any errors or warnings in this structure.
3494%
3495*/
3496MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3497 const double azimuth,const double elevation,ExceptionInfo *exception)
3498{
3499#define ShadeImageTag "Shade/Image"
3500
cristyc4c8d132010-01-07 01:58:38 +00003501 CacheView
3502 *image_view,
3503 *shade_view;
3504
cristy3ed852e2009-09-05 21:47:34 +00003505 Image
3506 *shade_image;
3507
cristy3ed852e2009-09-05 21:47:34 +00003508 MagickBooleanType
3509 status;
3510
cristybb503372010-05-27 20:51:26 +00003511 MagickOffsetType
3512 progress;
3513
cristy3ed852e2009-09-05 21:47:34 +00003514 PrimaryInfo
3515 light;
3516
cristybb503372010-05-27 20:51:26 +00003517 ssize_t
3518 y;
3519
cristy3ed852e2009-09-05 21:47:34 +00003520 /*
3521 Initialize shaded image attributes.
3522 */
3523 assert(image != (const Image *) NULL);
3524 assert(image->signature == MagickSignature);
3525 if (image->debug != MagickFalse)
3526 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3527 assert(exception != (ExceptionInfo *) NULL);
3528 assert(exception->signature == MagickSignature);
3529 shade_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
3530 if (shade_image == (Image *) NULL)
3531 return((Image *) NULL);
3532 if (SetImageStorageClass(shade_image,DirectClass) == MagickFalse)
3533 {
3534 InheritException(exception,&shade_image->exception);
3535 shade_image=DestroyImage(shade_image);
3536 return((Image *) NULL);
3537 }
3538 /*
3539 Compute the light vector.
3540 */
3541 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
3542 cos(DegreesToRadians(elevation));
3543 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
3544 cos(DegreesToRadians(elevation));
3545 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
3546 /*
3547 Shade image.
3548 */
3549 status=MagickTrue;
3550 progress=0;
3551 image_view=AcquireCacheView(image);
3552 shade_view=AcquireCacheView(shade_image);
cristyb5d5f722009-11-04 03:03:49 +00003553#if defined(MAGICKCORE_OPENMP_SUPPORT)
3554 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003555#endif
cristybb503372010-05-27 20:51:26 +00003556 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003557 {
3558 MagickRealType
3559 distance,
3560 normal_distance,
3561 shade;
3562
3563 PrimaryInfo
3564 normal;
3565
cristy4c08aed2011-07-01 19:47:50 +00003566 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003567 *restrict p,
3568 *restrict s0,
3569 *restrict s1,
3570 *restrict s2;
cristy3ed852e2009-09-05 21:47:34 +00003571
cristy4c08aed2011-07-01 19:47:50 +00003572 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003573 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003574
cristy117ff172010-08-15 21:35:32 +00003575 register ssize_t
3576 x;
3577
cristy3ed852e2009-09-05 21:47:34 +00003578 if (status == MagickFalse)
3579 continue;
3580 p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception);
3581 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
3582 exception);
cristy4c08aed2011-07-01 19:47:50 +00003583 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003584 {
3585 status=MagickFalse;
3586 continue;
3587 }
3588 /*
3589 Shade this row of pixels.
3590 */
3591 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
cristyed231572011-07-14 02:18:59 +00003592 s0=p+GetPixelChannels(image);
3593 s1=s0+(image->columns+2)*GetPixelChannels(image);
3594 s2=s1+(image->columns+2)*GetPixelChannels(image);
cristybb503372010-05-27 20:51:26 +00003595 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003596 {
3597 /*
3598 Determine the surface normal and compute shading.
3599 */
cristyed231572011-07-14 02:18:59 +00003600 normal.x=(double) (GetPixelIntensity(image,s0-GetPixelChannels(image))+
3601 GetPixelIntensity(image,s1-GetPixelChannels(image))+
3602 GetPixelIntensity(image,s2-GetPixelChannels(image))-
3603 GetPixelIntensity(image,s0+GetPixelChannels(image))-
3604 GetPixelIntensity(image,s1+GetPixelChannels(image))-
3605 GetPixelIntensity(image,s2+GetPixelChannels(image)));
3606 normal.y=(double) (GetPixelIntensity(image,s2-GetPixelChannels(image))+
cristy4c08aed2011-07-01 19:47:50 +00003607 GetPixelIntensity(image,s2)+
cristyed231572011-07-14 02:18:59 +00003608 GetPixelIntensity(image,s2+GetPixelChannels(image))-
3609 GetPixelIntensity(image,s0-GetPixelChannels(image))-
cristy4c08aed2011-07-01 19:47:50 +00003610 GetPixelIntensity(image,s0)-
cristyed231572011-07-14 02:18:59 +00003611 GetPixelIntensity(image,s0+GetPixelChannels(image)));
cristy3ed852e2009-09-05 21:47:34 +00003612 if ((normal.x == 0.0) && (normal.y == 0.0))
3613 shade=light.z;
3614 else
3615 {
3616 shade=0.0;
3617 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
3618 if (distance > MagickEpsilon)
3619 {
3620 normal_distance=
3621 normal.x*normal.x+normal.y*normal.y+normal.z*normal.z;
3622 if (normal_distance > (MagickEpsilon*MagickEpsilon))
3623 shade=distance/sqrt((double) normal_distance);
3624 }
3625 }
3626 if (gray != MagickFalse)
3627 {
cristy4c08aed2011-07-01 19:47:50 +00003628 SetPixelRed(shade_image,ClampToQuantum(shade),q);
3629 SetPixelGreen(shade_image,ClampToQuantum(shade),q);
3630 SetPixelBlue(shade_image,ClampToQuantum(shade),q);
cristy3ed852e2009-09-05 21:47:34 +00003631 }
3632 else
3633 {
cristy4c08aed2011-07-01 19:47:50 +00003634 SetPixelRed(shade_image,ClampToQuantum(QuantumScale*shade*
3635 GetPixelRed(image,s1)),q);
3636 SetPixelGreen(shade_image,ClampToQuantum(QuantumScale*shade*
3637 GetPixelGreen(image,s1)),q);
3638 SetPixelBlue(shade_image,ClampToQuantum(QuantumScale*shade*
3639 GetPixelBlue(image,s1)),q);
cristy3ed852e2009-09-05 21:47:34 +00003640 }
cristy4c08aed2011-07-01 19:47:50 +00003641 SetPixelAlpha(shade_image,GetPixelAlpha(image,s1),q);
cristyed231572011-07-14 02:18:59 +00003642 s0+=GetPixelChannels(image);
3643 s1+=GetPixelChannels(image);
3644 s2+=GetPixelChannels(image);
3645 q+=GetPixelChannels(shade_image);
cristy3ed852e2009-09-05 21:47:34 +00003646 }
3647 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
3648 status=MagickFalse;
3649 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3650 {
3651 MagickBooleanType
3652 proceed;
3653
cristyb5d5f722009-11-04 03:03:49 +00003654#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003655 #pragma omp critical (MagickCore_ShadeImage)
3656#endif
3657 proceed=SetImageProgress(image,ShadeImageTag,progress++,image->rows);
3658 if (proceed == MagickFalse)
3659 status=MagickFalse;
3660 }
3661 }
3662 shade_view=DestroyCacheView(shade_view);
3663 image_view=DestroyCacheView(image_view);
3664 if (status == MagickFalse)
3665 shade_image=DestroyImage(shade_image);
3666 return(shade_image);
3667}
3668
3669/*
3670%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3671% %
3672% %
3673% %
3674% S h a r p e n I m a g e %
3675% %
3676% %
3677% %
3678%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3679%
3680% SharpenImage() sharpens the image. We convolve the image with a Gaussian
3681% operator of the given radius and standard deviation (sigma). For
3682% reasonable results, radius should be larger than sigma. Use a radius of 0
3683% and SharpenImage() selects a suitable radius for you.
3684%
3685% Using a separable kernel would be faster, but the negative weights cancel
3686% out on the corners of the kernel producing often undesirable ringing in the
3687% filtered result; this can be avoided by using a 2D gaussian shaped image
3688% sharpening kernel instead.
3689%
3690% The format of the SharpenImage method is:
3691%
3692% Image *SharpenImage(const Image *image,const double radius,
3693% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003694%
3695% A description of each parameter follows:
3696%
3697% o image: the image.
3698%
cristy3ed852e2009-09-05 21:47:34 +00003699% o radius: the radius of the Gaussian, in pixels, not counting the center
3700% pixel.
3701%
3702% o sigma: the standard deviation of the Laplacian, in pixels.
3703%
3704% o exception: return any errors or warnings in this structure.
3705%
3706*/
cristy3ed852e2009-09-05 21:47:34 +00003707MagickExport Image *SharpenImage(const Image *image,const double radius,
3708 const double sigma,ExceptionInfo *exception)
3709{
cristy3ed852e2009-09-05 21:47:34 +00003710 double
cristy47e00502009-12-17 19:19:57 +00003711 normalize;
cristy3ed852e2009-09-05 21:47:34 +00003712
3713 Image
3714 *sharp_image;
3715
cristy41cbe682011-07-15 19:12:37 +00003716 KernelInfo
3717 *kernel_info;
3718
cristybb503372010-05-27 20:51:26 +00003719 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003720 i;
3721
cristybb503372010-05-27 20:51:26 +00003722 size_t
cristy3ed852e2009-09-05 21:47:34 +00003723 width;
3724
cristy117ff172010-08-15 21:35:32 +00003725 ssize_t
3726 j,
3727 u,
3728 v;
3729
cristy3ed852e2009-09-05 21:47:34 +00003730 assert(image != (const Image *) NULL);
3731 assert(image->signature == MagickSignature);
3732 if (image->debug != MagickFalse)
3733 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3734 assert(exception != (ExceptionInfo *) NULL);
3735 assert(exception->signature == MagickSignature);
3736 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00003737 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00003738 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003739 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00003740 (void) ResetMagickMemory(kernel_info,0,sizeof(*kernel_info));
3741 kernel_info->width=width;
3742 kernel_info->height=width;
3743 kernel_info->signature=MagickSignature;
3744 kernel_info->values=(double *) AcquireAlignedMemory(kernel_info->width,
3745 kernel_info->width*sizeof(*kernel_info->values));
3746 if (kernel_info->values == (double *) NULL)
3747 {
3748 kernel_info=DestroyKernelInfo(kernel_info);
3749 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3750 }
cristy3ed852e2009-09-05 21:47:34 +00003751 normalize=0.0;
cristy41cbe682011-07-15 19:12:37 +00003752 j=(ssize_t) kernel_info->width/2;
cristy47e00502009-12-17 19:19:57 +00003753 i=0;
3754 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003755 {
cristy47e00502009-12-17 19:19:57 +00003756 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00003757 {
cristy41cbe682011-07-15 19:12:37 +00003758 kernel_info->values[i]=(double) (-exp(-((double) u*u+v*v)/(2.0*
3759 MagickSigma*MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
3760 normalize+=kernel_info->values[i];
cristy3ed852e2009-09-05 21:47:34 +00003761 i++;
3762 }
3763 }
cristy41cbe682011-07-15 19:12:37 +00003764 kernel_info->values[i/2]=(double) ((-2.0)*normalize);
cristy0a922382011-07-16 15:30:34 +00003765 kernel_info->bias=image->bias;
cristy5e6be1e2011-07-16 01:23:39 +00003766 sharp_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00003767 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00003768 return(sharp_image);
3769}
3770
3771/*
3772%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3773% %
3774% %
3775% %
3776% S p r e a d I m a g e %
3777% %
3778% %
3779% %
3780%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3781%
3782% SpreadImage() is a special effects method that randomly displaces each
3783% pixel in a block defined by the radius parameter.
3784%
3785% The format of the SpreadImage method is:
3786%
3787% Image *SpreadImage(const Image *image,const double radius,
3788% ExceptionInfo *exception)
3789%
3790% A description of each parameter follows:
3791%
3792% o image: the image.
3793%
3794% o radius: Choose a random pixel in a neighborhood of this extent.
3795%
3796% o exception: return any errors or warnings in this structure.
3797%
3798*/
3799MagickExport Image *SpreadImage(const Image *image,const double radius,
3800 ExceptionInfo *exception)
3801{
3802#define SpreadImageTag "Spread/Image"
3803
cristyfa112112010-01-04 17:48:07 +00003804 CacheView
cristy9f7e7cb2011-03-26 00:49:57 +00003805 *image_view,
3806 *spread_view;
cristyfa112112010-01-04 17:48:07 +00003807
cristy3ed852e2009-09-05 21:47:34 +00003808 Image
3809 *spread_image;
3810
cristy3ed852e2009-09-05 21:47:34 +00003811 MagickBooleanType
3812 status;
3813
cristybb503372010-05-27 20:51:26 +00003814 MagickOffsetType
3815 progress;
3816
cristy4c08aed2011-07-01 19:47:50 +00003817 PixelInfo
cristyddd82202009-11-03 20:14:50 +00003818 bias;
cristy3ed852e2009-09-05 21:47:34 +00003819
3820 RandomInfo
cristyfa112112010-01-04 17:48:07 +00003821 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00003822
cristybb503372010-05-27 20:51:26 +00003823 size_t
cristy3ed852e2009-09-05 21:47:34 +00003824 width;
3825
cristybb503372010-05-27 20:51:26 +00003826 ssize_t
3827 y;
3828
cristy3ed852e2009-09-05 21:47:34 +00003829 /*
3830 Initialize spread image attributes.
3831 */
3832 assert(image != (Image *) NULL);
3833 assert(image->signature == MagickSignature);
3834 if (image->debug != MagickFalse)
3835 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3836 assert(exception != (ExceptionInfo *) NULL);
3837 assert(exception->signature == MagickSignature);
3838 spread_image=CloneImage(image,image->columns,image->rows,MagickTrue,
3839 exception);
3840 if (spread_image == (Image *) NULL)
3841 return((Image *) NULL);
3842 if (SetImageStorageClass(spread_image,DirectClass) == MagickFalse)
3843 {
3844 InheritException(exception,&spread_image->exception);
3845 spread_image=DestroyImage(spread_image);
3846 return((Image *) NULL);
3847 }
3848 /*
3849 Spread image.
3850 */
3851 status=MagickTrue;
3852 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00003853 GetPixelInfo(spread_image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003854 width=GetOptimalKernelWidth1D(radius,0.5);
cristy3ed852e2009-09-05 21:47:34 +00003855 random_info=AcquireRandomInfoThreadSet();
cristy9f7e7cb2011-03-26 00:49:57 +00003856 image_view=AcquireCacheView(image);
3857 spread_view=AcquireCacheView(spread_image);
cristyb557a152011-02-22 12:14:30 +00003858#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00003859 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00003860#endif
cristybb503372010-05-27 20:51:26 +00003861 for (y=0; y < (ssize_t) spread_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003862 {
cristy5c9e6f22010-09-17 17:31:01 +00003863 const int
3864 id = GetOpenMPThreadId();
cristy6ebe97c2010-07-03 01:17:28 +00003865
cristy4c08aed2011-07-01 19:47:50 +00003866 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003867 pixel;
3868
cristy4c08aed2011-07-01 19:47:50 +00003869 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003870 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003871
cristy117ff172010-08-15 21:35:32 +00003872 register ssize_t
3873 x;
3874
cristy3ed852e2009-09-05 21:47:34 +00003875 if (status == MagickFalse)
3876 continue;
cristy9f7e7cb2011-03-26 00:49:57 +00003877 q=QueueCacheViewAuthenticPixels(spread_view,0,y,spread_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +00003878 exception);
cristy4c08aed2011-07-01 19:47:50 +00003879 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003880 {
3881 status=MagickFalse;
3882 continue;
3883 }
cristyddd82202009-11-03 20:14:50 +00003884 pixel=bias;
cristybb503372010-05-27 20:51:26 +00003885 for (x=0; x < (ssize_t) spread_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003886 {
cristy4c08aed2011-07-01 19:47:50 +00003887 (void) InterpolatePixelInfo(image,image_view,
cristy8a7c3e82011-03-26 02:10:53 +00003888 UndefinedInterpolatePixel,(double) x+width*(GetPseudoRandomValue(
3889 random_info[id])-0.5),(double) y+width*(GetPseudoRandomValue(
3890 random_info[id])-0.5),&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00003891 SetPixelPixelInfo(spread_image,&pixel,q);
cristyed231572011-07-14 02:18:59 +00003892 q+=GetPixelChannels(spread_image);
cristy3ed852e2009-09-05 21:47:34 +00003893 }
cristy9f7e7cb2011-03-26 00:49:57 +00003894 if (SyncCacheViewAuthenticPixels(spread_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003895 status=MagickFalse;
3896 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3897 {
3898 MagickBooleanType
3899 proceed;
3900
cristyb557a152011-02-22 12:14:30 +00003901#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003902 #pragma omp critical (MagickCore_SpreadImage)
3903#endif
3904 proceed=SetImageProgress(image,SpreadImageTag,progress++,image->rows);
3905 if (proceed == MagickFalse)
3906 status=MagickFalse;
3907 }
3908 }
cristy9f7e7cb2011-03-26 00:49:57 +00003909 spread_view=DestroyCacheView(spread_view);
cristy3ed852e2009-09-05 21:47:34 +00003910 image_view=DestroyCacheView(image_view);
3911 random_info=DestroyRandomInfoThreadSet(random_info);
cristy3ed852e2009-09-05 21:47:34 +00003912 return(spread_image);
3913}
3914
3915/*
3916%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3917% %
3918% %
3919% %
cristy0834d642011-03-18 18:26:08 +00003920% S t a t i s t i c I m a g e %
3921% %
3922% %
3923% %
3924%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3925%
3926% StatisticImage() makes each pixel the min / max / median / mode / etc. of
cristy8d752042011-03-19 01:00:36 +00003927% the neighborhood of the specified width and height.
cristy0834d642011-03-18 18:26:08 +00003928%
3929% The format of the StatisticImage method is:
3930%
3931% Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00003932% const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00003933%
3934% A description of each parameter follows:
3935%
3936% o image: the image.
3937%
cristy0834d642011-03-18 18:26:08 +00003938% o type: the statistic type (median, mode, etc.).
3939%
cristy95c38342011-03-18 22:39:51 +00003940% o width: the width of the pixel neighborhood.
3941%
3942% o height: the height of the pixel neighborhood.
cristy0834d642011-03-18 18:26:08 +00003943%
3944% o exception: return any errors or warnings in this structure.
3945%
3946*/
3947
cristy733678d2011-03-18 21:29:28 +00003948#define ListChannels 5
3949
3950typedef struct _ListNode
3951{
3952 size_t
3953 next[9],
3954 count,
3955 signature;
3956} ListNode;
3957
3958typedef struct _SkipList
3959{
3960 ssize_t
3961 level;
3962
3963 ListNode
3964 *nodes;
3965} SkipList;
3966
3967typedef struct _PixelList
3968{
3969 size_t
cristy6fc86bb2011-03-18 23:45:16 +00003970 length,
cristy733678d2011-03-18 21:29:28 +00003971 seed,
3972 signature;
3973
3974 SkipList
3975 lists[ListChannels];
3976} PixelList;
3977
3978static PixelList *DestroyPixelList(PixelList *pixel_list)
3979{
3980 register ssize_t
3981 i;
3982
3983 if (pixel_list == (PixelList *) NULL)
3984 return((PixelList *) NULL);
3985 for (i=0; i < ListChannels; i++)
3986 if (pixel_list->lists[i].nodes != (ListNode *) NULL)
3987 pixel_list->lists[i].nodes=(ListNode *) RelinquishMagickMemory(
3988 pixel_list->lists[i].nodes);
3989 pixel_list=(PixelList *) RelinquishMagickMemory(pixel_list);
3990 return(pixel_list);
3991}
3992
3993static PixelList **DestroyPixelListThreadSet(PixelList **pixel_list)
3994{
3995 register ssize_t
3996 i;
3997
3998 assert(pixel_list != (PixelList **) NULL);
3999 for (i=0; i < (ssize_t) GetOpenMPMaximumThreads(); i++)
4000 if (pixel_list[i] != (PixelList *) NULL)
4001 pixel_list[i]=DestroyPixelList(pixel_list[i]);
4002 pixel_list=(PixelList **) RelinquishMagickMemory(pixel_list);
4003 return(pixel_list);
4004}
4005
cristy6fc86bb2011-03-18 23:45:16 +00004006static PixelList *AcquirePixelList(const size_t width,const size_t height)
cristy733678d2011-03-18 21:29:28 +00004007{
4008 PixelList
4009 *pixel_list;
4010
4011 register ssize_t
4012 i;
4013
4014 pixel_list=(PixelList *) AcquireMagickMemory(sizeof(*pixel_list));
4015 if (pixel_list == (PixelList *) NULL)
4016 return(pixel_list);
4017 (void) ResetMagickMemory((void *) pixel_list,0,sizeof(*pixel_list));
cristy6fc86bb2011-03-18 23:45:16 +00004018 pixel_list->length=width*height;
cristy733678d2011-03-18 21:29:28 +00004019 for (i=0; i < ListChannels; i++)
4020 {
4021 pixel_list->lists[i].nodes=(ListNode *) AcquireQuantumMemory(65537UL,
4022 sizeof(*pixel_list->lists[i].nodes));
4023 if (pixel_list->lists[i].nodes == (ListNode *) NULL)
4024 return(DestroyPixelList(pixel_list));
4025 (void) ResetMagickMemory(pixel_list->lists[i].nodes,0,65537UL*
4026 sizeof(*pixel_list->lists[i].nodes));
4027 }
4028 pixel_list->signature=MagickSignature;
4029 return(pixel_list);
4030}
4031
cristy6fc86bb2011-03-18 23:45:16 +00004032static PixelList **AcquirePixelListThreadSet(const size_t width,
4033 const size_t height)
cristy733678d2011-03-18 21:29:28 +00004034{
4035 PixelList
4036 **pixel_list;
4037
4038 register ssize_t
4039 i;
4040
4041 size_t
4042 number_threads;
4043
4044 number_threads=GetOpenMPMaximumThreads();
4045 pixel_list=(PixelList **) AcquireQuantumMemory(number_threads,
4046 sizeof(*pixel_list));
4047 if (pixel_list == (PixelList **) NULL)
4048 return((PixelList **) NULL);
4049 (void) ResetMagickMemory(pixel_list,0,number_threads*sizeof(*pixel_list));
4050 for (i=0; i < (ssize_t) number_threads; i++)
4051 {
cristy6fc86bb2011-03-18 23:45:16 +00004052 pixel_list[i]=AcquirePixelList(width,height);
cristy733678d2011-03-18 21:29:28 +00004053 if (pixel_list[i] == (PixelList *) NULL)
4054 return(DestroyPixelListThreadSet(pixel_list));
4055 }
4056 return(pixel_list);
4057}
4058
4059static void AddNodePixelList(PixelList *pixel_list,const ssize_t channel,
4060 const size_t color)
4061{
4062 register SkipList
4063 *list;
4064
4065 register ssize_t
4066 level;
4067
4068 size_t
4069 search,
4070 update[9];
4071
4072 /*
4073 Initialize the node.
4074 */
4075 list=pixel_list->lists+channel;
4076 list->nodes[color].signature=pixel_list->signature;
4077 list->nodes[color].count=1;
4078 /*
4079 Determine where it belongs in the list.
4080 */
4081 search=65536UL;
4082 for (level=list->level; level >= 0; level--)
4083 {
4084 while (list->nodes[search].next[level] < color)
4085 search=list->nodes[search].next[level];
4086 update[level]=search;
4087 }
4088 /*
4089 Generate a pseudo-random level for this node.
4090 */
4091 for (level=0; ; level++)
4092 {
4093 pixel_list->seed=(pixel_list->seed*42893621L)+1L;
4094 if ((pixel_list->seed & 0x300) != 0x300)
4095 break;
4096 }
4097 if (level > 8)
4098 level=8;
4099 if (level > (list->level+2))
4100 level=list->level+2;
4101 /*
4102 If we're raising the list's level, link back to the root node.
4103 */
4104 while (level > list->level)
4105 {
4106 list->level++;
4107 update[list->level]=65536UL;
4108 }
4109 /*
4110 Link the node into the skip-list.
4111 */
4112 do
4113 {
4114 list->nodes[color].next[level]=list->nodes[update[level]].next[level];
4115 list->nodes[update[level]].next[level]=color;
cristy3cba8ca2011-03-19 01:29:12 +00004116 } while (level-- > 0);
cristy733678d2011-03-18 21:29:28 +00004117}
4118
cristy4c08aed2011-07-01 19:47:50 +00004119static PixelInfo GetMaximumPixelList(PixelList *pixel_list)
cristy6fc86bb2011-03-18 23:45:16 +00004120{
cristy4c08aed2011-07-01 19:47:50 +00004121 PixelInfo
cristy6fc86bb2011-03-18 23:45:16 +00004122 pixel;
4123
4124 register SkipList
4125 *list;
4126
4127 register ssize_t
4128 channel;
4129
4130 size_t
cristyd76c51e2011-03-26 00:21:26 +00004131 color,
4132 maximum;
cristy49f37242011-03-22 18:18:23 +00004133
4134 ssize_t
cristy6fc86bb2011-03-18 23:45:16 +00004135 count;
4136
4137 unsigned short
cristyd76c51e2011-03-26 00:21:26 +00004138 channels[ListChannels];
cristy6fc86bb2011-03-18 23:45:16 +00004139
4140 /*
4141 Find the maximum value for each of the color.
4142 */
4143 for (channel=0; channel < 5; channel++)
4144 {
4145 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004146 color=65536L;
cristy6fc86bb2011-03-18 23:45:16 +00004147 count=0;
cristy49f37242011-03-22 18:18:23 +00004148 maximum=list->nodes[color].next[0];
cristy6fc86bb2011-03-18 23:45:16 +00004149 do
4150 {
4151 color=list->nodes[color].next[0];
cristy49f37242011-03-22 18:18:23 +00004152 if (color > maximum)
4153 maximum=color;
cristy6fc86bb2011-03-18 23:45:16 +00004154 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004155 } while (count < (ssize_t) pixel_list->length);
cristy49f37242011-03-22 18:18:23 +00004156 channels[channel]=(unsigned short) maximum;
4157 }
cristy4c08aed2011-07-01 19:47:50 +00004158 GetPixelInfo((const Image *) NULL,&pixel);
cristy49f37242011-03-22 18:18:23 +00004159 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4160 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4161 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004162 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4163 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy49f37242011-03-22 18:18:23 +00004164 return(pixel);
4165}
4166
cristy4c08aed2011-07-01 19:47:50 +00004167static PixelInfo GetMeanPixelList(PixelList *pixel_list)
cristy49f37242011-03-22 18:18:23 +00004168{
cristy4c08aed2011-07-01 19:47:50 +00004169 PixelInfo
cristy49f37242011-03-22 18:18:23 +00004170 pixel;
4171
cristy80a99a32011-03-30 01:30:23 +00004172 MagickRealType
4173 sum;
4174
cristy49f37242011-03-22 18:18:23 +00004175 register SkipList
4176 *list;
4177
4178 register ssize_t
4179 channel;
4180
4181 size_t
cristy80a99a32011-03-30 01:30:23 +00004182 color;
cristy49f37242011-03-22 18:18:23 +00004183
4184 ssize_t
4185 count;
4186
4187 unsigned short
4188 channels[ListChannels];
4189
4190 /*
4191 Find the mean value for each of the color.
4192 */
4193 for (channel=0; channel < 5; channel++)
4194 {
4195 list=pixel_list->lists+channel;
4196 color=65536L;
4197 count=0;
cristy80a99a32011-03-30 01:30:23 +00004198 sum=0.0;
cristy49f37242011-03-22 18:18:23 +00004199 do
4200 {
4201 color=list->nodes[color].next[0];
cristy80a99a32011-03-30 01:30:23 +00004202 sum+=(MagickRealType) list->nodes[color].count*color;
cristy49f37242011-03-22 18:18:23 +00004203 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004204 } while (count < (ssize_t) pixel_list->length);
cristy80a99a32011-03-30 01:30:23 +00004205 sum/=pixel_list->length;
4206 channels[channel]=(unsigned short) sum;
cristy6fc86bb2011-03-18 23:45:16 +00004207 }
cristy4c08aed2011-07-01 19:47:50 +00004208 GetPixelInfo((const Image *) NULL,&pixel);
cristy6fc86bb2011-03-18 23:45:16 +00004209 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4210 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4211 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004212 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4213 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy6fc86bb2011-03-18 23:45:16 +00004214 return(pixel);
4215}
4216
cristy4c08aed2011-07-01 19:47:50 +00004217static PixelInfo GetMedianPixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004218{
cristy4c08aed2011-07-01 19:47:50 +00004219 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004220 pixel;
4221
4222 register SkipList
4223 *list;
4224
4225 register ssize_t
4226 channel;
4227
4228 size_t
cristy49f37242011-03-22 18:18:23 +00004229 color;
4230
4231 ssize_t
cristy733678d2011-03-18 21:29:28 +00004232 count;
4233
4234 unsigned short
4235 channels[ListChannels];
4236
4237 /*
4238 Find the median value for each of the color.
4239 */
cristy733678d2011-03-18 21:29:28 +00004240 for (channel=0; channel < 5; channel++)
4241 {
4242 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004243 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004244 count=0;
4245 do
4246 {
4247 color=list->nodes[color].next[0];
4248 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004249 } while (count <= (ssize_t) (pixel_list->length >> 1));
cristy6fc86bb2011-03-18 23:45:16 +00004250 channels[channel]=(unsigned short) color;
4251 }
cristy4c08aed2011-07-01 19:47:50 +00004252 GetPixelInfo((const Image *) NULL,&pixel);
cristy6fc86bb2011-03-18 23:45:16 +00004253 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4254 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4255 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004256 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4257 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy6fc86bb2011-03-18 23:45:16 +00004258 return(pixel);
4259}
4260
cristy4c08aed2011-07-01 19:47:50 +00004261static PixelInfo GetMinimumPixelList(PixelList *pixel_list)
cristy6fc86bb2011-03-18 23:45:16 +00004262{
cristy4c08aed2011-07-01 19:47:50 +00004263 PixelInfo
cristy6fc86bb2011-03-18 23:45:16 +00004264 pixel;
4265
4266 register SkipList
4267 *list;
4268
4269 register ssize_t
4270 channel;
4271
4272 size_t
cristyd76c51e2011-03-26 00:21:26 +00004273 color,
4274 minimum;
cristy6fc86bb2011-03-18 23:45:16 +00004275
cristy49f37242011-03-22 18:18:23 +00004276 ssize_t
4277 count;
4278
cristy6fc86bb2011-03-18 23:45:16 +00004279 unsigned short
cristyd76c51e2011-03-26 00:21:26 +00004280 channels[ListChannels];
cristy6fc86bb2011-03-18 23:45:16 +00004281
4282 /*
4283 Find the minimum value for each of the color.
4284 */
4285 for (channel=0; channel < 5; channel++)
4286 {
4287 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004288 count=0;
cristy6fc86bb2011-03-18 23:45:16 +00004289 color=65536UL;
cristy49f37242011-03-22 18:18:23 +00004290 minimum=list->nodes[color].next[0];
4291 do
4292 {
4293 color=list->nodes[color].next[0];
4294 if (color < minimum)
4295 minimum=color;
4296 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004297 } while (count < (ssize_t) pixel_list->length);
cristy49f37242011-03-22 18:18:23 +00004298 channels[channel]=(unsigned short) minimum;
cristy733678d2011-03-18 21:29:28 +00004299 }
cristy4c08aed2011-07-01 19:47:50 +00004300 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004301 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4302 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4303 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004304 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4305 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy733678d2011-03-18 21:29:28 +00004306 return(pixel);
4307}
4308
cristy4c08aed2011-07-01 19:47:50 +00004309static PixelInfo GetModePixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004310{
cristy4c08aed2011-07-01 19:47:50 +00004311 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004312 pixel;
4313
4314 register SkipList
4315 *list;
4316
4317 register ssize_t
4318 channel;
4319
4320 size_t
4321 color,
cristy733678d2011-03-18 21:29:28 +00004322 max_count,
cristy6fc86bb2011-03-18 23:45:16 +00004323 mode;
cristy733678d2011-03-18 21:29:28 +00004324
cristy49f37242011-03-22 18:18:23 +00004325 ssize_t
4326 count;
4327
cristy733678d2011-03-18 21:29:28 +00004328 unsigned short
4329 channels[5];
4330
4331 /*
glennrp30d2dc62011-06-25 03:17:16 +00004332 Make each pixel the 'predominant color' of the specified neighborhood.
cristy733678d2011-03-18 21:29:28 +00004333 */
cristy733678d2011-03-18 21:29:28 +00004334 for (channel=0; channel < 5; channel++)
4335 {
4336 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004337 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004338 mode=color;
4339 max_count=list->nodes[mode].count;
4340 count=0;
4341 do
4342 {
4343 color=list->nodes[color].next[0];
4344 if (list->nodes[color].count > max_count)
4345 {
4346 mode=color;
4347 max_count=list->nodes[mode].count;
4348 }
4349 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004350 } while (count < (ssize_t) pixel_list->length);
cristy733678d2011-03-18 21:29:28 +00004351 channels[channel]=(unsigned short) mode;
4352 }
cristy4c08aed2011-07-01 19:47:50 +00004353 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004354 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4355 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4356 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004357 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4358 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy733678d2011-03-18 21:29:28 +00004359 return(pixel);
4360}
4361
cristy4c08aed2011-07-01 19:47:50 +00004362static PixelInfo GetNonpeakPixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004363{
cristy4c08aed2011-07-01 19:47:50 +00004364 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004365 pixel;
4366
4367 register SkipList
4368 *list;
4369
4370 register ssize_t
4371 channel;
4372
4373 size_t
cristy733678d2011-03-18 21:29:28 +00004374 color,
cristy733678d2011-03-18 21:29:28 +00004375 next,
4376 previous;
4377
cristy49f37242011-03-22 18:18:23 +00004378 ssize_t
4379 count;
4380
cristy733678d2011-03-18 21:29:28 +00004381 unsigned short
4382 channels[5];
4383
4384 /*
cristy49f37242011-03-22 18:18:23 +00004385 Finds the non peak value for each of the colors.
cristy733678d2011-03-18 21:29:28 +00004386 */
cristy733678d2011-03-18 21:29:28 +00004387 for (channel=0; channel < 5; channel++)
4388 {
4389 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004390 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004391 next=list->nodes[color].next[0];
4392 count=0;
4393 do
4394 {
4395 previous=color;
4396 color=next;
4397 next=list->nodes[color].next[0];
4398 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004399 } while (count <= (ssize_t) (pixel_list->length >> 1));
cristy733678d2011-03-18 21:29:28 +00004400 if ((previous == 65536UL) && (next != 65536UL))
4401 color=next;
4402 else
4403 if ((previous != 65536UL) && (next == 65536UL))
4404 color=previous;
4405 channels[channel]=(unsigned short) color;
4406 }
cristy4c08aed2011-07-01 19:47:50 +00004407 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004408 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4409 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4410 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004411 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4412 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy733678d2011-03-18 21:29:28 +00004413 return(pixel);
4414}
4415
cristy4c08aed2011-07-01 19:47:50 +00004416static PixelInfo GetStandardDeviationPixelList(PixelList *pixel_list)
cristy9a68cbb2011-03-29 00:51:23 +00004417{
cristy4c08aed2011-07-01 19:47:50 +00004418 PixelInfo
cristy9a68cbb2011-03-29 00:51:23 +00004419 pixel;
4420
cristy80a99a32011-03-30 01:30:23 +00004421 MagickRealType
4422 sum,
4423 sum_squared;
4424
cristy9a68cbb2011-03-29 00:51:23 +00004425 register SkipList
4426 *list;
4427
4428 register ssize_t
4429 channel;
4430
4431 size_t
cristy80a99a32011-03-30 01:30:23 +00004432 color;
cristy9a68cbb2011-03-29 00:51:23 +00004433
4434 ssize_t
4435 count;
4436
4437 unsigned short
4438 channels[ListChannels];
4439
4440 /*
cristy80a99a32011-03-30 01:30:23 +00004441 Find the standard-deviation value for each of the color.
cristy9a68cbb2011-03-29 00:51:23 +00004442 */
4443 for (channel=0; channel < 5; channel++)
4444 {
4445 list=pixel_list->lists+channel;
4446 color=65536L;
4447 count=0;
cristy80a99a32011-03-30 01:30:23 +00004448 sum=0.0;
4449 sum_squared=0.0;
cristy9a68cbb2011-03-29 00:51:23 +00004450 do
4451 {
cristy80a99a32011-03-30 01:30:23 +00004452 register ssize_t
4453 i;
4454
cristy9a68cbb2011-03-29 00:51:23 +00004455 color=list->nodes[color].next[0];
cristy80a99a32011-03-30 01:30:23 +00004456 sum+=(MagickRealType) list->nodes[color].count*color;
4457 for (i=0; i < (ssize_t) list->nodes[color].count; i++)
4458 sum_squared+=((MagickRealType) color)*((MagickRealType) color);
cristy9a68cbb2011-03-29 00:51:23 +00004459 count+=list->nodes[color].count;
4460 } while (count < (ssize_t) pixel_list->length);
cristy80a99a32011-03-30 01:30:23 +00004461 sum/=pixel_list->length;
4462 sum_squared/=pixel_list->length;
4463 channels[channel]=(unsigned short) sqrt(sum_squared-(sum*sum));
cristy9a68cbb2011-03-29 00:51:23 +00004464 }
cristy4c08aed2011-07-01 19:47:50 +00004465 GetPixelInfo((const Image *) NULL,&pixel);
cristy9a68cbb2011-03-29 00:51:23 +00004466 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4467 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4468 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004469 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4470 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy9a68cbb2011-03-29 00:51:23 +00004471 return(pixel);
4472}
4473
cristy4c08aed2011-07-01 19:47:50 +00004474static inline void InsertPixelList(const Image *image,const Quantum *pixel,
4475 PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004476{
4477 size_t
4478 signature;
4479
4480 unsigned short
4481 index;
4482
cristy4c08aed2011-07-01 19:47:50 +00004483 index=ScaleQuantumToShort(GetPixelRed(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004484 signature=pixel_list->lists[0].nodes[index].signature;
4485 if (signature == pixel_list->signature)
4486 pixel_list->lists[0].nodes[index].count++;
4487 else
4488 AddNodePixelList(pixel_list,0,index);
cristy4c08aed2011-07-01 19:47:50 +00004489 index=ScaleQuantumToShort(GetPixelGreen(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004490 signature=pixel_list->lists[1].nodes[index].signature;
4491 if (signature == pixel_list->signature)
4492 pixel_list->lists[1].nodes[index].count++;
4493 else
4494 AddNodePixelList(pixel_list,1,index);
cristy4c08aed2011-07-01 19:47:50 +00004495 index=ScaleQuantumToShort(GetPixelBlue(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004496 signature=pixel_list->lists[2].nodes[index].signature;
4497 if (signature == pixel_list->signature)
4498 pixel_list->lists[2].nodes[index].count++;
4499 else
4500 AddNodePixelList(pixel_list,2,index);
cristy4c08aed2011-07-01 19:47:50 +00004501 index=ScaleQuantumToShort(GetPixelAlpha(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004502 signature=pixel_list->lists[3].nodes[index].signature;
4503 if (signature == pixel_list->signature)
4504 pixel_list->lists[3].nodes[index].count++;
4505 else
4506 AddNodePixelList(pixel_list,3,index);
4507 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00004508 index=ScaleQuantumToShort(GetPixelBlack(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004509 signature=pixel_list->lists[4].nodes[index].signature;
4510 if (signature == pixel_list->signature)
4511 pixel_list->lists[4].nodes[index].count++;
4512 else
4513 AddNodePixelList(pixel_list,4,index);
4514}
4515
cristy80c99742011-04-04 14:46:39 +00004516static inline MagickRealType MagickAbsoluteValue(const MagickRealType x)
4517{
4518 if (x < 0)
4519 return(-x);
4520 return(x);
4521}
4522
cristy733678d2011-03-18 21:29:28 +00004523static void ResetPixelList(PixelList *pixel_list)
4524{
4525 int
4526 level;
4527
4528 register ListNode
4529 *root;
4530
4531 register SkipList
4532 *list;
4533
4534 register ssize_t
4535 channel;
4536
4537 /*
4538 Reset the skip-list.
4539 */
4540 for (channel=0; channel < 5; channel++)
4541 {
4542 list=pixel_list->lists+channel;
4543 root=list->nodes+65536UL;
4544 list->level=0;
4545 for (level=0; level < 9; level++)
4546 root->next[level]=65536UL;
4547 }
4548 pixel_list->seed=pixel_list->signature++;
4549}
4550
cristy0834d642011-03-18 18:26:08 +00004551MagickExport Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00004552 const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00004553{
cristy3cba8ca2011-03-19 01:29:12 +00004554#define StatisticWidth \
cristyd76c51e2011-03-26 00:21:26 +00004555 (width == 0 ? GetOptimalKernelWidth2D((double) width,0.5) : width)
cristy3cba8ca2011-03-19 01:29:12 +00004556#define StatisticHeight \
cristyd76c51e2011-03-26 00:21:26 +00004557 (height == 0 ? GetOptimalKernelWidth2D((double) height,0.5) : height)
cristy0834d642011-03-18 18:26:08 +00004558#define StatisticImageTag "Statistic/Image"
4559
4560 CacheView
4561 *image_view,
4562 *statistic_view;
4563
4564 Image
4565 *statistic_image;
4566
4567 MagickBooleanType
4568 status;
4569
4570 MagickOffsetType
4571 progress;
4572
4573 PixelList
4574 **restrict pixel_list;
4575
cristy0834d642011-03-18 18:26:08 +00004576 ssize_t
4577 y;
4578
4579 /*
4580 Initialize statistics image attributes.
4581 */
4582 assert(image != (Image *) NULL);
4583 assert(image->signature == MagickSignature);
4584 if (image->debug != MagickFalse)
4585 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4586 assert(exception != (ExceptionInfo *) NULL);
4587 assert(exception->signature == MagickSignature);
cristy0834d642011-03-18 18:26:08 +00004588 statistic_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4589 exception);
4590 if (statistic_image == (Image *) NULL)
4591 return((Image *) NULL);
4592 if (SetImageStorageClass(statistic_image,DirectClass) == MagickFalse)
4593 {
4594 InheritException(exception,&statistic_image->exception);
4595 statistic_image=DestroyImage(statistic_image);
4596 return((Image *) NULL);
4597 }
cristy6fc86bb2011-03-18 23:45:16 +00004598 pixel_list=AcquirePixelListThreadSet(StatisticWidth,StatisticHeight);
cristy0834d642011-03-18 18:26:08 +00004599 if (pixel_list == (PixelList **) NULL)
4600 {
4601 statistic_image=DestroyImage(statistic_image);
4602 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
4603 }
4604 /*
cristy8d752042011-03-19 01:00:36 +00004605 Make each pixel the min / max / median / mode / etc. of the neighborhood.
cristy0834d642011-03-18 18:26:08 +00004606 */
4607 status=MagickTrue;
4608 progress=0;
4609 image_view=AcquireCacheView(image);
4610 statistic_view=AcquireCacheView(statistic_image);
4611#if defined(MAGICKCORE_OPENMP_SUPPORT)
4612 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
4613#endif
4614 for (y=0; y < (ssize_t) statistic_image->rows; y++)
4615 {
4616 const int
4617 id = GetOpenMPThreadId();
4618
cristy4c08aed2011-07-01 19:47:50 +00004619 register const Quantum
cristy0834d642011-03-18 18:26:08 +00004620 *restrict p;
4621
cristy4c08aed2011-07-01 19:47:50 +00004622 register Quantum
cristy0834d642011-03-18 18:26:08 +00004623 *restrict q;
4624
4625 register ssize_t
4626 x;
4627
4628 if (status == MagickFalse)
4629 continue;
cristy6fc86bb2011-03-18 23:45:16 +00004630 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) StatisticWidth/2L),y-
4631 (ssize_t) (StatisticHeight/2L),image->columns+StatisticWidth,
4632 StatisticHeight,exception);
cristy3cba8ca2011-03-19 01:29:12 +00004633 q=QueueCacheViewAuthenticPixels(statistic_view,0,y,statistic_image->columns, 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00004634 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy0834d642011-03-18 18:26:08 +00004635 {
4636 status=MagickFalse;
4637 continue;
4638 }
cristy0834d642011-03-18 18:26:08 +00004639 for (x=0; x < (ssize_t) statistic_image->columns; x++)
4640 {
cristy4c08aed2011-07-01 19:47:50 +00004641 PixelInfo
cristy0834d642011-03-18 18:26:08 +00004642 pixel;
4643
cristy4c08aed2011-07-01 19:47:50 +00004644 register const Quantum
cristy6e3026a2011-03-19 00:54:38 +00004645 *restrict r;
4646
cristy0834d642011-03-18 18:26:08 +00004647 register ssize_t
4648 u,
4649 v;
4650
4651 r=p;
cristy0834d642011-03-18 18:26:08 +00004652 ResetPixelList(pixel_list[id]);
cristy6e4c3292011-03-19 00:53:55 +00004653 for (v=0; v < (ssize_t) StatisticHeight; v++)
cristy0834d642011-03-18 18:26:08 +00004654 {
cristy6e4c3292011-03-19 00:53:55 +00004655 for (u=0; u < (ssize_t) StatisticWidth; u++)
cristyed231572011-07-14 02:18:59 +00004656 InsertPixelList(image,r+u*GetPixelChannels(image),pixel_list[id]);
4657 r+=(image->columns+StatisticWidth)*GetPixelChannels(image);
cristy0834d642011-03-18 18:26:08 +00004658 }
cristy4c08aed2011-07-01 19:47:50 +00004659 GetPixelInfo(image,&pixel);
cristy490408a2011-07-07 14:42:05 +00004660 SetPixelInfo(image,p+(StatisticWidth*StatisticHeight/2)*
cristyed231572011-07-14 02:18:59 +00004661 GetPixelChannels(image),&pixel);
cristy0834d642011-03-18 18:26:08 +00004662 switch (type)
4663 {
cristy80c99742011-04-04 14:46:39 +00004664 case GradientStatistic:
4665 {
cristy4c08aed2011-07-01 19:47:50 +00004666 PixelInfo
cristy80c99742011-04-04 14:46:39 +00004667 maximum,
4668 minimum;
4669
4670 minimum=GetMinimumPixelList(pixel_list[id]);
4671 maximum=GetMaximumPixelList(pixel_list[id]);
4672 pixel.red=MagickAbsoluteValue(maximum.red-minimum.red);
4673 pixel.green=MagickAbsoluteValue(maximum.green-minimum.green);
4674 pixel.blue=MagickAbsoluteValue(maximum.blue-minimum.blue);
cristy4c08aed2011-07-01 19:47:50 +00004675 pixel.alpha=MagickAbsoluteValue(maximum.alpha-minimum.alpha);
cristy80c99742011-04-04 14:46:39 +00004676 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00004677 pixel.black=MagickAbsoluteValue(maximum.black-minimum.black);
cristy80c99742011-04-04 14:46:39 +00004678 break;
4679 }
cristy6fc86bb2011-03-18 23:45:16 +00004680 case MaximumStatistic:
4681 {
4682 pixel=GetMaximumPixelList(pixel_list[id]);
4683 break;
4684 }
cristy49f37242011-03-22 18:18:23 +00004685 case MeanStatistic:
4686 {
4687 pixel=GetMeanPixelList(pixel_list[id]);
4688 break;
4689 }
cristyf2ad14a2011-03-18 18:57:25 +00004690 case MedianStatistic:
cristy6fc86bb2011-03-18 23:45:16 +00004691 default:
cristyf2ad14a2011-03-18 18:57:25 +00004692 {
4693 pixel=GetMedianPixelList(pixel_list[id]);
4694 break;
4695 }
cristy6fc86bb2011-03-18 23:45:16 +00004696 case MinimumStatistic:
4697 {
4698 pixel=GetMinimumPixelList(pixel_list[id]);
4699 break;
4700 }
cristyf2ad14a2011-03-18 18:57:25 +00004701 case ModeStatistic:
4702 {
4703 pixel=GetModePixelList(pixel_list[id]);
4704 break;
4705 }
4706 case NonpeakStatistic:
4707 {
4708 pixel=GetNonpeakPixelList(pixel_list[id]);
4709 break;
4710 }
cristy9a68cbb2011-03-29 00:51:23 +00004711 case StandardDeviationStatistic:
4712 {
4713 pixel=GetStandardDeviationPixelList(pixel_list[id]);
4714 break;
4715 }
cristy0834d642011-03-18 18:26:08 +00004716 }
cristyed231572011-07-14 02:18:59 +00004717 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00004718 SetPixelRed(statistic_image,ClampToQuantum(pixel.red),q);
cristyed231572011-07-14 02:18:59 +00004719 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00004720 SetPixelGreen(statistic_image,ClampToQuantum(pixel.green),q);
cristyed231572011-07-14 02:18:59 +00004721 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00004722 SetPixelBlue(statistic_image,ClampToQuantum(pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00004723 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy0834d642011-03-18 18:26:08 +00004724 (image->colorspace == CMYKColorspace))
cristy490408a2011-07-07 14:42:05 +00004725 SetPixelBlack(statistic_image,ClampToQuantum(pixel.black),q);
cristyed231572011-07-14 02:18:59 +00004726 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00004727 (image->matte != MagickFalse))
cristy490408a2011-07-07 14:42:05 +00004728 SetPixelAlpha(statistic_image,ClampToQuantum(pixel.alpha),q);
cristyed231572011-07-14 02:18:59 +00004729 p+=GetPixelChannels(image);
4730 q+=GetPixelChannels(statistic_image);
cristy0834d642011-03-18 18:26:08 +00004731 }
4732 if (SyncCacheViewAuthenticPixels(statistic_view,exception) == MagickFalse)
4733 status=MagickFalse;
4734 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4735 {
4736 MagickBooleanType
4737 proceed;
4738
4739#if defined(MAGICKCORE_OPENMP_SUPPORT)
4740 #pragma omp critical (MagickCore_StatisticImage)
4741#endif
4742 proceed=SetImageProgress(image,StatisticImageTag,progress++,
4743 image->rows);
4744 if (proceed == MagickFalse)
4745 status=MagickFalse;
4746 }
4747 }
4748 statistic_view=DestroyCacheView(statistic_view);
4749 image_view=DestroyCacheView(image_view);
4750 pixel_list=DestroyPixelListThreadSet(pixel_list);
4751 return(statistic_image);
4752}
4753
4754/*
4755%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4756% %
4757% %
4758% %
cristy3ed852e2009-09-05 21:47:34 +00004759% U n s h a r p M a s k I m a g e %
4760% %
4761% %
4762% %
4763%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4764%
4765% UnsharpMaskImage() sharpens one or more image channels. We convolve the
4766% image with a Gaussian operator of the given radius and standard deviation
4767% (sigma). For reasonable results, radius should be larger than sigma. Use a
4768% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
4769%
4770% The format of the UnsharpMaskImage method is:
4771%
4772% Image *UnsharpMaskImage(const Image *image,const double radius,
4773% const double sigma,const double amount,const double threshold,
4774% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00004775%
4776% A description of each parameter follows:
4777%
4778% o image: the image.
4779%
cristy3ed852e2009-09-05 21:47:34 +00004780% o radius: the radius of the Gaussian, in pixels, not counting the center
4781% pixel.
4782%
4783% o sigma: the standard deviation of the Gaussian, in pixels.
4784%
4785% o amount: the percentage of the difference between the original and the
4786% blur image that is added back into the original.
4787%
4788% o threshold: the threshold in pixels needed to apply the diffence amount.
4789%
4790% o exception: return any errors or warnings in this structure.
4791%
4792*/
cristyf4ad9df2011-07-08 16:49:03 +00004793MagickExport Image *UnsharpMaskImage(const Image *image,
4794 const double radius,const double sigma,const double amount,
4795 const double threshold,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00004796{
4797#define SharpenImageTag "Sharpen/Image"
4798
cristyc4c8d132010-01-07 01:58:38 +00004799 CacheView
4800 *image_view,
4801 *unsharp_view;
4802
cristy3ed852e2009-09-05 21:47:34 +00004803 Image
4804 *unsharp_image;
4805
cristy3ed852e2009-09-05 21:47:34 +00004806 MagickBooleanType
4807 status;
4808
cristybb503372010-05-27 20:51:26 +00004809 MagickOffsetType
4810 progress;
4811
cristy4c08aed2011-07-01 19:47:50 +00004812 PixelInfo
cristyddd82202009-11-03 20:14:50 +00004813 bias;
cristy3ed852e2009-09-05 21:47:34 +00004814
4815 MagickRealType
4816 quantum_threshold;
4817
cristybb503372010-05-27 20:51:26 +00004818 ssize_t
4819 y;
4820
cristy3ed852e2009-09-05 21:47:34 +00004821 assert(image != (const Image *) NULL);
4822 assert(image->signature == MagickSignature);
4823 if (image->debug != MagickFalse)
4824 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4825 assert(exception != (ExceptionInfo *) NULL);
cristyf4ad9df2011-07-08 16:49:03 +00004826 unsharp_image=BlurImage(image,radius,sigma,exception);
cristy3ed852e2009-09-05 21:47:34 +00004827 if (unsharp_image == (Image *) NULL)
4828 return((Image *) NULL);
4829 quantum_threshold=(MagickRealType) QuantumRange*threshold;
4830 /*
4831 Unsharp-mask image.
4832 */
4833 status=MagickTrue;
4834 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00004835 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00004836 image_view=AcquireCacheView(image);
4837 unsharp_view=AcquireCacheView(unsharp_image);
cristyb5d5f722009-11-04 03:03:49 +00004838#if defined(MAGICKCORE_OPENMP_SUPPORT)
4839 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004840#endif
cristybb503372010-05-27 20:51:26 +00004841 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00004842 {
cristy4c08aed2011-07-01 19:47:50 +00004843 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00004844 pixel;
4845
cristy4c08aed2011-07-01 19:47:50 +00004846 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00004847 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00004848
cristy4c08aed2011-07-01 19:47:50 +00004849 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00004850 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004851
cristy117ff172010-08-15 21:35:32 +00004852 register ssize_t
4853 x;
4854
cristy3ed852e2009-09-05 21:47:34 +00004855 if (status == MagickFalse)
4856 continue;
4857 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
4858 q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
4859 exception);
cristy4c08aed2011-07-01 19:47:50 +00004860 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00004861 {
4862 status=MagickFalse;
4863 continue;
4864 }
cristyddd82202009-11-03 20:14:50 +00004865 pixel=bias;
cristybb503372010-05-27 20:51:26 +00004866 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004867 {
cristyed231572011-07-14 02:18:59 +00004868 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00004869 {
cristy4c08aed2011-07-01 19:47:50 +00004870 pixel.red=GetPixelRed(image,p)-(MagickRealType) GetPixelRed(image,q);
cristy3ed852e2009-09-05 21:47:34 +00004871 if (fabs(2.0*pixel.red) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00004872 pixel.red=(MagickRealType) GetPixelRed(image,p);
cristy3ed852e2009-09-05 21:47:34 +00004873 else
cristy4c08aed2011-07-01 19:47:50 +00004874 pixel.red=(MagickRealType) GetPixelRed(image,p)+(pixel.red*amount);
4875 SetPixelRed(unsharp_image,ClampToQuantum(pixel.red),q);
cristy3ed852e2009-09-05 21:47:34 +00004876 }
cristyed231572011-07-14 02:18:59 +00004877 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00004878 {
cristy4c08aed2011-07-01 19:47:50 +00004879 pixel.green=GetPixelGreen(image,p)-
4880 (MagickRealType) GetPixelGreen(image,q);
cristy3ed852e2009-09-05 21:47:34 +00004881 if (fabs(2.0*pixel.green) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00004882 pixel.green=(MagickRealType)
4883 GetPixelGreen(image,p);
cristy3ed852e2009-09-05 21:47:34 +00004884 else
cristy4c08aed2011-07-01 19:47:50 +00004885 pixel.green=(MagickRealType)
4886 GetPixelGreen(image,p)+
4887 (pixel.green*amount);
4888 SetPixelGreen(unsharp_image,
4889 ClampToQuantum(pixel.green),q);
cristy3ed852e2009-09-05 21:47:34 +00004890 }
cristyed231572011-07-14 02:18:59 +00004891 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00004892 {
cristy4c08aed2011-07-01 19:47:50 +00004893 pixel.blue=GetPixelBlue(image,p)-
4894 (MagickRealType) GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00004895 if (fabs(2.0*pixel.blue) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00004896 pixel.blue=(MagickRealType)
4897 GetPixelBlue(image,p);
cristy3ed852e2009-09-05 21:47:34 +00004898 else
cristy4c08aed2011-07-01 19:47:50 +00004899 pixel.blue=(MagickRealType)
4900 GetPixelBlue(image,p)+(pixel.blue*amount);
4901 SetPixelBlue(unsharp_image,
4902 ClampToQuantum(pixel.blue),q);
cristy3ed852e2009-09-05 21:47:34 +00004903 }
cristyed231572011-07-14 02:18:59 +00004904 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00004905 (image->colorspace == CMYKColorspace))
4906 {
cristy4c08aed2011-07-01 19:47:50 +00004907 pixel.black=GetPixelBlack(image,p)-
4908 (MagickRealType) GetPixelBlack(image,q);
4909 if (fabs(2.0*pixel.black) < quantum_threshold)
4910 pixel.black=(MagickRealType)
4911 GetPixelBlack(image,p);
cristy3ed852e2009-09-05 21:47:34 +00004912 else
cristy4c08aed2011-07-01 19:47:50 +00004913 pixel.black=(MagickRealType)
4914 GetPixelBlack(image,p)+(pixel.black*
4915 amount);
4916 SetPixelBlack(unsharp_image,
4917 ClampToQuantum(pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00004918 }
cristyed231572011-07-14 02:18:59 +00004919 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00004920 {
4921 pixel.alpha=GetPixelAlpha(image,p)-
4922 (MagickRealType) GetPixelAlpha(image,q);
4923 if (fabs(2.0*pixel.alpha) < quantum_threshold)
4924 pixel.alpha=(MagickRealType)
4925 GetPixelAlpha(image,p);
4926 else
4927 pixel.alpha=GetPixelAlpha(image,p)+
4928 (pixel.alpha*amount);
4929 SetPixelAlpha(unsharp_image,
4930 ClampToQuantum(pixel.alpha),q);
4931 }
cristyed231572011-07-14 02:18:59 +00004932 p+=GetPixelChannels(image);
4933 q+=GetPixelChannels(unsharp_image);
cristy3ed852e2009-09-05 21:47:34 +00004934 }
4935 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
4936 status=MagickFalse;
4937 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4938 {
4939 MagickBooleanType
4940 proceed;
4941
cristyb5d5f722009-11-04 03:03:49 +00004942#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00004943 #pragma omp critical (MagickCore_UnsharpMaskImage)
cristy3ed852e2009-09-05 21:47:34 +00004944#endif
4945 proceed=SetImageProgress(image,SharpenImageTag,progress++,image->rows);
4946 if (proceed == MagickFalse)
4947 status=MagickFalse;
4948 }
4949 }
4950 unsharp_image->type=image->type;
4951 unsharp_view=DestroyCacheView(unsharp_view);
4952 image_view=DestroyCacheView(image_view);
4953 if (status == MagickFalse)
4954 unsharp_image=DestroyImage(unsharp_image);
4955 return(unsharp_image);
4956}