blob: fa49098bcecffc8d31f863b17596ecd070f075ab [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"
cristy8ea81222011-09-04 10:33:32 +000059#include "MagickCore/gem-private.h"
cristy4c08aed2011-07-01 19:47:50 +000060#include "MagickCore/geometry.h"
61#include "MagickCore/image-private.h"
62#include "MagickCore/list.h"
63#include "MagickCore/log.h"
64#include "MagickCore/memory_.h"
65#include "MagickCore/monitor.h"
66#include "MagickCore/monitor-private.h"
67#include "MagickCore/montage.h"
68#include "MagickCore/morphology.h"
69#include "MagickCore/paint.h"
70#include "MagickCore/pixel-accessor.h"
71#include "MagickCore/property.h"
72#include "MagickCore/quantize.h"
73#include "MagickCore/quantum.h"
74#include "MagickCore/quantum-private.h"
75#include "MagickCore/random_.h"
76#include "MagickCore/random-private.h"
77#include "MagickCore/resample.h"
78#include "MagickCore/resample-private.h"
79#include "MagickCore/resize.h"
80#include "MagickCore/resource_.h"
81#include "MagickCore/segment.h"
82#include "MagickCore/shear.h"
83#include "MagickCore/signature-private.h"
84#include "MagickCore/string_.h"
85#include "MagickCore/thread-private.h"
86#include "MagickCore/transform.h"
87#include "MagickCore/threshold.h"
cristy3ed852e2009-09-05 21:47:34 +000088
89/*
90%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
91% %
92% %
93% %
94% A d a p t i v e B l u r I m a g e %
95% %
96% %
97% %
98%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
99%
100% AdaptiveBlurImage() adaptively blurs the image by blurring less
101% intensely near image edges and more intensely far from edges. We blur the
102% image with a Gaussian operator of the given radius and standard deviation
103% (sigma). For reasonable results, radius should be larger than sigma. Use a
104% radius of 0 and AdaptiveBlurImage() selects a suitable radius for you.
105%
106% The format of the AdaptiveBlurImage method is:
107%
108% Image *AdaptiveBlurImage(const Image *image,const double radius,
109% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000110%
111% A description of each parameter follows:
112%
113% o image: the image.
114%
cristy3ed852e2009-09-05 21:47:34 +0000115% o radius: the radius of the Gaussian, in pixels, not counting the center
116% pixel.
117%
118% o sigma: the standard deviation of the Laplacian, in pixels.
119%
120% o exception: return any errors or warnings in this structure.
121%
122*/
123
cristyf89cb1d2011-07-07 01:24:37 +0000124MagickExport MagickBooleanType AdaptiveLevelImage(Image *image,
cristy051718b2011-08-28 22:49:25 +0000125 const char *levels,ExceptionInfo *exception)
cristyf89cb1d2011-07-07 01:24:37 +0000126{
127 double
128 black_point,
129 gamma,
130 white_point;
131
132 GeometryInfo
133 geometry_info;
134
135 MagickBooleanType
136 status;
137
138 MagickStatusType
139 flags;
140
141 /*
142 Parse levels.
143 */
144 if (levels == (char *) NULL)
145 return(MagickFalse);
146 flags=ParseGeometry(levels,&geometry_info);
147 black_point=geometry_info.rho;
148 white_point=(double) QuantumRange;
149 if ((flags & SigmaValue) != 0)
150 white_point=geometry_info.sigma;
151 gamma=1.0;
152 if ((flags & XiValue) != 0)
153 gamma=geometry_info.xi;
154 if ((flags & PercentValue) != 0)
155 {
156 black_point*=(double) image->columns*image->rows/100.0;
157 white_point*=(double) image->columns*image->rows/100.0;
158 }
159 if ((flags & SigmaValue) == 0)
160 white_point=(double) QuantumRange-black_point;
161 if ((flags & AspectValue ) == 0)
cristy7c0a0a42011-08-23 17:57:25 +0000162 status=LevelImage(image,black_point,white_point,gamma,exception);
cristyf89cb1d2011-07-07 01:24:37 +0000163 else
cristy7c0a0a42011-08-23 17:57:25 +0000164 status=LevelizeImage(image,black_point,white_point,gamma,exception);
cristyf89cb1d2011-07-07 01:24:37 +0000165 return(status);
166}
167
cristyf4ad9df2011-07-08 16:49:03 +0000168MagickExport Image *AdaptiveBlurImage(const Image *image,
169 const double radius,const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000170{
171#define AdaptiveBlurImageTag "Convolve/Image"
172#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
173
cristyc4c8d132010-01-07 01:58:38 +0000174 CacheView
175 *blur_view,
176 *edge_view,
177 *image_view;
178
cristy3ed852e2009-09-05 21:47:34 +0000179 double
cristy47e00502009-12-17 19:19:57 +0000180 **kernel,
181 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000182
183 Image
184 *blur_image,
185 *edge_image,
186 *gaussian_image;
187
cristy3ed852e2009-09-05 21:47:34 +0000188 MagickBooleanType
189 status;
190
cristybb503372010-05-27 20:51:26 +0000191 MagickOffsetType
192 progress;
193
cristy4c08aed2011-07-01 19:47:50 +0000194 PixelInfo
cristyddd82202009-11-03 20:14:50 +0000195 bias;
cristy3ed852e2009-09-05 21:47:34 +0000196
cristybb503372010-05-27 20:51:26 +0000197 register ssize_t
cristy47e00502009-12-17 19:19:57 +0000198 i;
cristy3ed852e2009-09-05 21:47:34 +0000199
cristybb503372010-05-27 20:51:26 +0000200 size_t
cristy3ed852e2009-09-05 21:47:34 +0000201 width;
202
cristybb503372010-05-27 20:51:26 +0000203 ssize_t
204 j,
205 k,
206 u,
207 v,
208 y;
209
cristy3ed852e2009-09-05 21:47:34 +0000210 assert(image != (const Image *) NULL);
211 assert(image->signature == MagickSignature);
212 if (image->debug != MagickFalse)
213 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
214 assert(exception != (ExceptionInfo *) NULL);
215 assert(exception->signature == MagickSignature);
216 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
217 if (blur_image == (Image *) NULL)
218 return((Image *) NULL);
219 if (fabs(sigma) <= MagickEpsilon)
220 return(blur_image);
cristy574cc262011-08-05 01:23:58 +0000221 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000222 {
cristy3ed852e2009-09-05 21:47:34 +0000223 blur_image=DestroyImage(blur_image);
224 return((Image *) NULL);
225 }
226 /*
227 Edge detect the image brighness channel, level, blur, and level again.
228 */
cristy8ae632d2011-09-05 17:29:53 +0000229 edge_image=EdgeImage(image,radius,sigma,exception);
cristy3ed852e2009-09-05 21:47:34 +0000230 if (edge_image == (Image *) NULL)
231 {
232 blur_image=DestroyImage(blur_image);
233 return((Image *) NULL);
234 }
cristy051718b2011-08-28 22:49:25 +0000235 (void) AdaptiveLevelImage(edge_image,"20%,95%",exception);
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 }
cristy051718b2011-08-28 22:49:25 +0000242 (void) AdaptiveLevelImage(edge_image,"10%,95%",exception);
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);
cristyacd2ed22011-08-30 01:44:23 +0000319 if ((r == (const Quantum *) NULL) || (q == (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)
cristy8ae632d2011-09-05 17:29:53 +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);
cristy574cc262011-08-05 01:23:58 +0000512 if (SetImageStorageClass(sharp_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000513 {
cristy3ed852e2009-09-05 21:47:34 +0000514 sharp_image=DestroyImage(sharp_image);
515 return((Image *) NULL);
516 }
517 /*
518 Edge detect the image brighness channel, level, sharp, and level again.
519 */
cristy8ae632d2011-09-05 17:29:53 +0000520 edge_image=EdgeImage(image,radius,sigma,exception);
cristy3ed852e2009-09-05 21:47:34 +0000521 if (edge_image == (Image *) NULL)
522 {
523 sharp_image=DestroyImage(sharp_image);
524 return((Image *) NULL);
525 }
cristy051718b2011-08-28 22:49:25 +0000526 (void) AdaptiveLevelImage(edge_image,"20%,95%",exception);
cristy3ed852e2009-09-05 21:47:34 +0000527 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,exception);
528 if (gaussian_image != (Image *) NULL)
529 {
530 edge_image=DestroyImage(edge_image);
531 edge_image=gaussian_image;
532 }
cristy051718b2011-08-28 22:49:25 +0000533 (void) AdaptiveLevelImage(edge_image,"10%,95%",exception);
cristy3ed852e2009-09-05 21:47:34 +0000534 /*
535 Create a set of kernels from maximum (radius,sigma) to minimum.
536 */
537 width=GetOptimalKernelWidth2D(radius,sigma);
538 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
539 if (kernel == (double **) NULL)
540 {
541 edge_image=DestroyImage(edge_image);
542 sharp_image=DestroyImage(sharp_image);
543 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
544 }
545 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
cristybb503372010-05-27 20:51:26 +0000546 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000547 {
548 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
549 sizeof(**kernel));
550 if (kernel[i] == (double *) NULL)
551 break;
cristy47e00502009-12-17 19:19:57 +0000552 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000553 j=(ssize_t) (width-i)/2;
cristy47e00502009-12-17 19:19:57 +0000554 k=0;
555 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000556 {
cristy47e00502009-12-17 19:19:57 +0000557 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000558 {
cristy4205a3c2010-09-12 20:19:59 +0000559 kernel[i][k]=(double) (-exp(-((double) u*u+v*v)/(2.0*MagickSigma*
560 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +0000561 normalize+=kernel[i][k];
562 k++;
cristy3ed852e2009-09-05 21:47:34 +0000563 }
564 }
cristy3ed852e2009-09-05 21:47:34 +0000565 if (fabs(normalize) <= MagickEpsilon)
566 normalize=1.0;
567 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000568 for (k=0; k < (j*j); k++)
569 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000570 }
cristybb503372010-05-27 20:51:26 +0000571 if (i < (ssize_t) width)
cristy3ed852e2009-09-05 21:47:34 +0000572 {
573 for (i-=2; i >= 0; i-=2)
574 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
575 kernel=(double **) RelinquishMagickMemory(kernel);
576 edge_image=DestroyImage(edge_image);
577 sharp_image=DestroyImage(sharp_image);
578 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
579 }
580 /*
581 Adaptively sharpen image.
582 */
583 status=MagickTrue;
584 progress=0;
cristy4c08aed2011-07-01 19:47:50 +0000585 GetPixelInfo(image,&bias);
586 SetPixelInfoBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000587 image_view=AcquireCacheView(image);
588 edge_view=AcquireCacheView(edge_image);
589 sharp_view=AcquireCacheView(sharp_image);
cristyb5d5f722009-11-04 03:03:49 +0000590#if defined(MAGICKCORE_OPENMP_SUPPORT)
591 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000592#endif
cristybb503372010-05-27 20:51:26 +0000593 for (y=0; y < (ssize_t) sharp_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000594 {
cristy4c08aed2011-07-01 19:47:50 +0000595 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000596 *restrict p,
597 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000598
cristy4c08aed2011-07-01 19:47:50 +0000599 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000600 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000601
cristy117ff172010-08-15 21:35:32 +0000602 register ssize_t
603 x;
604
cristy3ed852e2009-09-05 21:47:34 +0000605 if (status == MagickFalse)
606 continue;
607 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
608 q=QueueCacheViewAuthenticPixels(sharp_view,0,y,sharp_image->columns,1,
609 exception);
cristy4c08aed2011-07-01 19:47:50 +0000610 if ((r == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000611 {
612 status=MagickFalse;
613 continue;
614 }
cristybb503372010-05-27 20:51:26 +0000615 for (x=0; x < (ssize_t) sharp_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000616 {
cristy4c08aed2011-07-01 19:47:50 +0000617 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000618 pixel;
619
620 MagickRealType
621 alpha,
622 gamma;
623
624 register const double
cristyc47d1f82009-11-26 01:44:43 +0000625 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000626
cristybb503372010-05-27 20:51:26 +0000627 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000628 i,
629 u,
630 v;
631
632 gamma=0.0;
cristy4c08aed2011-07-01 19:47:50 +0000633 i=(ssize_t) ceil((double) width*QuantumScale*
634 GetPixelIntensity(edge_image,r)-0.5);
cristy3ed852e2009-09-05 21:47:34 +0000635 if (i < 0)
636 i=0;
637 else
cristybb503372010-05-27 20:51:26 +0000638 if (i > (ssize_t) width)
639 i=(ssize_t) width;
cristy3ed852e2009-09-05 21:47:34 +0000640 if ((i & 0x01) != 0)
641 i--;
cristy117ff172010-08-15 21:35:32 +0000642 p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) (width-i)/2L),y-
643 (ssize_t) ((width-i)/2L),width-i,width-i,exception);
cristy4c08aed2011-07-01 19:47:50 +0000644 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000645 break;
cristy3ed852e2009-09-05 21:47:34 +0000646 k=kernel[i];
cristyddd82202009-11-03 20:14:50 +0000647 pixel=bias;
cristybb503372010-05-27 20:51:26 +0000648 for (v=0; v < (ssize_t) (width-i); v++)
cristy3ed852e2009-09-05 21:47:34 +0000649 {
cristybb503372010-05-27 20:51:26 +0000650 for (u=0; u < (ssize_t) (width-i); u++)
cristy3ed852e2009-09-05 21:47:34 +0000651 {
652 alpha=1.0;
cristyed231572011-07-14 02:18:59 +0000653 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000654 (image->matte != MagickFalse))
cristy4c08aed2011-07-01 19:47:50 +0000655 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,p));
cristyed231572011-07-14 02:18:59 +0000656 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000657 pixel.red+=(*k)*alpha*GetPixelRed(image,p);
cristyed231572011-07-14 02:18:59 +0000658 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000659 pixel.green+=(*k)*alpha*GetPixelGreen(image,p);
cristyed231572011-07-14 02:18:59 +0000660 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000661 pixel.blue+=(*k)*alpha*GetPixelBlue(image,p);
cristyed231572011-07-14 02:18:59 +0000662 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000663 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +0000664 pixel.black+=(*k)*alpha*GetPixelBlack(image,p);
cristyed231572011-07-14 02:18:59 +0000665 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000666 pixel.alpha+=(*k)*GetPixelAlpha(image,p);
cristy3ed852e2009-09-05 21:47:34 +0000667 gamma+=(*k)*alpha;
668 k++;
cristyed231572011-07-14 02:18:59 +0000669 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000670 }
671 }
672 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +0000673 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000674 SetPixelRed(sharp_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +0000675 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000676 SetPixelGreen(sharp_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +0000677 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000678 SetPixelBlue(sharp_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +0000679 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000680 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +0000681 SetPixelBlack(sharp_image,ClampToQuantum(gamma*pixel.black),q);
cristyed231572011-07-14 02:18:59 +0000682 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000683 SetPixelAlpha(sharp_image,ClampToQuantum(pixel.alpha),q);
cristyed231572011-07-14 02:18:59 +0000684 q+=GetPixelChannels(sharp_image);
685 r+=GetPixelChannels(edge_image);
cristy3ed852e2009-09-05 21:47:34 +0000686 }
687 if (SyncCacheViewAuthenticPixels(sharp_view,exception) == MagickFalse)
688 status=MagickFalse;
689 if (image->progress_monitor != (MagickProgressMonitor) NULL)
690 {
691 MagickBooleanType
692 proceed;
693
cristyb5d5f722009-11-04 03:03:49 +0000694#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +0000695 #pragma omp critical (MagickCore_AdaptiveSharpenImage)
cristy3ed852e2009-09-05 21:47:34 +0000696#endif
697 proceed=SetImageProgress(image,AdaptiveSharpenImageTag,progress++,
698 image->rows);
699 if (proceed == MagickFalse)
700 status=MagickFalse;
701 }
702 }
703 sharp_image->type=image->type;
704 sharp_view=DestroyCacheView(sharp_view);
705 edge_view=DestroyCacheView(edge_view);
706 image_view=DestroyCacheView(image_view);
707 edge_image=DestroyImage(edge_image);
cristybb503372010-05-27 20:51:26 +0000708 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000709 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
710 kernel=(double **) RelinquishMagickMemory(kernel);
711 if (status == MagickFalse)
712 sharp_image=DestroyImage(sharp_image);
713 return(sharp_image);
714}
715
716/*
717%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
718% %
719% %
720% %
721% B l u r I m a g e %
722% %
723% %
724% %
725%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
726%
727% BlurImage() blurs an image. We convolve the image with a Gaussian operator
728% of the given radius and standard deviation (sigma). For reasonable results,
729% the radius should be larger than sigma. Use a radius of 0 and BlurImage()
730% selects a suitable radius for you.
731%
732% BlurImage() differs from GaussianBlurImage() in that it uses a separable
733% kernel which is faster but mathematically equivalent to the non-separable
734% kernel.
735%
736% The format of the BlurImage method is:
737%
738% Image *BlurImage(const Image *image,const double radius,
739% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000740%
741% A description of each parameter follows:
742%
743% o image: the image.
744%
cristy3ed852e2009-09-05 21:47:34 +0000745% o radius: the radius of the Gaussian, in pixels, not counting the center
746% pixel.
747%
748% o sigma: the standard deviation of the Gaussian, in pixels.
749%
750% o exception: return any errors or warnings in this structure.
751%
752*/
753
cristybb503372010-05-27 20:51:26 +0000754static double *GetBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +0000755{
cristy3ed852e2009-09-05 21:47:34 +0000756 double
cristy47e00502009-12-17 19:19:57 +0000757 *kernel,
758 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000759
cristy117ff172010-08-15 21:35:32 +0000760 register ssize_t
761 i;
762
cristybb503372010-05-27 20:51:26 +0000763 ssize_t
cristy47e00502009-12-17 19:19:57 +0000764 j,
765 k;
cristy3ed852e2009-09-05 21:47:34 +0000766
cristy3ed852e2009-09-05 21:47:34 +0000767 /*
768 Generate a 1-D convolution kernel.
769 */
770 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
771 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
772 if (kernel == (double *) NULL)
773 return(0);
cristy3ed852e2009-09-05 21:47:34 +0000774 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000775 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +0000776 i=0;
777 for (k=(-j); k <= j; k++)
778 {
cristy4205a3c2010-09-12 20:19:59 +0000779 kernel[i]=(double) (exp(-((double) k*k)/(2.0*MagickSigma*MagickSigma))/
780 (MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +0000781 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +0000782 i++;
783 }
cristybb503372010-05-27 20:51:26 +0000784 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000785 kernel[i]/=normalize;
786 return(kernel);
787}
788
cristyf4ad9df2011-07-08 16:49:03 +0000789MagickExport Image *BlurImage(const Image *image,const double radius,
790 const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000791{
792#define BlurImageTag "Blur/Image"
793
cristyc4c8d132010-01-07 01:58:38 +0000794 CacheView
795 *blur_view,
796 *image_view;
797
cristy3ed852e2009-09-05 21:47:34 +0000798 double
799 *kernel;
800
801 Image
802 *blur_image;
803
cristy3ed852e2009-09-05 21:47:34 +0000804 MagickBooleanType
805 status;
806
cristybb503372010-05-27 20:51:26 +0000807 MagickOffsetType
808 progress;
809
cristy4c08aed2011-07-01 19:47:50 +0000810 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000811 bias;
812
cristybb503372010-05-27 20:51:26 +0000813 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000814 i;
815
cristybb503372010-05-27 20:51:26 +0000816 size_t
cristy3ed852e2009-09-05 21:47:34 +0000817 width;
818
cristybb503372010-05-27 20:51:26 +0000819 ssize_t
820 x,
821 y;
822
cristy3ed852e2009-09-05 21:47:34 +0000823 /*
824 Initialize blur image attributes.
825 */
826 assert(image != (Image *) NULL);
827 assert(image->signature == MagickSignature);
828 if (image->debug != MagickFalse)
829 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
830 assert(exception != (ExceptionInfo *) NULL);
831 assert(exception->signature == MagickSignature);
832 blur_image=CloneImage(image,0,0,MagickTrue,exception);
833 if (blur_image == (Image *) NULL)
834 return((Image *) NULL);
835 if (fabs(sigma) <= MagickEpsilon)
836 return(blur_image);
cristy574cc262011-08-05 01:23:58 +0000837 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000838 {
cristy3ed852e2009-09-05 21:47:34 +0000839 blur_image=DestroyImage(blur_image);
840 return((Image *) NULL);
841 }
842 width=GetOptimalKernelWidth1D(radius,sigma);
843 kernel=GetBlurKernel(width,sigma);
844 if (kernel == (double *) NULL)
845 {
846 blur_image=DestroyImage(blur_image);
847 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
848 }
849 if (image->debug != MagickFalse)
850 {
851 char
852 format[MaxTextExtent],
853 *message;
854
855 register const double
856 *k;
857
858 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +0000859 " BlurImage with %.20g kernel:",(double) width);
cristy3ed852e2009-09-05 21:47:34 +0000860 message=AcquireString("");
861 k=kernel;
cristybb503372010-05-27 20:51:26 +0000862 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000863 {
864 *message='\0';
cristyb51dff52011-05-19 16:55:47 +0000865 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) i);
cristy3ed852e2009-09-05 21:47:34 +0000866 (void) ConcatenateString(&message,format);
cristyb51dff52011-05-19 16:55:47 +0000867 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristy3ed852e2009-09-05 21:47:34 +0000868 (void) ConcatenateString(&message,format);
869 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
870 }
871 message=DestroyString(message);
872 }
873 /*
874 Blur rows.
875 */
876 status=MagickTrue;
877 progress=0;
cristy4c08aed2011-07-01 19:47:50 +0000878 GetPixelInfo(image,&bias);
879 SetPixelInfoBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000880 image_view=AcquireCacheView(image);
881 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000882#if defined(MAGICKCORE_OPENMP_SUPPORT)
883 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000884#endif
cristybb503372010-05-27 20:51:26 +0000885 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000886 {
cristy4c08aed2011-07-01 19:47:50 +0000887 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000888 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000889
cristy4c08aed2011-07-01 19:47:50 +0000890 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000891 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000892
cristy117ff172010-08-15 21:35:32 +0000893 register ssize_t
894 x;
895
cristy3ed852e2009-09-05 21:47:34 +0000896 if (status == MagickFalse)
897 continue;
cristy117ff172010-08-15 21:35:32 +0000898 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y,
899 image->columns+width,1,exception);
cristy3ed852e2009-09-05 21:47:34 +0000900 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
901 exception);
cristy4c08aed2011-07-01 19:47:50 +0000902 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000903 {
904 status=MagickFalse;
905 continue;
906 }
cristybb503372010-05-27 20:51:26 +0000907 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000908 {
cristy4c08aed2011-07-01 19:47:50 +0000909 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000910 pixel;
911
912 register const double
cristyc47d1f82009-11-26 01:44:43 +0000913 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000914
cristy4c08aed2011-07-01 19:47:50 +0000915 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000916 *restrict kernel_pixels;
cristy3ed852e2009-09-05 21:47:34 +0000917
cristybb503372010-05-27 20:51:26 +0000918 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000919 i;
920
cristyddd82202009-11-03 20:14:50 +0000921 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +0000922 k=kernel;
923 kernel_pixels=p;
cristyed231572011-07-14 02:18:59 +0000924 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) ||
cristyf4ad9df2011-07-08 16:49:03 +0000925 (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +0000926 {
cristybb503372010-05-27 20:51:26 +0000927 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000928 {
cristy4c08aed2011-07-01 19:47:50 +0000929 pixel.red+=(*k)*GetPixelRed(image,kernel_pixels);
930 pixel.green+=(*k)*GetPixelGreen(image,kernel_pixels);
931 pixel.blue+=(*k)*GetPixelBlue(image,kernel_pixels);
932 if (image->colorspace == CMYKColorspace)
933 pixel.black+=(*k)*GetPixelBlack(image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +0000934 k++;
cristyed231572011-07-14 02:18:59 +0000935 kernel_pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000936 }
cristyed231572011-07-14 02:18:59 +0000937 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000938 SetPixelRed(blur_image,ClampToQuantum(pixel.red),q);
cristyed231572011-07-14 02:18:59 +0000939 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000940 SetPixelGreen(blur_image,ClampToQuantum(pixel.green),q);
cristyed231572011-07-14 02:18:59 +0000941 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000942 SetPixelBlue(blur_image,ClampToQuantum(pixel.blue),q);
cristyed231572011-07-14 02:18:59 +0000943 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000944 (blur_image->colorspace == CMYKColorspace))
945 SetPixelBlack(blur_image,ClampToQuantum(pixel.black),q);
cristyed231572011-07-14 02:18:59 +0000946 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +0000947 {
948 k=kernel;
949 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +0000950 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000951 {
cristy4c08aed2011-07-01 19:47:50 +0000952 pixel.alpha+=(*k)*GetPixelAlpha(image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +0000953 k++;
cristyed231572011-07-14 02:18:59 +0000954 kernel_pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000955 }
cristy4c08aed2011-07-01 19:47:50 +0000956 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +0000957 }
958 }
959 else
960 {
961 MagickRealType
962 alpha,
963 gamma;
964
965 gamma=0.0;
cristybb503372010-05-27 20:51:26 +0000966 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000967 {
cristy4c08aed2011-07-01 19:47:50 +0000968 alpha=(MagickRealType) (QuantumScale*
969 GetPixelAlpha(image,kernel_pixels));
970 pixel.red+=(*k)*alpha*GetPixelRed(image,kernel_pixels);
971 pixel.green+=(*k)*alpha*GetPixelGreen(image,kernel_pixels);
972 pixel.blue+=(*k)*alpha*GetPixelBlue(image,kernel_pixels);
973 if (image->colorspace == CMYKColorspace)
974 pixel.black+=(*k)*alpha*GetPixelBlack(image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +0000975 gamma+=(*k)*alpha;
976 k++;
cristyed231572011-07-14 02:18:59 +0000977 kernel_pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000978 }
979 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +0000980 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000981 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +0000982 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000983 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +0000984 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000985 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +0000986 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000987 (blur_image->colorspace == CMYKColorspace))
988 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristyed231572011-07-14 02:18:59 +0000989 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +0000990 {
991 k=kernel;
992 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +0000993 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000994 {
cristy4c08aed2011-07-01 19:47:50 +0000995 pixel.alpha+=(*k)*GetPixelAlpha(image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +0000996 k++;
cristyed231572011-07-14 02:18:59 +0000997 kernel_pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000998 }
cristy4c08aed2011-07-01 19:47:50 +0000999 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00001000 }
1001 }
cristyed231572011-07-14 02:18:59 +00001002 p+=GetPixelChannels(image);
1003 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001004 }
1005 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1006 status=MagickFalse;
1007 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1008 {
1009 MagickBooleanType
1010 proceed;
1011
cristyb5d5f722009-11-04 03:03:49 +00001012#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00001013 #pragma omp critical (MagickCore_BlurImage)
cristy3ed852e2009-09-05 21:47:34 +00001014#endif
1015 proceed=SetImageProgress(image,BlurImageTag,progress++,blur_image->rows+
1016 blur_image->columns);
1017 if (proceed == MagickFalse)
1018 status=MagickFalse;
1019 }
1020 }
1021 blur_view=DestroyCacheView(blur_view);
1022 image_view=DestroyCacheView(image_view);
1023 /*
1024 Blur columns.
1025 */
1026 image_view=AcquireCacheView(blur_image);
1027 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00001028#if defined(MAGICKCORE_OPENMP_SUPPORT)
1029 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001030#endif
cristybb503372010-05-27 20:51:26 +00001031 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001032 {
cristy4c08aed2011-07-01 19:47:50 +00001033 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001034 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001035
cristy4c08aed2011-07-01 19:47:50 +00001036 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001037 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001038
cristy117ff172010-08-15 21:35:32 +00001039 register ssize_t
1040 y;
1041
cristy3ed852e2009-09-05 21:47:34 +00001042 if (status == MagickFalse)
1043 continue;
cristy117ff172010-08-15 21:35:32 +00001044 p=GetCacheViewVirtualPixels(image_view,x,-((ssize_t) width/2L),1,
1045 image->rows+width,exception);
cristy3ed852e2009-09-05 21:47:34 +00001046 q=GetCacheViewAuthenticPixels(blur_view,x,0,1,blur_image->rows,exception);
cristy4c08aed2011-07-01 19:47:50 +00001047 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001048 {
1049 status=MagickFalse;
1050 continue;
1051 }
cristybb503372010-05-27 20:51:26 +00001052 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001053 {
cristy4c08aed2011-07-01 19:47:50 +00001054 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001055 pixel;
1056
1057 register const double
cristyc47d1f82009-11-26 01:44:43 +00001058 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00001059
cristy4c08aed2011-07-01 19:47:50 +00001060 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001061 *restrict kernel_pixels;
cristy3ed852e2009-09-05 21:47:34 +00001062
cristybb503372010-05-27 20:51:26 +00001063 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001064 i;
1065
cristyddd82202009-11-03 20:14:50 +00001066 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00001067 k=kernel;
1068 kernel_pixels=p;
cristyed231572011-07-14 02:18:59 +00001069 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) ||
cristyf4ad9df2011-07-08 16:49:03 +00001070 (blur_image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00001071 {
cristybb503372010-05-27 20:51:26 +00001072 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001073 {
cristy4c08aed2011-07-01 19:47:50 +00001074 pixel.red+=(*k)*GetPixelRed(blur_image,kernel_pixels);
1075 pixel.green+=(*k)*GetPixelGreen(blur_image,kernel_pixels);
1076 pixel.blue+=(*k)*GetPixelBlue(blur_image,kernel_pixels);
1077 if (blur_image->colorspace == CMYKColorspace)
1078 pixel.black+=(*k)*GetPixelBlack(blur_image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001079 k++;
cristyed231572011-07-14 02:18:59 +00001080 kernel_pixels+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001081 }
cristyed231572011-07-14 02:18:59 +00001082 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001083 SetPixelRed(blur_image,ClampToQuantum(pixel.red),q);
cristyed231572011-07-14 02:18:59 +00001084 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001085 SetPixelGreen(blur_image,ClampToQuantum(pixel.green),q);
cristyed231572011-07-14 02:18:59 +00001086 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001087 SetPixelBlue(blur_image,ClampToQuantum(pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00001088 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001089 (blur_image->colorspace == CMYKColorspace))
1090 SetPixelBlack(blur_image,ClampToQuantum(pixel.black),q);
cristyed231572011-07-14 02:18:59 +00001091 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001092 {
1093 k=kernel;
1094 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001095 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001096 {
cristy4c08aed2011-07-01 19:47:50 +00001097 pixel.alpha+=(*k)*GetPixelAlpha(blur_image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001098 k++;
cristyed231572011-07-14 02:18:59 +00001099 kernel_pixels+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001100 }
cristy4c08aed2011-07-01 19:47:50 +00001101 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00001102 }
1103 }
1104 else
1105 {
1106 MagickRealType
1107 alpha,
1108 gamma;
1109
1110 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00001111 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001112 {
cristy46f08202010-01-10 04:04:21 +00001113 alpha=(MagickRealType) (QuantumScale*
cristy4c08aed2011-07-01 19:47:50 +00001114 GetPixelAlpha(blur_image,kernel_pixels));
1115 pixel.red+=(*k)*alpha*GetPixelRed(blur_image,kernel_pixels);
1116 pixel.green+=(*k)*alpha*GetPixelGreen(blur_image,kernel_pixels);
1117 pixel.blue+=(*k)*alpha*GetPixelBlue(blur_image,kernel_pixels);
1118 if (blur_image->colorspace == CMYKColorspace)
1119 pixel.black+=(*k)*alpha*GetPixelBlack(blur_image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001120 gamma+=(*k)*alpha;
1121 k++;
cristyed231572011-07-14 02:18:59 +00001122 kernel_pixels+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001123 }
1124 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00001125 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001126 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +00001127 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001128 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +00001129 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001130 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00001131 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001132 (blur_image->colorspace == CMYKColorspace))
1133 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristyed231572011-07-14 02:18:59 +00001134 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001135 {
1136 k=kernel;
1137 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001138 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001139 {
cristy4c08aed2011-07-01 19:47:50 +00001140 pixel.alpha+=(*k)*GetPixelAlpha(blur_image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001141 k++;
cristyed231572011-07-14 02:18:59 +00001142 kernel_pixels+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001143 }
cristy4c08aed2011-07-01 19:47:50 +00001144 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00001145 }
1146 }
cristyed231572011-07-14 02:18:59 +00001147 p+=GetPixelChannels(blur_image);
1148 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001149 }
1150 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1151 status=MagickFalse;
cristy4c08aed2011-07-01 19:47:50 +00001152 if (blur_image->progress_monitor != (MagickProgressMonitor) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001153 {
1154 MagickBooleanType
1155 proceed;
1156
cristyb5d5f722009-11-04 03:03:49 +00001157#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00001158 #pragma omp critical (MagickCore_BlurImage)
cristy3ed852e2009-09-05 21:47:34 +00001159#endif
cristy4c08aed2011-07-01 19:47:50 +00001160 proceed=SetImageProgress(blur_image,BlurImageTag,progress++,
1161 blur_image->rows+blur_image->columns);
cristy3ed852e2009-09-05 21:47:34 +00001162 if (proceed == MagickFalse)
1163 status=MagickFalse;
1164 }
1165 }
1166 blur_view=DestroyCacheView(blur_view);
1167 image_view=DestroyCacheView(image_view);
1168 kernel=(double *) RelinquishMagickMemory(kernel);
1169 if (status == MagickFalse)
1170 blur_image=DestroyImage(blur_image);
1171 blur_image->type=image->type;
1172 return(blur_image);
1173}
1174
1175/*
1176%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1177% %
1178% %
1179% %
cristyfccdab92009-11-30 16:43:57 +00001180% C o n v o l v e I m a g e %
1181% %
1182% %
1183% %
1184%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1185%
1186% ConvolveImage() applies a custom convolution kernel to the image.
1187%
1188% The format of the ConvolveImage method is:
1189%
cristy5e6be1e2011-07-16 01:23:39 +00001190% Image *ConvolveImage(const Image *image,const KernelInfo *kernel,
1191% ExceptionInfo *exception)
1192%
cristyfccdab92009-11-30 16:43:57 +00001193% A description of each parameter follows:
1194%
1195% o image: the image.
1196%
cristy5e6be1e2011-07-16 01:23:39 +00001197% o kernel: the filtering kernel.
cristyfccdab92009-11-30 16:43:57 +00001198%
1199% o exception: return any errors or warnings in this structure.
1200%
1201*/
cristy5e6be1e2011-07-16 01:23:39 +00001202MagickExport Image *ConvolveImage(const Image *image,
1203 const KernelInfo *kernel_info,ExceptionInfo *exception)
cristyfccdab92009-11-30 16:43:57 +00001204{
cristyfccdab92009-11-30 16:43:57 +00001205#define ConvolveImageTag "Convolve/Image"
1206
cristyc4c8d132010-01-07 01:58:38 +00001207 CacheView
1208 *convolve_view,
cristy105ba3c2011-07-18 02:28:38 +00001209 *image_view;
cristyc4c8d132010-01-07 01:58:38 +00001210
cristyfccdab92009-11-30 16:43:57 +00001211 Image
1212 *convolve_image;
1213
cristyfccdab92009-11-30 16:43:57 +00001214 MagickBooleanType
1215 status;
1216
cristybb503372010-05-27 20:51:26 +00001217 MagickOffsetType
1218 progress;
1219
cristybb503372010-05-27 20:51:26 +00001220 ssize_t
cristy574cc262011-08-05 01:23:58 +00001221 center,
cristybb503372010-05-27 20:51:26 +00001222 y;
1223
cristyfccdab92009-11-30 16:43:57 +00001224 /*
1225 Initialize convolve image attributes.
1226 */
1227 assert(image != (Image *) NULL);
1228 assert(image->signature == MagickSignature);
1229 if (image->debug != MagickFalse)
1230 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1231 assert(exception != (ExceptionInfo *) NULL);
1232 assert(exception->signature == MagickSignature);
cristy5e6be1e2011-07-16 01:23:39 +00001233 if ((kernel_info->width % 2) == 0)
cristyfccdab92009-11-30 16:43:57 +00001234 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
cristy08429172011-07-14 17:18:16 +00001235 convolve_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1236 exception);
cristyfccdab92009-11-30 16:43:57 +00001237 if (convolve_image == (Image *) NULL)
1238 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00001239 if (SetImageStorageClass(convolve_image,DirectClass,exception) == MagickFalse)
cristyfccdab92009-11-30 16:43:57 +00001240 {
cristyfccdab92009-11-30 16:43:57 +00001241 convolve_image=DestroyImage(convolve_image);
1242 return((Image *) NULL);
1243 }
1244 if (image->debug != MagickFalse)
1245 {
1246 char
1247 format[MaxTextExtent],
1248 *message;
1249
cristy117ff172010-08-15 21:35:32 +00001250 register const double
1251 *k;
1252
cristy4e154852011-07-14 13:28:53 +00001253 register ssize_t
1254 u;
1255
cristybb503372010-05-27 20:51:26 +00001256 ssize_t
cristyfccdab92009-11-30 16:43:57 +00001257 v;
1258
cristyfccdab92009-11-30 16:43:57 +00001259 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristy5e6be1e2011-07-16 01:23:39 +00001260 " ConvolveImage with %.20gx%.20g kernel:",(double) kernel_info->width,
1261 (double) kernel_info->height);
cristyfccdab92009-11-30 16:43:57 +00001262 message=AcquireString("");
cristy5e6be1e2011-07-16 01:23:39 +00001263 k=kernel_info->values;
1264 for (v=0; v < (ssize_t) kernel_info->width; v++)
cristyfccdab92009-11-30 16:43:57 +00001265 {
1266 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00001267 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristyfccdab92009-11-30 16:43:57 +00001268 (void) ConcatenateString(&message,format);
cristy5e6be1e2011-07-16 01:23:39 +00001269 for (u=0; u < (ssize_t) kernel_info->height; u++)
cristyfccdab92009-11-30 16:43:57 +00001270 {
cristyb51dff52011-05-19 16:55:47 +00001271 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristyfccdab92009-11-30 16:43:57 +00001272 (void) ConcatenateString(&message,format);
1273 }
1274 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
1275 }
1276 message=DestroyString(message);
1277 }
1278 /*
cristyfccdab92009-11-30 16:43:57 +00001279 Convolve image.
1280 */
cristy574cc262011-08-05 01:23:58 +00001281 center=(ssize_t) GetPixelChannels(image)*(image->columns+kernel_info->width)*
1282 (kernel_info->height/2L)+GetPixelChannels(image)*(kernel_info->width/2);
cristyfccdab92009-11-30 16:43:57 +00001283 status=MagickTrue;
1284 progress=0;
cristyfccdab92009-11-30 16:43:57 +00001285 image_view=AcquireCacheView(image);
1286 convolve_view=AcquireCacheView(convolve_image);
1287#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy175653e2011-07-10 23:13:34 +00001288 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristyfccdab92009-11-30 16:43:57 +00001289#endif
cristybb503372010-05-27 20:51:26 +00001290 for (y=0; y < (ssize_t) image->rows; y++)
cristyfccdab92009-11-30 16:43:57 +00001291 {
cristy4c08aed2011-07-01 19:47:50 +00001292 register const Quantum
cristy105ba3c2011-07-18 02:28:38 +00001293 *restrict p;
cristyfccdab92009-11-30 16:43:57 +00001294
cristy4c08aed2011-07-01 19:47:50 +00001295 register Quantum
cristyfccdab92009-11-30 16:43:57 +00001296 *restrict q;
1297
cristy117ff172010-08-15 21:35:32 +00001298 register ssize_t
1299 x;
1300
cristyfccdab92009-11-30 16:43:57 +00001301 if (status == MagickFalse)
1302 continue;
cristy105ba3c2011-07-18 02:28:38 +00001303 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) kernel_info->width/2L),y-
1304 (ssize_t) (kernel_info->height/2L),image->columns+kernel_info->width,
1305 kernel_info->height,exception);
cristy08429172011-07-14 17:18:16 +00001306 q=QueueCacheViewAuthenticPixels(convolve_view,0,y,convolve_image->columns,1,
cristyfccdab92009-11-30 16:43:57 +00001307 exception);
cristy105ba3c2011-07-18 02:28:38 +00001308 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristyfccdab92009-11-30 16:43:57 +00001309 {
1310 status=MagickFalse;
1311 continue;
1312 }
cristybb503372010-05-27 20:51:26 +00001313 for (x=0; x < (ssize_t) image->columns; x++)
cristyfccdab92009-11-30 16:43:57 +00001314 {
cristybb503372010-05-27 20:51:26 +00001315 register ssize_t
cristyed231572011-07-14 02:18:59 +00001316 i;
cristyfccdab92009-11-30 16:43:57 +00001317
cristya30d9ba2011-07-23 21:00:48 +00001318 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristyed231572011-07-14 02:18:59 +00001319 {
cristyed231572011-07-14 02:18:59 +00001320 MagickRealType
cristy4e154852011-07-14 13:28:53 +00001321 alpha,
1322 gamma,
cristyed231572011-07-14 02:18:59 +00001323 pixel;
1324
1325 PixelChannel
1326 channel;
1327
1328 PixelTrait
1329 convolve_traits,
1330 traits;
1331
1332 register const double
1333 *restrict k;
1334
1335 register const Quantum
cristyeb52cde2011-07-17 01:52:52 +00001336 *restrict pixels;
cristyed231572011-07-14 02:18:59 +00001337
1338 register ssize_t
1339 u;
1340
1341 ssize_t
1342 v;
1343
cristy30301712011-07-18 15:06:51 +00001344 traits=GetPixelChannelMapTraits(image,(PixelChannel) i);
cristy30301712011-07-18 15:06:51 +00001345 channel=GetPixelChannelMapChannel(image,(PixelChannel) i);
cristy4e154852011-07-14 13:28:53 +00001346 convolve_traits=GetPixelChannelMapTraits(convolve_image,channel);
cristy010d7d12011-08-31 01:02:48 +00001347 if ((traits == UndefinedPixelTrait) ||
1348 (convolve_traits == UndefinedPixelTrait))
cristy4e154852011-07-14 13:28:53 +00001349 continue;
1350 if ((convolve_traits & CopyPixelTrait) != 0)
1351 {
cristyf7dc44c2011-07-20 14:41:15 +00001352 q[channel]=p[center+i];
cristy4e154852011-07-14 13:28:53 +00001353 continue;
1354 }
cristy5e6be1e2011-07-16 01:23:39 +00001355 k=kernel_info->values;
cristy105ba3c2011-07-18 02:28:38 +00001356 pixels=p;
cristy0a922382011-07-16 15:30:34 +00001357 pixel=kernel_info->bias;
cristy222b19c2011-08-04 01:35:11 +00001358 if ((convolve_traits & BlendPixelTrait) == 0)
cristyfccdab92009-11-30 16:43:57 +00001359 {
cristyed231572011-07-14 02:18:59 +00001360 /*
cristy4e154852011-07-14 13:28:53 +00001361 No alpha blending.
cristyed231572011-07-14 02:18:59 +00001362 */
cristyeb52cde2011-07-17 01:52:52 +00001363 for (v=0; v < (ssize_t) kernel_info->height; v++)
cristyfccdab92009-11-30 16:43:57 +00001364 {
cristyeb52cde2011-07-17 01:52:52 +00001365 for (u=0; u < (ssize_t) kernel_info->width; u++)
cristy175653e2011-07-10 23:13:34 +00001366 {
cristyeb52cde2011-07-17 01:52:52 +00001367 pixel+=(*k)*pixels[i];
cristyed231572011-07-14 02:18:59 +00001368 k++;
cristya30d9ba2011-07-23 21:00:48 +00001369 pixels+=GetPixelChannels(image);
cristy175653e2011-07-10 23:13:34 +00001370 }
cristya30d9ba2011-07-23 21:00:48 +00001371 pixels+=image->columns*GetPixelChannels(image);
cristy175653e2011-07-10 23:13:34 +00001372 }
cristyf7dc44c2011-07-20 14:41:15 +00001373 q[channel]=ClampToQuantum(pixel);
cristy4e154852011-07-14 13:28:53 +00001374 continue;
cristyed231572011-07-14 02:18:59 +00001375 }
cristy4e154852011-07-14 13:28:53 +00001376 /*
1377 Alpha blending.
1378 */
1379 gamma=0.0;
cristyeb52cde2011-07-17 01:52:52 +00001380 for (v=0; v < (ssize_t) kernel_info->height; v++)
cristy4e154852011-07-14 13:28:53 +00001381 {
cristyeb52cde2011-07-17 01:52:52 +00001382 for (u=0; u < (ssize_t) kernel_info->width; u++)
cristy4e154852011-07-14 13:28:53 +00001383 {
cristyeb52cde2011-07-17 01:52:52 +00001384 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,pixels));
1385 pixel+=(*k)*alpha*pixels[i];
cristy4e154852011-07-14 13:28:53 +00001386 gamma+=(*k)*alpha;
1387 k++;
cristya30d9ba2011-07-23 21:00:48 +00001388 pixels+=GetPixelChannels(image);
cristy4e154852011-07-14 13:28:53 +00001389 }
cristya30d9ba2011-07-23 21:00:48 +00001390 pixels+=image->columns*GetPixelChannels(image);
cristy4e154852011-07-14 13:28:53 +00001391 }
cristy1ce96d02011-07-14 17:57:24 +00001392 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristye7a41c92011-07-20 21:31:01 +00001393 q[channel]=ClampToQuantum(gamma*pixel);
cristyed231572011-07-14 02:18:59 +00001394 }
cristya30d9ba2011-07-23 21:00:48 +00001395 p+=GetPixelChannels(image);
1396 q+=GetPixelChannels(convolve_image);
cristyfccdab92009-11-30 16:43:57 +00001397 }
cristyed231572011-07-14 02:18:59 +00001398 if (SyncCacheViewAuthenticPixels(convolve_view,exception) == MagickFalse)
cristyfccdab92009-11-30 16:43:57 +00001399 status=MagickFalse;
1400 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1401 {
1402 MagickBooleanType
1403 proceed;
1404
1405#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00001406 #pragma omp critical (MagickCore_ConvolveImage)
cristyfccdab92009-11-30 16:43:57 +00001407#endif
1408 proceed=SetImageProgress(image,ConvolveImageTag,progress++,image->rows);
1409 if (proceed == MagickFalse)
1410 status=MagickFalse;
1411 }
1412 }
1413 convolve_image->type=image->type;
1414 convolve_view=DestroyCacheView(convolve_view);
1415 image_view=DestroyCacheView(image_view);
cristyfccdab92009-11-30 16:43:57 +00001416 if (status == MagickFalse)
1417 convolve_image=DestroyImage(convolve_image);
1418 return(convolve_image);
1419}
1420
1421/*
1422%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1423% %
1424% %
1425% %
cristy3ed852e2009-09-05 21:47:34 +00001426% D e s p e c k l e I m a g e %
1427% %
1428% %
1429% %
1430%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1431%
1432% DespeckleImage() reduces the speckle noise in an image while perserving the
1433% edges of the original image.
1434%
1435% The format of the DespeckleImage method is:
1436%
1437% Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1438%
1439% A description of each parameter follows:
1440%
1441% o image: the image.
1442%
1443% o exception: return any errors or warnings in this structure.
1444%
1445*/
1446
cristybb503372010-05-27 20:51:26 +00001447static void Hull(const ssize_t x_offset,const ssize_t y_offset,
1448 const size_t columns,const size_t rows,Quantum *f,Quantum *g,
cristy3ed852e2009-09-05 21:47:34 +00001449 const int polarity)
1450{
cristy3ed852e2009-09-05 21:47:34 +00001451 MagickRealType
1452 v;
1453
cristy3ed852e2009-09-05 21:47:34 +00001454 register Quantum
1455 *p,
1456 *q,
1457 *r,
1458 *s;
1459
cristy117ff172010-08-15 21:35:32 +00001460 register ssize_t
1461 x;
1462
1463 ssize_t
1464 y;
1465
cristy3ed852e2009-09-05 21:47:34 +00001466 assert(f != (Quantum *) NULL);
1467 assert(g != (Quantum *) NULL);
1468 p=f+(columns+2);
1469 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001470 r=p+(y_offset*((ssize_t) columns+2)+x_offset);
1471 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001472 {
1473 p++;
1474 q++;
1475 r++;
1476 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001477 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001478 {
1479 v=(MagickRealType) (*p);
1480 if ((MagickRealType) *r >= (v+(MagickRealType) ScaleCharToQuantum(2)))
1481 v+=ScaleCharToQuantum(1);
1482 *q=(Quantum) v;
1483 p++;
1484 q++;
1485 r++;
1486 }
1487 else
cristybb503372010-05-27 20:51:26 +00001488 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001489 {
1490 v=(MagickRealType) (*p);
1491 if ((MagickRealType) *r <= (v-(MagickRealType) ScaleCharToQuantum(2)))
cristybb503372010-05-27 20:51:26 +00001492 v-=(ssize_t) ScaleCharToQuantum(1);
cristy3ed852e2009-09-05 21:47:34 +00001493 *q=(Quantum) v;
1494 p++;
1495 q++;
1496 r++;
1497 }
1498 p++;
1499 q++;
1500 r++;
1501 }
1502 p=f+(columns+2);
1503 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001504 r=q+(y_offset*((ssize_t) columns+2)+x_offset);
1505 s=q-(y_offset*((ssize_t) columns+2)+x_offset);
1506 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001507 {
1508 p++;
1509 q++;
1510 r++;
1511 s++;
1512 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001513 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001514 {
1515 v=(MagickRealType) (*q);
1516 if (((MagickRealType) *s >=
1517 (v+(MagickRealType) ScaleCharToQuantum(2))) &&
1518 ((MagickRealType) *r > v))
1519 v+=ScaleCharToQuantum(1);
1520 *p=(Quantum) v;
1521 p++;
1522 q++;
1523 r++;
1524 s++;
1525 }
1526 else
cristybb503372010-05-27 20:51:26 +00001527 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001528 {
1529 v=(MagickRealType) (*q);
1530 if (((MagickRealType) *s <=
1531 (v-(MagickRealType) ScaleCharToQuantum(2))) &&
1532 ((MagickRealType) *r < v))
1533 v-=(MagickRealType) ScaleCharToQuantum(1);
1534 *p=(Quantum) v;
1535 p++;
1536 q++;
1537 r++;
1538 s++;
1539 }
1540 p++;
1541 q++;
1542 r++;
1543 s++;
1544 }
1545}
1546
1547MagickExport Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1548{
1549#define DespeckleImageTag "Despeckle/Image"
1550
cristy2407fc22009-09-11 00:55:25 +00001551 CacheView
1552 *despeckle_view,
1553 *image_view;
1554
cristy3ed852e2009-09-05 21:47:34 +00001555 Image
1556 *despeckle_image;
1557
cristy3ed852e2009-09-05 21:47:34 +00001558 MagickBooleanType
1559 status;
1560
cristya58c3172011-02-19 19:23:11 +00001561 register ssize_t
1562 i;
1563
cristy3ed852e2009-09-05 21:47:34 +00001564 Quantum
cristy65b9f392011-02-22 14:22:54 +00001565 *restrict buffers,
1566 *restrict pixels;
cristy3ed852e2009-09-05 21:47:34 +00001567
1568 size_t
cristya58c3172011-02-19 19:23:11 +00001569 length,
1570 number_channels;
cristy117ff172010-08-15 21:35:32 +00001571
cristybb503372010-05-27 20:51:26 +00001572 static const ssize_t
cristy691a29e2009-09-11 00:44:10 +00001573 X[4] = {0, 1, 1,-1},
1574 Y[4] = {1, 0, 1, 1};
cristy3ed852e2009-09-05 21:47:34 +00001575
cristy3ed852e2009-09-05 21:47:34 +00001576 /*
1577 Allocate despeckled image.
1578 */
1579 assert(image != (const Image *) NULL);
1580 assert(image->signature == MagickSignature);
1581 if (image->debug != MagickFalse)
1582 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1583 assert(exception != (ExceptionInfo *) NULL);
1584 assert(exception->signature == MagickSignature);
1585 despeckle_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1586 exception);
1587 if (despeckle_image == (Image *) NULL)
1588 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00001589 if (SetImageStorageClass(despeckle_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001590 {
cristy3ed852e2009-09-05 21:47:34 +00001591 despeckle_image=DestroyImage(despeckle_image);
1592 return((Image *) NULL);
1593 }
1594 /*
1595 Allocate image buffers.
1596 */
1597 length=(size_t) ((image->columns+2)*(image->rows+2));
cristy65b9f392011-02-22 14:22:54 +00001598 pixels=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1599 buffers=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1600 if ((pixels == (Quantum *) NULL) || (buffers == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001601 {
cristy65b9f392011-02-22 14:22:54 +00001602 if (buffers != (Quantum *) NULL)
1603 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1604 if (pixels != (Quantum *) NULL)
1605 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001606 despeckle_image=DestroyImage(despeckle_image);
1607 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1608 }
1609 /*
1610 Reduce speckle in the image.
1611 */
1612 status=MagickTrue;
cristy109695a2011-02-19 19:38:14 +00001613 number_channels=(size_t) (image->colorspace == CMYKColorspace ? 5 : 4);
cristy3ed852e2009-09-05 21:47:34 +00001614 image_view=AcquireCacheView(image);
1615 despeckle_view=AcquireCacheView(despeckle_image);
cristy8df3d002011-02-19 19:40:59 +00001616 for (i=0; i < (ssize_t) number_channels; i++)
cristy3ed852e2009-09-05 21:47:34 +00001617 {
cristy3ed852e2009-09-05 21:47:34 +00001618 register Quantum
1619 *buffer,
1620 *pixel;
1621
cristyc1488b52011-02-19 18:54:15 +00001622 register ssize_t
cristya58c3172011-02-19 19:23:11 +00001623 k,
cristyc1488b52011-02-19 18:54:15 +00001624 x;
1625
cristy117ff172010-08-15 21:35:32 +00001626 ssize_t
1627 j,
1628 y;
1629
cristy3ed852e2009-09-05 21:47:34 +00001630 if (status == MagickFalse)
1631 continue;
cristy65b9f392011-02-22 14:22:54 +00001632 pixel=pixels;
cristy3ed852e2009-09-05 21:47:34 +00001633 (void) ResetMagickMemory(pixel,0,length*sizeof(*pixel));
cristy65b9f392011-02-22 14:22:54 +00001634 buffer=buffers;
cristybb503372010-05-27 20:51:26 +00001635 j=(ssize_t) image->columns+2;
1636 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001637 {
cristy4c08aed2011-07-01 19:47:50 +00001638 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001639 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001640
1641 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001642 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001643 break;
1644 j++;
cristybb503372010-05-27 20:51:26 +00001645 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001646 {
cristya58c3172011-02-19 19:23:11 +00001647 switch (i)
cristy3ed852e2009-09-05 21:47:34 +00001648 {
cristy4c08aed2011-07-01 19:47:50 +00001649 case 0: pixel[j]=GetPixelRed(image,p); break;
1650 case 1: pixel[j]=GetPixelGreen(image,p); break;
1651 case 2: pixel[j]=GetPixelBlue(image,p); break;
1652 case 3: pixel[j]=GetPixelAlpha(image,p); break;
1653 case 4: pixel[j]=GetPixelBlack(image,p); break;
cristy3ed852e2009-09-05 21:47:34 +00001654 default: break;
1655 }
cristyed231572011-07-14 02:18:59 +00001656 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001657 j++;
1658 }
1659 j++;
1660 }
cristy3ed852e2009-09-05 21:47:34 +00001661 (void) ResetMagickMemory(buffer,0,length*sizeof(*buffer));
cristya58c3172011-02-19 19:23:11 +00001662 for (k=0; k < 4; k++)
cristy3ed852e2009-09-05 21:47:34 +00001663 {
cristya58c3172011-02-19 19:23:11 +00001664 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,1);
1665 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,1);
1666 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,-1);
1667 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,-1);
cristy3ed852e2009-09-05 21:47:34 +00001668 }
cristybb503372010-05-27 20:51:26 +00001669 j=(ssize_t) image->columns+2;
1670 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001671 {
1672 MagickBooleanType
1673 sync;
1674
cristy4c08aed2011-07-01 19:47:50 +00001675 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001676 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001677
1678 q=GetCacheViewAuthenticPixels(despeckle_view,0,y,despeckle_image->columns,
1679 1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001680 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001681 break;
1682 j++;
cristybb503372010-05-27 20:51:26 +00001683 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001684 {
cristya58c3172011-02-19 19:23:11 +00001685 switch (i)
cristy3ed852e2009-09-05 21:47:34 +00001686 {
cristy4c08aed2011-07-01 19:47:50 +00001687 case 0: SetPixelRed(despeckle_image,pixel[j],q); break;
1688 case 1: SetPixelGreen(despeckle_image,pixel[j],q); break;
1689 case 2: SetPixelBlue(despeckle_image,pixel[j],q); break;
1690 case 3: SetPixelAlpha(despeckle_image,pixel[j],q); break;
1691 case 4: SetPixelBlack(despeckle_image,pixel[j],q); break;
cristy3ed852e2009-09-05 21:47:34 +00001692 default: break;
1693 }
cristyed231572011-07-14 02:18:59 +00001694 q+=GetPixelChannels(despeckle_image);
cristy3ed852e2009-09-05 21:47:34 +00001695 j++;
1696 }
1697 sync=SyncCacheViewAuthenticPixels(despeckle_view,exception);
1698 if (sync == MagickFalse)
1699 {
1700 status=MagickFalse;
1701 break;
1702 }
1703 j++;
1704 }
1705 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1706 {
1707 MagickBooleanType
1708 proceed;
1709
cristya58c3172011-02-19 19:23:11 +00001710 proceed=SetImageProgress(image,DespeckleImageTag,(MagickOffsetType) i,
1711 number_channels);
cristy3ed852e2009-09-05 21:47:34 +00001712 if (proceed == MagickFalse)
1713 status=MagickFalse;
1714 }
1715 }
1716 despeckle_view=DestroyCacheView(despeckle_view);
1717 image_view=DestroyCacheView(image_view);
cristy65b9f392011-02-22 14:22:54 +00001718 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1719 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001720 despeckle_image->type=image->type;
1721 if (status == MagickFalse)
1722 despeckle_image=DestroyImage(despeckle_image);
1723 return(despeckle_image);
1724}
1725
1726/*
1727%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1728% %
1729% %
1730% %
1731% E d g e I m a g e %
1732% %
1733% %
1734% %
1735%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1736%
1737% EdgeImage() finds edges in an image. Radius defines the radius of the
1738% convolution filter. Use a radius of 0 and EdgeImage() selects a suitable
1739% radius for you.
1740%
1741% The format of the EdgeImage method is:
1742%
1743% Image *EdgeImage(const Image *image,const double radius,
cristy8ae632d2011-09-05 17:29:53 +00001744% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001745%
1746% A description of each parameter follows:
1747%
1748% o image: the image.
1749%
1750% o radius: the radius of the pixel neighborhood.
1751%
cristy8ae632d2011-09-05 17:29:53 +00001752% o sigma: the standard deviation of the Gaussian, in pixels.
1753%
cristy3ed852e2009-09-05 21:47:34 +00001754% o exception: return any errors or warnings in this structure.
1755%
1756*/
1757MagickExport Image *EdgeImage(const Image *image,const double radius,
cristy8ae632d2011-09-05 17:29:53 +00001758 const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001759{
1760 Image
1761 *edge_image;
1762
cristy41cbe682011-07-15 19:12:37 +00001763 KernelInfo
1764 *kernel_info;
cristy3ed852e2009-09-05 21:47:34 +00001765
cristybb503372010-05-27 20:51:26 +00001766 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001767 i;
1768
cristybb503372010-05-27 20:51:26 +00001769 size_t
cristy3ed852e2009-09-05 21:47:34 +00001770 width;
1771
cristy41cbe682011-07-15 19:12:37 +00001772 ssize_t
1773 j,
1774 u,
1775 v;
1776
cristy3ed852e2009-09-05 21:47:34 +00001777 assert(image != (const Image *) NULL);
1778 assert(image->signature == MagickSignature);
1779 if (image->debug != MagickFalse)
1780 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1781 assert(exception != (ExceptionInfo *) NULL);
1782 assert(exception->signature == MagickSignature);
cristy8ae632d2011-09-05 17:29:53 +00001783 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00001784 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00001785 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001786 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00001787 kernel_info->width=width;
1788 kernel_info->height=width;
cristy41cbe682011-07-15 19:12:37 +00001789 kernel_info->values=(double *) AcquireAlignedMemory(kernel_info->width,
1790 kernel_info->width*sizeof(*kernel_info->values));
1791 if (kernel_info->values == (double *) NULL)
1792 {
1793 kernel_info=DestroyKernelInfo(kernel_info);
1794 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1795 }
1796 j=(ssize_t) kernel_info->width/2;
1797 i=0;
1798 for (v=(-j); v <= j; v++)
1799 {
1800 for (u=(-j); u <= j; u++)
1801 {
1802 kernel_info->values[i]=(-1.0);
1803 i++;
1804 }
1805 }
1806 kernel_info->values[i/2]=(double) (width*width-1.0);
cristy0a922382011-07-16 15:30:34 +00001807 kernel_info->bias=image->bias;
cristy5e6be1e2011-07-16 01:23:39 +00001808 edge_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00001809 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00001810 return(edge_image);
1811}
1812
1813/*
1814%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1815% %
1816% %
1817% %
1818% E m b o s s I m a g e %
1819% %
1820% %
1821% %
1822%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1823%
1824% EmbossImage() returns a grayscale image with a three-dimensional effect.
1825% We convolve the image with a Gaussian operator of the given radius and
1826% standard deviation (sigma). For reasonable results, radius should be
1827% larger than sigma. Use a radius of 0 and Emboss() selects a suitable
1828% radius for you.
1829%
1830% The format of the EmbossImage method is:
1831%
1832% Image *EmbossImage(const Image *image,const double radius,
1833% const double sigma,ExceptionInfo *exception)
1834%
1835% A description of each parameter follows:
1836%
1837% o image: the image.
1838%
1839% o radius: the radius of the pixel neighborhood.
1840%
1841% o sigma: the standard deviation of the Gaussian, in pixels.
1842%
1843% o exception: return any errors or warnings in this structure.
1844%
1845*/
1846MagickExport Image *EmbossImage(const Image *image,const double radius,
1847 const double sigma,ExceptionInfo *exception)
1848{
cristy3ed852e2009-09-05 21:47:34 +00001849 Image
1850 *emboss_image;
1851
cristy41cbe682011-07-15 19:12:37 +00001852 KernelInfo
1853 *kernel_info;
1854
cristybb503372010-05-27 20:51:26 +00001855 register ssize_t
cristy47e00502009-12-17 19:19:57 +00001856 i;
1857
cristybb503372010-05-27 20:51:26 +00001858 size_t
cristy3ed852e2009-09-05 21:47:34 +00001859 width;
1860
cristy117ff172010-08-15 21:35:32 +00001861 ssize_t
1862 j,
1863 k,
1864 u,
1865 v;
1866
cristy41cbe682011-07-15 19:12:37 +00001867 assert(image != (const Image *) NULL);
cristy3ed852e2009-09-05 21:47:34 +00001868 assert(image->signature == MagickSignature);
1869 if (image->debug != MagickFalse)
1870 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1871 assert(exception != (ExceptionInfo *) NULL);
1872 assert(exception->signature == MagickSignature);
1873 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00001874 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00001875 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001876 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00001877 kernel_info->width=width;
1878 kernel_info->height=width;
cristy41cbe682011-07-15 19:12:37 +00001879 kernel_info->values=(double *) AcquireAlignedMemory(kernel_info->width,
1880 kernel_info->width*sizeof(*kernel_info->values));
1881 if (kernel_info->values == (double *) NULL)
1882 {
1883 kernel_info=DestroyKernelInfo(kernel_info);
1884 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1885 }
1886 j=(ssize_t) kernel_info->width/2;
cristy47e00502009-12-17 19:19:57 +00001887 k=j;
1888 i=0;
1889 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00001890 {
cristy47e00502009-12-17 19:19:57 +00001891 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00001892 {
cristy41cbe682011-07-15 19:12:37 +00001893 kernel_info->values[i]=(double) (((u < 0) || (v < 0) ? -8.0 : 8.0)*
cristy47e00502009-12-17 19:19:57 +00001894 exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
cristy4205a3c2010-09-12 20:19:59 +00001895 (2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +00001896 if (u != k)
cristy41cbe682011-07-15 19:12:37 +00001897 kernel_info->values[i]=0.0;
cristy3ed852e2009-09-05 21:47:34 +00001898 i++;
1899 }
cristy47e00502009-12-17 19:19:57 +00001900 k--;
cristy3ed852e2009-09-05 21:47:34 +00001901 }
cristy0a922382011-07-16 15:30:34 +00001902 kernel_info->bias=image->bias;
cristy5e6be1e2011-07-16 01:23:39 +00001903 emboss_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00001904 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00001905 if (emboss_image != (Image *) NULL)
cristy6d8c3d72011-08-22 01:20:01 +00001906 (void) EqualizeImage(emboss_image,exception);
cristy3ed852e2009-09-05 21:47:34 +00001907 return(emboss_image);
1908}
1909
1910/*
1911%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1912% %
1913% %
1914% %
1915% G a u s s i a n B l u r I m a g e %
1916% %
1917% %
1918% %
1919%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1920%
1921% GaussianBlurImage() blurs an image. We convolve the image with a
1922% Gaussian operator of the given radius and standard deviation (sigma).
1923% For reasonable results, the radius should be larger than sigma. Use a
1924% radius of 0 and GaussianBlurImage() selects a suitable radius for you
1925%
1926% The format of the GaussianBlurImage method is:
1927%
1928% Image *GaussianBlurImage(const Image *image,onst double radius,
1929% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001930%
1931% A description of each parameter follows:
1932%
1933% o image: the image.
1934%
cristy3ed852e2009-09-05 21:47:34 +00001935% o radius: the radius of the Gaussian, in pixels, not counting the center
1936% pixel.
1937%
1938% o sigma: the standard deviation of the Gaussian, in pixels.
1939%
1940% o exception: return any errors or warnings in this structure.
1941%
1942*/
cristy41cbe682011-07-15 19:12:37 +00001943MagickExport Image *GaussianBlurImage(const Image *image,const double radius,
1944 const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001945{
cristy3ed852e2009-09-05 21:47:34 +00001946 Image
1947 *blur_image;
1948
cristy41cbe682011-07-15 19:12:37 +00001949 KernelInfo
1950 *kernel_info;
1951
cristybb503372010-05-27 20:51:26 +00001952 register ssize_t
cristy47e00502009-12-17 19:19:57 +00001953 i;
1954
cristybb503372010-05-27 20:51:26 +00001955 size_t
cristy3ed852e2009-09-05 21:47:34 +00001956 width;
1957
cristy117ff172010-08-15 21:35:32 +00001958 ssize_t
1959 j,
1960 u,
1961 v;
1962
cristy3ed852e2009-09-05 21:47:34 +00001963 assert(image != (const Image *) NULL);
1964 assert(image->signature == MagickSignature);
1965 if (image->debug != MagickFalse)
1966 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1967 assert(exception != (ExceptionInfo *) NULL);
1968 assert(exception->signature == MagickSignature);
1969 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00001970 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00001971 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001972 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00001973 (void) ResetMagickMemory(kernel_info,0,sizeof(*kernel_info));
1974 kernel_info->width=width;
1975 kernel_info->height=width;
1976 kernel_info->signature=MagickSignature;
1977 kernel_info->values=(double *) AcquireAlignedMemory(kernel_info->width,
1978 kernel_info->width*sizeof(*kernel_info->values));
1979 if (kernel_info->values == (double *) NULL)
1980 {
1981 kernel_info=DestroyKernelInfo(kernel_info);
1982 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1983 }
1984 j=(ssize_t) kernel_info->width/2;
cristy3ed852e2009-09-05 21:47:34 +00001985 i=0;
cristy47e00502009-12-17 19:19:57 +00001986 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00001987 {
cristy47e00502009-12-17 19:19:57 +00001988 for (u=(-j); u <= j; u++)
cristy41cbe682011-07-15 19:12:37 +00001989 {
1990 kernel_info->values[i]=(double) (exp(-((double) u*u+v*v)/(2.0*
1991 MagickSigma*MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
1992 i++;
1993 }
cristy3ed852e2009-09-05 21:47:34 +00001994 }
cristy0a922382011-07-16 15:30:34 +00001995 kernel_info->bias=image->bias;
cristy5e6be1e2011-07-16 01:23:39 +00001996 blur_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00001997 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00001998 return(blur_image);
1999}
2000
2001/*
2002%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2003% %
2004% %
2005% %
cristy3ed852e2009-09-05 21:47:34 +00002006% M o t i o n B l u r I m a g e %
2007% %
2008% %
2009% %
2010%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2011%
2012% MotionBlurImage() simulates motion blur. We convolve the image with a
2013% Gaussian operator of the given radius and standard deviation (sigma).
2014% For reasonable results, radius should be larger than sigma. Use a
2015% radius of 0 and MotionBlurImage() selects a suitable radius for you.
2016% Angle gives the angle of the blurring motion.
2017%
2018% Andrew Protano contributed this effect.
2019%
2020% The format of the MotionBlurImage method is:
2021%
2022% Image *MotionBlurImage(const Image *image,const double radius,
2023% const double sigma,const double angle,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002024%
2025% A description of each parameter follows:
2026%
2027% o image: the image.
2028%
cristy3ed852e2009-09-05 21:47:34 +00002029% o radius: the radius of the Gaussian, in pixels, not counting
2030% the center pixel.
2031%
2032% o sigma: the standard deviation of the Gaussian, in pixels.
2033%
cristycee97112010-05-28 00:44:52 +00002034% o angle: Apply the effect along this angle.
cristy3ed852e2009-09-05 21:47:34 +00002035%
2036% o exception: return any errors or warnings in this structure.
2037%
2038*/
2039
cristybb503372010-05-27 20:51:26 +00002040static double *GetMotionBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +00002041{
cristy3ed852e2009-09-05 21:47:34 +00002042 double
cristy47e00502009-12-17 19:19:57 +00002043 *kernel,
cristy3ed852e2009-09-05 21:47:34 +00002044 normalize;
2045
cristybb503372010-05-27 20:51:26 +00002046 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002047 i;
2048
2049 /*
cristy47e00502009-12-17 19:19:57 +00002050 Generate a 1-D convolution kernel.
cristy3ed852e2009-09-05 21:47:34 +00002051 */
2052 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
2053 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
2054 if (kernel == (double *) NULL)
2055 return(kernel);
cristy3ed852e2009-09-05 21:47:34 +00002056 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00002057 for (i=0; i < (ssize_t) width; i++)
cristy47e00502009-12-17 19:19:57 +00002058 {
cristy4205a3c2010-09-12 20:19:59 +00002059 kernel[i]=(double) (exp((-((double) i*i)/(double) (2.0*MagickSigma*
2060 MagickSigma)))/(MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00002061 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +00002062 }
cristybb503372010-05-27 20:51:26 +00002063 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002064 kernel[i]/=normalize;
2065 return(kernel);
2066}
2067
cristyf4ad9df2011-07-08 16:49:03 +00002068MagickExport Image *MotionBlurImage(const Image *image,
2069 const double radius,const double sigma,const double angle,
2070 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002071{
cristyc4c8d132010-01-07 01:58:38 +00002072 CacheView
2073 *blur_view,
2074 *image_view;
2075
cristy3ed852e2009-09-05 21:47:34 +00002076 double
2077 *kernel;
2078
2079 Image
2080 *blur_image;
2081
cristy3ed852e2009-09-05 21:47:34 +00002082 MagickBooleanType
2083 status;
2084
cristybb503372010-05-27 20:51:26 +00002085 MagickOffsetType
2086 progress;
2087
cristy4c08aed2011-07-01 19:47:50 +00002088 PixelInfo
cristyddd82202009-11-03 20:14:50 +00002089 bias;
cristy3ed852e2009-09-05 21:47:34 +00002090
2091 OffsetInfo
2092 *offset;
2093
2094 PointInfo
2095 point;
2096
cristybb503372010-05-27 20:51:26 +00002097 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002098 i;
2099
cristybb503372010-05-27 20:51:26 +00002100 size_t
cristy3ed852e2009-09-05 21:47:34 +00002101 width;
2102
cristybb503372010-05-27 20:51:26 +00002103 ssize_t
2104 y;
2105
cristy3ed852e2009-09-05 21:47:34 +00002106 assert(image != (Image *) NULL);
2107 assert(image->signature == MagickSignature);
2108 if (image->debug != MagickFalse)
2109 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2110 assert(exception != (ExceptionInfo *) NULL);
2111 width=GetOptimalKernelWidth1D(radius,sigma);
2112 kernel=GetMotionBlurKernel(width,sigma);
2113 if (kernel == (double *) NULL)
2114 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2115 offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset));
2116 if (offset == (OffsetInfo *) NULL)
2117 {
2118 kernel=(double *) RelinquishMagickMemory(kernel);
2119 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2120 }
2121 blur_image=CloneImage(image,0,0,MagickTrue,exception);
2122 if (blur_image == (Image *) NULL)
2123 {
2124 kernel=(double *) RelinquishMagickMemory(kernel);
2125 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2126 return((Image *) NULL);
2127 }
cristy574cc262011-08-05 01:23:58 +00002128 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00002129 {
2130 kernel=(double *) RelinquishMagickMemory(kernel);
2131 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
cristy3ed852e2009-09-05 21:47:34 +00002132 blur_image=DestroyImage(blur_image);
2133 return((Image *) NULL);
2134 }
2135 point.x=(double) width*sin(DegreesToRadians(angle));
2136 point.y=(double) width*cos(DegreesToRadians(angle));
cristybb503372010-05-27 20:51:26 +00002137 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002138 {
cristybb503372010-05-27 20:51:26 +00002139 offset[i].x=(ssize_t) ceil((double) (i*point.y)/hypot(point.x,point.y)-0.5);
2140 offset[i].y=(ssize_t) ceil((double) (i*point.x)/hypot(point.x,point.y)-0.5);
cristy3ed852e2009-09-05 21:47:34 +00002141 }
2142 /*
2143 Motion blur image.
2144 */
2145 status=MagickTrue;
2146 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00002147 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00002148 image_view=AcquireCacheView(image);
2149 blur_view=AcquireCacheView(blur_image);
cristyb557a152011-02-22 12:14:30 +00002150#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00002151 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00002152#endif
cristybb503372010-05-27 20:51:26 +00002153 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002154 {
cristy4c08aed2011-07-01 19:47:50 +00002155 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002156 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002157
cristy117ff172010-08-15 21:35:32 +00002158 register ssize_t
2159 x;
2160
cristy3ed852e2009-09-05 21:47:34 +00002161 if (status == MagickFalse)
2162 continue;
2163 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
2164 exception);
cristyacd2ed22011-08-30 01:44:23 +00002165 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002166 {
2167 status=MagickFalse;
2168 continue;
2169 }
cristybb503372010-05-27 20:51:26 +00002170 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002171 {
cristy4c08aed2011-07-01 19:47:50 +00002172 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002173 qixel;
2174
2175 PixelPacket
2176 pixel;
2177
2178 register double
cristyc47d1f82009-11-26 01:44:43 +00002179 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00002180
cristybb503372010-05-27 20:51:26 +00002181 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002182 i;
2183
cristy3ed852e2009-09-05 21:47:34 +00002184 k=kernel;
cristyddd82202009-11-03 20:14:50 +00002185 qixel=bias;
cristyed231572011-07-14 02:18:59 +00002186 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00002187 {
cristybb503372010-05-27 20:51:26 +00002188 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002189 {
2190 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
2191 offset[i].y,&pixel,exception);
2192 qixel.red+=(*k)*pixel.red;
2193 qixel.green+=(*k)*pixel.green;
2194 qixel.blue+=(*k)*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00002195 qixel.alpha+=(*k)*pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00002196 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00002197 qixel.black+=(*k)*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00002198 k++;
2199 }
cristyed231572011-07-14 02:18:59 +00002200 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002201 SetPixelRed(blur_image,
2202 ClampToQuantum(qixel.red),q);
cristyed231572011-07-14 02:18:59 +00002203 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002204 SetPixelGreen(blur_image,
2205 ClampToQuantum(qixel.green),q);
cristyed231572011-07-14 02:18:59 +00002206 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002207 SetPixelBlue(blur_image,
2208 ClampToQuantum(qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00002209 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002210 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002211 SetPixelBlack(blur_image,
2212 ClampToQuantum(qixel.black),q);
cristyed231572011-07-14 02:18:59 +00002213 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002214 SetPixelAlpha(blur_image,
2215 ClampToQuantum(qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00002216 }
2217 else
2218 {
2219 MagickRealType
2220 alpha,
2221 gamma;
2222
2223 alpha=0.0;
2224 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00002225 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002226 {
2227 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
2228 offset[i].y,&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00002229 alpha=(MagickRealType) (QuantumScale*pixel.alpha);
cristy3ed852e2009-09-05 21:47:34 +00002230 qixel.red+=(*k)*alpha*pixel.red;
2231 qixel.green+=(*k)*alpha*pixel.green;
2232 qixel.blue+=(*k)*alpha*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00002233 qixel.alpha+=(*k)*pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00002234 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00002235 qixel.black+=(*k)*alpha*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00002236 gamma+=(*k)*alpha;
2237 k++;
2238 }
2239 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00002240 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002241 SetPixelRed(blur_image,
2242 ClampToQuantum(gamma*qixel.red),q);
cristyed231572011-07-14 02:18:59 +00002243 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002244 SetPixelGreen(blur_image,
2245 ClampToQuantum(gamma*qixel.green),q);
cristyed231572011-07-14 02:18:59 +00002246 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002247 SetPixelBlue(blur_image,
2248 ClampToQuantum(gamma*qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00002249 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002250 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002251 SetPixelBlack(blur_image,
2252 ClampToQuantum(gamma*qixel.black),q);
cristyed231572011-07-14 02:18:59 +00002253 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002254 SetPixelAlpha(blur_image,
2255 ClampToQuantum(qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00002256 }
cristyed231572011-07-14 02:18:59 +00002257 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00002258 }
2259 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
2260 status=MagickFalse;
2261 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2262 {
2263 MagickBooleanType
2264 proceed;
2265
cristyb557a152011-02-22 12:14:30 +00002266#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00002267 #pragma omp critical (MagickCore_MotionBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00002268#endif
2269 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
2270 if (proceed == MagickFalse)
2271 status=MagickFalse;
2272 }
2273 }
2274 blur_view=DestroyCacheView(blur_view);
2275 image_view=DestroyCacheView(image_view);
2276 kernel=(double *) RelinquishMagickMemory(kernel);
2277 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2278 if (status == MagickFalse)
2279 blur_image=DestroyImage(blur_image);
2280 return(blur_image);
2281}
2282
2283/*
2284%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2285% %
2286% %
2287% %
2288% P r e v i e w I m a g e %
2289% %
2290% %
2291% %
2292%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2293%
2294% PreviewImage() tiles 9 thumbnails of the specified image with an image
2295% processing operation applied with varying parameters. This may be helpful
2296% pin-pointing an appropriate parameter for a particular image processing
2297% operation.
2298%
2299% The format of the PreviewImages method is:
2300%
2301% Image *PreviewImages(const Image *image,const PreviewType preview,
2302% ExceptionInfo *exception)
2303%
2304% A description of each parameter follows:
2305%
2306% o image: the image.
2307%
2308% o preview: the image processing operation.
2309%
2310% o exception: return any errors or warnings in this structure.
2311%
2312*/
2313MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
2314 ExceptionInfo *exception)
2315{
2316#define NumberTiles 9
2317#define PreviewImageTag "Preview/Image"
2318#define DefaultPreviewGeometry "204x204+10+10"
2319
2320 char
2321 factor[MaxTextExtent],
2322 label[MaxTextExtent];
2323
2324 double
2325 degrees,
2326 gamma,
2327 percentage,
2328 radius,
2329 sigma,
2330 threshold;
2331
2332 Image
2333 *images,
2334 *montage_image,
2335 *preview_image,
2336 *thumbnail;
2337
2338 ImageInfo
2339 *preview_info;
2340
cristy3ed852e2009-09-05 21:47:34 +00002341 MagickBooleanType
2342 proceed;
2343
2344 MontageInfo
2345 *montage_info;
2346
2347 QuantizeInfo
2348 quantize_info;
2349
2350 RectangleInfo
2351 geometry;
2352
cristybb503372010-05-27 20:51:26 +00002353 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002354 i,
2355 x;
2356
cristybb503372010-05-27 20:51:26 +00002357 size_t
cristy3ed852e2009-09-05 21:47:34 +00002358 colors;
2359
cristy117ff172010-08-15 21:35:32 +00002360 ssize_t
2361 y;
2362
cristy3ed852e2009-09-05 21:47:34 +00002363 /*
2364 Open output image file.
2365 */
2366 assert(image != (Image *) NULL);
2367 assert(image->signature == MagickSignature);
2368 if (image->debug != MagickFalse)
2369 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2370 colors=2;
2371 degrees=0.0;
2372 gamma=(-0.2f);
2373 preview_info=AcquireImageInfo();
2374 SetGeometry(image,&geometry);
2375 (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
2376 &geometry.width,&geometry.height);
2377 images=NewImageList();
2378 percentage=12.5;
2379 GetQuantizeInfo(&quantize_info);
2380 radius=0.0;
2381 sigma=1.0;
2382 threshold=0.0;
2383 x=0;
2384 y=0;
2385 for (i=0; i < NumberTiles; i++)
2386 {
2387 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
2388 if (thumbnail == (Image *) NULL)
2389 break;
2390 (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
2391 (void *) NULL);
2392 (void) SetImageProperty(thumbnail,"label",DefaultTileLabel);
2393 if (i == (NumberTiles/2))
2394 {
2395 (void) QueryColorDatabase("#dfdfdf",&thumbnail->matte_color,exception);
2396 AppendImageToList(&images,thumbnail);
2397 continue;
2398 }
2399 switch (preview)
2400 {
2401 case RotatePreview:
2402 {
2403 degrees+=45.0;
2404 preview_image=RotateImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002405 (void) FormatLocaleString(label,MaxTextExtent,"rotate %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002406 break;
2407 }
2408 case ShearPreview:
2409 {
2410 degrees+=5.0;
2411 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002412 (void) FormatLocaleString(label,MaxTextExtent,"shear %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002413 degrees,2.0*degrees);
2414 break;
2415 }
2416 case RollPreview:
2417 {
cristybb503372010-05-27 20:51:26 +00002418 x=(ssize_t) ((i+1)*thumbnail->columns)/NumberTiles;
2419 y=(ssize_t) ((i+1)*thumbnail->rows)/NumberTiles;
cristy3ed852e2009-09-05 21:47:34 +00002420 preview_image=RollImage(thumbnail,x,y,exception);
cristyb51dff52011-05-19 16:55:47 +00002421 (void) FormatLocaleString(label,MaxTextExtent,"roll %+.20gx%+.20g",
cristye8c25f92010-06-03 00:53:06 +00002422 (double) x,(double) y);
cristy3ed852e2009-09-05 21:47:34 +00002423 break;
2424 }
2425 case HuePreview:
2426 {
2427 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2428 if (preview_image == (Image *) NULL)
2429 break;
cristyb51dff52011-05-19 16:55:47 +00002430 (void) FormatLocaleString(factor,MaxTextExtent,"100,100,%g",
cristy3ed852e2009-09-05 21:47:34 +00002431 2.0*percentage);
cristy33bd5152011-08-24 01:42:24 +00002432 (void) ModulateImage(preview_image,factor,exception);
cristyb51dff52011-05-19 16:55:47 +00002433 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002434 break;
2435 }
2436 case SaturationPreview:
2437 {
2438 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2439 if (preview_image == (Image *) NULL)
2440 break;
cristyb51dff52011-05-19 16:55:47 +00002441 (void) FormatLocaleString(factor,MaxTextExtent,"100,%g",
cristy8cd5b312010-01-07 01:10:24 +00002442 2.0*percentage);
cristy33bd5152011-08-24 01:42:24 +00002443 (void) ModulateImage(preview_image,factor,exception);
cristyb51dff52011-05-19 16:55:47 +00002444 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002445 break;
2446 }
2447 case BrightnessPreview:
2448 {
2449 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2450 if (preview_image == (Image *) NULL)
2451 break;
cristyb51dff52011-05-19 16:55:47 +00002452 (void) FormatLocaleString(factor,MaxTextExtent,"%g",2.0*percentage);
cristy33bd5152011-08-24 01:42:24 +00002453 (void) ModulateImage(preview_image,factor,exception);
cristyb51dff52011-05-19 16:55:47 +00002454 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002455 break;
2456 }
2457 case GammaPreview:
2458 default:
2459 {
2460 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2461 if (preview_image == (Image *) NULL)
2462 break;
2463 gamma+=0.4f;
cristyb3e7c6c2011-07-24 01:43:55 +00002464 (void) GammaImage(preview_image,gamma,exception);
cristyb51dff52011-05-19 16:55:47 +00002465 (void) FormatLocaleString(label,MaxTextExtent,"gamma %g",gamma);
cristy3ed852e2009-09-05 21:47:34 +00002466 break;
2467 }
2468 case SpiffPreview:
2469 {
2470 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2471 if (preview_image != (Image *) NULL)
2472 for (x=0; x < i; x++)
cristye23ec9d2011-08-16 18:15:40 +00002473 (void) ContrastImage(preview_image,MagickTrue,exception);
cristyb51dff52011-05-19 16:55:47 +00002474 (void) FormatLocaleString(label,MaxTextExtent,"contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002475 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002476 break;
2477 }
2478 case DullPreview:
2479 {
2480 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2481 if (preview_image == (Image *) NULL)
2482 break;
2483 for (x=0; x < i; x++)
cristye23ec9d2011-08-16 18:15:40 +00002484 (void) ContrastImage(preview_image,MagickFalse,exception);
cristyb51dff52011-05-19 16:55:47 +00002485 (void) FormatLocaleString(label,MaxTextExtent,"+contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002486 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002487 break;
2488 }
2489 case GrayscalePreview:
2490 {
2491 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2492 if (preview_image == (Image *) NULL)
2493 break;
2494 colors<<=1;
2495 quantize_info.number_colors=colors;
2496 quantize_info.colorspace=GRAYColorspace;
cristy018f07f2011-09-04 21:15:19 +00002497 (void) QuantizeImage(&quantize_info,preview_image,exception);
cristyb51dff52011-05-19 16:55:47 +00002498 (void) FormatLocaleString(label,MaxTextExtent,
cristye8c25f92010-06-03 00:53:06 +00002499 "-colorspace gray -colors %.20g",(double) colors);
cristy3ed852e2009-09-05 21:47:34 +00002500 break;
2501 }
2502 case QuantizePreview:
2503 {
2504 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2505 if (preview_image == (Image *) NULL)
2506 break;
2507 colors<<=1;
2508 quantize_info.number_colors=colors;
cristy018f07f2011-09-04 21:15:19 +00002509 (void) QuantizeImage(&quantize_info,preview_image,exception);
cristyb51dff52011-05-19 16:55:47 +00002510 (void) FormatLocaleString(label,MaxTextExtent,"colors %.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00002511 colors);
cristy3ed852e2009-09-05 21:47:34 +00002512 break;
2513 }
2514 case DespecklePreview:
2515 {
2516 for (x=0; x < (i-1); x++)
2517 {
2518 preview_image=DespeckleImage(thumbnail,exception);
2519 if (preview_image == (Image *) NULL)
2520 break;
2521 thumbnail=DestroyImage(thumbnail);
2522 thumbnail=preview_image;
2523 }
2524 preview_image=DespeckleImage(thumbnail,exception);
2525 if (preview_image == (Image *) NULL)
2526 break;
cristyb51dff52011-05-19 16:55:47 +00002527 (void) FormatLocaleString(label,MaxTextExtent,"despeckle (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002528 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002529 break;
2530 }
2531 case ReduceNoisePreview:
2532 {
cristy95c38342011-03-18 22:39:51 +00002533 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) radius,
2534 (size_t) radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002535 (void) FormatLocaleString(label,MaxTextExtent,"noise %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002536 break;
2537 }
2538 case AddNoisePreview:
2539 {
2540 switch ((int) i)
2541 {
2542 case 0:
2543 {
2544 (void) CopyMagickString(factor,"uniform",MaxTextExtent);
2545 break;
2546 }
2547 case 1:
2548 {
2549 (void) CopyMagickString(factor,"gaussian",MaxTextExtent);
2550 break;
2551 }
2552 case 2:
2553 {
2554 (void) CopyMagickString(factor,"multiplicative",MaxTextExtent);
2555 break;
2556 }
2557 case 3:
2558 {
2559 (void) CopyMagickString(factor,"impulse",MaxTextExtent);
2560 break;
2561 }
2562 case 4:
2563 {
2564 (void) CopyMagickString(factor,"laplacian",MaxTextExtent);
2565 break;
2566 }
2567 case 5:
2568 {
2569 (void) CopyMagickString(factor,"Poisson",MaxTextExtent);
2570 break;
2571 }
2572 default:
2573 {
2574 (void) CopyMagickString(thumbnail->magick,"NULL",MaxTextExtent);
2575 break;
2576 }
2577 }
cristyd76c51e2011-03-26 00:21:26 +00002578 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) i,
2579 (size_t) i,exception);
cristyb51dff52011-05-19 16:55:47 +00002580 (void) FormatLocaleString(label,MaxTextExtent,"+noise %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002581 break;
2582 }
2583 case SharpenPreview:
2584 {
2585 preview_image=SharpenImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00002586 (void) FormatLocaleString(label,MaxTextExtent,"sharpen %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002587 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002588 break;
2589 }
2590 case BlurPreview:
2591 {
2592 preview_image=BlurImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00002593 (void) FormatLocaleString(label,MaxTextExtent,"blur %gx%g",radius,
cristy3ed852e2009-09-05 21:47:34 +00002594 sigma);
2595 break;
2596 }
2597 case ThresholdPreview:
2598 {
2599 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2600 if (preview_image == (Image *) NULL)
2601 break;
2602 (void) BilevelImage(thumbnail,
2603 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
cristyb51dff52011-05-19 16:55:47 +00002604 (void) FormatLocaleString(label,MaxTextExtent,"threshold %g",
cristy3ed852e2009-09-05 21:47:34 +00002605 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
2606 break;
2607 }
2608 case EdgeDetectPreview:
2609 {
cristy8ae632d2011-09-05 17:29:53 +00002610 preview_image=EdgeImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00002611 (void) FormatLocaleString(label,MaxTextExtent,"edge %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002612 break;
2613 }
2614 case SpreadPreview:
2615 {
2616 preview_image=SpreadImage(thumbnail,radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002617 (void) FormatLocaleString(label,MaxTextExtent,"spread %g",
cristy8cd5b312010-01-07 01:10:24 +00002618 radius+0.5);
cristy3ed852e2009-09-05 21:47:34 +00002619 break;
2620 }
2621 case SolarizePreview:
2622 {
2623 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2624 if (preview_image == (Image *) NULL)
2625 break;
2626 (void) SolarizeImage(preview_image,(double) QuantumRange*
cristy5cbc0162011-08-29 00:36:28 +00002627 percentage/100.0,exception);
cristyb51dff52011-05-19 16:55:47 +00002628 (void) FormatLocaleString(label,MaxTextExtent,"solarize %g",
cristy3ed852e2009-09-05 21:47:34 +00002629 (QuantumRange*percentage)/100.0);
2630 break;
2631 }
2632 case ShadePreview:
2633 {
2634 degrees+=10.0;
2635 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
2636 exception);
cristyb51dff52011-05-19 16:55:47 +00002637 (void) FormatLocaleString(label,MaxTextExtent,"shade %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002638 degrees,degrees);
cristy3ed852e2009-09-05 21:47:34 +00002639 break;
2640 }
2641 case RaisePreview:
2642 {
2643 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2644 if (preview_image == (Image *) NULL)
2645 break;
cristybb503372010-05-27 20:51:26 +00002646 geometry.width=(size_t) (2*i+2);
2647 geometry.height=(size_t) (2*i+2);
cristy3ed852e2009-09-05 21:47:34 +00002648 geometry.x=i/2;
2649 geometry.y=i/2;
cristy6170ac32011-08-28 14:15:37 +00002650 (void) RaiseImage(preview_image,&geometry,MagickTrue,exception);
cristyb51dff52011-05-19 16:55:47 +00002651 (void) FormatLocaleString(label,MaxTextExtent,
cristy6d8abba2010-06-03 01:10:47 +00002652 "raise %.20gx%.20g%+.20g%+.20g",(double) geometry.width,(double)
cristye8c25f92010-06-03 00:53:06 +00002653 geometry.height,(double) geometry.x,(double) geometry.y);
cristy3ed852e2009-09-05 21:47:34 +00002654 break;
2655 }
2656 case SegmentPreview:
2657 {
2658 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2659 if (preview_image == (Image *) NULL)
2660 break;
2661 threshold+=0.4f;
2662 (void) SegmentImage(preview_image,RGBColorspace,MagickFalse,threshold,
cristy018f07f2011-09-04 21:15:19 +00002663 threshold,exception);
cristyb51dff52011-05-19 16:55:47 +00002664 (void) FormatLocaleString(label,MaxTextExtent,"segment %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002665 threshold,threshold);
2666 break;
2667 }
2668 case SwirlPreview:
2669 {
2670 preview_image=SwirlImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002671 (void) FormatLocaleString(label,MaxTextExtent,"swirl %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002672 degrees+=45.0;
2673 break;
2674 }
2675 case ImplodePreview:
2676 {
2677 degrees+=0.1f;
2678 preview_image=ImplodeImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002679 (void) FormatLocaleString(label,MaxTextExtent,"implode %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002680 break;
2681 }
2682 case WavePreview:
2683 {
2684 degrees+=5.0f;
2685 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002686 (void) FormatLocaleString(label,MaxTextExtent,"wave %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002687 0.5*degrees,2.0*degrees);
cristy3ed852e2009-09-05 21:47:34 +00002688 break;
2689 }
2690 case OilPaintPreview:
2691 {
cristy14973ba2011-08-27 23:48:07 +00002692 preview_image=OilPaintImage(thumbnail,(double) radius,(double) sigma,
2693 exception);
2694 (void) FormatLocaleString(label,MaxTextExtent,"charcoal %gx%g",
2695 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002696 break;
2697 }
2698 case CharcoalDrawingPreview:
2699 {
2700 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
2701 exception);
cristyb51dff52011-05-19 16:55:47 +00002702 (void) FormatLocaleString(label,MaxTextExtent,"charcoal %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002703 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002704 break;
2705 }
2706 case JPEGPreview:
2707 {
2708 char
2709 filename[MaxTextExtent];
2710
2711 int
2712 file;
2713
2714 MagickBooleanType
2715 status;
2716
2717 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2718 if (preview_image == (Image *) NULL)
2719 break;
cristybb503372010-05-27 20:51:26 +00002720 preview_info->quality=(size_t) percentage;
cristyb51dff52011-05-19 16:55:47 +00002721 (void) FormatLocaleString(factor,MaxTextExtent,"%.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00002722 preview_info->quality);
cristy3ed852e2009-09-05 21:47:34 +00002723 file=AcquireUniqueFileResource(filename);
2724 if (file != -1)
2725 file=close(file)-1;
cristyb51dff52011-05-19 16:55:47 +00002726 (void) FormatLocaleString(preview_image->filename,MaxTextExtent,
cristy3ed852e2009-09-05 21:47:34 +00002727 "jpeg:%s",filename);
cristy6f9e0d32011-08-28 16:32:09 +00002728 status=WriteImage(preview_info,preview_image,exception);
cristy3ed852e2009-09-05 21:47:34 +00002729 if (status != MagickFalse)
2730 {
2731 Image
2732 *quality_image;
2733
2734 (void) CopyMagickString(preview_info->filename,
2735 preview_image->filename,MaxTextExtent);
2736 quality_image=ReadImage(preview_info,exception);
2737 if (quality_image != (Image *) NULL)
2738 {
2739 preview_image=DestroyImage(preview_image);
2740 preview_image=quality_image;
2741 }
2742 }
2743 (void) RelinquishUniqueFileResource(preview_image->filename);
2744 if ((GetBlobSize(preview_image)/1024) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00002745 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%gmb ",
cristy3ed852e2009-09-05 21:47:34 +00002746 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
2747 1024.0/1024.0);
2748 else
2749 if (GetBlobSize(preview_image) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00002750 (void) FormatLocaleString(label,MaxTextExtent,
cristye7f51092010-01-17 00:39:37 +00002751 "quality %s\n%gkb ",factor,(double) ((MagickOffsetType)
cristy8cd5b312010-01-07 01:10:24 +00002752 GetBlobSize(preview_image))/1024.0);
cristy3ed852e2009-09-05 21:47:34 +00002753 else
cristyb51dff52011-05-19 16:55:47 +00002754 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%.20gb ",
cristy54ea5732011-06-10 12:39:53 +00002755 factor,(double) ((MagickOffsetType) GetBlobSize(thumbnail)));
cristy3ed852e2009-09-05 21:47:34 +00002756 break;
2757 }
2758 }
2759 thumbnail=DestroyImage(thumbnail);
2760 percentage+=12.5;
2761 radius+=0.5;
2762 sigma+=0.25;
2763 if (preview_image == (Image *) NULL)
2764 break;
2765 (void) DeleteImageProperty(preview_image,"label");
2766 (void) SetImageProperty(preview_image,"label",label);
2767 AppendImageToList(&images,preview_image);
cristybb503372010-05-27 20:51:26 +00002768 proceed=SetImageProgress(image,PreviewImageTag,(MagickOffsetType) i,
2769 NumberTiles);
cristy3ed852e2009-09-05 21:47:34 +00002770 if (proceed == MagickFalse)
2771 break;
2772 }
2773 if (images == (Image *) NULL)
2774 {
2775 preview_info=DestroyImageInfo(preview_info);
2776 return((Image *) NULL);
2777 }
2778 /*
2779 Create the montage.
2780 */
2781 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
2782 (void) CopyMagickString(montage_info->filename,image->filename,MaxTextExtent);
2783 montage_info->shadow=MagickTrue;
2784 (void) CloneString(&montage_info->tile,"3x3");
2785 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
2786 (void) CloneString(&montage_info->frame,DefaultTileFrame);
2787 montage_image=MontageImages(images,montage_info,exception);
2788 montage_info=DestroyMontageInfo(montage_info);
2789 images=DestroyImageList(images);
2790 if (montage_image == (Image *) NULL)
2791 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2792 if (montage_image->montage != (char *) NULL)
2793 {
2794 /*
2795 Free image directory.
2796 */
2797 montage_image->montage=(char *) RelinquishMagickMemory(
2798 montage_image->montage);
2799 if (image->directory != (char *) NULL)
2800 montage_image->directory=(char *) RelinquishMagickMemory(
2801 montage_image->directory);
2802 }
2803 preview_info=DestroyImageInfo(preview_info);
2804 return(montage_image);
2805}
2806
2807/*
2808%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2809% %
2810% %
2811% %
2812% R a d i a l B l u r I m a g e %
2813% %
2814% %
2815% %
2816%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2817%
2818% RadialBlurImage() applies a radial blur to the image.
2819%
2820% Andrew Protano contributed this effect.
2821%
2822% The format of the RadialBlurImage method is:
2823%
2824% Image *RadialBlurImage(const Image *image,const double angle,
2825% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002826%
2827% A description of each parameter follows:
2828%
2829% o image: the image.
2830%
cristy3ed852e2009-09-05 21:47:34 +00002831% o angle: the angle of the radial blur.
2832%
2833% o exception: return any errors or warnings in this structure.
2834%
2835*/
cristyf4ad9df2011-07-08 16:49:03 +00002836MagickExport Image *RadialBlurImage(const Image *image,
2837 const double angle,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002838{
cristyc4c8d132010-01-07 01:58:38 +00002839 CacheView
2840 *blur_view,
2841 *image_view;
2842
cristy3ed852e2009-09-05 21:47:34 +00002843 Image
2844 *blur_image;
2845
cristy3ed852e2009-09-05 21:47:34 +00002846 MagickBooleanType
2847 status;
2848
cristybb503372010-05-27 20:51:26 +00002849 MagickOffsetType
2850 progress;
2851
cristy4c08aed2011-07-01 19:47:50 +00002852 PixelInfo
cristyddd82202009-11-03 20:14:50 +00002853 bias;
cristy3ed852e2009-09-05 21:47:34 +00002854
2855 MagickRealType
2856 blur_radius,
2857 *cos_theta,
2858 offset,
2859 *sin_theta,
2860 theta;
2861
2862 PointInfo
2863 blur_center;
2864
cristybb503372010-05-27 20:51:26 +00002865 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002866 i;
2867
cristybb503372010-05-27 20:51:26 +00002868 size_t
cristy3ed852e2009-09-05 21:47:34 +00002869 n;
2870
cristybb503372010-05-27 20:51:26 +00002871 ssize_t
2872 y;
2873
cristy3ed852e2009-09-05 21:47:34 +00002874 /*
2875 Allocate blur image.
2876 */
2877 assert(image != (Image *) NULL);
2878 assert(image->signature == MagickSignature);
2879 if (image->debug != MagickFalse)
2880 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2881 assert(exception != (ExceptionInfo *) NULL);
2882 assert(exception->signature == MagickSignature);
2883 blur_image=CloneImage(image,0,0,MagickTrue,exception);
2884 if (blur_image == (Image *) NULL)
2885 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00002886 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00002887 {
cristy3ed852e2009-09-05 21:47:34 +00002888 blur_image=DestroyImage(blur_image);
2889 return((Image *) NULL);
2890 }
2891 blur_center.x=(double) image->columns/2.0;
2892 blur_center.y=(double) image->rows/2.0;
2893 blur_radius=hypot(blur_center.x,blur_center.y);
cristy117ff172010-08-15 21:35:32 +00002894 n=(size_t) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+2UL);
cristy3ed852e2009-09-05 21:47:34 +00002895 theta=DegreesToRadians(angle)/(MagickRealType) (n-1);
2896 cos_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
2897 sizeof(*cos_theta));
2898 sin_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
2899 sizeof(*sin_theta));
2900 if ((cos_theta == (MagickRealType *) NULL) ||
2901 (sin_theta == (MagickRealType *) NULL))
2902 {
2903 blur_image=DestroyImage(blur_image);
2904 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2905 }
2906 offset=theta*(MagickRealType) (n-1)/2.0;
cristybb503372010-05-27 20:51:26 +00002907 for (i=0; i < (ssize_t) n; i++)
cristy3ed852e2009-09-05 21:47:34 +00002908 {
2909 cos_theta[i]=cos((double) (theta*i-offset));
2910 sin_theta[i]=sin((double) (theta*i-offset));
2911 }
2912 /*
2913 Radial blur image.
2914 */
2915 status=MagickTrue;
2916 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00002917 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00002918 image_view=AcquireCacheView(image);
2919 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00002920#if defined(MAGICKCORE_OPENMP_SUPPORT)
2921 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002922#endif
cristybb503372010-05-27 20:51:26 +00002923 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002924 {
cristy4c08aed2011-07-01 19:47:50 +00002925 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002926 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002927
cristy117ff172010-08-15 21:35:32 +00002928 register ssize_t
2929 x;
2930
cristy3ed852e2009-09-05 21:47:34 +00002931 if (status == MagickFalse)
2932 continue;
2933 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
2934 exception);
cristyacd2ed22011-08-30 01:44:23 +00002935 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002936 {
2937 status=MagickFalse;
2938 continue;
2939 }
cristybb503372010-05-27 20:51:26 +00002940 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002941 {
cristy4c08aed2011-07-01 19:47:50 +00002942 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002943 qixel;
2944
2945 MagickRealType
2946 normalize,
2947 radius;
2948
2949 PixelPacket
2950 pixel;
2951
2952 PointInfo
2953 center;
2954
cristybb503372010-05-27 20:51:26 +00002955 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002956 i;
2957
cristybb503372010-05-27 20:51:26 +00002958 size_t
cristy3ed852e2009-09-05 21:47:34 +00002959 step;
2960
2961 center.x=(double) x-blur_center.x;
2962 center.y=(double) y-blur_center.y;
2963 radius=hypot((double) center.x,center.y);
2964 if (radius == 0)
2965 step=1;
2966 else
2967 {
cristybb503372010-05-27 20:51:26 +00002968 step=(size_t) (blur_radius/radius);
cristy3ed852e2009-09-05 21:47:34 +00002969 if (step == 0)
2970 step=1;
2971 else
2972 if (step >= n)
2973 step=n-1;
2974 }
2975 normalize=0.0;
cristyddd82202009-11-03 20:14:50 +00002976 qixel=bias;
cristyed231572011-07-14 02:18:59 +00002977 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00002978 {
cristyeaedf062010-05-29 22:36:02 +00002979 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00002980 {
cristyeaedf062010-05-29 22:36:02 +00002981 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
2982 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
2983 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
2984 cos_theta[i]+0.5),&pixel,exception);
cristy3ed852e2009-09-05 21:47:34 +00002985 qixel.red+=pixel.red;
2986 qixel.green+=pixel.green;
2987 qixel.blue+=pixel.blue;
cristy3ed852e2009-09-05 21:47:34 +00002988 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00002989 qixel.black+=pixel.black;
2990 qixel.alpha+=pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00002991 normalize+=1.0;
2992 }
2993 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
2994 normalize);
cristyed231572011-07-14 02:18:59 +00002995 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002996 SetPixelRed(blur_image,
2997 ClampToQuantum(normalize*qixel.red),q);
cristyed231572011-07-14 02:18:59 +00002998 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002999 SetPixelGreen(blur_image,
3000 ClampToQuantum(normalize*qixel.green),q);
cristyed231572011-07-14 02:18:59 +00003001 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003002 SetPixelBlue(blur_image,
3003 ClampToQuantum(normalize*qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003004 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003005 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003006 SetPixelBlack(blur_image,
3007 ClampToQuantum(normalize*qixel.black),q);
cristyed231572011-07-14 02:18:59 +00003008 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003009 SetPixelAlpha(blur_image,
3010 ClampToQuantum(normalize*qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003011 }
3012 else
3013 {
3014 MagickRealType
3015 alpha,
3016 gamma;
3017
3018 alpha=1.0;
3019 gamma=0.0;
cristyeaedf062010-05-29 22:36:02 +00003020 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00003021 {
cristyeaedf062010-05-29 22:36:02 +00003022 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
3023 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
3024 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
3025 cos_theta[i]+0.5),&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00003026 alpha=(MagickRealType) (QuantumScale*pixel.alpha);
cristy3ed852e2009-09-05 21:47:34 +00003027 qixel.red+=alpha*pixel.red;
3028 qixel.green+=alpha*pixel.green;
3029 qixel.blue+=alpha*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00003030 qixel.alpha+=pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00003031 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00003032 qixel.black+=alpha*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00003033 gamma+=alpha;
3034 normalize+=1.0;
3035 }
3036 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3037 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
3038 normalize);
cristyed231572011-07-14 02:18:59 +00003039 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003040 SetPixelRed(blur_image,
3041 ClampToQuantum(gamma*qixel.red),q);
cristyed231572011-07-14 02:18:59 +00003042 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003043 SetPixelGreen(blur_image,
3044 ClampToQuantum(gamma*qixel.green),q);
cristyed231572011-07-14 02:18:59 +00003045 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003046 SetPixelBlue(blur_image,
3047 ClampToQuantum(gamma*qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003048 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003049 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003050 SetPixelBlack(blur_image,
3051 ClampToQuantum(gamma*qixel.black),q);
cristyed231572011-07-14 02:18:59 +00003052 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003053 SetPixelAlpha(blur_image,
3054 ClampToQuantum(normalize*qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003055 }
cristyed231572011-07-14 02:18:59 +00003056 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003057 }
3058 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
3059 status=MagickFalse;
3060 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3061 {
3062 MagickBooleanType
3063 proceed;
3064
cristyb5d5f722009-11-04 03:03:49 +00003065#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00003066 #pragma omp critical (MagickCore_RadialBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00003067#endif
3068 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
3069 if (proceed == MagickFalse)
3070 status=MagickFalse;
3071 }
3072 }
3073 blur_view=DestroyCacheView(blur_view);
3074 image_view=DestroyCacheView(image_view);
3075 cos_theta=(MagickRealType *) RelinquishMagickMemory(cos_theta);
3076 sin_theta=(MagickRealType *) RelinquishMagickMemory(sin_theta);
3077 if (status == MagickFalse)
3078 blur_image=DestroyImage(blur_image);
3079 return(blur_image);
3080}
3081
3082/*
3083%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3084% %
3085% %
3086% %
cristy3ed852e2009-09-05 21:47:34 +00003087% S e l e c t i v e B l u r I m a g e %
3088% %
3089% %
3090% %
3091%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3092%
3093% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
3094% It is similar to the unsharpen mask that sharpens everything with contrast
3095% above a certain threshold.
3096%
3097% The format of the SelectiveBlurImage method is:
3098%
3099% Image *SelectiveBlurImage(const Image *image,const double radius,
3100% const double sigma,const double threshold,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003101%
3102% A description of each parameter follows:
3103%
3104% o image: the image.
3105%
cristy3ed852e2009-09-05 21:47:34 +00003106% o radius: the radius of the Gaussian, in pixels, not counting the center
3107% pixel.
3108%
3109% o sigma: the standard deviation of the Gaussian, in pixels.
3110%
3111% o threshold: only pixels within this contrast threshold are included
3112% in the blur operation.
3113%
3114% o exception: return any errors or warnings in this structure.
3115%
3116*/
cristyf4ad9df2011-07-08 16:49:03 +00003117MagickExport Image *SelectiveBlurImage(const Image *image,
3118 const double radius,const double sigma,const double threshold,
3119 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003120{
3121#define SelectiveBlurImageTag "SelectiveBlur/Image"
3122
cristy47e00502009-12-17 19:19:57 +00003123 CacheView
3124 *blur_view,
3125 *image_view;
3126
cristy3ed852e2009-09-05 21:47:34 +00003127 double
cristy3ed852e2009-09-05 21:47:34 +00003128 *kernel;
3129
3130 Image
3131 *blur_image;
3132
cristy3ed852e2009-09-05 21:47:34 +00003133 MagickBooleanType
3134 status;
3135
cristybb503372010-05-27 20:51:26 +00003136 MagickOffsetType
3137 progress;
3138
cristy4c08aed2011-07-01 19:47:50 +00003139 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003140 bias;
3141
cristybb503372010-05-27 20:51:26 +00003142 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003143 i;
cristy3ed852e2009-09-05 21:47:34 +00003144
cristybb503372010-05-27 20:51:26 +00003145 size_t
cristy3ed852e2009-09-05 21:47:34 +00003146 width;
3147
cristybb503372010-05-27 20:51:26 +00003148 ssize_t
3149 j,
3150 u,
3151 v,
3152 y;
3153
cristy3ed852e2009-09-05 21:47:34 +00003154 /*
3155 Initialize blur image attributes.
3156 */
3157 assert(image != (Image *) NULL);
3158 assert(image->signature == MagickSignature);
3159 if (image->debug != MagickFalse)
3160 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3161 assert(exception != (ExceptionInfo *) NULL);
3162 assert(exception->signature == MagickSignature);
3163 width=GetOptimalKernelWidth1D(radius,sigma);
3164 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
3165 if (kernel == (double *) NULL)
3166 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00003167 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00003168 i=0;
cristy47e00502009-12-17 19:19:57 +00003169 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003170 {
cristy47e00502009-12-17 19:19:57 +00003171 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00003172 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
3173 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00003174 }
3175 if (image->debug != MagickFalse)
3176 {
3177 char
3178 format[MaxTextExtent],
3179 *message;
3180
cristy117ff172010-08-15 21:35:32 +00003181 register const double
3182 *k;
3183
cristybb503372010-05-27 20:51:26 +00003184 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003185 u,
3186 v;
3187
cristy3ed852e2009-09-05 21:47:34 +00003188 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00003189 " SelectiveBlurImage with %.20gx%.20g kernel:",(double) width,(double)
3190 width);
cristy3ed852e2009-09-05 21:47:34 +00003191 message=AcquireString("");
3192 k=kernel;
cristybb503372010-05-27 20:51:26 +00003193 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003194 {
3195 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00003196 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy3ed852e2009-09-05 21:47:34 +00003197 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00003198 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003199 {
cristyb51dff52011-05-19 16:55:47 +00003200 (void) FormatLocaleString(format,MaxTextExtent,"%+f ",*k++);
cristy3ed852e2009-09-05 21:47:34 +00003201 (void) ConcatenateString(&message,format);
3202 }
3203 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
3204 }
3205 message=DestroyString(message);
3206 }
3207 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3208 if (blur_image == (Image *) NULL)
3209 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003210 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003211 {
cristy3ed852e2009-09-05 21:47:34 +00003212 blur_image=DestroyImage(blur_image);
3213 return((Image *) NULL);
3214 }
3215 /*
3216 Threshold blur image.
3217 */
3218 status=MagickTrue;
3219 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00003220 GetPixelInfo(image,&bias);
3221 SetPixelInfoBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003222 image_view=AcquireCacheView(image);
3223 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003224#if defined(MAGICKCORE_OPENMP_SUPPORT)
3225 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003226#endif
cristybb503372010-05-27 20:51:26 +00003227 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003228 {
cristy4c08aed2011-07-01 19:47:50 +00003229 double
3230 contrast;
3231
cristy3ed852e2009-09-05 21:47:34 +00003232 MagickBooleanType
3233 sync;
3234
3235 MagickRealType
3236 gamma;
3237
cristy4c08aed2011-07-01 19:47:50 +00003238 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003239 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003240
cristy4c08aed2011-07-01 19:47:50 +00003241 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003242 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003243
cristy117ff172010-08-15 21:35:32 +00003244 register ssize_t
3245 x;
3246
cristy3ed852e2009-09-05 21:47:34 +00003247 if (status == MagickFalse)
3248 continue;
cristy117ff172010-08-15 21:35:32 +00003249 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
3250 (width/2L),image->columns+width,width,exception);
cristy3ed852e2009-09-05 21:47:34 +00003251 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3252 exception);
cristy4c08aed2011-07-01 19:47:50 +00003253 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003254 {
3255 status=MagickFalse;
3256 continue;
3257 }
cristybb503372010-05-27 20:51:26 +00003258 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003259 {
cristy4c08aed2011-07-01 19:47:50 +00003260 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003261 pixel;
3262
3263 register const double
cristyc47d1f82009-11-26 01:44:43 +00003264 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00003265
cristybb503372010-05-27 20:51:26 +00003266 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003267 u;
3268
cristy117ff172010-08-15 21:35:32 +00003269 ssize_t
3270 j,
3271 v;
3272
cristyddd82202009-11-03 20:14:50 +00003273 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00003274 k=kernel;
3275 gamma=0.0;
3276 j=0;
cristyed231572011-07-14 02:18:59 +00003277 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00003278 {
cristybb503372010-05-27 20:51:26 +00003279 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003280 {
cristybb503372010-05-27 20:51:26 +00003281 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003282 {
cristyed231572011-07-14 02:18:59 +00003283 contrast=GetPixelIntensity(image,p+(u+j)*GetPixelChannels(image))-
cristy4c08aed2011-07-01 19:47:50 +00003284 (double) GetPixelIntensity(blur_image,q);
3285 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003286 {
cristy4c08aed2011-07-01 19:47:50 +00003287 pixel.red+=(*k)*
cristyed231572011-07-14 02:18:59 +00003288 GetPixelRed(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003289 pixel.green+=(*k)*
cristyed231572011-07-14 02:18:59 +00003290 GetPixelGreen(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003291 pixel.blue+=(*k)*
cristyed231572011-07-14 02:18:59 +00003292 GetPixelBlue(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003293 if (image->colorspace == CMYKColorspace)
3294 pixel.black+=(*k)*
cristyed231572011-07-14 02:18:59 +00003295 GetPixelBlack(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003296 gamma+=(*k);
3297 k++;
3298 }
3299 }
cristyd99b0962010-05-29 23:14:26 +00003300 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003301 }
3302 if (gamma != 0.0)
3303 {
3304 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00003305 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003306 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +00003307 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003308 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +00003309 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003310 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003311 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00003312 (image->colorspace == CMYKColorspace))
3313 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00003314 }
cristyed231572011-07-14 02:18:59 +00003315 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003316 {
3317 gamma=0.0;
3318 j=0;
cristybb503372010-05-27 20:51:26 +00003319 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003320 {
cristybb503372010-05-27 20:51:26 +00003321 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003322 {
cristy4c08aed2011-07-01 19:47:50 +00003323 contrast=GetPixelIntensity(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003324 GetPixelChannels(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003325 GetPixelIntensity(blur_image,q);
3326 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003327 {
cristy4c08aed2011-07-01 19:47:50 +00003328 pixel.alpha+=(*k)*
cristyed231572011-07-14 02:18:59 +00003329 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003330 gamma+=(*k);
3331 k++;
3332 }
3333 }
cristyeaedf062010-05-29 22:36:02 +00003334 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003335 }
3336 if (gamma != 0.0)
3337 {
3338 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
3339 gamma);
cristy4c08aed2011-07-01 19:47:50 +00003340 SetPixelAlpha(blur_image,ClampToQuantum(gamma*pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003341 }
3342 }
3343 }
3344 else
3345 {
3346 MagickRealType
3347 alpha;
3348
cristybb503372010-05-27 20:51:26 +00003349 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003350 {
cristybb503372010-05-27 20:51:26 +00003351 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003352 {
cristy4c08aed2011-07-01 19:47:50 +00003353 contrast=GetPixelIntensity(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003354 GetPixelChannels(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003355 GetPixelIntensity(blur_image,q);
3356 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003357 {
cristy4c08aed2011-07-01 19:47:50 +00003358 alpha=(MagickRealType) (QuantumScale*
cristyed231572011-07-14 02:18:59 +00003359 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image)));
cristy4c08aed2011-07-01 19:47:50 +00003360 pixel.red+=(*k)*alpha*
cristyed231572011-07-14 02:18:59 +00003361 GetPixelRed(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003362 pixel.green+=(*k)*alpha*GetPixelGreen(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003363 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003364 pixel.blue+=(*k)*alpha*GetPixelBlue(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003365 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003366 pixel.alpha+=(*k)*GetPixelAlpha(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003367 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003368 if (image->colorspace == CMYKColorspace)
3369 pixel.black+=(*k)*GetPixelBlack(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003370 GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003371 gamma+=(*k)*alpha;
3372 k++;
3373 }
3374 }
cristyeaedf062010-05-29 22:36:02 +00003375 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003376 }
3377 if (gamma != 0.0)
3378 {
3379 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00003380 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003381 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +00003382 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003383 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +00003384 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003385 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003386 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00003387 (image->colorspace == CMYKColorspace))
3388 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00003389 }
cristyed231572011-07-14 02:18:59 +00003390 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003391 {
3392 gamma=0.0;
3393 j=0;
cristybb503372010-05-27 20:51:26 +00003394 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003395 {
cristybb503372010-05-27 20:51:26 +00003396 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003397 {
cristy4c08aed2011-07-01 19:47:50 +00003398 contrast=GetPixelIntensity(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003399 GetPixelChannels(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003400 GetPixelIntensity(blur_image,q);
3401 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003402 {
cristy4c08aed2011-07-01 19:47:50 +00003403 pixel.alpha+=(*k)*
cristyed231572011-07-14 02:18:59 +00003404 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003405 gamma+=(*k);
3406 k++;
3407 }
3408 }
cristyeaedf062010-05-29 22:36:02 +00003409 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003410 }
3411 if (gamma != 0.0)
3412 {
3413 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
3414 gamma);
cristy4c08aed2011-07-01 19:47:50 +00003415 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003416 }
3417 }
3418 }
cristyed231572011-07-14 02:18:59 +00003419 p+=GetPixelChannels(image);
3420 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003421 }
3422 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
3423 if (sync == MagickFalse)
3424 status=MagickFalse;
3425 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3426 {
3427 MagickBooleanType
3428 proceed;
3429
cristyb5d5f722009-11-04 03:03:49 +00003430#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00003431 #pragma omp critical (MagickCore_SelectiveBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00003432#endif
3433 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress++,
3434 image->rows);
3435 if (proceed == MagickFalse)
3436 status=MagickFalse;
3437 }
3438 }
3439 blur_image->type=image->type;
3440 blur_view=DestroyCacheView(blur_view);
3441 image_view=DestroyCacheView(image_view);
3442 kernel=(double *) RelinquishMagickMemory(kernel);
3443 if (status == MagickFalse)
3444 blur_image=DestroyImage(blur_image);
3445 return(blur_image);
3446}
3447
3448/*
3449%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3450% %
3451% %
3452% %
3453% S h a d e I m a g e %
3454% %
3455% %
3456% %
3457%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3458%
3459% ShadeImage() shines a distant light on an image to create a
3460% three-dimensional effect. You control the positioning of the light with
3461% azimuth and elevation; azimuth is measured in degrees off the x axis
3462% and elevation is measured in pixels above the Z axis.
3463%
3464% The format of the ShadeImage method is:
3465%
3466% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3467% const double azimuth,const double elevation,ExceptionInfo *exception)
3468%
3469% A description of each parameter follows:
3470%
3471% o image: the image.
3472%
3473% o gray: A value other than zero shades the intensity of each pixel.
3474%
3475% o azimuth, elevation: Define the light source direction.
3476%
3477% o exception: return any errors or warnings in this structure.
3478%
3479*/
3480MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3481 const double azimuth,const double elevation,ExceptionInfo *exception)
3482{
3483#define ShadeImageTag "Shade/Image"
3484
cristyc4c8d132010-01-07 01:58:38 +00003485 CacheView
3486 *image_view,
3487 *shade_view;
3488
cristy3ed852e2009-09-05 21:47:34 +00003489 Image
3490 *shade_image;
3491
cristy3ed852e2009-09-05 21:47:34 +00003492 MagickBooleanType
3493 status;
3494
cristybb503372010-05-27 20:51:26 +00003495 MagickOffsetType
3496 progress;
3497
cristy3ed852e2009-09-05 21:47:34 +00003498 PrimaryInfo
3499 light;
3500
cristybb503372010-05-27 20:51:26 +00003501 ssize_t
3502 y;
3503
cristy3ed852e2009-09-05 21:47:34 +00003504 /*
3505 Initialize shaded image attributes.
3506 */
3507 assert(image != (const Image *) NULL);
3508 assert(image->signature == MagickSignature);
3509 if (image->debug != MagickFalse)
3510 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3511 assert(exception != (ExceptionInfo *) NULL);
3512 assert(exception->signature == MagickSignature);
3513 shade_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
3514 if (shade_image == (Image *) NULL)
3515 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003516 if (SetImageStorageClass(shade_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003517 {
cristy3ed852e2009-09-05 21:47:34 +00003518 shade_image=DestroyImage(shade_image);
3519 return((Image *) NULL);
3520 }
3521 /*
3522 Compute the light vector.
3523 */
3524 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
3525 cos(DegreesToRadians(elevation));
3526 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
3527 cos(DegreesToRadians(elevation));
3528 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
3529 /*
3530 Shade image.
3531 */
3532 status=MagickTrue;
3533 progress=0;
3534 image_view=AcquireCacheView(image);
3535 shade_view=AcquireCacheView(shade_image);
cristyb5d5f722009-11-04 03:03:49 +00003536#if defined(MAGICKCORE_OPENMP_SUPPORT)
3537 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003538#endif
cristybb503372010-05-27 20:51:26 +00003539 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003540 {
3541 MagickRealType
3542 distance,
3543 normal_distance,
3544 shade;
3545
3546 PrimaryInfo
3547 normal;
3548
cristy4c08aed2011-07-01 19:47:50 +00003549 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003550 *restrict p,
3551 *restrict s0,
3552 *restrict s1,
3553 *restrict s2;
cristy3ed852e2009-09-05 21:47:34 +00003554
cristy4c08aed2011-07-01 19:47:50 +00003555 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003556 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003557
cristy117ff172010-08-15 21:35:32 +00003558 register ssize_t
3559 x;
3560
cristy3ed852e2009-09-05 21:47:34 +00003561 if (status == MagickFalse)
3562 continue;
3563 p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception);
3564 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
3565 exception);
cristy4c08aed2011-07-01 19:47:50 +00003566 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003567 {
3568 status=MagickFalse;
3569 continue;
3570 }
3571 /*
3572 Shade this row of pixels.
3573 */
3574 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
cristyed231572011-07-14 02:18:59 +00003575 s0=p+GetPixelChannels(image);
3576 s1=s0+(image->columns+2)*GetPixelChannels(image);
3577 s2=s1+(image->columns+2)*GetPixelChannels(image);
cristybb503372010-05-27 20:51:26 +00003578 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003579 {
3580 /*
3581 Determine the surface normal and compute shading.
3582 */
cristyed231572011-07-14 02:18:59 +00003583 normal.x=(double) (GetPixelIntensity(image,s0-GetPixelChannels(image))+
3584 GetPixelIntensity(image,s1-GetPixelChannels(image))+
3585 GetPixelIntensity(image,s2-GetPixelChannels(image))-
3586 GetPixelIntensity(image,s0+GetPixelChannels(image))-
3587 GetPixelIntensity(image,s1+GetPixelChannels(image))-
3588 GetPixelIntensity(image,s2+GetPixelChannels(image)));
3589 normal.y=(double) (GetPixelIntensity(image,s2-GetPixelChannels(image))+
cristy4c08aed2011-07-01 19:47:50 +00003590 GetPixelIntensity(image,s2)+
cristyed231572011-07-14 02:18:59 +00003591 GetPixelIntensity(image,s2+GetPixelChannels(image))-
3592 GetPixelIntensity(image,s0-GetPixelChannels(image))-
cristy4c08aed2011-07-01 19:47:50 +00003593 GetPixelIntensity(image,s0)-
cristyed231572011-07-14 02:18:59 +00003594 GetPixelIntensity(image,s0+GetPixelChannels(image)));
cristy3ed852e2009-09-05 21:47:34 +00003595 if ((normal.x == 0.0) && (normal.y == 0.0))
3596 shade=light.z;
3597 else
3598 {
3599 shade=0.0;
3600 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
3601 if (distance > MagickEpsilon)
3602 {
3603 normal_distance=
3604 normal.x*normal.x+normal.y*normal.y+normal.z*normal.z;
3605 if (normal_distance > (MagickEpsilon*MagickEpsilon))
3606 shade=distance/sqrt((double) normal_distance);
3607 }
3608 }
3609 if (gray != MagickFalse)
3610 {
cristy4c08aed2011-07-01 19:47:50 +00003611 SetPixelRed(shade_image,ClampToQuantum(shade),q);
3612 SetPixelGreen(shade_image,ClampToQuantum(shade),q);
3613 SetPixelBlue(shade_image,ClampToQuantum(shade),q);
cristy3ed852e2009-09-05 21:47:34 +00003614 }
3615 else
3616 {
cristy4c08aed2011-07-01 19:47:50 +00003617 SetPixelRed(shade_image,ClampToQuantum(QuantumScale*shade*
3618 GetPixelRed(image,s1)),q);
3619 SetPixelGreen(shade_image,ClampToQuantum(QuantumScale*shade*
3620 GetPixelGreen(image,s1)),q);
3621 SetPixelBlue(shade_image,ClampToQuantum(QuantumScale*shade*
3622 GetPixelBlue(image,s1)),q);
cristy3ed852e2009-09-05 21:47:34 +00003623 }
cristy4c08aed2011-07-01 19:47:50 +00003624 SetPixelAlpha(shade_image,GetPixelAlpha(image,s1),q);
cristyed231572011-07-14 02:18:59 +00003625 s0+=GetPixelChannels(image);
3626 s1+=GetPixelChannels(image);
3627 s2+=GetPixelChannels(image);
3628 q+=GetPixelChannels(shade_image);
cristy3ed852e2009-09-05 21:47:34 +00003629 }
3630 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
3631 status=MagickFalse;
3632 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3633 {
3634 MagickBooleanType
3635 proceed;
3636
cristyb5d5f722009-11-04 03:03:49 +00003637#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003638 #pragma omp critical (MagickCore_ShadeImage)
3639#endif
3640 proceed=SetImageProgress(image,ShadeImageTag,progress++,image->rows);
3641 if (proceed == MagickFalse)
3642 status=MagickFalse;
3643 }
3644 }
3645 shade_view=DestroyCacheView(shade_view);
3646 image_view=DestroyCacheView(image_view);
3647 if (status == MagickFalse)
3648 shade_image=DestroyImage(shade_image);
3649 return(shade_image);
3650}
3651
3652/*
3653%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3654% %
3655% %
3656% %
3657% S h a r p e n I m a g e %
3658% %
3659% %
3660% %
3661%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3662%
3663% SharpenImage() sharpens the image. We convolve the image with a Gaussian
3664% operator of the given radius and standard deviation (sigma). For
3665% reasonable results, radius should be larger than sigma. Use a radius of 0
3666% and SharpenImage() selects a suitable radius for you.
3667%
3668% Using a separable kernel would be faster, but the negative weights cancel
3669% out on the corners of the kernel producing often undesirable ringing in the
3670% filtered result; this can be avoided by using a 2D gaussian shaped image
3671% sharpening kernel instead.
3672%
3673% The format of the SharpenImage method is:
3674%
3675% Image *SharpenImage(const Image *image,const double radius,
3676% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003677%
3678% A description of each parameter follows:
3679%
3680% o image: the image.
3681%
cristy3ed852e2009-09-05 21:47:34 +00003682% o radius: the radius of the Gaussian, in pixels, not counting the center
3683% pixel.
3684%
3685% o sigma: the standard deviation of the Laplacian, in pixels.
3686%
3687% o exception: return any errors or warnings in this structure.
3688%
3689*/
cristy3ed852e2009-09-05 21:47:34 +00003690MagickExport Image *SharpenImage(const Image *image,const double radius,
3691 const double sigma,ExceptionInfo *exception)
3692{
cristy3ed852e2009-09-05 21:47:34 +00003693 double
cristy47e00502009-12-17 19:19:57 +00003694 normalize;
cristy3ed852e2009-09-05 21:47:34 +00003695
3696 Image
3697 *sharp_image;
3698
cristy41cbe682011-07-15 19:12:37 +00003699 KernelInfo
3700 *kernel_info;
3701
cristybb503372010-05-27 20:51:26 +00003702 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003703 i;
3704
cristybb503372010-05-27 20:51:26 +00003705 size_t
cristy3ed852e2009-09-05 21:47:34 +00003706 width;
3707
cristy117ff172010-08-15 21:35:32 +00003708 ssize_t
3709 j,
3710 u,
3711 v;
3712
cristy3ed852e2009-09-05 21:47:34 +00003713 assert(image != (const Image *) NULL);
3714 assert(image->signature == MagickSignature);
3715 if (image->debug != MagickFalse)
3716 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3717 assert(exception != (ExceptionInfo *) NULL);
3718 assert(exception->signature == MagickSignature);
3719 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00003720 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00003721 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003722 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00003723 (void) ResetMagickMemory(kernel_info,0,sizeof(*kernel_info));
3724 kernel_info->width=width;
3725 kernel_info->height=width;
3726 kernel_info->signature=MagickSignature;
3727 kernel_info->values=(double *) AcquireAlignedMemory(kernel_info->width,
3728 kernel_info->width*sizeof(*kernel_info->values));
3729 if (kernel_info->values == (double *) NULL)
3730 {
3731 kernel_info=DestroyKernelInfo(kernel_info);
3732 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3733 }
cristy3ed852e2009-09-05 21:47:34 +00003734 normalize=0.0;
cristy41cbe682011-07-15 19:12:37 +00003735 j=(ssize_t) kernel_info->width/2;
cristy47e00502009-12-17 19:19:57 +00003736 i=0;
3737 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003738 {
cristy47e00502009-12-17 19:19:57 +00003739 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00003740 {
cristy41cbe682011-07-15 19:12:37 +00003741 kernel_info->values[i]=(double) (-exp(-((double) u*u+v*v)/(2.0*
3742 MagickSigma*MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
3743 normalize+=kernel_info->values[i];
cristy3ed852e2009-09-05 21:47:34 +00003744 i++;
3745 }
3746 }
cristy41cbe682011-07-15 19:12:37 +00003747 kernel_info->values[i/2]=(double) ((-2.0)*normalize);
cristy0a922382011-07-16 15:30:34 +00003748 kernel_info->bias=image->bias;
cristy5e6be1e2011-07-16 01:23:39 +00003749 sharp_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00003750 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00003751 return(sharp_image);
3752}
3753
3754/*
3755%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3756% %
3757% %
3758% %
3759% S p r e a d I m a g e %
3760% %
3761% %
3762% %
3763%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3764%
3765% SpreadImage() is a special effects method that randomly displaces each
3766% pixel in a block defined by the radius parameter.
3767%
3768% The format of the SpreadImage method is:
3769%
3770% Image *SpreadImage(const Image *image,const double radius,
3771% ExceptionInfo *exception)
3772%
3773% A description of each parameter follows:
3774%
3775% o image: the image.
3776%
3777% o radius: Choose a random pixel in a neighborhood of this extent.
3778%
3779% o exception: return any errors or warnings in this structure.
3780%
3781*/
3782MagickExport Image *SpreadImage(const Image *image,const double radius,
3783 ExceptionInfo *exception)
3784{
3785#define SpreadImageTag "Spread/Image"
3786
cristyfa112112010-01-04 17:48:07 +00003787 CacheView
cristy9f7e7cb2011-03-26 00:49:57 +00003788 *image_view,
3789 *spread_view;
cristyfa112112010-01-04 17:48:07 +00003790
cristy3ed852e2009-09-05 21:47:34 +00003791 Image
3792 *spread_image;
3793
cristy3ed852e2009-09-05 21:47:34 +00003794 MagickBooleanType
3795 status;
3796
cristybb503372010-05-27 20:51:26 +00003797 MagickOffsetType
3798 progress;
3799
cristy4c08aed2011-07-01 19:47:50 +00003800 PixelInfo
cristyddd82202009-11-03 20:14:50 +00003801 bias;
cristy3ed852e2009-09-05 21:47:34 +00003802
3803 RandomInfo
cristyfa112112010-01-04 17:48:07 +00003804 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00003805
cristybb503372010-05-27 20:51:26 +00003806 size_t
cristy3ed852e2009-09-05 21:47:34 +00003807 width;
3808
cristybb503372010-05-27 20:51:26 +00003809 ssize_t
3810 y;
3811
cristy3ed852e2009-09-05 21:47:34 +00003812 /*
3813 Initialize spread image attributes.
3814 */
3815 assert(image != (Image *) NULL);
3816 assert(image->signature == MagickSignature);
3817 if (image->debug != MagickFalse)
3818 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3819 assert(exception != (ExceptionInfo *) NULL);
3820 assert(exception->signature == MagickSignature);
3821 spread_image=CloneImage(image,image->columns,image->rows,MagickTrue,
3822 exception);
3823 if (spread_image == (Image *) NULL)
3824 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003825 if (SetImageStorageClass(spread_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003826 {
cristy3ed852e2009-09-05 21:47:34 +00003827 spread_image=DestroyImage(spread_image);
3828 return((Image *) NULL);
3829 }
3830 /*
3831 Spread image.
3832 */
3833 status=MagickTrue;
3834 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00003835 GetPixelInfo(spread_image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003836 width=GetOptimalKernelWidth1D(radius,0.5);
cristy3ed852e2009-09-05 21:47:34 +00003837 random_info=AcquireRandomInfoThreadSet();
cristy9f7e7cb2011-03-26 00:49:57 +00003838 image_view=AcquireCacheView(image);
3839 spread_view=AcquireCacheView(spread_image);
cristyb557a152011-02-22 12:14:30 +00003840#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00003841 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00003842#endif
cristybb503372010-05-27 20:51:26 +00003843 for (y=0; y < (ssize_t) spread_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003844 {
cristy5c9e6f22010-09-17 17:31:01 +00003845 const int
3846 id = GetOpenMPThreadId();
cristy6ebe97c2010-07-03 01:17:28 +00003847
cristy4c08aed2011-07-01 19:47:50 +00003848 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003849 pixel;
3850
cristy4c08aed2011-07-01 19:47:50 +00003851 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003852 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003853
cristy117ff172010-08-15 21:35:32 +00003854 register ssize_t
3855 x;
3856
cristy3ed852e2009-09-05 21:47:34 +00003857 if (status == MagickFalse)
3858 continue;
cristy9f7e7cb2011-03-26 00:49:57 +00003859 q=QueueCacheViewAuthenticPixels(spread_view,0,y,spread_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +00003860 exception);
cristyacd2ed22011-08-30 01:44:23 +00003861 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003862 {
3863 status=MagickFalse;
3864 continue;
3865 }
cristyddd82202009-11-03 20:14:50 +00003866 pixel=bias;
cristybb503372010-05-27 20:51:26 +00003867 for (x=0; x < (ssize_t) spread_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003868 {
cristy4c08aed2011-07-01 19:47:50 +00003869 (void) InterpolatePixelInfo(image,image_view,
cristy8a7c3e82011-03-26 02:10:53 +00003870 UndefinedInterpolatePixel,(double) x+width*(GetPseudoRandomValue(
3871 random_info[id])-0.5),(double) y+width*(GetPseudoRandomValue(
3872 random_info[id])-0.5),&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00003873 SetPixelPixelInfo(spread_image,&pixel,q);
cristyed231572011-07-14 02:18:59 +00003874 q+=GetPixelChannels(spread_image);
cristy3ed852e2009-09-05 21:47:34 +00003875 }
cristy9f7e7cb2011-03-26 00:49:57 +00003876 if (SyncCacheViewAuthenticPixels(spread_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003877 status=MagickFalse;
3878 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3879 {
3880 MagickBooleanType
3881 proceed;
3882
cristyb557a152011-02-22 12:14:30 +00003883#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003884 #pragma omp critical (MagickCore_SpreadImage)
3885#endif
3886 proceed=SetImageProgress(image,SpreadImageTag,progress++,image->rows);
3887 if (proceed == MagickFalse)
3888 status=MagickFalse;
3889 }
3890 }
cristy9f7e7cb2011-03-26 00:49:57 +00003891 spread_view=DestroyCacheView(spread_view);
cristy3ed852e2009-09-05 21:47:34 +00003892 image_view=DestroyCacheView(image_view);
3893 random_info=DestroyRandomInfoThreadSet(random_info);
cristy3ed852e2009-09-05 21:47:34 +00003894 return(spread_image);
3895}
3896
3897/*
3898%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3899% %
3900% %
3901% %
cristy0834d642011-03-18 18:26:08 +00003902% S t a t i s t i c I m a g e %
3903% %
3904% %
3905% %
3906%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3907%
3908% StatisticImage() makes each pixel the min / max / median / mode / etc. of
cristy8d752042011-03-19 01:00:36 +00003909% the neighborhood of the specified width and height.
cristy0834d642011-03-18 18:26:08 +00003910%
3911% The format of the StatisticImage method is:
3912%
3913% Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00003914% const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00003915%
3916% A description of each parameter follows:
3917%
3918% o image: the image.
3919%
cristy0834d642011-03-18 18:26:08 +00003920% o type: the statistic type (median, mode, etc.).
3921%
cristy95c38342011-03-18 22:39:51 +00003922% o width: the width of the pixel neighborhood.
3923%
3924% o height: the height of the pixel neighborhood.
cristy0834d642011-03-18 18:26:08 +00003925%
3926% o exception: return any errors or warnings in this structure.
3927%
3928*/
3929
cristy733678d2011-03-18 21:29:28 +00003930#define ListChannels 5
3931
3932typedef struct _ListNode
3933{
3934 size_t
3935 next[9],
3936 count,
3937 signature;
3938} ListNode;
3939
3940typedef struct _SkipList
3941{
3942 ssize_t
3943 level;
3944
3945 ListNode
3946 *nodes;
3947} SkipList;
3948
3949typedef struct _PixelList
3950{
3951 size_t
cristy6fc86bb2011-03-18 23:45:16 +00003952 length,
cristy733678d2011-03-18 21:29:28 +00003953 seed,
3954 signature;
3955
3956 SkipList
3957 lists[ListChannels];
3958} PixelList;
3959
3960static PixelList *DestroyPixelList(PixelList *pixel_list)
3961{
3962 register ssize_t
3963 i;
3964
3965 if (pixel_list == (PixelList *) NULL)
3966 return((PixelList *) NULL);
3967 for (i=0; i < ListChannels; i++)
3968 if (pixel_list->lists[i].nodes != (ListNode *) NULL)
3969 pixel_list->lists[i].nodes=(ListNode *) RelinquishMagickMemory(
3970 pixel_list->lists[i].nodes);
3971 pixel_list=(PixelList *) RelinquishMagickMemory(pixel_list);
3972 return(pixel_list);
3973}
3974
3975static PixelList **DestroyPixelListThreadSet(PixelList **pixel_list)
3976{
3977 register ssize_t
3978 i;
3979
3980 assert(pixel_list != (PixelList **) NULL);
3981 for (i=0; i < (ssize_t) GetOpenMPMaximumThreads(); i++)
3982 if (pixel_list[i] != (PixelList *) NULL)
3983 pixel_list[i]=DestroyPixelList(pixel_list[i]);
3984 pixel_list=(PixelList **) RelinquishMagickMemory(pixel_list);
3985 return(pixel_list);
3986}
3987
cristy6fc86bb2011-03-18 23:45:16 +00003988static PixelList *AcquirePixelList(const size_t width,const size_t height)
cristy733678d2011-03-18 21:29:28 +00003989{
3990 PixelList
3991 *pixel_list;
3992
3993 register ssize_t
3994 i;
3995
3996 pixel_list=(PixelList *) AcquireMagickMemory(sizeof(*pixel_list));
3997 if (pixel_list == (PixelList *) NULL)
3998 return(pixel_list);
3999 (void) ResetMagickMemory((void *) pixel_list,0,sizeof(*pixel_list));
cristy6fc86bb2011-03-18 23:45:16 +00004000 pixel_list->length=width*height;
cristy733678d2011-03-18 21:29:28 +00004001 for (i=0; i < ListChannels; i++)
4002 {
4003 pixel_list->lists[i].nodes=(ListNode *) AcquireQuantumMemory(65537UL,
4004 sizeof(*pixel_list->lists[i].nodes));
4005 if (pixel_list->lists[i].nodes == (ListNode *) NULL)
4006 return(DestroyPixelList(pixel_list));
4007 (void) ResetMagickMemory(pixel_list->lists[i].nodes,0,65537UL*
4008 sizeof(*pixel_list->lists[i].nodes));
4009 }
4010 pixel_list->signature=MagickSignature;
4011 return(pixel_list);
4012}
4013
cristy6fc86bb2011-03-18 23:45:16 +00004014static PixelList **AcquirePixelListThreadSet(const size_t width,
4015 const size_t height)
cristy733678d2011-03-18 21:29:28 +00004016{
4017 PixelList
4018 **pixel_list;
4019
4020 register ssize_t
4021 i;
4022
4023 size_t
4024 number_threads;
4025
4026 number_threads=GetOpenMPMaximumThreads();
4027 pixel_list=(PixelList **) AcquireQuantumMemory(number_threads,
4028 sizeof(*pixel_list));
4029 if (pixel_list == (PixelList **) NULL)
4030 return((PixelList **) NULL);
4031 (void) ResetMagickMemory(pixel_list,0,number_threads*sizeof(*pixel_list));
4032 for (i=0; i < (ssize_t) number_threads; i++)
4033 {
cristy6fc86bb2011-03-18 23:45:16 +00004034 pixel_list[i]=AcquirePixelList(width,height);
cristy733678d2011-03-18 21:29:28 +00004035 if (pixel_list[i] == (PixelList *) NULL)
4036 return(DestroyPixelListThreadSet(pixel_list));
4037 }
4038 return(pixel_list);
4039}
4040
4041static void AddNodePixelList(PixelList *pixel_list,const ssize_t channel,
4042 const size_t color)
4043{
4044 register SkipList
4045 *list;
4046
4047 register ssize_t
4048 level;
4049
4050 size_t
4051 search,
4052 update[9];
4053
4054 /*
4055 Initialize the node.
4056 */
4057 list=pixel_list->lists+channel;
4058 list->nodes[color].signature=pixel_list->signature;
4059 list->nodes[color].count=1;
4060 /*
4061 Determine where it belongs in the list.
4062 */
4063 search=65536UL;
4064 for (level=list->level; level >= 0; level--)
4065 {
4066 while (list->nodes[search].next[level] < color)
4067 search=list->nodes[search].next[level];
4068 update[level]=search;
4069 }
4070 /*
4071 Generate a pseudo-random level for this node.
4072 */
4073 for (level=0; ; level++)
4074 {
4075 pixel_list->seed=(pixel_list->seed*42893621L)+1L;
4076 if ((pixel_list->seed & 0x300) != 0x300)
4077 break;
4078 }
4079 if (level > 8)
4080 level=8;
4081 if (level > (list->level+2))
4082 level=list->level+2;
4083 /*
4084 If we're raising the list's level, link back to the root node.
4085 */
4086 while (level > list->level)
4087 {
4088 list->level++;
4089 update[list->level]=65536UL;
4090 }
4091 /*
4092 Link the node into the skip-list.
4093 */
4094 do
4095 {
4096 list->nodes[color].next[level]=list->nodes[update[level]].next[level];
4097 list->nodes[update[level]].next[level]=color;
cristy3cba8ca2011-03-19 01:29:12 +00004098 } while (level-- > 0);
cristy733678d2011-03-18 21:29:28 +00004099}
4100
cristy4c08aed2011-07-01 19:47:50 +00004101static PixelInfo GetMaximumPixelList(PixelList *pixel_list)
cristy6fc86bb2011-03-18 23:45:16 +00004102{
cristy4c08aed2011-07-01 19:47:50 +00004103 PixelInfo
cristy6fc86bb2011-03-18 23:45:16 +00004104 pixel;
4105
4106 register SkipList
4107 *list;
4108
4109 register ssize_t
4110 channel;
4111
4112 size_t
cristyd76c51e2011-03-26 00:21:26 +00004113 color,
4114 maximum;
cristy49f37242011-03-22 18:18:23 +00004115
4116 ssize_t
cristy6fc86bb2011-03-18 23:45:16 +00004117 count;
4118
4119 unsigned short
cristyd76c51e2011-03-26 00:21:26 +00004120 channels[ListChannels];
cristy6fc86bb2011-03-18 23:45:16 +00004121
4122 /*
4123 Find the maximum value for each of the color.
4124 */
4125 for (channel=0; channel < 5; channel++)
4126 {
4127 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004128 color=65536L;
cristy6fc86bb2011-03-18 23:45:16 +00004129 count=0;
cristy49f37242011-03-22 18:18:23 +00004130 maximum=list->nodes[color].next[0];
cristy6fc86bb2011-03-18 23:45:16 +00004131 do
4132 {
4133 color=list->nodes[color].next[0];
cristy49f37242011-03-22 18:18:23 +00004134 if (color > maximum)
4135 maximum=color;
cristy6fc86bb2011-03-18 23:45:16 +00004136 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004137 } while (count < (ssize_t) pixel_list->length);
cristy49f37242011-03-22 18:18:23 +00004138 channels[channel]=(unsigned short) maximum;
4139 }
cristy4c08aed2011-07-01 19:47:50 +00004140 GetPixelInfo((const Image *) NULL,&pixel);
cristy49f37242011-03-22 18:18:23 +00004141 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4142 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4143 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004144 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4145 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy49f37242011-03-22 18:18:23 +00004146 return(pixel);
4147}
4148
cristy4c08aed2011-07-01 19:47:50 +00004149static PixelInfo GetMeanPixelList(PixelList *pixel_list)
cristy49f37242011-03-22 18:18:23 +00004150{
cristy4c08aed2011-07-01 19:47:50 +00004151 PixelInfo
cristy49f37242011-03-22 18:18:23 +00004152 pixel;
4153
cristy80a99a32011-03-30 01:30:23 +00004154 MagickRealType
4155 sum;
4156
cristy49f37242011-03-22 18:18:23 +00004157 register SkipList
4158 *list;
4159
4160 register ssize_t
4161 channel;
4162
4163 size_t
cristy80a99a32011-03-30 01:30:23 +00004164 color;
cristy49f37242011-03-22 18:18:23 +00004165
4166 ssize_t
4167 count;
4168
4169 unsigned short
4170 channels[ListChannels];
4171
4172 /*
4173 Find the mean value for each of the color.
4174 */
4175 for (channel=0; channel < 5; channel++)
4176 {
4177 list=pixel_list->lists+channel;
4178 color=65536L;
4179 count=0;
cristy80a99a32011-03-30 01:30:23 +00004180 sum=0.0;
cristy49f37242011-03-22 18:18:23 +00004181 do
4182 {
4183 color=list->nodes[color].next[0];
cristy80a99a32011-03-30 01:30:23 +00004184 sum+=(MagickRealType) list->nodes[color].count*color;
cristy49f37242011-03-22 18:18:23 +00004185 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004186 } while (count < (ssize_t) pixel_list->length);
cristy80a99a32011-03-30 01:30:23 +00004187 sum/=pixel_list->length;
4188 channels[channel]=(unsigned short) sum;
cristy6fc86bb2011-03-18 23:45:16 +00004189 }
cristy4c08aed2011-07-01 19:47:50 +00004190 GetPixelInfo((const Image *) NULL,&pixel);
cristy6fc86bb2011-03-18 23:45:16 +00004191 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4192 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4193 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004194 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4195 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy6fc86bb2011-03-18 23:45:16 +00004196 return(pixel);
4197}
4198
cristy4c08aed2011-07-01 19:47:50 +00004199static PixelInfo GetMedianPixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004200{
cristy4c08aed2011-07-01 19:47:50 +00004201 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004202 pixel;
4203
4204 register SkipList
4205 *list;
4206
4207 register ssize_t
4208 channel;
4209
4210 size_t
cristy49f37242011-03-22 18:18:23 +00004211 color;
4212
4213 ssize_t
cristy733678d2011-03-18 21:29:28 +00004214 count;
4215
4216 unsigned short
4217 channels[ListChannels];
4218
4219 /*
4220 Find the median value for each of the color.
4221 */
cristy733678d2011-03-18 21:29:28 +00004222 for (channel=0; channel < 5; channel++)
4223 {
4224 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004225 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004226 count=0;
4227 do
4228 {
4229 color=list->nodes[color].next[0];
4230 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004231 } while (count <= (ssize_t) (pixel_list->length >> 1));
cristy6fc86bb2011-03-18 23:45:16 +00004232 channels[channel]=(unsigned short) color;
4233 }
cristy4c08aed2011-07-01 19:47:50 +00004234 GetPixelInfo((const Image *) NULL,&pixel);
cristy6fc86bb2011-03-18 23:45:16 +00004235 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4236 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4237 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004238 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4239 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy6fc86bb2011-03-18 23:45:16 +00004240 return(pixel);
4241}
4242
cristy4c08aed2011-07-01 19:47:50 +00004243static PixelInfo GetMinimumPixelList(PixelList *pixel_list)
cristy6fc86bb2011-03-18 23:45:16 +00004244{
cristy4c08aed2011-07-01 19:47:50 +00004245 PixelInfo
cristy6fc86bb2011-03-18 23:45:16 +00004246 pixel;
4247
4248 register SkipList
4249 *list;
4250
4251 register ssize_t
4252 channel;
4253
4254 size_t
cristyd76c51e2011-03-26 00:21:26 +00004255 color,
4256 minimum;
cristy6fc86bb2011-03-18 23:45:16 +00004257
cristy49f37242011-03-22 18:18:23 +00004258 ssize_t
4259 count;
4260
cristy6fc86bb2011-03-18 23:45:16 +00004261 unsigned short
cristyd76c51e2011-03-26 00:21:26 +00004262 channels[ListChannels];
cristy6fc86bb2011-03-18 23:45:16 +00004263
4264 /*
4265 Find the minimum value for each of the color.
4266 */
4267 for (channel=0; channel < 5; channel++)
4268 {
4269 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004270 count=0;
cristy6fc86bb2011-03-18 23:45:16 +00004271 color=65536UL;
cristy49f37242011-03-22 18:18:23 +00004272 minimum=list->nodes[color].next[0];
4273 do
4274 {
4275 color=list->nodes[color].next[0];
4276 if (color < minimum)
4277 minimum=color;
4278 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004279 } while (count < (ssize_t) pixel_list->length);
cristy49f37242011-03-22 18:18:23 +00004280 channels[channel]=(unsigned short) minimum;
cristy733678d2011-03-18 21:29:28 +00004281 }
cristy4c08aed2011-07-01 19:47:50 +00004282 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004283 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4284 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4285 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004286 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4287 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy733678d2011-03-18 21:29:28 +00004288 return(pixel);
4289}
4290
cristy4c08aed2011-07-01 19:47:50 +00004291static PixelInfo GetModePixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004292{
cristy4c08aed2011-07-01 19:47:50 +00004293 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004294 pixel;
4295
4296 register SkipList
4297 *list;
4298
4299 register ssize_t
4300 channel;
4301
4302 size_t
4303 color,
cristy733678d2011-03-18 21:29:28 +00004304 max_count,
cristy6fc86bb2011-03-18 23:45:16 +00004305 mode;
cristy733678d2011-03-18 21:29:28 +00004306
cristy49f37242011-03-22 18:18:23 +00004307 ssize_t
4308 count;
4309
cristy733678d2011-03-18 21:29:28 +00004310 unsigned short
4311 channels[5];
4312
4313 /*
glennrp30d2dc62011-06-25 03:17:16 +00004314 Make each pixel the 'predominant color' of the specified neighborhood.
cristy733678d2011-03-18 21:29:28 +00004315 */
cristy733678d2011-03-18 21:29:28 +00004316 for (channel=0; channel < 5; channel++)
4317 {
4318 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004319 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004320 mode=color;
4321 max_count=list->nodes[mode].count;
4322 count=0;
4323 do
4324 {
4325 color=list->nodes[color].next[0];
4326 if (list->nodes[color].count > max_count)
4327 {
4328 mode=color;
4329 max_count=list->nodes[mode].count;
4330 }
4331 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004332 } while (count < (ssize_t) pixel_list->length);
cristy733678d2011-03-18 21:29:28 +00004333 channels[channel]=(unsigned short) mode;
4334 }
cristy4c08aed2011-07-01 19:47:50 +00004335 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004336 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4337 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4338 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004339 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4340 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy733678d2011-03-18 21:29:28 +00004341 return(pixel);
4342}
4343
cristy4c08aed2011-07-01 19:47:50 +00004344static PixelInfo GetNonpeakPixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004345{
cristy4c08aed2011-07-01 19:47:50 +00004346 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004347 pixel;
4348
4349 register SkipList
4350 *list;
4351
4352 register ssize_t
4353 channel;
4354
4355 size_t
cristy733678d2011-03-18 21:29:28 +00004356 color,
cristy733678d2011-03-18 21:29:28 +00004357 next,
4358 previous;
4359
cristy49f37242011-03-22 18:18:23 +00004360 ssize_t
4361 count;
4362
cristy733678d2011-03-18 21:29:28 +00004363 unsigned short
4364 channels[5];
4365
4366 /*
cristy49f37242011-03-22 18:18:23 +00004367 Finds the non peak value for each of the colors.
cristy733678d2011-03-18 21:29:28 +00004368 */
cristy733678d2011-03-18 21:29:28 +00004369 for (channel=0; channel < 5; channel++)
4370 {
4371 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004372 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004373 next=list->nodes[color].next[0];
4374 count=0;
4375 do
4376 {
4377 previous=color;
4378 color=next;
4379 next=list->nodes[color].next[0];
4380 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004381 } while (count <= (ssize_t) (pixel_list->length >> 1));
cristy733678d2011-03-18 21:29:28 +00004382 if ((previous == 65536UL) && (next != 65536UL))
4383 color=next;
4384 else
4385 if ((previous != 65536UL) && (next == 65536UL))
4386 color=previous;
4387 channels[channel]=(unsigned short) color;
4388 }
cristy4c08aed2011-07-01 19:47:50 +00004389 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004390 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4391 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4392 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004393 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4394 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy733678d2011-03-18 21:29:28 +00004395 return(pixel);
4396}
4397
cristy4c08aed2011-07-01 19:47:50 +00004398static PixelInfo GetStandardDeviationPixelList(PixelList *pixel_list)
cristy9a68cbb2011-03-29 00:51:23 +00004399{
cristy4c08aed2011-07-01 19:47:50 +00004400 PixelInfo
cristy9a68cbb2011-03-29 00:51:23 +00004401 pixel;
4402
cristy80a99a32011-03-30 01:30:23 +00004403 MagickRealType
4404 sum,
4405 sum_squared;
4406
cristy9a68cbb2011-03-29 00:51:23 +00004407 register SkipList
4408 *list;
4409
4410 register ssize_t
4411 channel;
4412
4413 size_t
cristy80a99a32011-03-30 01:30:23 +00004414 color;
cristy9a68cbb2011-03-29 00:51:23 +00004415
4416 ssize_t
4417 count;
4418
4419 unsigned short
4420 channels[ListChannels];
4421
4422 /*
cristy80a99a32011-03-30 01:30:23 +00004423 Find the standard-deviation value for each of the color.
cristy9a68cbb2011-03-29 00:51:23 +00004424 */
4425 for (channel=0; channel < 5; channel++)
4426 {
4427 list=pixel_list->lists+channel;
4428 color=65536L;
4429 count=0;
cristy80a99a32011-03-30 01:30:23 +00004430 sum=0.0;
4431 sum_squared=0.0;
cristy9a68cbb2011-03-29 00:51:23 +00004432 do
4433 {
cristy80a99a32011-03-30 01:30:23 +00004434 register ssize_t
4435 i;
4436
cristy9a68cbb2011-03-29 00:51:23 +00004437 color=list->nodes[color].next[0];
cristy80a99a32011-03-30 01:30:23 +00004438 sum+=(MagickRealType) list->nodes[color].count*color;
4439 for (i=0; i < (ssize_t) list->nodes[color].count; i++)
4440 sum_squared+=((MagickRealType) color)*((MagickRealType) color);
cristy9a68cbb2011-03-29 00:51:23 +00004441 count+=list->nodes[color].count;
4442 } while (count < (ssize_t) pixel_list->length);
cristy80a99a32011-03-30 01:30:23 +00004443 sum/=pixel_list->length;
4444 sum_squared/=pixel_list->length;
4445 channels[channel]=(unsigned short) sqrt(sum_squared-(sum*sum));
cristy9a68cbb2011-03-29 00:51:23 +00004446 }
cristy4c08aed2011-07-01 19:47:50 +00004447 GetPixelInfo((const Image *) NULL,&pixel);
cristy9a68cbb2011-03-29 00:51:23 +00004448 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4449 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4450 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004451 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4452 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy9a68cbb2011-03-29 00:51:23 +00004453 return(pixel);
4454}
4455
cristy4c08aed2011-07-01 19:47:50 +00004456static inline void InsertPixelList(const Image *image,const Quantum *pixel,
4457 PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004458{
4459 size_t
4460 signature;
4461
4462 unsigned short
4463 index;
4464
cristy4c08aed2011-07-01 19:47:50 +00004465 index=ScaleQuantumToShort(GetPixelRed(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004466 signature=pixel_list->lists[0].nodes[index].signature;
4467 if (signature == pixel_list->signature)
4468 pixel_list->lists[0].nodes[index].count++;
4469 else
4470 AddNodePixelList(pixel_list,0,index);
cristy4c08aed2011-07-01 19:47:50 +00004471 index=ScaleQuantumToShort(GetPixelGreen(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004472 signature=pixel_list->lists[1].nodes[index].signature;
4473 if (signature == pixel_list->signature)
4474 pixel_list->lists[1].nodes[index].count++;
4475 else
4476 AddNodePixelList(pixel_list,1,index);
cristy4c08aed2011-07-01 19:47:50 +00004477 index=ScaleQuantumToShort(GetPixelBlue(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004478 signature=pixel_list->lists[2].nodes[index].signature;
4479 if (signature == pixel_list->signature)
4480 pixel_list->lists[2].nodes[index].count++;
4481 else
4482 AddNodePixelList(pixel_list,2,index);
cristy4c08aed2011-07-01 19:47:50 +00004483 index=ScaleQuantumToShort(GetPixelAlpha(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004484 signature=pixel_list->lists[3].nodes[index].signature;
4485 if (signature == pixel_list->signature)
4486 pixel_list->lists[3].nodes[index].count++;
4487 else
4488 AddNodePixelList(pixel_list,3,index);
4489 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00004490 index=ScaleQuantumToShort(GetPixelBlack(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004491 signature=pixel_list->lists[4].nodes[index].signature;
4492 if (signature == pixel_list->signature)
4493 pixel_list->lists[4].nodes[index].count++;
4494 else
4495 AddNodePixelList(pixel_list,4,index);
4496}
4497
cristy80c99742011-04-04 14:46:39 +00004498static inline MagickRealType MagickAbsoluteValue(const MagickRealType x)
4499{
4500 if (x < 0)
4501 return(-x);
4502 return(x);
4503}
4504
cristy733678d2011-03-18 21:29:28 +00004505static void ResetPixelList(PixelList *pixel_list)
4506{
4507 int
4508 level;
4509
4510 register ListNode
4511 *root;
4512
4513 register SkipList
4514 *list;
4515
4516 register ssize_t
4517 channel;
4518
4519 /*
4520 Reset the skip-list.
4521 */
4522 for (channel=0; channel < 5; channel++)
4523 {
4524 list=pixel_list->lists+channel;
4525 root=list->nodes+65536UL;
4526 list->level=0;
4527 for (level=0; level < 9; level++)
4528 root->next[level]=65536UL;
4529 }
4530 pixel_list->seed=pixel_list->signature++;
4531}
4532
cristy0834d642011-03-18 18:26:08 +00004533MagickExport Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00004534 const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00004535{
cristy3cba8ca2011-03-19 01:29:12 +00004536#define StatisticWidth \
cristyd76c51e2011-03-26 00:21:26 +00004537 (width == 0 ? GetOptimalKernelWidth2D((double) width,0.5) : width)
cristy3cba8ca2011-03-19 01:29:12 +00004538#define StatisticHeight \
cristyd76c51e2011-03-26 00:21:26 +00004539 (height == 0 ? GetOptimalKernelWidth2D((double) height,0.5) : height)
cristy0834d642011-03-18 18:26:08 +00004540#define StatisticImageTag "Statistic/Image"
4541
4542 CacheView
4543 *image_view,
4544 *statistic_view;
4545
4546 Image
4547 *statistic_image;
4548
4549 MagickBooleanType
4550 status;
4551
4552 MagickOffsetType
4553 progress;
4554
4555 PixelList
4556 **restrict pixel_list;
4557
cristy0834d642011-03-18 18:26:08 +00004558 ssize_t
4559 y;
4560
4561 /*
4562 Initialize statistics image attributes.
4563 */
4564 assert(image != (Image *) NULL);
4565 assert(image->signature == MagickSignature);
4566 if (image->debug != MagickFalse)
4567 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4568 assert(exception != (ExceptionInfo *) NULL);
4569 assert(exception->signature == MagickSignature);
cristy0834d642011-03-18 18:26:08 +00004570 statistic_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4571 exception);
4572 if (statistic_image == (Image *) NULL)
4573 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00004574 if (SetImageStorageClass(statistic_image,DirectClass,exception) == MagickFalse)
cristy0834d642011-03-18 18:26:08 +00004575 {
cristy0834d642011-03-18 18:26:08 +00004576 statistic_image=DestroyImage(statistic_image);
4577 return((Image *) NULL);
4578 }
cristy6fc86bb2011-03-18 23:45:16 +00004579 pixel_list=AcquirePixelListThreadSet(StatisticWidth,StatisticHeight);
cristy0834d642011-03-18 18:26:08 +00004580 if (pixel_list == (PixelList **) NULL)
4581 {
4582 statistic_image=DestroyImage(statistic_image);
4583 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
4584 }
4585 /*
cristy8d752042011-03-19 01:00:36 +00004586 Make each pixel the min / max / median / mode / etc. of the neighborhood.
cristy0834d642011-03-18 18:26:08 +00004587 */
4588 status=MagickTrue;
4589 progress=0;
4590 image_view=AcquireCacheView(image);
4591 statistic_view=AcquireCacheView(statistic_image);
4592#if defined(MAGICKCORE_OPENMP_SUPPORT)
4593 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
4594#endif
4595 for (y=0; y < (ssize_t) statistic_image->rows; y++)
4596 {
4597 const int
4598 id = GetOpenMPThreadId();
4599
cristy4c08aed2011-07-01 19:47:50 +00004600 register const Quantum
cristy0834d642011-03-18 18:26:08 +00004601 *restrict p;
4602
cristy4c08aed2011-07-01 19:47:50 +00004603 register Quantum
cristy0834d642011-03-18 18:26:08 +00004604 *restrict q;
4605
4606 register ssize_t
4607 x;
4608
4609 if (status == MagickFalse)
4610 continue;
cristy6fc86bb2011-03-18 23:45:16 +00004611 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) StatisticWidth/2L),y-
4612 (ssize_t) (StatisticHeight/2L),image->columns+StatisticWidth,
4613 StatisticHeight,exception);
cristy3cba8ca2011-03-19 01:29:12 +00004614 q=QueueCacheViewAuthenticPixels(statistic_view,0,y,statistic_image->columns, 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00004615 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy0834d642011-03-18 18:26:08 +00004616 {
4617 status=MagickFalse;
4618 continue;
4619 }
cristy0834d642011-03-18 18:26:08 +00004620 for (x=0; x < (ssize_t) statistic_image->columns; x++)
4621 {
cristy4c08aed2011-07-01 19:47:50 +00004622 PixelInfo
cristy0834d642011-03-18 18:26:08 +00004623 pixel;
4624
cristy4c08aed2011-07-01 19:47:50 +00004625 register const Quantum
cristy6e3026a2011-03-19 00:54:38 +00004626 *restrict r;
4627
cristy0834d642011-03-18 18:26:08 +00004628 register ssize_t
4629 u,
4630 v;
4631
4632 r=p;
cristy0834d642011-03-18 18:26:08 +00004633 ResetPixelList(pixel_list[id]);
cristy6e4c3292011-03-19 00:53:55 +00004634 for (v=0; v < (ssize_t) StatisticHeight; v++)
cristy0834d642011-03-18 18:26:08 +00004635 {
cristy6e4c3292011-03-19 00:53:55 +00004636 for (u=0; u < (ssize_t) StatisticWidth; u++)
cristyed231572011-07-14 02:18:59 +00004637 InsertPixelList(image,r+u*GetPixelChannels(image),pixel_list[id]);
4638 r+=(image->columns+StatisticWidth)*GetPixelChannels(image);
cristy0834d642011-03-18 18:26:08 +00004639 }
cristy4c08aed2011-07-01 19:47:50 +00004640 GetPixelInfo(image,&pixel);
cristy490408a2011-07-07 14:42:05 +00004641 SetPixelInfo(image,p+(StatisticWidth*StatisticHeight/2)*
cristyed231572011-07-14 02:18:59 +00004642 GetPixelChannels(image),&pixel);
cristy0834d642011-03-18 18:26:08 +00004643 switch (type)
4644 {
cristy80c99742011-04-04 14:46:39 +00004645 case GradientStatistic:
4646 {
cristy4c08aed2011-07-01 19:47:50 +00004647 PixelInfo
cristy80c99742011-04-04 14:46:39 +00004648 maximum,
4649 minimum;
4650
4651 minimum=GetMinimumPixelList(pixel_list[id]);
4652 maximum=GetMaximumPixelList(pixel_list[id]);
4653 pixel.red=MagickAbsoluteValue(maximum.red-minimum.red);
4654 pixel.green=MagickAbsoluteValue(maximum.green-minimum.green);
4655 pixel.blue=MagickAbsoluteValue(maximum.blue-minimum.blue);
cristy4c08aed2011-07-01 19:47:50 +00004656 pixel.alpha=MagickAbsoluteValue(maximum.alpha-minimum.alpha);
cristy80c99742011-04-04 14:46:39 +00004657 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00004658 pixel.black=MagickAbsoluteValue(maximum.black-minimum.black);
cristy80c99742011-04-04 14:46:39 +00004659 break;
4660 }
cristy6fc86bb2011-03-18 23:45:16 +00004661 case MaximumStatistic:
4662 {
4663 pixel=GetMaximumPixelList(pixel_list[id]);
4664 break;
4665 }
cristy49f37242011-03-22 18:18:23 +00004666 case MeanStatistic:
4667 {
4668 pixel=GetMeanPixelList(pixel_list[id]);
4669 break;
4670 }
cristyf2ad14a2011-03-18 18:57:25 +00004671 case MedianStatistic:
cristy6fc86bb2011-03-18 23:45:16 +00004672 default:
cristyf2ad14a2011-03-18 18:57:25 +00004673 {
4674 pixel=GetMedianPixelList(pixel_list[id]);
4675 break;
4676 }
cristy6fc86bb2011-03-18 23:45:16 +00004677 case MinimumStatistic:
4678 {
4679 pixel=GetMinimumPixelList(pixel_list[id]);
4680 break;
4681 }
cristyf2ad14a2011-03-18 18:57:25 +00004682 case ModeStatistic:
4683 {
4684 pixel=GetModePixelList(pixel_list[id]);
4685 break;
4686 }
4687 case NonpeakStatistic:
4688 {
4689 pixel=GetNonpeakPixelList(pixel_list[id]);
4690 break;
4691 }
cristy9a68cbb2011-03-29 00:51:23 +00004692 case StandardDeviationStatistic:
4693 {
4694 pixel=GetStandardDeviationPixelList(pixel_list[id]);
4695 break;
4696 }
cristy0834d642011-03-18 18:26:08 +00004697 }
cristyed231572011-07-14 02:18:59 +00004698 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00004699 SetPixelRed(statistic_image,ClampToQuantum(pixel.red),q);
cristyed231572011-07-14 02:18:59 +00004700 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00004701 SetPixelGreen(statistic_image,ClampToQuantum(pixel.green),q);
cristyed231572011-07-14 02:18:59 +00004702 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00004703 SetPixelBlue(statistic_image,ClampToQuantum(pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00004704 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy0834d642011-03-18 18:26:08 +00004705 (image->colorspace == CMYKColorspace))
cristy490408a2011-07-07 14:42:05 +00004706 SetPixelBlack(statistic_image,ClampToQuantum(pixel.black),q);
cristyed231572011-07-14 02:18:59 +00004707 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00004708 (image->matte != MagickFalse))
cristy490408a2011-07-07 14:42:05 +00004709 SetPixelAlpha(statistic_image,ClampToQuantum(pixel.alpha),q);
cristyed231572011-07-14 02:18:59 +00004710 p+=GetPixelChannels(image);
4711 q+=GetPixelChannels(statistic_image);
cristy0834d642011-03-18 18:26:08 +00004712 }
4713 if (SyncCacheViewAuthenticPixels(statistic_view,exception) == MagickFalse)
4714 status=MagickFalse;
4715 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4716 {
4717 MagickBooleanType
4718 proceed;
4719
4720#if defined(MAGICKCORE_OPENMP_SUPPORT)
4721 #pragma omp critical (MagickCore_StatisticImage)
4722#endif
4723 proceed=SetImageProgress(image,StatisticImageTag,progress++,
4724 image->rows);
4725 if (proceed == MagickFalse)
4726 status=MagickFalse;
4727 }
4728 }
4729 statistic_view=DestroyCacheView(statistic_view);
4730 image_view=DestroyCacheView(image_view);
4731 pixel_list=DestroyPixelListThreadSet(pixel_list);
4732 return(statistic_image);
4733}
4734
4735/*
4736%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4737% %
4738% %
4739% %
cristy3ed852e2009-09-05 21:47:34 +00004740% U n s h a r p M a s k I m a g e %
4741% %
4742% %
4743% %
4744%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4745%
4746% UnsharpMaskImage() sharpens one or more image channels. We convolve the
4747% image with a Gaussian operator of the given radius and standard deviation
4748% (sigma). For reasonable results, radius should be larger than sigma. Use a
4749% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
4750%
4751% The format of the UnsharpMaskImage method is:
4752%
4753% Image *UnsharpMaskImage(const Image *image,const double radius,
4754% const double sigma,const double amount,const double threshold,
4755% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00004756%
4757% A description of each parameter follows:
4758%
4759% o image: the image.
4760%
cristy3ed852e2009-09-05 21:47:34 +00004761% o radius: the radius of the Gaussian, in pixels, not counting the center
4762% pixel.
4763%
4764% o sigma: the standard deviation of the Gaussian, in pixels.
4765%
4766% o amount: the percentage of the difference between the original and the
4767% blur image that is added back into the original.
4768%
4769% o threshold: the threshold in pixels needed to apply the diffence amount.
4770%
4771% o exception: return any errors or warnings in this structure.
4772%
4773*/
cristyf4ad9df2011-07-08 16:49:03 +00004774MagickExport Image *UnsharpMaskImage(const Image *image,
4775 const double radius,const double sigma,const double amount,
4776 const double threshold,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00004777{
4778#define SharpenImageTag "Sharpen/Image"
4779
cristyc4c8d132010-01-07 01:58:38 +00004780 CacheView
4781 *image_view,
4782 *unsharp_view;
4783
cristy3ed852e2009-09-05 21:47:34 +00004784 Image
4785 *unsharp_image;
4786
cristy3ed852e2009-09-05 21:47:34 +00004787 MagickBooleanType
4788 status;
4789
cristybb503372010-05-27 20:51:26 +00004790 MagickOffsetType
4791 progress;
4792
cristy4c08aed2011-07-01 19:47:50 +00004793 PixelInfo
cristyddd82202009-11-03 20:14:50 +00004794 bias;
cristy3ed852e2009-09-05 21:47:34 +00004795
4796 MagickRealType
4797 quantum_threshold;
4798
cristybb503372010-05-27 20:51:26 +00004799 ssize_t
4800 y;
4801
cristy3ed852e2009-09-05 21:47:34 +00004802 assert(image != (const Image *) NULL);
4803 assert(image->signature == MagickSignature);
4804 if (image->debug != MagickFalse)
4805 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4806 assert(exception != (ExceptionInfo *) NULL);
cristyf4ad9df2011-07-08 16:49:03 +00004807 unsharp_image=BlurImage(image,radius,sigma,exception);
cristy3ed852e2009-09-05 21:47:34 +00004808 if (unsharp_image == (Image *) NULL)
4809 return((Image *) NULL);
4810 quantum_threshold=(MagickRealType) QuantumRange*threshold;
4811 /*
4812 Unsharp-mask image.
4813 */
4814 status=MagickTrue;
4815 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00004816 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00004817 image_view=AcquireCacheView(image);
4818 unsharp_view=AcquireCacheView(unsharp_image);
cristyb5d5f722009-11-04 03:03:49 +00004819#if defined(MAGICKCORE_OPENMP_SUPPORT)
4820 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004821#endif
cristybb503372010-05-27 20:51:26 +00004822 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00004823 {
cristy4c08aed2011-07-01 19:47:50 +00004824 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00004825 pixel;
4826
cristy4c08aed2011-07-01 19:47:50 +00004827 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00004828 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00004829
cristy4c08aed2011-07-01 19:47:50 +00004830 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00004831 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004832
cristy117ff172010-08-15 21:35:32 +00004833 register ssize_t
4834 x;
4835
cristy3ed852e2009-09-05 21:47:34 +00004836 if (status == MagickFalse)
4837 continue;
4838 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
4839 q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
4840 exception);
cristy4c08aed2011-07-01 19:47:50 +00004841 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00004842 {
4843 status=MagickFalse;
4844 continue;
4845 }
cristyddd82202009-11-03 20:14:50 +00004846 pixel=bias;
cristybb503372010-05-27 20:51:26 +00004847 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004848 {
cristyed231572011-07-14 02:18:59 +00004849 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00004850 {
cristy4c08aed2011-07-01 19:47:50 +00004851 pixel.red=GetPixelRed(image,p)-(MagickRealType) GetPixelRed(image,q);
cristy3ed852e2009-09-05 21:47:34 +00004852 if (fabs(2.0*pixel.red) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00004853 pixel.red=(MagickRealType) GetPixelRed(image,p);
cristy3ed852e2009-09-05 21:47:34 +00004854 else
cristy4c08aed2011-07-01 19:47:50 +00004855 pixel.red=(MagickRealType) GetPixelRed(image,p)+(pixel.red*amount);
4856 SetPixelRed(unsharp_image,ClampToQuantum(pixel.red),q);
cristy3ed852e2009-09-05 21:47:34 +00004857 }
cristyed231572011-07-14 02:18:59 +00004858 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00004859 {
cristy4c08aed2011-07-01 19:47:50 +00004860 pixel.green=GetPixelGreen(image,p)-
4861 (MagickRealType) GetPixelGreen(image,q);
cristy3ed852e2009-09-05 21:47:34 +00004862 if (fabs(2.0*pixel.green) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00004863 pixel.green=(MagickRealType)
4864 GetPixelGreen(image,p);
cristy3ed852e2009-09-05 21:47:34 +00004865 else
cristy4c08aed2011-07-01 19:47:50 +00004866 pixel.green=(MagickRealType)
4867 GetPixelGreen(image,p)+
4868 (pixel.green*amount);
4869 SetPixelGreen(unsharp_image,
4870 ClampToQuantum(pixel.green),q);
cristy3ed852e2009-09-05 21:47:34 +00004871 }
cristyed231572011-07-14 02:18:59 +00004872 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00004873 {
cristy4c08aed2011-07-01 19:47:50 +00004874 pixel.blue=GetPixelBlue(image,p)-
4875 (MagickRealType) GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00004876 if (fabs(2.0*pixel.blue) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00004877 pixel.blue=(MagickRealType)
4878 GetPixelBlue(image,p);
cristy3ed852e2009-09-05 21:47:34 +00004879 else
cristy4c08aed2011-07-01 19:47:50 +00004880 pixel.blue=(MagickRealType)
4881 GetPixelBlue(image,p)+(pixel.blue*amount);
4882 SetPixelBlue(unsharp_image,
4883 ClampToQuantum(pixel.blue),q);
cristy3ed852e2009-09-05 21:47:34 +00004884 }
cristyed231572011-07-14 02:18:59 +00004885 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00004886 (image->colorspace == CMYKColorspace))
4887 {
cristy4c08aed2011-07-01 19:47:50 +00004888 pixel.black=GetPixelBlack(image,p)-
4889 (MagickRealType) GetPixelBlack(image,q);
4890 if (fabs(2.0*pixel.black) < quantum_threshold)
4891 pixel.black=(MagickRealType)
4892 GetPixelBlack(image,p);
cristy3ed852e2009-09-05 21:47:34 +00004893 else
cristy4c08aed2011-07-01 19:47:50 +00004894 pixel.black=(MagickRealType)
4895 GetPixelBlack(image,p)+(pixel.black*
4896 amount);
4897 SetPixelBlack(unsharp_image,
4898 ClampToQuantum(pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00004899 }
cristyed231572011-07-14 02:18:59 +00004900 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00004901 {
4902 pixel.alpha=GetPixelAlpha(image,p)-
4903 (MagickRealType) GetPixelAlpha(image,q);
4904 if (fabs(2.0*pixel.alpha) < quantum_threshold)
4905 pixel.alpha=(MagickRealType)
4906 GetPixelAlpha(image,p);
4907 else
4908 pixel.alpha=GetPixelAlpha(image,p)+
4909 (pixel.alpha*amount);
4910 SetPixelAlpha(unsharp_image,
4911 ClampToQuantum(pixel.alpha),q);
4912 }
cristyed231572011-07-14 02:18:59 +00004913 p+=GetPixelChannels(image);
4914 q+=GetPixelChannels(unsharp_image);
cristy3ed852e2009-09-05 21:47:34 +00004915 }
4916 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
4917 status=MagickFalse;
4918 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4919 {
4920 MagickBooleanType
4921 proceed;
4922
cristyb5d5f722009-11-04 03:03:49 +00004923#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00004924 #pragma omp critical (MagickCore_UnsharpMaskImage)
cristy3ed852e2009-09-05 21:47:34 +00004925#endif
4926 proceed=SetImageProgress(image,SharpenImageTag,progress++,image->rows);
4927 if (proceed == MagickFalse)
4928 status=MagickFalse;
4929 }
4930 }
4931 unsharp_image->type=image->type;
4932 unsharp_view=DestroyCacheView(unsharp_view);
4933 image_view=DestroyCacheView(image_view);
4934 if (status == MagickFalse)
4935 unsharp_image=DestroyImage(unsharp_image);
4936 return(unsharp_image);
4937}