blob: 8b84b91e756ea456b57546166e6bd167bdda74cf [file] [log] [blame]
cristy3ed852e2009-09-05 21:47:34 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% EEEEE FFFFF FFFFF EEEEE CCCC TTTTT %
7% E F F E C T %
8% EEE FFF FFF EEE C T %
9% E F F E C T %
10% EEEEE F F EEEEE CCCC T %
11% %
12% %
13% MagickCore Image Effects Methods %
14% %
15% Software Design %
16% John Cristy %
17% October 1996 %
18% %
19% %
cristy7e41fe82010-12-04 23:12:08 +000020% Copyright 1999-2011 ImageMagick Studio LLC, a non-profit organization %
cristy3ed852e2009-09-05 21:47:34 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
36%
37%
38*/
39
40/*
41 Include declarations.
42*/
cristy4c08aed2011-07-01 19:47:50 +000043#include "MagickCore/studio.h"
44#include "MagickCore/accelerate.h"
45#include "MagickCore/blob.h"
46#include "MagickCore/cache-view.h"
47#include "MagickCore/color.h"
48#include "MagickCore/color-private.h"
49#include "MagickCore/colorspace.h"
50#include "MagickCore/constitute.h"
51#include "MagickCore/decorate.h"
52#include "MagickCore/draw.h"
53#include "MagickCore/enhance.h"
54#include "MagickCore/exception.h"
55#include "MagickCore/exception-private.h"
56#include "MagickCore/effect.h"
57#include "MagickCore/fx.h"
58#include "MagickCore/gem.h"
59#include "MagickCore/geometry.h"
60#include "MagickCore/image-private.h"
61#include "MagickCore/list.h"
62#include "MagickCore/log.h"
63#include "MagickCore/memory_.h"
64#include "MagickCore/monitor.h"
65#include "MagickCore/monitor-private.h"
66#include "MagickCore/montage.h"
67#include "MagickCore/morphology.h"
68#include "MagickCore/paint.h"
69#include "MagickCore/pixel-accessor.h"
70#include "MagickCore/property.h"
71#include "MagickCore/quantize.h"
72#include "MagickCore/quantum.h"
73#include "MagickCore/quantum-private.h"
74#include "MagickCore/random_.h"
75#include "MagickCore/random-private.h"
76#include "MagickCore/resample.h"
77#include "MagickCore/resample-private.h"
78#include "MagickCore/resize.h"
79#include "MagickCore/resource_.h"
80#include "MagickCore/segment.h"
81#include "MagickCore/shear.h"
82#include "MagickCore/signature-private.h"
83#include "MagickCore/string_.h"
84#include "MagickCore/thread-private.h"
85#include "MagickCore/transform.h"
86#include "MagickCore/threshold.h"
cristy3ed852e2009-09-05 21:47:34 +000087
88/*
89%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
90% %
91% %
92% %
93% A d a p t i v e B l u r I m a g e %
94% %
95% %
96% %
97%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
98%
99% AdaptiveBlurImage() adaptively blurs the image by blurring less
100% intensely near image edges and more intensely far from edges. We blur the
101% image with a Gaussian operator of the given radius and standard deviation
102% (sigma). For reasonable results, radius should be larger than sigma. Use a
103% radius of 0 and AdaptiveBlurImage() selects a suitable radius for you.
104%
105% The format of the AdaptiveBlurImage method is:
106%
107% Image *AdaptiveBlurImage(const Image *image,const double radius,
108% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000109%
110% A description of each parameter follows:
111%
112% o image: the image.
113%
cristy3ed852e2009-09-05 21:47:34 +0000114% o radius: the radius of the Gaussian, in pixels, not counting the center
115% pixel.
116%
117% o sigma: the standard deviation of the Laplacian, in pixels.
118%
119% o exception: return any errors or warnings in this structure.
120%
121*/
122
cristyf89cb1d2011-07-07 01:24:37 +0000123MagickExport MagickBooleanType AdaptiveLevelImage(Image *image,
124 const char *levels)
125{
126 double
127 black_point,
128 gamma,
129 white_point;
130
131 GeometryInfo
132 geometry_info;
133
134 MagickBooleanType
135 status;
136
137 MagickStatusType
138 flags;
139
140 /*
141 Parse levels.
142 */
143 if (levels == (char *) NULL)
144 return(MagickFalse);
145 flags=ParseGeometry(levels,&geometry_info);
146 black_point=geometry_info.rho;
147 white_point=(double) QuantumRange;
148 if ((flags & SigmaValue) != 0)
149 white_point=geometry_info.sigma;
150 gamma=1.0;
151 if ((flags & XiValue) != 0)
152 gamma=geometry_info.xi;
153 if ((flags & PercentValue) != 0)
154 {
155 black_point*=(double) image->columns*image->rows/100.0;
156 white_point*=(double) image->columns*image->rows/100.0;
157 }
158 if ((flags & SigmaValue) == 0)
159 white_point=(double) QuantumRange-black_point;
160 if ((flags & AspectValue ) == 0)
161 status=LevelImage(image,black_point,white_point,gamma);
162 else
163 status=LevelizeImage(image,black_point,white_point,gamma);
164 return(status);
165}
166
cristyf4ad9df2011-07-08 16:49:03 +0000167MagickExport Image *AdaptiveBlurImage(const Image *image,
168 const double radius,const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000169{
170#define AdaptiveBlurImageTag "Convolve/Image"
171#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
172
cristyc4c8d132010-01-07 01:58:38 +0000173 CacheView
174 *blur_view,
175 *edge_view,
176 *image_view;
177
cristy3ed852e2009-09-05 21:47:34 +0000178 double
cristy47e00502009-12-17 19:19:57 +0000179 **kernel,
180 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000181
182 Image
183 *blur_image,
184 *edge_image,
185 *gaussian_image;
186
cristy3ed852e2009-09-05 21:47:34 +0000187 MagickBooleanType
188 status;
189
cristybb503372010-05-27 20:51:26 +0000190 MagickOffsetType
191 progress;
192
cristy4c08aed2011-07-01 19:47:50 +0000193 PixelInfo
cristyddd82202009-11-03 20:14:50 +0000194 bias;
cristy3ed852e2009-09-05 21:47:34 +0000195
cristybb503372010-05-27 20:51:26 +0000196 register ssize_t
cristy47e00502009-12-17 19:19:57 +0000197 i;
cristy3ed852e2009-09-05 21:47:34 +0000198
cristybb503372010-05-27 20:51:26 +0000199 size_t
cristy3ed852e2009-09-05 21:47:34 +0000200 width;
201
cristybb503372010-05-27 20:51:26 +0000202 ssize_t
203 j,
204 k,
205 u,
206 v,
207 y;
208
cristy3ed852e2009-09-05 21:47:34 +0000209 assert(image != (const Image *) NULL);
210 assert(image->signature == MagickSignature);
211 if (image->debug != MagickFalse)
212 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
213 assert(exception != (ExceptionInfo *) NULL);
214 assert(exception->signature == MagickSignature);
215 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
216 if (blur_image == (Image *) NULL)
217 return((Image *) NULL);
218 if (fabs(sigma) <= MagickEpsilon)
219 return(blur_image);
220 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
221 {
222 InheritException(exception,&blur_image->exception);
223 blur_image=DestroyImage(blur_image);
224 return((Image *) NULL);
225 }
226 /*
227 Edge detect the image brighness channel, level, blur, and level again.
228 */
229 edge_image=EdgeImage(image,radius,exception);
230 if (edge_image == (Image *) NULL)
231 {
232 blur_image=DestroyImage(blur_image);
233 return((Image *) NULL);
234 }
cristyf89cb1d2011-07-07 01:24:37 +0000235 (void) AdaptiveLevelImage(edge_image,"20%,95%");
cristy3ed852e2009-09-05 21:47:34 +0000236 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,exception);
237 if (gaussian_image != (Image *) NULL)
238 {
239 edge_image=DestroyImage(edge_image);
240 edge_image=gaussian_image;
241 }
cristyf89cb1d2011-07-07 01:24:37 +0000242 (void) AdaptiveLevelImage(edge_image,"10%,95%");
cristy3ed852e2009-09-05 21:47:34 +0000243 /*
244 Create a set of kernels from maximum (radius,sigma) to minimum.
245 */
246 width=GetOptimalKernelWidth2D(radius,sigma);
247 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
248 if (kernel == (double **) NULL)
249 {
250 edge_image=DestroyImage(edge_image);
251 blur_image=DestroyImage(blur_image);
252 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
253 }
254 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
cristybb503372010-05-27 20:51:26 +0000255 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000256 {
257 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
258 sizeof(**kernel));
259 if (kernel[i] == (double *) NULL)
260 break;
cristy47e00502009-12-17 19:19:57 +0000261 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000262 j=(ssize_t) (width-i)/2;
cristy47e00502009-12-17 19:19:57 +0000263 k=0;
264 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000265 {
cristy47e00502009-12-17 19:19:57 +0000266 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000267 {
cristy4205a3c2010-09-12 20:19:59 +0000268 kernel[i][k]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
269 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +0000270 normalize+=kernel[i][k];
271 k++;
cristy3ed852e2009-09-05 21:47:34 +0000272 }
273 }
cristy3ed852e2009-09-05 21:47:34 +0000274 if (fabs(normalize) <= MagickEpsilon)
275 normalize=1.0;
276 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000277 for (k=0; k < (j*j); k++)
278 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000279 }
cristybb503372010-05-27 20:51:26 +0000280 if (i < (ssize_t) width)
cristy3ed852e2009-09-05 21:47:34 +0000281 {
282 for (i-=2; i >= 0; i-=2)
283 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
284 kernel=(double **) RelinquishMagickMemory(kernel);
285 edge_image=DestroyImage(edge_image);
286 blur_image=DestroyImage(blur_image);
287 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
288 }
289 /*
290 Adaptively blur image.
291 */
292 status=MagickTrue;
293 progress=0;
cristy4c08aed2011-07-01 19:47:50 +0000294 GetPixelInfo(image,&bias);
295 SetPixelInfoBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000296 image_view=AcquireCacheView(image);
297 edge_view=AcquireCacheView(edge_image);
298 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000299#if defined(MAGICKCORE_OPENMP_SUPPORT)
300 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000301#endif
cristybb503372010-05-27 20:51:26 +0000302 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000303 {
cristy4c08aed2011-07-01 19:47:50 +0000304 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000305 *restrict p,
306 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000307
cristy4c08aed2011-07-01 19:47:50 +0000308 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000309 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000310
cristy117ff172010-08-15 21:35:32 +0000311 register ssize_t
312 x;
313
cristy3ed852e2009-09-05 21:47:34 +0000314 if (status == MagickFalse)
315 continue;
316 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
317 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
318 exception);
cristy4c08aed2011-07-01 19:47:50 +0000319 if ((r == (const Quantum *) NULL) || (q == (const Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000320 {
321 status=MagickFalse;
322 continue;
323 }
cristybb503372010-05-27 20:51:26 +0000324 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000325 {
cristy4c08aed2011-07-01 19:47:50 +0000326 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000327 pixel;
328
329 MagickRealType
330 alpha,
331 gamma;
332
333 register const double
cristyc47d1f82009-11-26 01:44:43 +0000334 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000335
cristybb503372010-05-27 20:51:26 +0000336 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000337 i,
338 u,
339 v;
340
341 gamma=0.0;
cristy4c08aed2011-07-01 19:47:50 +0000342 i=(ssize_t) ceil((double) width*QuantumScale*
343 GetPixelIntensity(edge_image,r)-0.5);
cristy3ed852e2009-09-05 21:47:34 +0000344 if (i < 0)
345 i=0;
346 else
cristybb503372010-05-27 20:51:26 +0000347 if (i > (ssize_t) width)
348 i=(ssize_t) width;
cristy3ed852e2009-09-05 21:47:34 +0000349 if ((i & 0x01) != 0)
350 i--;
cristya21afde2010-07-02 00:45:40 +0000351 p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) (width-i)/2L),y-
352 (ssize_t) ((width-i)/2L),width-i,width-i,exception);
cristy4c08aed2011-07-01 19:47:50 +0000353 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000354 break;
cristyddd82202009-11-03 20:14:50 +0000355 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +0000356 k=kernel[i];
cristybb503372010-05-27 20:51:26 +0000357 for (v=0; v < (ssize_t) (width-i); v++)
cristy3ed852e2009-09-05 21:47:34 +0000358 {
cristybb503372010-05-27 20:51:26 +0000359 for (u=0; u < (ssize_t) (width-i); u++)
cristy3ed852e2009-09-05 21:47:34 +0000360 {
361 alpha=1.0;
cristyed231572011-07-14 02:18:59 +0000362 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000363 (image->matte != MagickFalse))
cristy4c08aed2011-07-01 19:47:50 +0000364 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,p));
cristyed231572011-07-14 02:18:59 +0000365 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000366 pixel.red+=(*k)*alpha*GetPixelRed(image,p);
cristyed231572011-07-14 02:18:59 +0000367 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000368 pixel.green+=(*k)*alpha*GetPixelGreen(image,p);
cristyed231572011-07-14 02:18:59 +0000369 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000370 pixel.blue+=(*k)*alpha*GetPixelBlue(image,p);
cristyed231572011-07-14 02:18:59 +0000371 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000372 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +0000373 pixel.black+=(*k)*alpha*GetPixelBlack(image,p);
cristyed231572011-07-14 02:18:59 +0000374 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000375 pixel.alpha+=(*k)*GetPixelAlpha(image,p);
cristy3ed852e2009-09-05 21:47:34 +0000376 gamma+=(*k)*alpha;
377 k++;
cristyed231572011-07-14 02:18:59 +0000378 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000379 }
380 }
381 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +0000382 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000383 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +0000384 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000385 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +0000386 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000387 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +0000388 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000389 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +0000390 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristyed231572011-07-14 02:18:59 +0000391 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000392 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristyed231572011-07-14 02:18:59 +0000393 q+=GetPixelChannels(blur_image);
394 r+=GetPixelChannels(edge_image);
cristy3ed852e2009-09-05 21:47:34 +0000395 }
396 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
397 status=MagickFalse;
398 if (image->progress_monitor != (MagickProgressMonitor) NULL)
399 {
400 MagickBooleanType
401 proceed;
402
cristyb5d5f722009-11-04 03:03:49 +0000403#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +0000404 #pragma omp critical (MagickCore_AdaptiveBlurImage)
cristy3ed852e2009-09-05 21:47:34 +0000405#endif
406 proceed=SetImageProgress(image,AdaptiveBlurImageTag,progress++,
407 image->rows);
408 if (proceed == MagickFalse)
409 status=MagickFalse;
410 }
411 }
412 blur_image->type=image->type;
413 blur_view=DestroyCacheView(blur_view);
414 edge_view=DestroyCacheView(edge_view);
415 image_view=DestroyCacheView(image_view);
416 edge_image=DestroyImage(edge_image);
cristybb503372010-05-27 20:51:26 +0000417 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000418 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
419 kernel=(double **) RelinquishMagickMemory(kernel);
420 if (status == MagickFalse)
421 blur_image=DestroyImage(blur_image);
422 return(blur_image);
423}
424
425/*
426%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
427% %
428% %
429% %
430% A d a p t i v e S h a r p e n I m a g e %
431% %
432% %
433% %
434%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
435%
436% AdaptiveSharpenImage() adaptively sharpens the image by sharpening more
437% intensely near image edges and less intensely far from edges. We sharpen the
438% image with a Gaussian operator of the given radius and standard deviation
439% (sigma). For reasonable results, radius should be larger than sigma. Use a
440% radius of 0 and AdaptiveSharpenImage() selects a suitable radius for you.
441%
442% The format of the AdaptiveSharpenImage method is:
443%
444% Image *AdaptiveSharpenImage(const Image *image,const double radius,
445% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000446%
447% A description of each parameter follows:
448%
449% o image: the image.
450%
cristy3ed852e2009-09-05 21:47:34 +0000451% o radius: the radius of the Gaussian, in pixels, not counting the center
452% pixel.
453%
454% o sigma: the standard deviation of the Laplacian, in pixels.
455%
456% o exception: return any errors or warnings in this structure.
457%
458*/
cristy3ed852e2009-09-05 21:47:34 +0000459MagickExport Image *AdaptiveSharpenImage(const Image *image,const double radius,
460 const double sigma,ExceptionInfo *exception)
461{
cristy3ed852e2009-09-05 21:47:34 +0000462#define AdaptiveSharpenImageTag "Convolve/Image"
463#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
464
cristyc4c8d132010-01-07 01:58:38 +0000465 CacheView
466 *sharp_view,
467 *edge_view,
468 *image_view;
469
cristy3ed852e2009-09-05 21:47:34 +0000470 double
cristy47e00502009-12-17 19:19:57 +0000471 **kernel,
472 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000473
474 Image
475 *sharp_image,
476 *edge_image,
477 *gaussian_image;
478
cristy3ed852e2009-09-05 21:47:34 +0000479 MagickBooleanType
480 status;
481
cristybb503372010-05-27 20:51:26 +0000482 MagickOffsetType
483 progress;
484
cristy4c08aed2011-07-01 19:47:50 +0000485 PixelInfo
cristyddd82202009-11-03 20:14:50 +0000486 bias;
cristy3ed852e2009-09-05 21:47:34 +0000487
cristybb503372010-05-27 20:51:26 +0000488 register ssize_t
cristy47e00502009-12-17 19:19:57 +0000489 i;
cristy3ed852e2009-09-05 21:47:34 +0000490
cristybb503372010-05-27 20:51:26 +0000491 size_t
cristy3ed852e2009-09-05 21:47:34 +0000492 width;
493
cristybb503372010-05-27 20:51:26 +0000494 ssize_t
495 j,
496 k,
497 u,
498 v,
499 y;
500
cristy3ed852e2009-09-05 21:47:34 +0000501 assert(image != (const Image *) NULL);
502 assert(image->signature == MagickSignature);
503 if (image->debug != MagickFalse)
504 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
505 assert(exception != (ExceptionInfo *) NULL);
506 assert(exception->signature == MagickSignature);
507 sharp_image=CloneImage(image,0,0,MagickTrue,exception);
508 if (sharp_image == (Image *) NULL)
509 return((Image *) NULL);
510 if (fabs(sigma) <= MagickEpsilon)
511 return(sharp_image);
512 if (SetImageStorageClass(sharp_image,DirectClass) == MagickFalse)
513 {
514 InheritException(exception,&sharp_image->exception);
515 sharp_image=DestroyImage(sharp_image);
516 return((Image *) NULL);
517 }
518 /*
519 Edge detect the image brighness channel, level, sharp, and level again.
520 */
521 edge_image=EdgeImage(image,radius,exception);
522 if (edge_image == (Image *) NULL)
523 {
524 sharp_image=DestroyImage(sharp_image);
525 return((Image *) NULL);
526 }
cristyf89cb1d2011-07-07 01:24:37 +0000527 (void) AdaptiveLevelImage(edge_image,"20%,95%");
cristy3ed852e2009-09-05 21:47:34 +0000528 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,exception);
529 if (gaussian_image != (Image *) NULL)
530 {
531 edge_image=DestroyImage(edge_image);
532 edge_image=gaussian_image;
533 }
cristyf89cb1d2011-07-07 01:24:37 +0000534 (void) AdaptiveLevelImage(edge_image,"10%,95%");
cristy3ed852e2009-09-05 21:47:34 +0000535 /*
536 Create a set of kernels from maximum (radius,sigma) to minimum.
537 */
538 width=GetOptimalKernelWidth2D(radius,sigma);
539 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
540 if (kernel == (double **) NULL)
541 {
542 edge_image=DestroyImage(edge_image);
543 sharp_image=DestroyImage(sharp_image);
544 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
545 }
546 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
cristybb503372010-05-27 20:51:26 +0000547 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000548 {
549 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
550 sizeof(**kernel));
551 if (kernel[i] == (double *) NULL)
552 break;
cristy47e00502009-12-17 19:19:57 +0000553 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000554 j=(ssize_t) (width-i)/2;
cristy47e00502009-12-17 19:19:57 +0000555 k=0;
556 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000557 {
cristy47e00502009-12-17 19:19:57 +0000558 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000559 {
cristy4205a3c2010-09-12 20:19:59 +0000560 kernel[i][k]=(double) (-exp(-((double) u*u+v*v)/(2.0*MagickSigma*
561 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +0000562 normalize+=kernel[i][k];
563 k++;
cristy3ed852e2009-09-05 21:47:34 +0000564 }
565 }
cristy3ed852e2009-09-05 21:47:34 +0000566 if (fabs(normalize) <= MagickEpsilon)
567 normalize=1.0;
568 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000569 for (k=0; k < (j*j); k++)
570 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000571 }
cristybb503372010-05-27 20:51:26 +0000572 if (i < (ssize_t) width)
cristy3ed852e2009-09-05 21:47:34 +0000573 {
574 for (i-=2; i >= 0; i-=2)
575 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
576 kernel=(double **) RelinquishMagickMemory(kernel);
577 edge_image=DestroyImage(edge_image);
578 sharp_image=DestroyImage(sharp_image);
579 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
580 }
581 /*
582 Adaptively sharpen image.
583 */
584 status=MagickTrue;
585 progress=0;
cristy4c08aed2011-07-01 19:47:50 +0000586 GetPixelInfo(image,&bias);
587 SetPixelInfoBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000588 image_view=AcquireCacheView(image);
589 edge_view=AcquireCacheView(edge_image);
590 sharp_view=AcquireCacheView(sharp_image);
cristyb5d5f722009-11-04 03:03:49 +0000591#if defined(MAGICKCORE_OPENMP_SUPPORT)
592 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000593#endif
cristybb503372010-05-27 20:51:26 +0000594 for (y=0; y < (ssize_t) sharp_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000595 {
cristy4c08aed2011-07-01 19:47:50 +0000596 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000597 *restrict p,
598 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000599
cristy4c08aed2011-07-01 19:47:50 +0000600 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000601 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000602
cristy117ff172010-08-15 21:35:32 +0000603 register ssize_t
604 x;
605
cristy3ed852e2009-09-05 21:47:34 +0000606 if (status == MagickFalse)
607 continue;
608 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
609 q=QueueCacheViewAuthenticPixels(sharp_view,0,y,sharp_image->columns,1,
610 exception);
cristy4c08aed2011-07-01 19:47:50 +0000611 if ((r == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000612 {
613 status=MagickFalse;
614 continue;
615 }
cristybb503372010-05-27 20:51:26 +0000616 for (x=0; x < (ssize_t) sharp_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000617 {
cristy4c08aed2011-07-01 19:47:50 +0000618 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000619 pixel;
620
621 MagickRealType
622 alpha,
623 gamma;
624
625 register const double
cristyc47d1f82009-11-26 01:44:43 +0000626 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000627
cristybb503372010-05-27 20:51:26 +0000628 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000629 i,
630 u,
631 v;
632
633 gamma=0.0;
cristy4c08aed2011-07-01 19:47:50 +0000634 i=(ssize_t) ceil((double) width*QuantumScale*
635 GetPixelIntensity(edge_image,r)-0.5);
cristy3ed852e2009-09-05 21:47:34 +0000636 if (i < 0)
637 i=0;
638 else
cristybb503372010-05-27 20:51:26 +0000639 if (i > (ssize_t) width)
640 i=(ssize_t) width;
cristy3ed852e2009-09-05 21:47:34 +0000641 if ((i & 0x01) != 0)
642 i--;
cristy117ff172010-08-15 21:35:32 +0000643 p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) (width-i)/2L),y-
644 (ssize_t) ((width-i)/2L),width-i,width-i,exception);
cristy4c08aed2011-07-01 19:47:50 +0000645 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000646 break;
cristy3ed852e2009-09-05 21:47:34 +0000647 k=kernel[i];
cristyddd82202009-11-03 20:14:50 +0000648 pixel=bias;
cristybb503372010-05-27 20:51:26 +0000649 for (v=0; v < (ssize_t) (width-i); v++)
cristy3ed852e2009-09-05 21:47:34 +0000650 {
cristybb503372010-05-27 20:51:26 +0000651 for (u=0; u < (ssize_t) (width-i); u++)
cristy3ed852e2009-09-05 21:47:34 +0000652 {
653 alpha=1.0;
cristyed231572011-07-14 02:18:59 +0000654 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000655 (image->matte != MagickFalse))
cristy4c08aed2011-07-01 19:47:50 +0000656 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,p));
cristyed231572011-07-14 02:18:59 +0000657 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000658 pixel.red+=(*k)*alpha*GetPixelRed(image,p);
cristyed231572011-07-14 02:18:59 +0000659 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000660 pixel.green+=(*k)*alpha*GetPixelGreen(image,p);
cristyed231572011-07-14 02:18:59 +0000661 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000662 pixel.blue+=(*k)*alpha*GetPixelBlue(image,p);
cristyed231572011-07-14 02:18:59 +0000663 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000664 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +0000665 pixel.black+=(*k)*alpha*GetPixelBlack(image,p);
cristyed231572011-07-14 02:18:59 +0000666 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000667 pixel.alpha+=(*k)*GetPixelAlpha(image,p);
cristy3ed852e2009-09-05 21:47:34 +0000668 gamma+=(*k)*alpha;
669 k++;
cristyed231572011-07-14 02:18:59 +0000670 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000671 }
672 }
673 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +0000674 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000675 SetPixelRed(sharp_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +0000676 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000677 SetPixelGreen(sharp_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +0000678 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000679 SetPixelBlue(sharp_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +0000680 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +0000681 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +0000682 SetPixelBlack(sharp_image,ClampToQuantum(gamma*pixel.black),q);
cristyed231572011-07-14 02:18:59 +0000683 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000684 SetPixelAlpha(sharp_image,ClampToQuantum(pixel.alpha),q);
cristyed231572011-07-14 02:18:59 +0000685 q+=GetPixelChannels(sharp_image);
686 r+=GetPixelChannels(edge_image);
cristy3ed852e2009-09-05 21:47:34 +0000687 }
688 if (SyncCacheViewAuthenticPixels(sharp_view,exception) == MagickFalse)
689 status=MagickFalse;
690 if (image->progress_monitor != (MagickProgressMonitor) NULL)
691 {
692 MagickBooleanType
693 proceed;
694
cristyb5d5f722009-11-04 03:03:49 +0000695#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +0000696 #pragma omp critical (MagickCore_AdaptiveSharpenImage)
cristy3ed852e2009-09-05 21:47:34 +0000697#endif
698 proceed=SetImageProgress(image,AdaptiveSharpenImageTag,progress++,
699 image->rows);
700 if (proceed == MagickFalse)
701 status=MagickFalse;
702 }
703 }
704 sharp_image->type=image->type;
705 sharp_view=DestroyCacheView(sharp_view);
706 edge_view=DestroyCacheView(edge_view);
707 image_view=DestroyCacheView(image_view);
708 edge_image=DestroyImage(edge_image);
cristybb503372010-05-27 20:51:26 +0000709 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000710 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
711 kernel=(double **) RelinquishMagickMemory(kernel);
712 if (status == MagickFalse)
713 sharp_image=DestroyImage(sharp_image);
714 return(sharp_image);
715}
716
717/*
718%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
719% %
720% %
721% %
722% B l u r I m a g e %
723% %
724% %
725% %
726%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
727%
728% BlurImage() blurs an image. We convolve the image with a Gaussian operator
729% of the given radius and standard deviation (sigma). For reasonable results,
730% the radius should be larger than sigma. Use a radius of 0 and BlurImage()
731% selects a suitable radius for you.
732%
733% BlurImage() differs from GaussianBlurImage() in that it uses a separable
734% kernel which is faster but mathematically equivalent to the non-separable
735% kernel.
736%
737% The format of the BlurImage method is:
738%
739% Image *BlurImage(const Image *image,const double radius,
740% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000741%
742% A description of each parameter follows:
743%
744% o image: the image.
745%
cristy3ed852e2009-09-05 21:47:34 +0000746% o radius: the radius of the Gaussian, in pixels, not counting the center
747% pixel.
748%
749% o sigma: the standard deviation of the Gaussian, in pixels.
750%
751% o exception: return any errors or warnings in this structure.
752%
753*/
754
cristybb503372010-05-27 20:51:26 +0000755static double *GetBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +0000756{
cristy3ed852e2009-09-05 21:47:34 +0000757 double
cristy47e00502009-12-17 19:19:57 +0000758 *kernel,
759 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000760
cristy117ff172010-08-15 21:35:32 +0000761 register ssize_t
762 i;
763
cristybb503372010-05-27 20:51:26 +0000764 ssize_t
cristy47e00502009-12-17 19:19:57 +0000765 j,
766 k;
cristy3ed852e2009-09-05 21:47:34 +0000767
cristy3ed852e2009-09-05 21:47:34 +0000768 /*
769 Generate a 1-D convolution kernel.
770 */
771 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
772 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
773 if (kernel == (double *) NULL)
774 return(0);
cristy3ed852e2009-09-05 21:47:34 +0000775 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000776 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +0000777 i=0;
778 for (k=(-j); k <= j; k++)
779 {
cristy4205a3c2010-09-12 20:19:59 +0000780 kernel[i]=(double) (exp(-((double) k*k)/(2.0*MagickSigma*MagickSigma))/
781 (MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +0000782 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +0000783 i++;
784 }
cristybb503372010-05-27 20:51:26 +0000785 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000786 kernel[i]/=normalize;
787 return(kernel);
788}
789
cristyf4ad9df2011-07-08 16:49:03 +0000790MagickExport Image *BlurImage(const Image *image,const double radius,
791 const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000792{
793#define BlurImageTag "Blur/Image"
794
cristyc4c8d132010-01-07 01:58:38 +0000795 CacheView
796 *blur_view,
797 *image_view;
798
cristy3ed852e2009-09-05 21:47:34 +0000799 double
800 *kernel;
801
802 Image
803 *blur_image;
804
cristy3ed852e2009-09-05 21:47:34 +0000805 MagickBooleanType
806 status;
807
cristybb503372010-05-27 20:51:26 +0000808 MagickOffsetType
809 progress;
810
cristy4c08aed2011-07-01 19:47:50 +0000811 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000812 bias;
813
cristybb503372010-05-27 20:51:26 +0000814 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000815 i;
816
cristybb503372010-05-27 20:51:26 +0000817 size_t
cristy3ed852e2009-09-05 21:47:34 +0000818 width;
819
cristybb503372010-05-27 20:51:26 +0000820 ssize_t
821 x,
822 y;
823
cristy3ed852e2009-09-05 21:47:34 +0000824 /*
825 Initialize blur image attributes.
826 */
827 assert(image != (Image *) NULL);
828 assert(image->signature == MagickSignature);
829 if (image->debug != MagickFalse)
830 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
831 assert(exception != (ExceptionInfo *) NULL);
832 assert(exception->signature == MagickSignature);
833 blur_image=CloneImage(image,0,0,MagickTrue,exception);
834 if (blur_image == (Image *) NULL)
835 return((Image *) NULL);
836 if (fabs(sigma) <= MagickEpsilon)
837 return(blur_image);
838 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
839 {
840 InheritException(exception,&blur_image->exception);
841 blur_image=DestroyImage(blur_image);
842 return((Image *) NULL);
843 }
844 width=GetOptimalKernelWidth1D(radius,sigma);
845 kernel=GetBlurKernel(width,sigma);
846 if (kernel == (double *) NULL)
847 {
848 blur_image=DestroyImage(blur_image);
849 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
850 }
851 if (image->debug != MagickFalse)
852 {
853 char
854 format[MaxTextExtent],
855 *message;
856
857 register const double
858 *k;
859
860 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +0000861 " BlurImage with %.20g kernel:",(double) width);
cristy3ed852e2009-09-05 21:47:34 +0000862 message=AcquireString("");
863 k=kernel;
cristybb503372010-05-27 20:51:26 +0000864 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000865 {
866 *message='\0';
cristyb51dff52011-05-19 16:55:47 +0000867 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) i);
cristy3ed852e2009-09-05 21:47:34 +0000868 (void) ConcatenateString(&message,format);
cristyb51dff52011-05-19 16:55:47 +0000869 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristy3ed852e2009-09-05 21:47:34 +0000870 (void) ConcatenateString(&message,format);
871 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
872 }
873 message=DestroyString(message);
874 }
875 /*
876 Blur rows.
877 */
878 status=MagickTrue;
879 progress=0;
cristy4c08aed2011-07-01 19:47:50 +0000880 GetPixelInfo(image,&bias);
881 SetPixelInfoBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000882 image_view=AcquireCacheView(image);
883 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000884#if defined(MAGICKCORE_OPENMP_SUPPORT)
885 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000886#endif
cristybb503372010-05-27 20:51:26 +0000887 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000888 {
cristy4c08aed2011-07-01 19:47:50 +0000889 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000890 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000891
cristy4c08aed2011-07-01 19:47:50 +0000892 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000893 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000894
cristy117ff172010-08-15 21:35:32 +0000895 register ssize_t
896 x;
897
cristy3ed852e2009-09-05 21:47:34 +0000898 if (status == MagickFalse)
899 continue;
cristy117ff172010-08-15 21:35:32 +0000900 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y,
901 image->columns+width,1,exception);
cristy3ed852e2009-09-05 21:47:34 +0000902 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
903 exception);
cristy4c08aed2011-07-01 19:47:50 +0000904 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000905 {
906 status=MagickFalse;
907 continue;
908 }
cristybb503372010-05-27 20:51:26 +0000909 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000910 {
cristy4c08aed2011-07-01 19:47:50 +0000911 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000912 pixel;
913
914 register const double
cristyc47d1f82009-11-26 01:44:43 +0000915 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000916
cristy4c08aed2011-07-01 19:47:50 +0000917 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000918 *restrict kernel_pixels;
cristy3ed852e2009-09-05 21:47:34 +0000919
cristybb503372010-05-27 20:51:26 +0000920 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000921 i;
922
cristyddd82202009-11-03 20:14:50 +0000923 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +0000924 k=kernel;
925 kernel_pixels=p;
cristyed231572011-07-14 02:18:59 +0000926 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) ||
cristyf4ad9df2011-07-08 16:49:03 +0000927 (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +0000928 {
cristybb503372010-05-27 20:51:26 +0000929 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000930 {
cristy4c08aed2011-07-01 19:47:50 +0000931 pixel.red+=(*k)*GetPixelRed(image,kernel_pixels);
932 pixel.green+=(*k)*GetPixelGreen(image,kernel_pixels);
933 pixel.blue+=(*k)*GetPixelBlue(image,kernel_pixels);
934 if (image->colorspace == CMYKColorspace)
935 pixel.black+=(*k)*GetPixelBlack(image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +0000936 k++;
cristyed231572011-07-14 02:18:59 +0000937 kernel_pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000938 }
cristyed231572011-07-14 02:18:59 +0000939 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000940 SetPixelRed(blur_image,ClampToQuantum(pixel.red),q);
cristyed231572011-07-14 02:18:59 +0000941 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000942 SetPixelGreen(blur_image,ClampToQuantum(pixel.green),q);
cristyed231572011-07-14 02:18:59 +0000943 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000944 SetPixelBlue(blur_image,ClampToQuantum(pixel.blue),q);
cristyed231572011-07-14 02:18:59 +0000945 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000946 (blur_image->colorspace == CMYKColorspace))
947 SetPixelBlack(blur_image,ClampToQuantum(pixel.black),q);
cristyed231572011-07-14 02:18:59 +0000948 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +0000949 {
950 k=kernel;
951 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +0000952 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000953 {
cristy4c08aed2011-07-01 19:47:50 +0000954 pixel.alpha+=(*k)*GetPixelAlpha(image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +0000955 k++;
cristyed231572011-07-14 02:18:59 +0000956 kernel_pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000957 }
cristy4c08aed2011-07-01 19:47:50 +0000958 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +0000959 }
960 }
961 else
962 {
963 MagickRealType
964 alpha,
965 gamma;
966
967 gamma=0.0;
cristybb503372010-05-27 20:51:26 +0000968 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000969 {
cristy4c08aed2011-07-01 19:47:50 +0000970 alpha=(MagickRealType) (QuantumScale*
971 GetPixelAlpha(image,kernel_pixels));
972 pixel.red+=(*k)*alpha*GetPixelRed(image,kernel_pixels);
973 pixel.green+=(*k)*alpha*GetPixelGreen(image,kernel_pixels);
974 pixel.blue+=(*k)*alpha*GetPixelBlue(image,kernel_pixels);
975 if (image->colorspace == CMYKColorspace)
976 pixel.black+=(*k)*alpha*GetPixelBlack(image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +0000977 gamma+=(*k)*alpha;
978 k++;
cristyed231572011-07-14 02:18:59 +0000979 kernel_pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000980 }
981 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +0000982 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000983 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +0000984 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000985 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +0000986 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +0000987 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +0000988 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +0000989 (blur_image->colorspace == CMYKColorspace))
990 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristyed231572011-07-14 02:18:59 +0000991 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +0000992 {
993 k=kernel;
994 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +0000995 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000996 {
cristy4c08aed2011-07-01 19:47:50 +0000997 pixel.alpha+=(*k)*GetPixelAlpha(image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +0000998 k++;
cristyed231572011-07-14 02:18:59 +0000999 kernel_pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001000 }
cristy4c08aed2011-07-01 19:47:50 +00001001 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00001002 }
1003 }
cristyed231572011-07-14 02:18:59 +00001004 p+=GetPixelChannels(image);
1005 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001006 }
1007 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1008 status=MagickFalse;
1009 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1010 {
1011 MagickBooleanType
1012 proceed;
1013
cristyb5d5f722009-11-04 03:03:49 +00001014#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00001015 #pragma omp critical (MagickCore_BlurImage)
cristy3ed852e2009-09-05 21:47:34 +00001016#endif
1017 proceed=SetImageProgress(image,BlurImageTag,progress++,blur_image->rows+
1018 blur_image->columns);
1019 if (proceed == MagickFalse)
1020 status=MagickFalse;
1021 }
1022 }
1023 blur_view=DestroyCacheView(blur_view);
1024 image_view=DestroyCacheView(image_view);
1025 /*
1026 Blur columns.
1027 */
1028 image_view=AcquireCacheView(blur_image);
1029 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00001030#if defined(MAGICKCORE_OPENMP_SUPPORT)
1031 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001032#endif
cristybb503372010-05-27 20:51:26 +00001033 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001034 {
cristy4c08aed2011-07-01 19:47:50 +00001035 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001036 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001037
cristy4c08aed2011-07-01 19:47:50 +00001038 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001039 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001040
cristy117ff172010-08-15 21:35:32 +00001041 register ssize_t
1042 y;
1043
cristy3ed852e2009-09-05 21:47:34 +00001044 if (status == MagickFalse)
1045 continue;
cristy117ff172010-08-15 21:35:32 +00001046 p=GetCacheViewVirtualPixels(image_view,x,-((ssize_t) width/2L),1,
1047 image->rows+width,exception);
cristy3ed852e2009-09-05 21:47:34 +00001048 q=GetCacheViewAuthenticPixels(blur_view,x,0,1,blur_image->rows,exception);
cristy4c08aed2011-07-01 19:47:50 +00001049 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001050 {
1051 status=MagickFalse;
1052 continue;
1053 }
cristybb503372010-05-27 20:51:26 +00001054 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001055 {
cristy4c08aed2011-07-01 19:47:50 +00001056 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001057 pixel;
1058
1059 register const double
cristyc47d1f82009-11-26 01:44:43 +00001060 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00001061
cristy4c08aed2011-07-01 19:47:50 +00001062 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001063 *restrict kernel_pixels;
cristy3ed852e2009-09-05 21:47:34 +00001064
cristybb503372010-05-27 20:51:26 +00001065 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001066 i;
1067
cristyddd82202009-11-03 20:14:50 +00001068 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00001069 k=kernel;
1070 kernel_pixels=p;
cristyed231572011-07-14 02:18:59 +00001071 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) ||
cristyf4ad9df2011-07-08 16:49:03 +00001072 (blur_image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00001073 {
cristybb503372010-05-27 20:51:26 +00001074 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001075 {
cristy4c08aed2011-07-01 19:47:50 +00001076 pixel.red+=(*k)*GetPixelRed(blur_image,kernel_pixels);
1077 pixel.green+=(*k)*GetPixelGreen(blur_image,kernel_pixels);
1078 pixel.blue+=(*k)*GetPixelBlue(blur_image,kernel_pixels);
1079 if (blur_image->colorspace == CMYKColorspace)
1080 pixel.black+=(*k)*GetPixelBlack(blur_image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001081 k++;
cristyed231572011-07-14 02:18:59 +00001082 kernel_pixels+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001083 }
cristyed231572011-07-14 02:18:59 +00001084 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001085 SetPixelRed(blur_image,ClampToQuantum(pixel.red),q);
cristyed231572011-07-14 02:18:59 +00001086 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001087 SetPixelGreen(blur_image,ClampToQuantum(pixel.green),q);
cristyed231572011-07-14 02:18:59 +00001088 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001089 SetPixelBlue(blur_image,ClampToQuantum(pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00001090 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001091 (blur_image->colorspace == CMYKColorspace))
1092 SetPixelBlack(blur_image,ClampToQuantum(pixel.black),q);
cristyed231572011-07-14 02:18:59 +00001093 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001094 {
1095 k=kernel;
1096 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001097 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001098 {
cristy4c08aed2011-07-01 19:47:50 +00001099 pixel.alpha+=(*k)*GetPixelAlpha(blur_image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001100 k++;
cristyed231572011-07-14 02:18:59 +00001101 kernel_pixels+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001102 }
cristy4c08aed2011-07-01 19:47:50 +00001103 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00001104 }
1105 }
1106 else
1107 {
1108 MagickRealType
1109 alpha,
1110 gamma;
1111
1112 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00001113 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001114 {
cristy46f08202010-01-10 04:04:21 +00001115 alpha=(MagickRealType) (QuantumScale*
cristy4c08aed2011-07-01 19:47:50 +00001116 GetPixelAlpha(blur_image,kernel_pixels));
1117 pixel.red+=(*k)*alpha*GetPixelRed(blur_image,kernel_pixels);
1118 pixel.green+=(*k)*alpha*GetPixelGreen(blur_image,kernel_pixels);
1119 pixel.blue+=(*k)*alpha*GetPixelBlue(blur_image,kernel_pixels);
1120 if (blur_image->colorspace == CMYKColorspace)
1121 pixel.black+=(*k)*alpha*GetPixelBlack(blur_image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001122 gamma+=(*k)*alpha;
1123 k++;
cristyed231572011-07-14 02:18:59 +00001124 kernel_pixels+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001125 }
1126 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00001127 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001128 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +00001129 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001130 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +00001131 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00001132 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00001133 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00001134 (blur_image->colorspace == CMYKColorspace))
1135 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristyed231572011-07-14 02:18:59 +00001136 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001137 {
1138 k=kernel;
1139 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00001140 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001141 {
cristy4c08aed2011-07-01 19:47:50 +00001142 pixel.alpha+=(*k)*GetPixelAlpha(blur_image,kernel_pixels);
cristy3ed852e2009-09-05 21:47:34 +00001143 k++;
cristyed231572011-07-14 02:18:59 +00001144 kernel_pixels+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001145 }
cristy4c08aed2011-07-01 19:47:50 +00001146 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00001147 }
1148 }
cristyed231572011-07-14 02:18:59 +00001149 p+=GetPixelChannels(blur_image);
1150 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001151 }
1152 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1153 status=MagickFalse;
cristy4c08aed2011-07-01 19:47:50 +00001154 if (blur_image->progress_monitor != (MagickProgressMonitor) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001155 {
1156 MagickBooleanType
1157 proceed;
1158
cristyb5d5f722009-11-04 03:03:49 +00001159#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00001160 #pragma omp critical (MagickCore_BlurImage)
cristy3ed852e2009-09-05 21:47:34 +00001161#endif
cristy4c08aed2011-07-01 19:47:50 +00001162 proceed=SetImageProgress(blur_image,BlurImageTag,progress++,
1163 blur_image->rows+blur_image->columns);
cristy3ed852e2009-09-05 21:47:34 +00001164 if (proceed == MagickFalse)
1165 status=MagickFalse;
1166 }
1167 }
1168 blur_view=DestroyCacheView(blur_view);
1169 image_view=DestroyCacheView(image_view);
1170 kernel=(double *) RelinquishMagickMemory(kernel);
1171 if (status == MagickFalse)
1172 blur_image=DestroyImage(blur_image);
1173 blur_image->type=image->type;
1174 return(blur_image);
1175}
1176
1177/*
1178%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1179% %
1180% %
1181% %
cristyfccdab92009-11-30 16:43:57 +00001182% C o n v o l v e I m a g e %
1183% %
1184% %
1185% %
1186%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1187%
1188% ConvolveImage() applies a custom convolution kernel to the image.
1189%
1190% The format of the ConvolveImage method is:
1191%
cristybb503372010-05-27 20:51:26 +00001192% Image *ConvolveImage(const Image *image,const size_t order,
cristyfccdab92009-11-30 16:43:57 +00001193% const double *kernel,ExceptionInfo *exception)
cristyfccdab92009-11-30 16:43:57 +00001194% A description of each parameter follows:
1195%
1196% o image: the image.
1197%
cristyfccdab92009-11-30 16:43:57 +00001198% o order: the number of columns and rows in the filter kernel.
1199%
1200% o kernel: An array of double representing the convolution kernel.
1201%
1202% o exception: return any errors or warnings in this structure.
1203%
1204*/
cristybb503372010-05-27 20:51:26 +00001205MagickExport Image *ConvolveImage(const Image *image,const size_t order,
cristyfccdab92009-11-30 16:43:57 +00001206 const double *kernel,ExceptionInfo *exception)
1207{
cristyfccdab92009-11-30 16:43:57 +00001208#define ConvolveImageTag "Convolve/Image"
1209
cristyc4c8d132010-01-07 01:58:38 +00001210 CacheView
1211 *convolve_view,
1212 *image_view;
1213
cristyfccdab92009-11-30 16:43:57 +00001214 double
1215 *normal_kernel;
1216
1217 Image
1218 *convolve_image;
1219
cristyfccdab92009-11-30 16:43:57 +00001220 MagickBooleanType
1221 status;
1222
cristybb503372010-05-27 20:51:26 +00001223 MagickOffsetType
1224 progress;
1225
cristyfccdab92009-11-30 16:43:57 +00001226 MagickRealType
1227 gamma;
1228
cristybb503372010-05-27 20:51:26 +00001229 register ssize_t
cristyfccdab92009-11-30 16:43:57 +00001230 i;
1231
cristybb503372010-05-27 20:51:26 +00001232 size_t
cristyfccdab92009-11-30 16:43:57 +00001233 width;
1234
cristybb503372010-05-27 20:51:26 +00001235 ssize_t
1236 y;
1237
cristyfccdab92009-11-30 16:43:57 +00001238 /*
1239 Initialize convolve image attributes.
1240 */
1241 assert(image != (Image *) NULL);
1242 assert(image->signature == MagickSignature);
1243 if (image->debug != MagickFalse)
1244 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1245 assert(exception != (ExceptionInfo *) NULL);
1246 assert(exception->signature == MagickSignature);
1247 width=order;
1248 if ((width % 2) == 0)
1249 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
1250 convolve_image=CloneImage(image,0,0,MagickTrue,exception);
1251 if (convolve_image == (Image *) NULL)
1252 return((Image *) NULL);
1253 if (SetImageStorageClass(convolve_image,DirectClass) == MagickFalse)
1254 {
1255 InheritException(exception,&convolve_image->exception);
1256 convolve_image=DestroyImage(convolve_image);
1257 return((Image *) NULL);
1258 }
1259 if (image->debug != MagickFalse)
1260 {
1261 char
1262 format[MaxTextExtent],
1263 *message;
1264
cristy117ff172010-08-15 21:35:32 +00001265 register const double
1266 *k;
1267
cristybb503372010-05-27 20:51:26 +00001268 ssize_t
cristyfccdab92009-11-30 16:43:57 +00001269 u,
1270 v;
1271
cristyfccdab92009-11-30 16:43:57 +00001272 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00001273 " ConvolveImage with %.20gx%.20g kernel:",(double) width,(double)
1274 width);
cristyfccdab92009-11-30 16:43:57 +00001275 message=AcquireString("");
1276 k=kernel;
cristybb503372010-05-27 20:51:26 +00001277 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001278 {
1279 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00001280 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristyfccdab92009-11-30 16:43:57 +00001281 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00001282 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001283 {
cristyb51dff52011-05-19 16:55:47 +00001284 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristyfccdab92009-11-30 16:43:57 +00001285 (void) ConcatenateString(&message,format);
1286 }
1287 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
1288 }
1289 message=DestroyString(message);
1290 }
1291 /*
1292 Normalize kernel.
1293 */
1294 normal_kernel=(double *) AcquireQuantumMemory(width*width,
1295 sizeof(*normal_kernel));
1296 if (normal_kernel == (double *) NULL)
1297 {
1298 convolve_image=DestroyImage(convolve_image);
1299 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1300 }
1301 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00001302 for (i=0; i < (ssize_t) (width*width); i++)
cristyfccdab92009-11-30 16:43:57 +00001303 gamma+=kernel[i];
1304 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristybb503372010-05-27 20:51:26 +00001305 for (i=0; i < (ssize_t) (width*width); i++)
cristyfccdab92009-11-30 16:43:57 +00001306 normal_kernel[i]=gamma*kernel[i];
1307 /*
1308 Convolve image.
1309 */
1310 status=MagickTrue;
1311 progress=0;
cristyfccdab92009-11-30 16:43:57 +00001312 image_view=AcquireCacheView(image);
1313 convolve_view=AcquireCacheView(convolve_image);
1314#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy175653e2011-07-10 23:13:34 +00001315 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristyfccdab92009-11-30 16:43:57 +00001316#endif
cristybb503372010-05-27 20:51:26 +00001317 for (y=0; y < (ssize_t) image->rows; y++)
cristyfccdab92009-11-30 16:43:57 +00001318 {
cristy4c08aed2011-07-01 19:47:50 +00001319 register const Quantum
cristyfccdab92009-11-30 16:43:57 +00001320 *restrict p;
1321
cristy4c08aed2011-07-01 19:47:50 +00001322 register Quantum
cristyfccdab92009-11-30 16:43:57 +00001323 *restrict q;
1324
cristy117ff172010-08-15 21:35:32 +00001325 register ssize_t
1326 x;
1327
cristyed231572011-07-14 02:18:59 +00001328 size_t
1329 channels,
1330 convolve_channels;
1331
cristyfccdab92009-11-30 16:43:57 +00001332 if (status == MagickFalse)
1333 continue;
cristyce889302010-06-30 19:16:36 +00001334 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
1335 (width/2L),image->columns+width,width,exception);
cristyfccdab92009-11-30 16:43:57 +00001336 q=GetCacheViewAuthenticPixels(convolve_view,0,y,convolve_image->columns,1,
1337 exception);
cristy4c08aed2011-07-01 19:47:50 +00001338 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristyfccdab92009-11-30 16:43:57 +00001339 {
1340 status=MagickFalse;
1341 continue;
1342 }
cristyed231572011-07-14 02:18:59 +00001343 channels=GetPixelChannels(image);
1344 convolve_channels=GetPixelChannels(convolve_image);
cristybb503372010-05-27 20:51:26 +00001345 for (x=0; x < (ssize_t) image->columns; x++)
cristyfccdab92009-11-30 16:43:57 +00001346 {
cristybb503372010-05-27 20:51:26 +00001347 register ssize_t
cristyed231572011-07-14 02:18:59 +00001348 i;
cristyfccdab92009-11-30 16:43:57 +00001349
cristyed231572011-07-14 02:18:59 +00001350 for (i=0; i < (ssize_t) channels; i++)
1351 {
1352 const Quantum
1353 *restrict center;
cristy117ff172010-08-15 21:35:32 +00001354
cristyed231572011-07-14 02:18:59 +00001355 MagickRealType
1356 pixel;
1357
1358 PixelChannel
1359 channel;
1360
1361 PixelTrait
1362 convolve_traits,
1363 traits;
1364
1365 register const double
1366 *restrict k;
1367
1368 register const Quantum
1369 *restrict kernel_pixels;
1370
1371 register ssize_t
1372 u;
1373
1374 ssize_t
1375 v;
1376
1377 channel=GetPixelChannelMapChannel(image,i);
1378 traits=GetPixelChannelMapTraits(image,i);
1379 convolve_traits=GetPixelChannelMapTraits(convolve_image,channel);
1380 if ((traits == UndefinedPixelTrait) ||
1381 (convolve_traits == UndefinedPixelTrait))
1382 continue;
1383 pixel=image->bias;
1384 k=normal_kernel;
1385 kernel_pixels=p;
1386 center=p+((image->columns+width)*width/2)*channels+i;
1387 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) ||
1388 (image->matte == MagickFalse))
cristyfccdab92009-11-30 16:43:57 +00001389 {
cristyed231572011-07-14 02:18:59 +00001390 /*
1391 No alpha blending (optimized).
1392 */
1393 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001394 {
cristyed231572011-07-14 02:18:59 +00001395 for (u=0; u < (ssize_t) width; u++)
cristy175653e2011-07-10 23:13:34 +00001396 {
cristyed231572011-07-14 02:18:59 +00001397 pixel+=(*k)*kernel_pixels[u*channels+i];
1398 k++;
cristy175653e2011-07-10 23:13:34 +00001399 }
cristyed231572011-07-14 02:18:59 +00001400 kernel_pixels+=(image->columns+width)*channels;
cristy175653e2011-07-10 23:13:34 +00001401 }
cristyed231572011-07-14 02:18:59 +00001402 if ((convolve_traits & UpdatePixelTrait) != 0)
1403 SetPixelChannel(convolve_image,channel,ClampToQuantum(pixel),q);
1404 else
1405 if ((convolve_traits & CopyPixelTrait) != 0)
1406 SetPixelChannel(convolve_image,channel,*center,q);
1407 }
1408 else
cristyfccdab92009-11-30 16:43:57 +00001409 {
cristyed231572011-07-14 02:18:59 +00001410 MagickRealType
1411 alpha,
1412 gamma;
1413
1414 /*
1415 Alpha blending (unoptimized).
1416 */
1417 gamma=0.0;
1418 for (v=0; v < (ssize_t) width; v++)
cristyfccdab92009-11-30 16:43:57 +00001419 {
cristyed231572011-07-14 02:18:59 +00001420 for (u=0; u < (ssize_t) width; u++)
cristyfccdab92009-11-30 16:43:57 +00001421 {
cristyed231572011-07-14 02:18:59 +00001422 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,
1423 kernel_pixels+u*channels));
1424 if ((traits & BlendPixelTrait) != 0)
1425 pixel+=(*k)*alpha*kernel_pixels[u*channels+i];
1426 else
1427 pixel+=(*k)*kernel_pixels[u*channels+i];
1428 gamma+=(*k)*alpha;
1429 k++;
cristyfccdab92009-11-30 16:43:57 +00001430 }
cristyed231572011-07-14 02:18:59 +00001431 kernel_pixels+=(image->columns+width)*channels;
cristy175653e2011-07-10 23:13:34 +00001432 }
cristyed231572011-07-14 02:18:59 +00001433 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1434 if ((convolve_traits & UpdatePixelTrait) != 0)
1435 {
1436 if ((convolve_traits & BlendPixelTrait) == 0)
1437 SetPixelChannel(convolve_image,channel,ClampToQuantum(pixel),
1438 q);
1439 else
1440 SetPixelChannel(convolve_image,channel,ClampToQuantum(gamma*
1441 pixel),q);
1442 }
1443 else
1444 if ((convolve_traits & CopyPixelTrait) != 0)
1445 SetPixelChannel(convolve_image,channel,*center,q);
1446 }
1447 }
1448 p+=channels;
1449 q+=convolve_channels;
cristyfccdab92009-11-30 16:43:57 +00001450 }
cristyed231572011-07-14 02:18:59 +00001451 if (SyncCacheViewAuthenticPixels(convolve_view,exception) == MagickFalse)
cristyfccdab92009-11-30 16:43:57 +00001452 status=MagickFalse;
1453 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1454 {
1455 MagickBooleanType
1456 proceed;
1457
1458#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00001459 #pragma omp critical (MagickCore_ConvolveImage)
cristyfccdab92009-11-30 16:43:57 +00001460#endif
1461 proceed=SetImageProgress(image,ConvolveImageTag,progress++,image->rows);
1462 if (proceed == MagickFalse)
1463 status=MagickFalse;
1464 }
1465 }
1466 convolve_image->type=image->type;
1467 convolve_view=DestroyCacheView(convolve_view);
1468 image_view=DestroyCacheView(image_view);
1469 normal_kernel=(double *) RelinquishMagickMemory(normal_kernel);
1470 if (status == MagickFalse)
1471 convolve_image=DestroyImage(convolve_image);
1472 return(convolve_image);
1473}
1474
1475/*
1476%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1477% %
1478% %
1479% %
cristy3ed852e2009-09-05 21:47:34 +00001480% D e s p e c k l e I m a g e %
1481% %
1482% %
1483% %
1484%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1485%
1486% DespeckleImage() reduces the speckle noise in an image while perserving the
1487% edges of the original image.
1488%
1489% The format of the DespeckleImage method is:
1490%
1491% Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1492%
1493% A description of each parameter follows:
1494%
1495% o image: the image.
1496%
1497% o exception: return any errors or warnings in this structure.
1498%
1499*/
1500
cristybb503372010-05-27 20:51:26 +00001501static void Hull(const ssize_t x_offset,const ssize_t y_offset,
1502 const size_t columns,const size_t rows,Quantum *f,Quantum *g,
cristy3ed852e2009-09-05 21:47:34 +00001503 const int polarity)
1504{
cristy3ed852e2009-09-05 21:47:34 +00001505 MagickRealType
1506 v;
1507
cristy3ed852e2009-09-05 21:47:34 +00001508 register Quantum
1509 *p,
1510 *q,
1511 *r,
1512 *s;
1513
cristy117ff172010-08-15 21:35:32 +00001514 register ssize_t
1515 x;
1516
1517 ssize_t
1518 y;
1519
cristy3ed852e2009-09-05 21:47:34 +00001520 assert(f != (Quantum *) NULL);
1521 assert(g != (Quantum *) NULL);
1522 p=f+(columns+2);
1523 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001524 r=p+(y_offset*((ssize_t) columns+2)+x_offset);
1525 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001526 {
1527 p++;
1528 q++;
1529 r++;
1530 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001531 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001532 {
1533 v=(MagickRealType) (*p);
1534 if ((MagickRealType) *r >= (v+(MagickRealType) ScaleCharToQuantum(2)))
1535 v+=ScaleCharToQuantum(1);
1536 *q=(Quantum) v;
1537 p++;
1538 q++;
1539 r++;
1540 }
1541 else
cristybb503372010-05-27 20:51:26 +00001542 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001543 {
1544 v=(MagickRealType) (*p);
1545 if ((MagickRealType) *r <= (v-(MagickRealType) ScaleCharToQuantum(2)))
cristybb503372010-05-27 20:51:26 +00001546 v-=(ssize_t) ScaleCharToQuantum(1);
cristy3ed852e2009-09-05 21:47:34 +00001547 *q=(Quantum) v;
1548 p++;
1549 q++;
1550 r++;
1551 }
1552 p++;
1553 q++;
1554 r++;
1555 }
1556 p=f+(columns+2);
1557 q=g+(columns+2);
cristybb503372010-05-27 20:51:26 +00001558 r=q+(y_offset*((ssize_t) columns+2)+x_offset);
1559 s=q-(y_offset*((ssize_t) columns+2)+x_offset);
1560 for (y=0; y < (ssize_t) rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001561 {
1562 p++;
1563 q++;
1564 r++;
1565 s++;
1566 if (polarity > 0)
cristybb503372010-05-27 20:51:26 +00001567 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001568 {
1569 v=(MagickRealType) (*q);
1570 if (((MagickRealType) *s >=
1571 (v+(MagickRealType) ScaleCharToQuantum(2))) &&
1572 ((MagickRealType) *r > v))
1573 v+=ScaleCharToQuantum(1);
1574 *p=(Quantum) v;
1575 p++;
1576 q++;
1577 r++;
1578 s++;
1579 }
1580 else
cristybb503372010-05-27 20:51:26 +00001581 for (x=(ssize_t) columns; x != 0; x--)
cristy3ed852e2009-09-05 21:47:34 +00001582 {
1583 v=(MagickRealType) (*q);
1584 if (((MagickRealType) *s <=
1585 (v-(MagickRealType) ScaleCharToQuantum(2))) &&
1586 ((MagickRealType) *r < v))
1587 v-=(MagickRealType) ScaleCharToQuantum(1);
1588 *p=(Quantum) v;
1589 p++;
1590 q++;
1591 r++;
1592 s++;
1593 }
1594 p++;
1595 q++;
1596 r++;
1597 s++;
1598 }
1599}
1600
1601MagickExport Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1602{
1603#define DespeckleImageTag "Despeckle/Image"
1604
cristy2407fc22009-09-11 00:55:25 +00001605 CacheView
1606 *despeckle_view,
1607 *image_view;
1608
cristy3ed852e2009-09-05 21:47:34 +00001609 Image
1610 *despeckle_image;
1611
cristy3ed852e2009-09-05 21:47:34 +00001612 MagickBooleanType
1613 status;
1614
cristya58c3172011-02-19 19:23:11 +00001615 register ssize_t
1616 i;
1617
cristy3ed852e2009-09-05 21:47:34 +00001618 Quantum
cristy65b9f392011-02-22 14:22:54 +00001619 *restrict buffers,
1620 *restrict pixels;
cristy3ed852e2009-09-05 21:47:34 +00001621
1622 size_t
cristya58c3172011-02-19 19:23:11 +00001623 length,
1624 number_channels;
cristy117ff172010-08-15 21:35:32 +00001625
cristybb503372010-05-27 20:51:26 +00001626 static const ssize_t
cristy691a29e2009-09-11 00:44:10 +00001627 X[4] = {0, 1, 1,-1},
1628 Y[4] = {1, 0, 1, 1};
cristy3ed852e2009-09-05 21:47:34 +00001629
cristy3ed852e2009-09-05 21:47:34 +00001630 /*
1631 Allocate despeckled image.
1632 */
1633 assert(image != (const Image *) NULL);
1634 assert(image->signature == MagickSignature);
1635 if (image->debug != MagickFalse)
1636 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1637 assert(exception != (ExceptionInfo *) NULL);
1638 assert(exception->signature == MagickSignature);
1639 despeckle_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1640 exception);
1641 if (despeckle_image == (Image *) NULL)
1642 return((Image *) NULL);
1643 if (SetImageStorageClass(despeckle_image,DirectClass) == MagickFalse)
1644 {
1645 InheritException(exception,&despeckle_image->exception);
1646 despeckle_image=DestroyImage(despeckle_image);
1647 return((Image *) NULL);
1648 }
1649 /*
1650 Allocate image buffers.
1651 */
1652 length=(size_t) ((image->columns+2)*(image->rows+2));
cristy65b9f392011-02-22 14:22:54 +00001653 pixels=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1654 buffers=(Quantum *) AcquireQuantumMemory(length,2*sizeof(*pixels));
1655 if ((pixels == (Quantum *) NULL) || (buffers == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001656 {
cristy65b9f392011-02-22 14:22:54 +00001657 if (buffers != (Quantum *) NULL)
1658 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1659 if (pixels != (Quantum *) NULL)
1660 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001661 despeckle_image=DestroyImage(despeckle_image);
1662 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1663 }
1664 /*
1665 Reduce speckle in the image.
1666 */
1667 status=MagickTrue;
cristy109695a2011-02-19 19:38:14 +00001668 number_channels=(size_t) (image->colorspace == CMYKColorspace ? 5 : 4);
cristy3ed852e2009-09-05 21:47:34 +00001669 image_view=AcquireCacheView(image);
1670 despeckle_view=AcquireCacheView(despeckle_image);
cristy8df3d002011-02-19 19:40:59 +00001671 for (i=0; i < (ssize_t) number_channels; i++)
cristy3ed852e2009-09-05 21:47:34 +00001672 {
cristy3ed852e2009-09-05 21:47:34 +00001673 register Quantum
1674 *buffer,
1675 *pixel;
1676
cristyc1488b52011-02-19 18:54:15 +00001677 register ssize_t
cristya58c3172011-02-19 19:23:11 +00001678 k,
cristyc1488b52011-02-19 18:54:15 +00001679 x;
1680
cristy117ff172010-08-15 21:35:32 +00001681 ssize_t
1682 j,
1683 y;
1684
cristy3ed852e2009-09-05 21:47:34 +00001685 if (status == MagickFalse)
1686 continue;
cristy65b9f392011-02-22 14:22:54 +00001687 pixel=pixels;
cristy3ed852e2009-09-05 21:47:34 +00001688 (void) ResetMagickMemory(pixel,0,length*sizeof(*pixel));
cristy65b9f392011-02-22 14:22:54 +00001689 buffer=buffers;
cristybb503372010-05-27 20:51:26 +00001690 j=(ssize_t) image->columns+2;
1691 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001692 {
cristy4c08aed2011-07-01 19:47:50 +00001693 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001694 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001695
1696 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001697 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001698 break;
1699 j++;
cristybb503372010-05-27 20:51:26 +00001700 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001701 {
cristya58c3172011-02-19 19:23:11 +00001702 switch (i)
cristy3ed852e2009-09-05 21:47:34 +00001703 {
cristy4c08aed2011-07-01 19:47:50 +00001704 case 0: pixel[j]=GetPixelRed(image,p); break;
1705 case 1: pixel[j]=GetPixelGreen(image,p); break;
1706 case 2: pixel[j]=GetPixelBlue(image,p); break;
1707 case 3: pixel[j]=GetPixelAlpha(image,p); break;
1708 case 4: pixel[j]=GetPixelBlack(image,p); break;
cristy3ed852e2009-09-05 21:47:34 +00001709 default: break;
1710 }
cristyed231572011-07-14 02:18:59 +00001711 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001712 j++;
1713 }
1714 j++;
1715 }
cristy3ed852e2009-09-05 21:47:34 +00001716 (void) ResetMagickMemory(buffer,0,length*sizeof(*buffer));
cristya58c3172011-02-19 19:23:11 +00001717 for (k=0; k < 4; k++)
cristy3ed852e2009-09-05 21:47:34 +00001718 {
cristya58c3172011-02-19 19:23:11 +00001719 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,1);
1720 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,1);
1721 Hull(-X[k],-Y[k],image->columns,image->rows,pixel,buffer,-1);
1722 Hull(X[k],Y[k],image->columns,image->rows,pixel,buffer,-1);
cristy3ed852e2009-09-05 21:47:34 +00001723 }
cristybb503372010-05-27 20:51:26 +00001724 j=(ssize_t) image->columns+2;
1725 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001726 {
1727 MagickBooleanType
1728 sync;
1729
cristy4c08aed2011-07-01 19:47:50 +00001730 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001731 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001732
1733 q=GetCacheViewAuthenticPixels(despeckle_view,0,y,despeckle_image->columns,
1734 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001735 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001736 break;
1737 j++;
cristybb503372010-05-27 20:51:26 +00001738 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001739 {
cristya58c3172011-02-19 19:23:11 +00001740 switch (i)
cristy3ed852e2009-09-05 21:47:34 +00001741 {
cristy4c08aed2011-07-01 19:47:50 +00001742 case 0: SetPixelRed(despeckle_image,pixel[j],q); break;
1743 case 1: SetPixelGreen(despeckle_image,pixel[j],q); break;
1744 case 2: SetPixelBlue(despeckle_image,pixel[j],q); break;
1745 case 3: SetPixelAlpha(despeckle_image,pixel[j],q); break;
1746 case 4: SetPixelBlack(despeckle_image,pixel[j],q); break;
cristy3ed852e2009-09-05 21:47:34 +00001747 default: break;
1748 }
cristyed231572011-07-14 02:18:59 +00001749 q+=GetPixelChannels(despeckle_image);
cristy3ed852e2009-09-05 21:47:34 +00001750 j++;
1751 }
1752 sync=SyncCacheViewAuthenticPixels(despeckle_view,exception);
1753 if (sync == MagickFalse)
1754 {
1755 status=MagickFalse;
1756 break;
1757 }
1758 j++;
1759 }
1760 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1761 {
1762 MagickBooleanType
1763 proceed;
1764
cristya58c3172011-02-19 19:23:11 +00001765 proceed=SetImageProgress(image,DespeckleImageTag,(MagickOffsetType) i,
1766 number_channels);
cristy3ed852e2009-09-05 21:47:34 +00001767 if (proceed == MagickFalse)
1768 status=MagickFalse;
1769 }
1770 }
1771 despeckle_view=DestroyCacheView(despeckle_view);
1772 image_view=DestroyCacheView(image_view);
cristy65b9f392011-02-22 14:22:54 +00001773 buffers=(Quantum *) RelinquishMagickMemory(buffers);
1774 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001775 despeckle_image->type=image->type;
1776 if (status == MagickFalse)
1777 despeckle_image=DestroyImage(despeckle_image);
1778 return(despeckle_image);
1779}
1780
1781/*
1782%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1783% %
1784% %
1785% %
1786% E d g e I m a g e %
1787% %
1788% %
1789% %
1790%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1791%
1792% EdgeImage() finds edges in an image. Radius defines the radius of the
1793% convolution filter. Use a radius of 0 and EdgeImage() selects a suitable
1794% radius for you.
1795%
1796% The format of the EdgeImage method is:
1797%
1798% Image *EdgeImage(const Image *image,const double radius,
1799% ExceptionInfo *exception)
1800%
1801% A description of each parameter follows:
1802%
1803% o image: the image.
1804%
1805% o radius: the radius of the pixel neighborhood.
1806%
1807% o exception: return any errors or warnings in this structure.
1808%
1809*/
1810MagickExport Image *EdgeImage(const Image *image,const double radius,
1811 ExceptionInfo *exception)
1812{
1813 Image
1814 *edge_image;
1815
1816 double
1817 *kernel;
1818
cristybb503372010-05-27 20:51:26 +00001819 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001820 i;
1821
cristybb503372010-05-27 20:51:26 +00001822 size_t
cristy3ed852e2009-09-05 21:47:34 +00001823 width;
1824
1825 assert(image != (const Image *) NULL);
1826 assert(image->signature == MagickSignature);
1827 if (image->debug != MagickFalse)
1828 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1829 assert(exception != (ExceptionInfo *) NULL);
1830 assert(exception->signature == MagickSignature);
1831 width=GetOptimalKernelWidth1D(radius,0.5);
1832 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
1833 if (kernel == (double *) NULL)
1834 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00001835 for (i=0; i < (ssize_t) (width*width); i++)
cristy3ed852e2009-09-05 21:47:34 +00001836 kernel[i]=(-1.0);
1837 kernel[i/2]=(double) (width*width-1.0);
1838 edge_image=ConvolveImage(image,width,kernel,exception);
1839 kernel=(double *) RelinquishMagickMemory(kernel);
1840 return(edge_image);
1841}
1842
1843/*
1844%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1845% %
1846% %
1847% %
1848% E m b o s s I m a g e %
1849% %
1850% %
1851% %
1852%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1853%
1854% EmbossImage() returns a grayscale image with a three-dimensional effect.
1855% We convolve the image with a Gaussian operator of the given radius and
1856% standard deviation (sigma). For reasonable results, radius should be
1857% larger than sigma. Use a radius of 0 and Emboss() selects a suitable
1858% radius for you.
1859%
1860% The format of the EmbossImage method is:
1861%
1862% Image *EmbossImage(const Image *image,const double radius,
1863% const double sigma,ExceptionInfo *exception)
1864%
1865% A description of each parameter follows:
1866%
1867% o image: the image.
1868%
1869% o radius: the radius of the pixel neighborhood.
1870%
1871% o sigma: the standard deviation of the Gaussian, in pixels.
1872%
1873% o exception: return any errors or warnings in this structure.
1874%
1875*/
1876MagickExport Image *EmbossImage(const Image *image,const double radius,
1877 const double sigma,ExceptionInfo *exception)
1878{
1879 double
1880 *kernel;
1881
1882 Image
1883 *emboss_image;
1884
cristybb503372010-05-27 20:51:26 +00001885 register ssize_t
cristy47e00502009-12-17 19:19:57 +00001886 i;
1887
cristybb503372010-05-27 20:51:26 +00001888 size_t
cristy3ed852e2009-09-05 21:47:34 +00001889 width;
1890
cristy117ff172010-08-15 21:35:32 +00001891 ssize_t
1892 j,
1893 k,
1894 u,
1895 v;
1896
cristy3ed852e2009-09-05 21:47:34 +00001897 assert(image != (Image *) NULL);
1898 assert(image->signature == MagickSignature);
1899 if (image->debug != MagickFalse)
1900 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1901 assert(exception != (ExceptionInfo *) NULL);
1902 assert(exception->signature == MagickSignature);
1903 width=GetOptimalKernelWidth2D(radius,sigma);
1904 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
1905 if (kernel == (double *) NULL)
1906 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00001907 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +00001908 k=j;
1909 i=0;
1910 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00001911 {
cristy47e00502009-12-17 19:19:57 +00001912 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00001913 {
cristy4205a3c2010-09-12 20:19:59 +00001914 kernel[i]=(double) (((u < 0) || (v < 0) ? -8.0 : 8.0)*
cristy47e00502009-12-17 19:19:57 +00001915 exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
cristy4205a3c2010-09-12 20:19:59 +00001916 (2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +00001917 if (u != k)
cristy3ed852e2009-09-05 21:47:34 +00001918 kernel[i]=0.0;
1919 i++;
1920 }
cristy47e00502009-12-17 19:19:57 +00001921 k--;
cristy3ed852e2009-09-05 21:47:34 +00001922 }
1923 emboss_image=ConvolveImage(image,width,kernel,exception);
1924 if (emboss_image != (Image *) NULL)
1925 (void) EqualizeImage(emboss_image);
1926 kernel=(double *) RelinquishMagickMemory(kernel);
1927 return(emboss_image);
1928}
1929
1930/*
1931%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1932% %
1933% %
1934% %
cristy56a9e512010-01-06 18:18:55 +00001935% F i l t e r I m a g e %
1936% %
1937% %
1938% %
1939%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1940%
1941% FilterImage() applies a custom convolution kernel to the image.
1942%
1943% The format of the FilterImage method is:
1944%
cristy2be15382010-01-21 02:38:03 +00001945% Image *FilterImage(const Image *image,const KernelInfo *kernel,
cristy56a9e512010-01-06 18:18:55 +00001946% ExceptionInfo *exception)
cristy56a9e512010-01-06 18:18:55 +00001947%
1948% A description of each parameter follows:
1949%
1950% o image: the image.
1951%
cristy56a9e512010-01-06 18:18:55 +00001952% o kernel: the filtering kernel.
1953%
1954% o exception: return any errors or warnings in this structure.
1955%
1956*/
cristyf4ad9df2011-07-08 16:49:03 +00001957MagickExport Image *FilterImage(const Image *image,
1958 const KernelInfo *kernel,ExceptionInfo *exception)
cristy56a9e512010-01-06 18:18:55 +00001959{
1960#define FilterImageTag "Filter/Image"
1961
1962 CacheView
1963 *filter_view,
1964 *image_view;
1965
cristy56a9e512010-01-06 18:18:55 +00001966 Image
1967 *filter_image;
1968
cristy56a9e512010-01-06 18:18:55 +00001969 MagickBooleanType
1970 status;
1971
cristybb503372010-05-27 20:51:26 +00001972 MagickOffsetType
1973 progress;
1974
cristy4c08aed2011-07-01 19:47:50 +00001975 PixelInfo
cristy56a9e512010-01-06 18:18:55 +00001976 bias;
1977
cristybb503372010-05-27 20:51:26 +00001978 ssize_t
1979 y;
1980
cristy56a9e512010-01-06 18:18:55 +00001981 /*
1982 Initialize filter image attributes.
1983 */
1984 assert(image != (Image *) NULL);
1985 assert(image->signature == MagickSignature);
1986 if (image->debug != MagickFalse)
1987 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1988 assert(exception != (ExceptionInfo *) NULL);
1989 assert(exception->signature == MagickSignature);
1990 if ((kernel->width % 2) == 0)
1991 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
1992 filter_image=CloneImage(image,0,0,MagickTrue,exception);
1993 if (filter_image == (Image *) NULL)
1994 return((Image *) NULL);
1995 if (SetImageStorageClass(filter_image,DirectClass) == MagickFalse)
1996 {
1997 InheritException(exception,&filter_image->exception);
1998 filter_image=DestroyImage(filter_image);
1999 return((Image *) NULL);
2000 }
2001 if (image->debug != MagickFalse)
2002 {
2003 char
2004 format[MaxTextExtent],
2005 *message;
2006
cristy117ff172010-08-15 21:35:32 +00002007 register const double
2008 *k;
2009
cristybb503372010-05-27 20:51:26 +00002010 ssize_t
cristy56a9e512010-01-06 18:18:55 +00002011 u,
2012 v;
2013
cristy56a9e512010-01-06 18:18:55 +00002014 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00002015 " FilterImage with %.20gx%.20g kernel:",(double) kernel->width,(double)
2016 kernel->height);
cristy56a9e512010-01-06 18:18:55 +00002017 message=AcquireString("");
2018 k=kernel->values;
cristybb503372010-05-27 20:51:26 +00002019 for (v=0; v < (ssize_t) kernel->height; v++)
cristy56a9e512010-01-06 18:18:55 +00002020 {
2021 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00002022 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy56a9e512010-01-06 18:18:55 +00002023 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00002024 for (u=0; u < (ssize_t) kernel->width; u++)
cristy56a9e512010-01-06 18:18:55 +00002025 {
cristyb51dff52011-05-19 16:55:47 +00002026 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristy56a9e512010-01-06 18:18:55 +00002027 (void) ConcatenateString(&message,format);
2028 }
2029 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
2030 }
2031 message=DestroyString(message);
2032 }
cristy36826ab2010-03-06 01:29:30 +00002033 status=AccelerateConvolveImage(image,kernel,filter_image,exception);
cristyd43a46b2010-01-21 02:13:41 +00002034 if (status == MagickTrue)
2035 return(filter_image);
cristy56a9e512010-01-06 18:18:55 +00002036 /*
2037 Filter image.
2038 */
2039 status=MagickTrue;
2040 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00002041 GetPixelInfo(image,&bias);
2042 SetPixelInfoBias(image,&bias);
cristy56a9e512010-01-06 18:18:55 +00002043 image_view=AcquireCacheView(image);
2044 filter_view=AcquireCacheView(filter_image);
2045#if defined(MAGICKCORE_OPENMP_SUPPORT)
2046 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2047#endif
cristybb503372010-05-27 20:51:26 +00002048 for (y=0; y < (ssize_t) image->rows; y++)
cristy56a9e512010-01-06 18:18:55 +00002049 {
2050 MagickBooleanType
2051 sync;
2052
cristy4c08aed2011-07-01 19:47:50 +00002053 register const Quantum
cristy56a9e512010-01-06 18:18:55 +00002054 *restrict p;
2055
cristy4c08aed2011-07-01 19:47:50 +00002056 register Quantum
cristy56a9e512010-01-06 18:18:55 +00002057 *restrict q;
2058
cristy117ff172010-08-15 21:35:32 +00002059 register ssize_t
2060 x;
2061
cristy56a9e512010-01-06 18:18:55 +00002062 if (status == MagickFalse)
2063 continue;
cristybb503372010-05-27 20:51:26 +00002064 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) kernel->width/2L),
cristy117ff172010-08-15 21:35:32 +00002065 y-(ssize_t) (kernel->height/2L),image->columns+kernel->width,
2066 kernel->height,exception);
cristy56a9e512010-01-06 18:18:55 +00002067 q=GetCacheViewAuthenticPixels(filter_view,0,y,filter_image->columns,1,
2068 exception);
cristy4c08aed2011-07-01 19:47:50 +00002069 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy56a9e512010-01-06 18:18:55 +00002070 {
2071 status=MagickFalse;
2072 continue;
2073 }
cristybb503372010-05-27 20:51:26 +00002074 for (x=0; x < (ssize_t) image->columns; x++)
cristy56a9e512010-01-06 18:18:55 +00002075 {
cristy4c08aed2011-07-01 19:47:50 +00002076 PixelInfo
cristy56a9e512010-01-06 18:18:55 +00002077 pixel;
2078
2079 register const double
2080 *restrict k;
2081
cristy4c08aed2011-07-01 19:47:50 +00002082 register const Quantum
cristy56a9e512010-01-06 18:18:55 +00002083 *restrict kernel_pixels;
2084
cristybb503372010-05-27 20:51:26 +00002085 register ssize_t
cristy56a9e512010-01-06 18:18:55 +00002086 u;
2087
cristy117ff172010-08-15 21:35:32 +00002088 ssize_t
2089 v;
2090
cristy56a9e512010-01-06 18:18:55 +00002091 pixel=bias;
cristy36826ab2010-03-06 01:29:30 +00002092 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002093 kernel_pixels=p;
cristyed231572011-07-14 02:18:59 +00002094 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) ||
cristy5ce8df82011-07-07 14:52:23 +00002095 (image->matte == MagickFalse))
cristy56a9e512010-01-06 18:18:55 +00002096 {
cristybb503372010-05-27 20:51:26 +00002097 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002098 {
cristybb503372010-05-27 20:51:26 +00002099 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002100 {
cristy4c08aed2011-07-01 19:47:50 +00002101 pixel.red+=(*k)*GetPixelRed(image,kernel_pixels+u*
cristyed231572011-07-14 02:18:59 +00002102 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00002103 pixel.green+=(*k)*GetPixelGreen(image,kernel_pixels+u*
cristyed231572011-07-14 02:18:59 +00002104 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00002105 pixel.blue+=(*k)*GetPixelBlue(image,kernel_pixels+u*
cristyed231572011-07-14 02:18:59 +00002106 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00002107 if (image->colorspace == CMYKColorspace)
2108 pixel.black+=(*k)*GetPixelBlack(image,kernel_pixels+u*
cristyed231572011-07-14 02:18:59 +00002109 GetPixelChannels(image));
cristy56a9e512010-01-06 18:18:55 +00002110 k++;
2111 }
cristy4c08aed2011-07-01 19:47:50 +00002112 kernel_pixels+=(image->columns+kernel->width)*
cristyed231572011-07-14 02:18:59 +00002113 GetPixelChannels(image);
cristy56a9e512010-01-06 18:18:55 +00002114 }
cristyed231572011-07-14 02:18:59 +00002115 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002116 SetPixelRed(filter_image,ClampToQuantum(pixel.red),q);
cristyed231572011-07-14 02:18:59 +00002117 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002118 SetPixelGreen(filter_image,ClampToQuantum(pixel.green),q);
cristyed231572011-07-14 02:18:59 +00002119 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002120 SetPixelBlue(filter_image,ClampToQuantum(pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00002121 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002122 (image->colorspace == CMYKColorspace))
2123 SetPixelBlack(filter_image,ClampToQuantum(pixel.black),q);
cristyed231572011-07-14 02:18:59 +00002124 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy56a9e512010-01-06 18:18:55 +00002125 {
cristy36826ab2010-03-06 01:29:30 +00002126 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002127 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00002128 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002129 {
cristybb503372010-05-27 20:51:26 +00002130 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002131 {
cristy4c08aed2011-07-01 19:47:50 +00002132 pixel.alpha+=(*k)*GetPixelRed(image,kernel_pixels+u*
cristyed231572011-07-14 02:18:59 +00002133 GetPixelChannels(image));
cristy56a9e512010-01-06 18:18:55 +00002134 k++;
2135 }
cristy4c08aed2011-07-01 19:47:50 +00002136 kernel_pixels+=(image->columns+kernel->width)*
cristyed231572011-07-14 02:18:59 +00002137 GetPixelChannels(image);
cristy56a9e512010-01-06 18:18:55 +00002138 }
cristy4c08aed2011-07-01 19:47:50 +00002139 SetPixelAlpha(filter_image,ClampToQuantum(pixel.alpha),q);
cristy56a9e512010-01-06 18:18:55 +00002140 }
2141 }
2142 else
2143 {
2144 MagickRealType
2145 alpha,
2146 gamma;
2147
2148 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00002149 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002150 {
cristybb503372010-05-27 20:51:26 +00002151 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002152 {
cristy4c08aed2011-07-01 19:47:50 +00002153 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,
cristyed231572011-07-14 02:18:59 +00002154 kernel_pixels+u*GetPixelChannels(image)));
cristy4c08aed2011-07-01 19:47:50 +00002155 pixel.red+=(*k)*alpha*GetPixelRed(image,kernel_pixels+u*
cristyed231572011-07-14 02:18:59 +00002156 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00002157 pixel.green+=(*k)*alpha*GetPixelGreen(image,kernel_pixels+u*
cristyed231572011-07-14 02:18:59 +00002158 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00002159 pixel.blue+=(*k)*alpha*GetPixelBlue(image,kernel_pixels+u*
cristyed231572011-07-14 02:18:59 +00002160 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00002161 if (image->colorspace == CMYKColorspace)
2162 pixel.black+=(*k)*alpha*GetPixelBlack(image,kernel_pixels+u*
cristyed231572011-07-14 02:18:59 +00002163 GetPixelChannels(image));
cristy56a9e512010-01-06 18:18:55 +00002164 gamma+=(*k)*alpha;
2165 k++;
2166 }
cristy5ce8df82011-07-07 14:52:23 +00002167 kernel_pixels+=(image->columns+kernel->width)*
cristyed231572011-07-14 02:18:59 +00002168 GetPixelChannels(image);
cristy56a9e512010-01-06 18:18:55 +00002169 }
2170 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00002171 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002172 SetPixelRed(filter_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +00002173 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002174 SetPixelGreen(filter_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +00002175 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002176 SetPixelBlue(filter_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00002177 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy56a9e512010-01-06 18:18:55 +00002178 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002179 SetPixelBlack(filter_image,ClampToQuantum(gamma*pixel.black),q);
cristyed231572011-07-14 02:18:59 +00002180 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy56a9e512010-01-06 18:18:55 +00002181 {
cristy36826ab2010-03-06 01:29:30 +00002182 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002183 kernel_pixels=p;
cristybb503372010-05-27 20:51:26 +00002184 for (v=0; v < (ssize_t) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002185 {
cristybb503372010-05-27 20:51:26 +00002186 for (u=0; u < (ssize_t) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002187 {
cristy4c08aed2011-07-01 19:47:50 +00002188 pixel.alpha+=(*k)*GetPixelAlpha(image,kernel_pixels+u*
cristyed231572011-07-14 02:18:59 +00002189 GetPixelChannels(image));
cristy56a9e512010-01-06 18:18:55 +00002190 k++;
2191 }
cristy4c08aed2011-07-01 19:47:50 +00002192 kernel_pixels+=(image->columns+kernel->width)*
cristyed231572011-07-14 02:18:59 +00002193 GetPixelChannels(image);
cristy56a9e512010-01-06 18:18:55 +00002194 }
cristy4c08aed2011-07-01 19:47:50 +00002195 SetPixelAlpha(filter_image,ClampToQuantum(pixel.alpha),q);
cristy56a9e512010-01-06 18:18:55 +00002196 }
2197 }
cristyed231572011-07-14 02:18:59 +00002198 p+=GetPixelChannels(image);
2199 q+=GetPixelChannels(filter_image);
cristy56a9e512010-01-06 18:18:55 +00002200 }
2201 sync=SyncCacheViewAuthenticPixels(filter_view,exception);
2202 if (sync == MagickFalse)
2203 status=MagickFalse;
2204 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2205 {
2206 MagickBooleanType
2207 proceed;
2208
2209#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00002210 #pragma omp critical (MagickCore_FilterImage)
cristy56a9e512010-01-06 18:18:55 +00002211#endif
2212 proceed=SetImageProgress(image,FilterImageTag,progress++,image->rows);
2213 if (proceed == MagickFalse)
2214 status=MagickFalse;
2215 }
2216 }
2217 filter_image->type=image->type;
2218 filter_view=DestroyCacheView(filter_view);
2219 image_view=DestroyCacheView(image_view);
cristy56a9e512010-01-06 18:18:55 +00002220 if (status == MagickFalse)
2221 filter_image=DestroyImage(filter_image);
2222 return(filter_image);
2223}
2224
2225/*
2226%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2227% %
2228% %
2229% %
cristy3ed852e2009-09-05 21:47:34 +00002230% G a u s s i a n B l u r I m a g e %
2231% %
2232% %
2233% %
2234%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2235%
2236% GaussianBlurImage() blurs an image. We convolve the image with a
2237% Gaussian operator of the given radius and standard deviation (sigma).
2238% For reasonable results, the radius should be larger than sigma. Use a
2239% radius of 0 and GaussianBlurImage() selects a suitable radius for you
2240%
2241% The format of the GaussianBlurImage method is:
2242%
2243% Image *GaussianBlurImage(const Image *image,onst double radius,
2244% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002245%
2246% A description of each parameter follows:
2247%
2248% o image: the image.
2249%
cristy3ed852e2009-09-05 21:47:34 +00002250% o radius: the radius of the Gaussian, in pixels, not counting the center
2251% pixel.
2252%
2253% o sigma: the standard deviation of the Gaussian, in pixels.
2254%
2255% o exception: return any errors or warnings in this structure.
2256%
2257*/
cristyf4ad9df2011-07-08 16:49:03 +00002258MagickExport Image *GaussianBlurImage(const Image *image,
2259 const double radius,const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002260{
2261 double
2262 *kernel;
2263
2264 Image
2265 *blur_image;
2266
cristybb503372010-05-27 20:51:26 +00002267 register ssize_t
cristy47e00502009-12-17 19:19:57 +00002268 i;
2269
cristybb503372010-05-27 20:51:26 +00002270 size_t
cristy3ed852e2009-09-05 21:47:34 +00002271 width;
2272
cristy117ff172010-08-15 21:35:32 +00002273 ssize_t
2274 j,
2275 u,
2276 v;
2277
cristy3ed852e2009-09-05 21:47:34 +00002278 assert(image != (const Image *) NULL);
2279 assert(image->signature == MagickSignature);
2280 if (image->debug != MagickFalse)
2281 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2282 assert(exception != (ExceptionInfo *) NULL);
2283 assert(exception->signature == MagickSignature);
2284 width=GetOptimalKernelWidth2D(radius,sigma);
2285 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2286 if (kernel == (double *) NULL)
2287 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00002288 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00002289 i=0;
cristy47e00502009-12-17 19:19:57 +00002290 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002291 {
cristy47e00502009-12-17 19:19:57 +00002292 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00002293 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
2294 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00002295 }
cristyf4ad9df2011-07-08 16:49:03 +00002296 blur_image=ConvolveImage(image,width,kernel,exception);
cristy3ed852e2009-09-05 21:47:34 +00002297 kernel=(double *) RelinquishMagickMemory(kernel);
2298 return(blur_image);
2299}
2300
2301/*
2302%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2303% %
2304% %
2305% %
cristy3ed852e2009-09-05 21:47:34 +00002306% M o t i o n B l u r I m a g e %
2307% %
2308% %
2309% %
2310%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2311%
2312% MotionBlurImage() simulates motion blur. We convolve the image with a
2313% Gaussian operator of the given radius and standard deviation (sigma).
2314% For reasonable results, radius should be larger than sigma. Use a
2315% radius of 0 and MotionBlurImage() selects a suitable radius for you.
2316% Angle gives the angle of the blurring motion.
2317%
2318% Andrew Protano contributed this effect.
2319%
2320% The format of the MotionBlurImage method is:
2321%
2322% Image *MotionBlurImage(const Image *image,const double radius,
2323% const double sigma,const double angle,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002324%
2325% A description of each parameter follows:
2326%
2327% o image: the image.
2328%
cristy3ed852e2009-09-05 21:47:34 +00002329% o radius: the radius of the Gaussian, in pixels, not counting
2330% the center pixel.
2331%
2332% o sigma: the standard deviation of the Gaussian, in pixels.
2333%
cristycee97112010-05-28 00:44:52 +00002334% o angle: Apply the effect along this angle.
cristy3ed852e2009-09-05 21:47:34 +00002335%
2336% o exception: return any errors or warnings in this structure.
2337%
2338*/
2339
cristybb503372010-05-27 20:51:26 +00002340static double *GetMotionBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +00002341{
cristy3ed852e2009-09-05 21:47:34 +00002342 double
cristy47e00502009-12-17 19:19:57 +00002343 *kernel,
cristy3ed852e2009-09-05 21:47:34 +00002344 normalize;
2345
cristybb503372010-05-27 20:51:26 +00002346 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002347 i;
2348
2349 /*
cristy47e00502009-12-17 19:19:57 +00002350 Generate a 1-D convolution kernel.
cristy3ed852e2009-09-05 21:47:34 +00002351 */
2352 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
2353 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
2354 if (kernel == (double *) NULL)
2355 return(kernel);
cristy3ed852e2009-09-05 21:47:34 +00002356 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00002357 for (i=0; i < (ssize_t) width; i++)
cristy47e00502009-12-17 19:19:57 +00002358 {
cristy4205a3c2010-09-12 20:19:59 +00002359 kernel[i]=(double) (exp((-((double) i*i)/(double) (2.0*MagickSigma*
2360 MagickSigma)))/(MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00002361 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +00002362 }
cristybb503372010-05-27 20:51:26 +00002363 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002364 kernel[i]/=normalize;
2365 return(kernel);
2366}
2367
cristyf4ad9df2011-07-08 16:49:03 +00002368MagickExport Image *MotionBlurImage(const Image *image,
2369 const double radius,const double sigma,const double angle,
2370 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002371{
cristyc4c8d132010-01-07 01:58:38 +00002372 CacheView
2373 *blur_view,
2374 *image_view;
2375
cristy3ed852e2009-09-05 21:47:34 +00002376 double
2377 *kernel;
2378
2379 Image
2380 *blur_image;
2381
cristy3ed852e2009-09-05 21:47:34 +00002382 MagickBooleanType
2383 status;
2384
cristybb503372010-05-27 20:51:26 +00002385 MagickOffsetType
2386 progress;
2387
cristy4c08aed2011-07-01 19:47:50 +00002388 PixelInfo
cristyddd82202009-11-03 20:14:50 +00002389 bias;
cristy3ed852e2009-09-05 21:47:34 +00002390
2391 OffsetInfo
2392 *offset;
2393
2394 PointInfo
2395 point;
2396
cristybb503372010-05-27 20:51:26 +00002397 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002398 i;
2399
cristybb503372010-05-27 20:51:26 +00002400 size_t
cristy3ed852e2009-09-05 21:47:34 +00002401 width;
2402
cristybb503372010-05-27 20:51:26 +00002403 ssize_t
2404 y;
2405
cristy3ed852e2009-09-05 21:47:34 +00002406 assert(image != (Image *) NULL);
2407 assert(image->signature == MagickSignature);
2408 if (image->debug != MagickFalse)
2409 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2410 assert(exception != (ExceptionInfo *) NULL);
2411 width=GetOptimalKernelWidth1D(radius,sigma);
2412 kernel=GetMotionBlurKernel(width,sigma);
2413 if (kernel == (double *) NULL)
2414 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2415 offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset));
2416 if (offset == (OffsetInfo *) NULL)
2417 {
2418 kernel=(double *) RelinquishMagickMemory(kernel);
2419 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2420 }
2421 blur_image=CloneImage(image,0,0,MagickTrue,exception);
2422 if (blur_image == (Image *) NULL)
2423 {
2424 kernel=(double *) RelinquishMagickMemory(kernel);
2425 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2426 return((Image *) NULL);
2427 }
2428 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
2429 {
2430 kernel=(double *) RelinquishMagickMemory(kernel);
2431 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2432 InheritException(exception,&blur_image->exception);
2433 blur_image=DestroyImage(blur_image);
2434 return((Image *) NULL);
2435 }
2436 point.x=(double) width*sin(DegreesToRadians(angle));
2437 point.y=(double) width*cos(DegreesToRadians(angle));
cristybb503372010-05-27 20:51:26 +00002438 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002439 {
cristybb503372010-05-27 20:51:26 +00002440 offset[i].x=(ssize_t) ceil((double) (i*point.y)/hypot(point.x,point.y)-0.5);
2441 offset[i].y=(ssize_t) ceil((double) (i*point.x)/hypot(point.x,point.y)-0.5);
cristy3ed852e2009-09-05 21:47:34 +00002442 }
2443 /*
2444 Motion blur image.
2445 */
2446 status=MagickTrue;
2447 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00002448 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00002449 image_view=AcquireCacheView(image);
2450 blur_view=AcquireCacheView(blur_image);
cristyb557a152011-02-22 12:14:30 +00002451#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00002452 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00002453#endif
cristybb503372010-05-27 20:51:26 +00002454 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002455 {
cristy4c08aed2011-07-01 19:47:50 +00002456 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002457 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002458
cristy117ff172010-08-15 21:35:32 +00002459 register ssize_t
2460 x;
2461
cristy3ed852e2009-09-05 21:47:34 +00002462 if (status == MagickFalse)
2463 continue;
2464 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
2465 exception);
cristy4c08aed2011-07-01 19:47:50 +00002466 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002467 {
2468 status=MagickFalse;
2469 continue;
2470 }
cristybb503372010-05-27 20:51:26 +00002471 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002472 {
cristy4c08aed2011-07-01 19:47:50 +00002473 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002474 qixel;
2475
2476 PixelPacket
2477 pixel;
2478
2479 register double
cristyc47d1f82009-11-26 01:44:43 +00002480 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00002481
cristybb503372010-05-27 20:51:26 +00002482 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002483 i;
2484
cristy3ed852e2009-09-05 21:47:34 +00002485 k=kernel;
cristyddd82202009-11-03 20:14:50 +00002486 qixel=bias;
cristyed231572011-07-14 02:18:59 +00002487 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00002488 {
cristybb503372010-05-27 20:51:26 +00002489 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002490 {
2491 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
2492 offset[i].y,&pixel,exception);
2493 qixel.red+=(*k)*pixel.red;
2494 qixel.green+=(*k)*pixel.green;
2495 qixel.blue+=(*k)*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00002496 qixel.alpha+=(*k)*pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00002497 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00002498 qixel.black+=(*k)*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00002499 k++;
2500 }
cristyed231572011-07-14 02:18:59 +00002501 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002502 SetPixelRed(blur_image,
2503 ClampToQuantum(qixel.red),q);
cristyed231572011-07-14 02:18:59 +00002504 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002505 SetPixelGreen(blur_image,
2506 ClampToQuantum(qixel.green),q);
cristyed231572011-07-14 02:18:59 +00002507 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002508 SetPixelBlue(blur_image,
2509 ClampToQuantum(qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00002510 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002511 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002512 SetPixelBlack(blur_image,
2513 ClampToQuantum(qixel.black),q);
cristyed231572011-07-14 02:18:59 +00002514 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002515 SetPixelAlpha(blur_image,
2516 ClampToQuantum(qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00002517 }
2518 else
2519 {
2520 MagickRealType
2521 alpha,
2522 gamma;
2523
2524 alpha=0.0;
2525 gamma=0.0;
cristybb503372010-05-27 20:51:26 +00002526 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002527 {
2528 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
2529 offset[i].y,&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00002530 alpha=(MagickRealType) (QuantumScale*pixel.alpha);
cristy3ed852e2009-09-05 21:47:34 +00002531 qixel.red+=(*k)*alpha*pixel.red;
2532 qixel.green+=(*k)*alpha*pixel.green;
2533 qixel.blue+=(*k)*alpha*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00002534 qixel.alpha+=(*k)*pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00002535 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00002536 qixel.black+=(*k)*alpha*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00002537 gamma+=(*k)*alpha;
2538 k++;
2539 }
2540 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00002541 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002542 SetPixelRed(blur_image,
2543 ClampToQuantum(gamma*qixel.red),q);
cristyed231572011-07-14 02:18:59 +00002544 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002545 SetPixelGreen(blur_image,
2546 ClampToQuantum(gamma*qixel.green),q);
cristyed231572011-07-14 02:18:59 +00002547 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002548 SetPixelBlue(blur_image,
2549 ClampToQuantum(gamma*qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00002550 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002551 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00002552 SetPixelBlack(blur_image,
2553 ClampToQuantum(gamma*qixel.black),q);
cristyed231572011-07-14 02:18:59 +00002554 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00002555 SetPixelAlpha(blur_image,
2556 ClampToQuantum(qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00002557 }
cristyed231572011-07-14 02:18:59 +00002558 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00002559 }
2560 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
2561 status=MagickFalse;
2562 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2563 {
2564 MagickBooleanType
2565 proceed;
2566
cristyb557a152011-02-22 12:14:30 +00002567#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00002568 #pragma omp critical (MagickCore_MotionBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00002569#endif
2570 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
2571 if (proceed == MagickFalse)
2572 status=MagickFalse;
2573 }
2574 }
2575 blur_view=DestroyCacheView(blur_view);
2576 image_view=DestroyCacheView(image_view);
2577 kernel=(double *) RelinquishMagickMemory(kernel);
2578 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2579 if (status == MagickFalse)
2580 blur_image=DestroyImage(blur_image);
2581 return(blur_image);
2582}
2583
2584/*
2585%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2586% %
2587% %
2588% %
2589% P r e v i e w I m a g e %
2590% %
2591% %
2592% %
2593%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2594%
2595% PreviewImage() tiles 9 thumbnails of the specified image with an image
2596% processing operation applied with varying parameters. This may be helpful
2597% pin-pointing an appropriate parameter for a particular image processing
2598% operation.
2599%
2600% The format of the PreviewImages method is:
2601%
2602% Image *PreviewImages(const Image *image,const PreviewType preview,
2603% ExceptionInfo *exception)
2604%
2605% A description of each parameter follows:
2606%
2607% o image: the image.
2608%
2609% o preview: the image processing operation.
2610%
2611% o exception: return any errors or warnings in this structure.
2612%
2613*/
2614MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
2615 ExceptionInfo *exception)
2616{
2617#define NumberTiles 9
2618#define PreviewImageTag "Preview/Image"
2619#define DefaultPreviewGeometry "204x204+10+10"
2620
2621 char
2622 factor[MaxTextExtent],
2623 label[MaxTextExtent];
2624
2625 double
2626 degrees,
2627 gamma,
2628 percentage,
2629 radius,
2630 sigma,
2631 threshold;
2632
2633 Image
2634 *images,
2635 *montage_image,
2636 *preview_image,
2637 *thumbnail;
2638
2639 ImageInfo
2640 *preview_info;
2641
cristy3ed852e2009-09-05 21:47:34 +00002642 MagickBooleanType
2643 proceed;
2644
2645 MontageInfo
2646 *montage_info;
2647
2648 QuantizeInfo
2649 quantize_info;
2650
2651 RectangleInfo
2652 geometry;
2653
cristybb503372010-05-27 20:51:26 +00002654 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002655 i,
2656 x;
2657
cristybb503372010-05-27 20:51:26 +00002658 size_t
cristy3ed852e2009-09-05 21:47:34 +00002659 colors;
2660
cristy117ff172010-08-15 21:35:32 +00002661 ssize_t
2662 y;
2663
cristy3ed852e2009-09-05 21:47:34 +00002664 /*
2665 Open output image file.
2666 */
2667 assert(image != (Image *) NULL);
2668 assert(image->signature == MagickSignature);
2669 if (image->debug != MagickFalse)
2670 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2671 colors=2;
2672 degrees=0.0;
2673 gamma=(-0.2f);
2674 preview_info=AcquireImageInfo();
2675 SetGeometry(image,&geometry);
2676 (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
2677 &geometry.width,&geometry.height);
2678 images=NewImageList();
2679 percentage=12.5;
2680 GetQuantizeInfo(&quantize_info);
2681 radius=0.0;
2682 sigma=1.0;
2683 threshold=0.0;
2684 x=0;
2685 y=0;
2686 for (i=0; i < NumberTiles; i++)
2687 {
2688 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
2689 if (thumbnail == (Image *) NULL)
2690 break;
2691 (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
2692 (void *) NULL);
2693 (void) SetImageProperty(thumbnail,"label",DefaultTileLabel);
2694 if (i == (NumberTiles/2))
2695 {
2696 (void) QueryColorDatabase("#dfdfdf",&thumbnail->matte_color,exception);
2697 AppendImageToList(&images,thumbnail);
2698 continue;
2699 }
2700 switch (preview)
2701 {
2702 case RotatePreview:
2703 {
2704 degrees+=45.0;
2705 preview_image=RotateImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002706 (void) FormatLocaleString(label,MaxTextExtent,"rotate %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002707 break;
2708 }
2709 case ShearPreview:
2710 {
2711 degrees+=5.0;
2712 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002713 (void) FormatLocaleString(label,MaxTextExtent,"shear %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002714 degrees,2.0*degrees);
2715 break;
2716 }
2717 case RollPreview:
2718 {
cristybb503372010-05-27 20:51:26 +00002719 x=(ssize_t) ((i+1)*thumbnail->columns)/NumberTiles;
2720 y=(ssize_t) ((i+1)*thumbnail->rows)/NumberTiles;
cristy3ed852e2009-09-05 21:47:34 +00002721 preview_image=RollImage(thumbnail,x,y,exception);
cristyb51dff52011-05-19 16:55:47 +00002722 (void) FormatLocaleString(label,MaxTextExtent,"roll %+.20gx%+.20g",
cristye8c25f92010-06-03 00:53:06 +00002723 (double) x,(double) y);
cristy3ed852e2009-09-05 21:47:34 +00002724 break;
2725 }
2726 case HuePreview:
2727 {
2728 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2729 if (preview_image == (Image *) NULL)
2730 break;
cristyb51dff52011-05-19 16:55:47 +00002731 (void) FormatLocaleString(factor,MaxTextExtent,"100,100,%g",
cristy3ed852e2009-09-05 21:47:34 +00002732 2.0*percentage);
2733 (void) ModulateImage(preview_image,factor);
cristyb51dff52011-05-19 16:55:47 +00002734 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002735 break;
2736 }
2737 case SaturationPreview:
2738 {
2739 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2740 if (preview_image == (Image *) NULL)
2741 break;
cristyb51dff52011-05-19 16:55:47 +00002742 (void) FormatLocaleString(factor,MaxTextExtent,"100,%g",
cristy8cd5b312010-01-07 01:10:24 +00002743 2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00002744 (void) ModulateImage(preview_image,factor);
cristyb51dff52011-05-19 16:55:47 +00002745 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002746 break;
2747 }
2748 case BrightnessPreview:
2749 {
2750 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2751 if (preview_image == (Image *) NULL)
2752 break;
cristyb51dff52011-05-19 16:55:47 +00002753 (void) FormatLocaleString(factor,MaxTextExtent,"%g",2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00002754 (void) ModulateImage(preview_image,factor);
cristyb51dff52011-05-19 16:55:47 +00002755 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002756 break;
2757 }
2758 case GammaPreview:
2759 default:
2760 {
2761 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2762 if (preview_image == (Image *) NULL)
2763 break;
2764 gamma+=0.4f;
cristy50fbc382011-07-07 02:19:17 +00002765 (void) GammaImage(preview_image,gamma);
cristyb51dff52011-05-19 16:55:47 +00002766 (void) FormatLocaleString(label,MaxTextExtent,"gamma %g",gamma);
cristy3ed852e2009-09-05 21:47:34 +00002767 break;
2768 }
2769 case SpiffPreview:
2770 {
2771 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2772 if (preview_image != (Image *) NULL)
2773 for (x=0; x < i; x++)
2774 (void) ContrastImage(preview_image,MagickTrue);
cristyb51dff52011-05-19 16:55:47 +00002775 (void) FormatLocaleString(label,MaxTextExtent,"contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002776 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002777 break;
2778 }
2779 case DullPreview:
2780 {
2781 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2782 if (preview_image == (Image *) NULL)
2783 break;
2784 for (x=0; x < i; x++)
2785 (void) ContrastImage(preview_image,MagickFalse);
cristyb51dff52011-05-19 16:55:47 +00002786 (void) FormatLocaleString(label,MaxTextExtent,"+contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002787 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002788 break;
2789 }
2790 case GrayscalePreview:
2791 {
2792 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2793 if (preview_image == (Image *) NULL)
2794 break;
2795 colors<<=1;
2796 quantize_info.number_colors=colors;
2797 quantize_info.colorspace=GRAYColorspace;
2798 (void) QuantizeImage(&quantize_info,preview_image);
cristyb51dff52011-05-19 16:55:47 +00002799 (void) FormatLocaleString(label,MaxTextExtent,
cristye8c25f92010-06-03 00:53:06 +00002800 "-colorspace gray -colors %.20g",(double) colors);
cristy3ed852e2009-09-05 21:47:34 +00002801 break;
2802 }
2803 case QuantizePreview:
2804 {
2805 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2806 if (preview_image == (Image *) NULL)
2807 break;
2808 colors<<=1;
2809 quantize_info.number_colors=colors;
2810 (void) QuantizeImage(&quantize_info,preview_image);
cristyb51dff52011-05-19 16:55:47 +00002811 (void) FormatLocaleString(label,MaxTextExtent,"colors %.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00002812 colors);
cristy3ed852e2009-09-05 21:47:34 +00002813 break;
2814 }
2815 case DespecklePreview:
2816 {
2817 for (x=0; x < (i-1); x++)
2818 {
2819 preview_image=DespeckleImage(thumbnail,exception);
2820 if (preview_image == (Image *) NULL)
2821 break;
2822 thumbnail=DestroyImage(thumbnail);
2823 thumbnail=preview_image;
2824 }
2825 preview_image=DespeckleImage(thumbnail,exception);
2826 if (preview_image == (Image *) NULL)
2827 break;
cristyb51dff52011-05-19 16:55:47 +00002828 (void) FormatLocaleString(label,MaxTextExtent,"despeckle (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002829 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002830 break;
2831 }
2832 case ReduceNoisePreview:
2833 {
cristy95c38342011-03-18 22:39:51 +00002834 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) radius,
2835 (size_t) radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002836 (void) FormatLocaleString(label,MaxTextExtent,"noise %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002837 break;
2838 }
2839 case AddNoisePreview:
2840 {
2841 switch ((int) i)
2842 {
2843 case 0:
2844 {
2845 (void) CopyMagickString(factor,"uniform",MaxTextExtent);
2846 break;
2847 }
2848 case 1:
2849 {
2850 (void) CopyMagickString(factor,"gaussian",MaxTextExtent);
2851 break;
2852 }
2853 case 2:
2854 {
2855 (void) CopyMagickString(factor,"multiplicative",MaxTextExtent);
2856 break;
2857 }
2858 case 3:
2859 {
2860 (void) CopyMagickString(factor,"impulse",MaxTextExtent);
2861 break;
2862 }
2863 case 4:
2864 {
2865 (void) CopyMagickString(factor,"laplacian",MaxTextExtent);
2866 break;
2867 }
2868 case 5:
2869 {
2870 (void) CopyMagickString(factor,"Poisson",MaxTextExtent);
2871 break;
2872 }
2873 default:
2874 {
2875 (void) CopyMagickString(thumbnail->magick,"NULL",MaxTextExtent);
2876 break;
2877 }
2878 }
cristyd76c51e2011-03-26 00:21:26 +00002879 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) i,
2880 (size_t) i,exception);
cristyb51dff52011-05-19 16:55:47 +00002881 (void) FormatLocaleString(label,MaxTextExtent,"+noise %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002882 break;
2883 }
2884 case SharpenPreview:
2885 {
2886 preview_image=SharpenImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00002887 (void) FormatLocaleString(label,MaxTextExtent,"sharpen %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002888 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002889 break;
2890 }
2891 case BlurPreview:
2892 {
2893 preview_image=BlurImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00002894 (void) FormatLocaleString(label,MaxTextExtent,"blur %gx%g",radius,
cristy3ed852e2009-09-05 21:47:34 +00002895 sigma);
2896 break;
2897 }
2898 case ThresholdPreview:
2899 {
2900 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2901 if (preview_image == (Image *) NULL)
2902 break;
2903 (void) BilevelImage(thumbnail,
2904 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
cristyb51dff52011-05-19 16:55:47 +00002905 (void) FormatLocaleString(label,MaxTextExtent,"threshold %g",
cristy3ed852e2009-09-05 21:47:34 +00002906 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
2907 break;
2908 }
2909 case EdgeDetectPreview:
2910 {
2911 preview_image=EdgeImage(thumbnail,radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002912 (void) FormatLocaleString(label,MaxTextExtent,"edge %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002913 break;
2914 }
2915 case SpreadPreview:
2916 {
2917 preview_image=SpreadImage(thumbnail,radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002918 (void) FormatLocaleString(label,MaxTextExtent,"spread %g",
cristy8cd5b312010-01-07 01:10:24 +00002919 radius+0.5);
cristy3ed852e2009-09-05 21:47:34 +00002920 break;
2921 }
2922 case SolarizePreview:
2923 {
2924 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2925 if (preview_image == (Image *) NULL)
2926 break;
2927 (void) SolarizeImage(preview_image,(double) QuantumRange*
2928 percentage/100.0);
cristyb51dff52011-05-19 16:55:47 +00002929 (void) FormatLocaleString(label,MaxTextExtent,"solarize %g",
cristy3ed852e2009-09-05 21:47:34 +00002930 (QuantumRange*percentage)/100.0);
2931 break;
2932 }
2933 case ShadePreview:
2934 {
2935 degrees+=10.0;
2936 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
2937 exception);
cristyb51dff52011-05-19 16:55:47 +00002938 (void) FormatLocaleString(label,MaxTextExtent,"shade %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002939 degrees,degrees);
cristy3ed852e2009-09-05 21:47:34 +00002940 break;
2941 }
2942 case RaisePreview:
2943 {
2944 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2945 if (preview_image == (Image *) NULL)
2946 break;
cristybb503372010-05-27 20:51:26 +00002947 geometry.width=(size_t) (2*i+2);
2948 geometry.height=(size_t) (2*i+2);
cristy3ed852e2009-09-05 21:47:34 +00002949 geometry.x=i/2;
2950 geometry.y=i/2;
2951 (void) RaiseImage(preview_image,&geometry,MagickTrue);
cristyb51dff52011-05-19 16:55:47 +00002952 (void) FormatLocaleString(label,MaxTextExtent,
cristy6d8abba2010-06-03 01:10:47 +00002953 "raise %.20gx%.20g%+.20g%+.20g",(double) geometry.width,(double)
cristye8c25f92010-06-03 00:53:06 +00002954 geometry.height,(double) geometry.x,(double) geometry.y);
cristy3ed852e2009-09-05 21:47:34 +00002955 break;
2956 }
2957 case SegmentPreview:
2958 {
2959 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2960 if (preview_image == (Image *) NULL)
2961 break;
2962 threshold+=0.4f;
2963 (void) SegmentImage(preview_image,RGBColorspace,MagickFalse,threshold,
2964 threshold);
cristyb51dff52011-05-19 16:55:47 +00002965 (void) FormatLocaleString(label,MaxTextExtent,"segment %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002966 threshold,threshold);
2967 break;
2968 }
2969 case SwirlPreview:
2970 {
2971 preview_image=SwirlImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002972 (void) FormatLocaleString(label,MaxTextExtent,"swirl %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002973 degrees+=45.0;
2974 break;
2975 }
2976 case ImplodePreview:
2977 {
2978 degrees+=0.1f;
2979 preview_image=ImplodeImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002980 (void) FormatLocaleString(label,MaxTextExtent,"implode %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002981 break;
2982 }
2983 case WavePreview:
2984 {
2985 degrees+=5.0f;
2986 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002987 (void) FormatLocaleString(label,MaxTextExtent,"wave %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002988 0.5*degrees,2.0*degrees);
cristy3ed852e2009-09-05 21:47:34 +00002989 break;
2990 }
2991 case OilPaintPreview:
2992 {
2993 preview_image=OilPaintImage(thumbnail,(double) radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002994 (void) FormatLocaleString(label,MaxTextExtent,"paint %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002995 break;
2996 }
2997 case CharcoalDrawingPreview:
2998 {
2999 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
3000 exception);
cristyb51dff52011-05-19 16:55:47 +00003001 (void) FormatLocaleString(label,MaxTextExtent,"charcoal %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003002 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00003003 break;
3004 }
3005 case JPEGPreview:
3006 {
3007 char
3008 filename[MaxTextExtent];
3009
3010 int
3011 file;
3012
3013 MagickBooleanType
3014 status;
3015
3016 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3017 if (preview_image == (Image *) NULL)
3018 break;
cristybb503372010-05-27 20:51:26 +00003019 preview_info->quality=(size_t) percentage;
cristyb51dff52011-05-19 16:55:47 +00003020 (void) FormatLocaleString(factor,MaxTextExtent,"%.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00003021 preview_info->quality);
cristy3ed852e2009-09-05 21:47:34 +00003022 file=AcquireUniqueFileResource(filename);
3023 if (file != -1)
3024 file=close(file)-1;
cristyb51dff52011-05-19 16:55:47 +00003025 (void) FormatLocaleString(preview_image->filename,MaxTextExtent,
cristy3ed852e2009-09-05 21:47:34 +00003026 "jpeg:%s",filename);
3027 status=WriteImage(preview_info,preview_image);
3028 if (status != MagickFalse)
3029 {
3030 Image
3031 *quality_image;
3032
3033 (void) CopyMagickString(preview_info->filename,
3034 preview_image->filename,MaxTextExtent);
3035 quality_image=ReadImage(preview_info,exception);
3036 if (quality_image != (Image *) NULL)
3037 {
3038 preview_image=DestroyImage(preview_image);
3039 preview_image=quality_image;
3040 }
3041 }
3042 (void) RelinquishUniqueFileResource(preview_image->filename);
3043 if ((GetBlobSize(preview_image)/1024) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00003044 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%gmb ",
cristy3ed852e2009-09-05 21:47:34 +00003045 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
3046 1024.0/1024.0);
3047 else
3048 if (GetBlobSize(preview_image) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00003049 (void) FormatLocaleString(label,MaxTextExtent,
cristye7f51092010-01-17 00:39:37 +00003050 "quality %s\n%gkb ",factor,(double) ((MagickOffsetType)
cristy8cd5b312010-01-07 01:10:24 +00003051 GetBlobSize(preview_image))/1024.0);
cristy3ed852e2009-09-05 21:47:34 +00003052 else
cristyb51dff52011-05-19 16:55:47 +00003053 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%.20gb ",
cristy54ea5732011-06-10 12:39:53 +00003054 factor,(double) ((MagickOffsetType) GetBlobSize(thumbnail)));
cristy3ed852e2009-09-05 21:47:34 +00003055 break;
3056 }
3057 }
3058 thumbnail=DestroyImage(thumbnail);
3059 percentage+=12.5;
3060 radius+=0.5;
3061 sigma+=0.25;
3062 if (preview_image == (Image *) NULL)
3063 break;
3064 (void) DeleteImageProperty(preview_image,"label");
3065 (void) SetImageProperty(preview_image,"label",label);
3066 AppendImageToList(&images,preview_image);
cristybb503372010-05-27 20:51:26 +00003067 proceed=SetImageProgress(image,PreviewImageTag,(MagickOffsetType) i,
3068 NumberTiles);
cristy3ed852e2009-09-05 21:47:34 +00003069 if (proceed == MagickFalse)
3070 break;
3071 }
3072 if (images == (Image *) NULL)
3073 {
3074 preview_info=DestroyImageInfo(preview_info);
3075 return((Image *) NULL);
3076 }
3077 /*
3078 Create the montage.
3079 */
3080 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
3081 (void) CopyMagickString(montage_info->filename,image->filename,MaxTextExtent);
3082 montage_info->shadow=MagickTrue;
3083 (void) CloneString(&montage_info->tile,"3x3");
3084 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
3085 (void) CloneString(&montage_info->frame,DefaultTileFrame);
3086 montage_image=MontageImages(images,montage_info,exception);
3087 montage_info=DestroyMontageInfo(montage_info);
3088 images=DestroyImageList(images);
3089 if (montage_image == (Image *) NULL)
3090 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3091 if (montage_image->montage != (char *) NULL)
3092 {
3093 /*
3094 Free image directory.
3095 */
3096 montage_image->montage=(char *) RelinquishMagickMemory(
3097 montage_image->montage);
3098 if (image->directory != (char *) NULL)
3099 montage_image->directory=(char *) RelinquishMagickMemory(
3100 montage_image->directory);
3101 }
3102 preview_info=DestroyImageInfo(preview_info);
3103 return(montage_image);
3104}
3105
3106/*
3107%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3108% %
3109% %
3110% %
3111% R a d i a l B l u r I m a g e %
3112% %
3113% %
3114% %
3115%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3116%
3117% RadialBlurImage() applies a radial blur to the image.
3118%
3119% Andrew Protano contributed this effect.
3120%
3121% The format of the RadialBlurImage method is:
3122%
3123% Image *RadialBlurImage(const Image *image,const double angle,
3124% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003125%
3126% A description of each parameter follows:
3127%
3128% o image: the image.
3129%
cristy3ed852e2009-09-05 21:47:34 +00003130% o angle: the angle of the radial blur.
3131%
3132% o exception: return any errors or warnings in this structure.
3133%
3134*/
cristyf4ad9df2011-07-08 16:49:03 +00003135MagickExport Image *RadialBlurImage(const Image *image,
3136 const double angle,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003137{
cristyc4c8d132010-01-07 01:58:38 +00003138 CacheView
3139 *blur_view,
3140 *image_view;
3141
cristy3ed852e2009-09-05 21:47:34 +00003142 Image
3143 *blur_image;
3144
cristy3ed852e2009-09-05 21:47:34 +00003145 MagickBooleanType
3146 status;
3147
cristybb503372010-05-27 20:51:26 +00003148 MagickOffsetType
3149 progress;
3150
cristy4c08aed2011-07-01 19:47:50 +00003151 PixelInfo
cristyddd82202009-11-03 20:14:50 +00003152 bias;
cristy3ed852e2009-09-05 21:47:34 +00003153
3154 MagickRealType
3155 blur_radius,
3156 *cos_theta,
3157 offset,
3158 *sin_theta,
3159 theta;
3160
3161 PointInfo
3162 blur_center;
3163
cristybb503372010-05-27 20:51:26 +00003164 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003165 i;
3166
cristybb503372010-05-27 20:51:26 +00003167 size_t
cristy3ed852e2009-09-05 21:47:34 +00003168 n;
3169
cristybb503372010-05-27 20:51:26 +00003170 ssize_t
3171 y;
3172
cristy3ed852e2009-09-05 21:47:34 +00003173 /*
3174 Allocate blur image.
3175 */
3176 assert(image != (Image *) NULL);
3177 assert(image->signature == MagickSignature);
3178 if (image->debug != MagickFalse)
3179 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3180 assert(exception != (ExceptionInfo *) NULL);
3181 assert(exception->signature == MagickSignature);
3182 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3183 if (blur_image == (Image *) NULL)
3184 return((Image *) NULL);
3185 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3186 {
3187 InheritException(exception,&blur_image->exception);
3188 blur_image=DestroyImage(blur_image);
3189 return((Image *) NULL);
3190 }
3191 blur_center.x=(double) image->columns/2.0;
3192 blur_center.y=(double) image->rows/2.0;
3193 blur_radius=hypot(blur_center.x,blur_center.y);
cristy117ff172010-08-15 21:35:32 +00003194 n=(size_t) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+2UL);
cristy3ed852e2009-09-05 21:47:34 +00003195 theta=DegreesToRadians(angle)/(MagickRealType) (n-1);
3196 cos_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3197 sizeof(*cos_theta));
3198 sin_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3199 sizeof(*sin_theta));
3200 if ((cos_theta == (MagickRealType *) NULL) ||
3201 (sin_theta == (MagickRealType *) NULL))
3202 {
3203 blur_image=DestroyImage(blur_image);
3204 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3205 }
3206 offset=theta*(MagickRealType) (n-1)/2.0;
cristybb503372010-05-27 20:51:26 +00003207 for (i=0; i < (ssize_t) n; i++)
cristy3ed852e2009-09-05 21:47:34 +00003208 {
3209 cos_theta[i]=cos((double) (theta*i-offset));
3210 sin_theta[i]=sin((double) (theta*i-offset));
3211 }
3212 /*
3213 Radial blur image.
3214 */
3215 status=MagickTrue;
3216 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00003217 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003218 image_view=AcquireCacheView(image);
3219 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003220#if defined(MAGICKCORE_OPENMP_SUPPORT)
3221 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003222#endif
cristybb503372010-05-27 20:51:26 +00003223 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003224 {
cristy4c08aed2011-07-01 19:47:50 +00003225 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003226 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003227
cristy117ff172010-08-15 21:35:32 +00003228 register ssize_t
3229 x;
3230
cristy3ed852e2009-09-05 21:47:34 +00003231 if (status == MagickFalse)
3232 continue;
3233 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3234 exception);
cristy4c08aed2011-07-01 19:47:50 +00003235 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003236 {
3237 status=MagickFalse;
3238 continue;
3239 }
cristybb503372010-05-27 20:51:26 +00003240 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003241 {
cristy4c08aed2011-07-01 19:47:50 +00003242 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003243 qixel;
3244
3245 MagickRealType
3246 normalize,
3247 radius;
3248
3249 PixelPacket
3250 pixel;
3251
3252 PointInfo
3253 center;
3254
cristybb503372010-05-27 20:51:26 +00003255 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003256 i;
3257
cristybb503372010-05-27 20:51:26 +00003258 size_t
cristy3ed852e2009-09-05 21:47:34 +00003259 step;
3260
3261 center.x=(double) x-blur_center.x;
3262 center.y=(double) y-blur_center.y;
3263 radius=hypot((double) center.x,center.y);
3264 if (radius == 0)
3265 step=1;
3266 else
3267 {
cristybb503372010-05-27 20:51:26 +00003268 step=(size_t) (blur_radius/radius);
cristy3ed852e2009-09-05 21:47:34 +00003269 if (step == 0)
3270 step=1;
3271 else
3272 if (step >= n)
3273 step=n-1;
3274 }
3275 normalize=0.0;
cristyddd82202009-11-03 20:14:50 +00003276 qixel=bias;
cristyed231572011-07-14 02:18:59 +00003277 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00003278 {
cristyeaedf062010-05-29 22:36:02 +00003279 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00003280 {
cristyeaedf062010-05-29 22:36:02 +00003281 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
3282 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
3283 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
3284 cos_theta[i]+0.5),&pixel,exception);
cristy3ed852e2009-09-05 21:47:34 +00003285 qixel.red+=pixel.red;
3286 qixel.green+=pixel.green;
3287 qixel.blue+=pixel.blue;
cristy3ed852e2009-09-05 21:47:34 +00003288 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00003289 qixel.black+=pixel.black;
3290 qixel.alpha+=pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00003291 normalize+=1.0;
3292 }
3293 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
3294 normalize);
cristyed231572011-07-14 02:18:59 +00003295 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003296 SetPixelRed(blur_image,
3297 ClampToQuantum(normalize*qixel.red),q);
cristyed231572011-07-14 02:18:59 +00003298 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003299 SetPixelGreen(blur_image,
3300 ClampToQuantum(normalize*qixel.green),q);
cristyed231572011-07-14 02:18:59 +00003301 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003302 SetPixelBlue(blur_image,
3303 ClampToQuantum(normalize*qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003304 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003305 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003306 SetPixelBlack(blur_image,
3307 ClampToQuantum(normalize*qixel.black),q);
cristyed231572011-07-14 02:18:59 +00003308 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003309 SetPixelAlpha(blur_image,
3310 ClampToQuantum(normalize*qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003311 }
3312 else
3313 {
3314 MagickRealType
3315 alpha,
3316 gamma;
3317
3318 alpha=1.0;
3319 gamma=0.0;
cristyeaedf062010-05-29 22:36:02 +00003320 for (i=0; i < (ssize_t) n; i+=(ssize_t) step)
cristy3ed852e2009-09-05 21:47:34 +00003321 {
cristyeaedf062010-05-29 22:36:02 +00003322 (void) GetOneCacheViewVirtualPixel(image_view,(ssize_t)
3323 (blur_center.x+center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),
3324 (ssize_t) (blur_center.y+center.x*sin_theta[i]+center.y*
3325 cos_theta[i]+0.5),&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00003326 alpha=(MagickRealType) (QuantumScale*pixel.alpha);
cristy3ed852e2009-09-05 21:47:34 +00003327 qixel.red+=alpha*pixel.red;
3328 qixel.green+=alpha*pixel.green;
3329 qixel.blue+=alpha*pixel.blue;
cristy4c08aed2011-07-01 19:47:50 +00003330 qixel.alpha+=pixel.alpha;
cristy3ed852e2009-09-05 21:47:34 +00003331 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00003332 qixel.black+=alpha*pixel.black;
cristy3ed852e2009-09-05 21:47:34 +00003333 gamma+=alpha;
3334 normalize+=1.0;
3335 }
3336 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3337 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
3338 normalize);
cristyed231572011-07-14 02:18:59 +00003339 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003340 SetPixelRed(blur_image,
3341 ClampToQuantum(gamma*qixel.red),q);
cristyed231572011-07-14 02:18:59 +00003342 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003343 SetPixelGreen(blur_image,
3344 ClampToQuantum(gamma*qixel.green),q);
cristyed231572011-07-14 02:18:59 +00003345 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003346 SetPixelBlue(blur_image,
3347 ClampToQuantum(gamma*qixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003348 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00003349 (image->colorspace == CMYKColorspace))
cristy4c08aed2011-07-01 19:47:50 +00003350 SetPixelBlack(blur_image,
3351 ClampToQuantum(gamma*qixel.black),q);
cristyed231572011-07-14 02:18:59 +00003352 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003353 SetPixelAlpha(blur_image,
3354 ClampToQuantum(normalize*qixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003355 }
cristyed231572011-07-14 02:18:59 +00003356 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003357 }
3358 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
3359 status=MagickFalse;
3360 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3361 {
3362 MagickBooleanType
3363 proceed;
3364
cristyb5d5f722009-11-04 03:03:49 +00003365#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00003366 #pragma omp critical (MagickCore_RadialBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00003367#endif
3368 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
3369 if (proceed == MagickFalse)
3370 status=MagickFalse;
3371 }
3372 }
3373 blur_view=DestroyCacheView(blur_view);
3374 image_view=DestroyCacheView(image_view);
3375 cos_theta=(MagickRealType *) RelinquishMagickMemory(cos_theta);
3376 sin_theta=(MagickRealType *) RelinquishMagickMemory(sin_theta);
3377 if (status == MagickFalse)
3378 blur_image=DestroyImage(blur_image);
3379 return(blur_image);
3380}
3381
3382/*
3383%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3384% %
3385% %
3386% %
cristy3ed852e2009-09-05 21:47:34 +00003387% S e l e c t i v e B l u r I m a g e %
3388% %
3389% %
3390% %
3391%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3392%
3393% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
3394% It is similar to the unsharpen mask that sharpens everything with contrast
3395% above a certain threshold.
3396%
3397% The format of the SelectiveBlurImage method is:
3398%
3399% Image *SelectiveBlurImage(const Image *image,const double radius,
3400% const double sigma,const double threshold,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003401%
3402% A description of each parameter follows:
3403%
3404% o image: the image.
3405%
cristy3ed852e2009-09-05 21:47:34 +00003406% o radius: the radius of the Gaussian, in pixels, not counting the center
3407% pixel.
3408%
3409% o sigma: the standard deviation of the Gaussian, in pixels.
3410%
3411% o threshold: only pixels within this contrast threshold are included
3412% in the blur operation.
3413%
3414% o exception: return any errors or warnings in this structure.
3415%
3416*/
cristyf4ad9df2011-07-08 16:49:03 +00003417MagickExport Image *SelectiveBlurImage(const Image *image,
3418 const double radius,const double sigma,const double threshold,
3419 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003420{
3421#define SelectiveBlurImageTag "SelectiveBlur/Image"
3422
cristy47e00502009-12-17 19:19:57 +00003423 CacheView
3424 *blur_view,
3425 *image_view;
3426
cristy3ed852e2009-09-05 21:47:34 +00003427 double
cristy3ed852e2009-09-05 21:47:34 +00003428 *kernel;
3429
3430 Image
3431 *blur_image;
3432
cristy3ed852e2009-09-05 21:47:34 +00003433 MagickBooleanType
3434 status;
3435
cristybb503372010-05-27 20:51:26 +00003436 MagickOffsetType
3437 progress;
3438
cristy4c08aed2011-07-01 19:47:50 +00003439 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003440 bias;
3441
cristybb503372010-05-27 20:51:26 +00003442 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003443 i;
cristy3ed852e2009-09-05 21:47:34 +00003444
cristybb503372010-05-27 20:51:26 +00003445 size_t
cristy3ed852e2009-09-05 21:47:34 +00003446 width;
3447
cristybb503372010-05-27 20:51:26 +00003448 ssize_t
3449 j,
3450 u,
3451 v,
3452 y;
3453
cristy3ed852e2009-09-05 21:47:34 +00003454 /*
3455 Initialize blur image attributes.
3456 */
3457 assert(image != (Image *) NULL);
3458 assert(image->signature == MagickSignature);
3459 if (image->debug != MagickFalse)
3460 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3461 assert(exception != (ExceptionInfo *) NULL);
3462 assert(exception->signature == MagickSignature);
3463 width=GetOptimalKernelWidth1D(radius,sigma);
3464 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
3465 if (kernel == (double *) NULL)
3466 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00003467 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00003468 i=0;
cristy47e00502009-12-17 19:19:57 +00003469 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003470 {
cristy47e00502009-12-17 19:19:57 +00003471 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00003472 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
3473 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00003474 }
3475 if (image->debug != MagickFalse)
3476 {
3477 char
3478 format[MaxTextExtent],
3479 *message;
3480
cristy117ff172010-08-15 21:35:32 +00003481 register const double
3482 *k;
3483
cristybb503372010-05-27 20:51:26 +00003484 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003485 u,
3486 v;
3487
cristy3ed852e2009-09-05 21:47:34 +00003488 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00003489 " SelectiveBlurImage with %.20gx%.20g kernel:",(double) width,(double)
3490 width);
cristy3ed852e2009-09-05 21:47:34 +00003491 message=AcquireString("");
3492 k=kernel;
cristybb503372010-05-27 20:51:26 +00003493 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003494 {
3495 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00003496 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy3ed852e2009-09-05 21:47:34 +00003497 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00003498 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003499 {
cristyb51dff52011-05-19 16:55:47 +00003500 (void) FormatLocaleString(format,MaxTextExtent,"%+f ",*k++);
cristy3ed852e2009-09-05 21:47:34 +00003501 (void) ConcatenateString(&message,format);
3502 }
3503 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
3504 }
3505 message=DestroyString(message);
3506 }
3507 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3508 if (blur_image == (Image *) NULL)
3509 return((Image *) NULL);
3510 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3511 {
3512 InheritException(exception,&blur_image->exception);
3513 blur_image=DestroyImage(blur_image);
3514 return((Image *) NULL);
3515 }
3516 /*
3517 Threshold blur image.
3518 */
3519 status=MagickTrue;
3520 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00003521 GetPixelInfo(image,&bias);
3522 SetPixelInfoBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003523 image_view=AcquireCacheView(image);
3524 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003525#if defined(MAGICKCORE_OPENMP_SUPPORT)
3526 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003527#endif
cristybb503372010-05-27 20:51:26 +00003528 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003529 {
cristy4c08aed2011-07-01 19:47:50 +00003530 double
3531 contrast;
3532
cristy3ed852e2009-09-05 21:47:34 +00003533 MagickBooleanType
3534 sync;
3535
3536 MagickRealType
3537 gamma;
3538
cristy4c08aed2011-07-01 19:47:50 +00003539 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003540 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003541
cristy4c08aed2011-07-01 19:47:50 +00003542 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003543 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003544
cristy117ff172010-08-15 21:35:32 +00003545 register ssize_t
3546 x;
3547
cristy3ed852e2009-09-05 21:47:34 +00003548 if (status == MagickFalse)
3549 continue;
cristy117ff172010-08-15 21:35:32 +00003550 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
3551 (width/2L),image->columns+width,width,exception);
cristy3ed852e2009-09-05 21:47:34 +00003552 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3553 exception);
cristy4c08aed2011-07-01 19:47:50 +00003554 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003555 {
3556 status=MagickFalse;
3557 continue;
3558 }
cristybb503372010-05-27 20:51:26 +00003559 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003560 {
cristy4c08aed2011-07-01 19:47:50 +00003561 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00003562 pixel;
3563
3564 register const double
cristyc47d1f82009-11-26 01:44:43 +00003565 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00003566
cristybb503372010-05-27 20:51:26 +00003567 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003568 u;
3569
cristy117ff172010-08-15 21:35:32 +00003570 ssize_t
3571 j,
3572 v;
3573
cristyddd82202009-11-03 20:14:50 +00003574 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00003575 k=kernel;
3576 gamma=0.0;
3577 j=0;
cristyed231572011-07-14 02:18:59 +00003578 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) == 0) || (image->matte == MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00003579 {
cristybb503372010-05-27 20:51:26 +00003580 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003581 {
cristybb503372010-05-27 20:51:26 +00003582 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003583 {
cristyed231572011-07-14 02:18:59 +00003584 contrast=GetPixelIntensity(image,p+(u+j)*GetPixelChannels(image))-
cristy4c08aed2011-07-01 19:47:50 +00003585 (double) GetPixelIntensity(blur_image,q);
3586 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003587 {
cristy4c08aed2011-07-01 19:47:50 +00003588 pixel.red+=(*k)*
cristyed231572011-07-14 02:18:59 +00003589 GetPixelRed(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003590 pixel.green+=(*k)*
cristyed231572011-07-14 02:18:59 +00003591 GetPixelGreen(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003592 pixel.blue+=(*k)*
cristyed231572011-07-14 02:18:59 +00003593 GetPixelBlue(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003594 if (image->colorspace == CMYKColorspace)
3595 pixel.black+=(*k)*
cristyed231572011-07-14 02:18:59 +00003596 GetPixelBlack(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003597 gamma+=(*k);
3598 k++;
3599 }
3600 }
cristyd99b0962010-05-29 23:14:26 +00003601 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003602 }
3603 if (gamma != 0.0)
3604 {
3605 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00003606 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003607 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +00003608 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003609 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +00003610 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003611 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003612 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00003613 (image->colorspace == CMYKColorspace))
3614 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00003615 }
cristyed231572011-07-14 02:18:59 +00003616 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003617 {
3618 gamma=0.0;
3619 j=0;
cristybb503372010-05-27 20:51:26 +00003620 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003621 {
cristybb503372010-05-27 20:51:26 +00003622 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003623 {
cristy4c08aed2011-07-01 19:47:50 +00003624 contrast=GetPixelIntensity(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003625 GetPixelChannels(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003626 GetPixelIntensity(blur_image,q);
3627 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003628 {
cristy4c08aed2011-07-01 19:47:50 +00003629 pixel.alpha+=(*k)*
cristyed231572011-07-14 02:18:59 +00003630 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003631 gamma+=(*k);
3632 k++;
3633 }
3634 }
cristyeaedf062010-05-29 22:36:02 +00003635 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003636 }
3637 if (gamma != 0.0)
3638 {
3639 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
3640 gamma);
cristy4c08aed2011-07-01 19:47:50 +00003641 SetPixelAlpha(blur_image,ClampToQuantum(gamma*pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003642 }
3643 }
3644 }
3645 else
3646 {
3647 MagickRealType
3648 alpha;
3649
cristybb503372010-05-27 20:51:26 +00003650 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003651 {
cristybb503372010-05-27 20:51:26 +00003652 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003653 {
cristy4c08aed2011-07-01 19:47:50 +00003654 contrast=GetPixelIntensity(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003655 GetPixelChannels(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003656 GetPixelIntensity(blur_image,q);
3657 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003658 {
cristy4c08aed2011-07-01 19:47:50 +00003659 alpha=(MagickRealType) (QuantumScale*
cristyed231572011-07-14 02:18:59 +00003660 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image)));
cristy4c08aed2011-07-01 19:47:50 +00003661 pixel.red+=(*k)*alpha*
cristyed231572011-07-14 02:18:59 +00003662 GetPixelRed(image,p+(u+j)*GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003663 pixel.green+=(*k)*alpha*GetPixelGreen(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003664 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003665 pixel.blue+=(*k)*alpha*GetPixelBlue(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003666 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003667 pixel.alpha+=(*k)*GetPixelAlpha(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003668 GetPixelChannels(image));
cristy4c08aed2011-07-01 19:47:50 +00003669 if (image->colorspace == CMYKColorspace)
3670 pixel.black+=(*k)*GetPixelBlack(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003671 GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003672 gamma+=(*k)*alpha;
3673 k++;
3674 }
3675 }
cristyeaedf062010-05-29 22:36:02 +00003676 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003677 }
3678 if (gamma != 0.0)
3679 {
3680 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
cristyed231572011-07-14 02:18:59 +00003681 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003682 SetPixelRed(blur_image,ClampToQuantum(gamma*pixel.red),q);
cristyed231572011-07-14 02:18:59 +00003683 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003684 SetPixelGreen(blur_image,ClampToQuantum(gamma*pixel.green),q);
cristyed231572011-07-14 02:18:59 +00003685 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00003686 SetPixelBlue(blur_image,ClampToQuantum(gamma*pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00003687 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00003688 (image->colorspace == CMYKColorspace))
3689 SetPixelBlack(blur_image,ClampToQuantum(gamma*pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00003690 }
cristyed231572011-07-14 02:18:59 +00003691 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003692 {
3693 gamma=0.0;
3694 j=0;
cristybb503372010-05-27 20:51:26 +00003695 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003696 {
cristybb503372010-05-27 20:51:26 +00003697 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003698 {
cristy4c08aed2011-07-01 19:47:50 +00003699 contrast=GetPixelIntensity(image,p+(u+j)*
cristyed231572011-07-14 02:18:59 +00003700 GetPixelChannels(image))-(double)
cristy4c08aed2011-07-01 19:47:50 +00003701 GetPixelIntensity(blur_image,q);
3702 if (fabs(contrast) < threshold)
cristy3ed852e2009-09-05 21:47:34 +00003703 {
cristy4c08aed2011-07-01 19:47:50 +00003704 pixel.alpha+=(*k)*
cristyed231572011-07-14 02:18:59 +00003705 GetPixelAlpha(image,p+(u+j)*GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00003706 gamma+=(*k);
3707 k++;
3708 }
3709 }
cristyeaedf062010-05-29 22:36:02 +00003710 j+=(ssize_t) (image->columns+width);
cristy3ed852e2009-09-05 21:47:34 +00003711 }
3712 if (gamma != 0.0)
3713 {
3714 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
3715 gamma);
cristy4c08aed2011-07-01 19:47:50 +00003716 SetPixelAlpha(blur_image,ClampToQuantum(pixel.alpha),q);
cristy3ed852e2009-09-05 21:47:34 +00003717 }
3718 }
3719 }
cristyed231572011-07-14 02:18:59 +00003720 p+=GetPixelChannels(image);
3721 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003722 }
3723 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
3724 if (sync == MagickFalse)
3725 status=MagickFalse;
3726 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3727 {
3728 MagickBooleanType
3729 proceed;
3730
cristyb5d5f722009-11-04 03:03:49 +00003731#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00003732 #pragma omp critical (MagickCore_SelectiveBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00003733#endif
3734 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress++,
3735 image->rows);
3736 if (proceed == MagickFalse)
3737 status=MagickFalse;
3738 }
3739 }
3740 blur_image->type=image->type;
3741 blur_view=DestroyCacheView(blur_view);
3742 image_view=DestroyCacheView(image_view);
3743 kernel=(double *) RelinquishMagickMemory(kernel);
3744 if (status == MagickFalse)
3745 blur_image=DestroyImage(blur_image);
3746 return(blur_image);
3747}
3748
3749/*
3750%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3751% %
3752% %
3753% %
3754% S h a d e I m a g e %
3755% %
3756% %
3757% %
3758%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3759%
3760% ShadeImage() shines a distant light on an image to create a
3761% three-dimensional effect. You control the positioning of the light with
3762% azimuth and elevation; azimuth is measured in degrees off the x axis
3763% and elevation is measured in pixels above the Z axis.
3764%
3765% The format of the ShadeImage method is:
3766%
3767% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3768% const double azimuth,const double elevation,ExceptionInfo *exception)
3769%
3770% A description of each parameter follows:
3771%
3772% o image: the image.
3773%
3774% o gray: A value other than zero shades the intensity of each pixel.
3775%
3776% o azimuth, elevation: Define the light source direction.
3777%
3778% o exception: return any errors or warnings in this structure.
3779%
3780*/
3781MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3782 const double azimuth,const double elevation,ExceptionInfo *exception)
3783{
3784#define ShadeImageTag "Shade/Image"
3785
cristyc4c8d132010-01-07 01:58:38 +00003786 CacheView
3787 *image_view,
3788 *shade_view;
3789
cristy3ed852e2009-09-05 21:47:34 +00003790 Image
3791 *shade_image;
3792
cristy3ed852e2009-09-05 21:47:34 +00003793 MagickBooleanType
3794 status;
3795
cristybb503372010-05-27 20:51:26 +00003796 MagickOffsetType
3797 progress;
3798
cristy3ed852e2009-09-05 21:47:34 +00003799 PrimaryInfo
3800 light;
3801
cristybb503372010-05-27 20:51:26 +00003802 ssize_t
3803 y;
3804
cristy3ed852e2009-09-05 21:47:34 +00003805 /*
3806 Initialize shaded image attributes.
3807 */
3808 assert(image != (const Image *) NULL);
3809 assert(image->signature == MagickSignature);
3810 if (image->debug != MagickFalse)
3811 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3812 assert(exception != (ExceptionInfo *) NULL);
3813 assert(exception->signature == MagickSignature);
3814 shade_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
3815 if (shade_image == (Image *) NULL)
3816 return((Image *) NULL);
3817 if (SetImageStorageClass(shade_image,DirectClass) == MagickFalse)
3818 {
3819 InheritException(exception,&shade_image->exception);
3820 shade_image=DestroyImage(shade_image);
3821 return((Image *) NULL);
3822 }
3823 /*
3824 Compute the light vector.
3825 */
3826 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
3827 cos(DegreesToRadians(elevation));
3828 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
3829 cos(DegreesToRadians(elevation));
3830 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
3831 /*
3832 Shade image.
3833 */
3834 status=MagickTrue;
3835 progress=0;
3836 image_view=AcquireCacheView(image);
3837 shade_view=AcquireCacheView(shade_image);
cristyb5d5f722009-11-04 03:03:49 +00003838#if defined(MAGICKCORE_OPENMP_SUPPORT)
3839 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003840#endif
cristybb503372010-05-27 20:51:26 +00003841 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003842 {
3843 MagickRealType
3844 distance,
3845 normal_distance,
3846 shade;
3847
3848 PrimaryInfo
3849 normal;
3850
cristy4c08aed2011-07-01 19:47:50 +00003851 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003852 *restrict p,
3853 *restrict s0,
3854 *restrict s1,
3855 *restrict s2;
cristy3ed852e2009-09-05 21:47:34 +00003856
cristy4c08aed2011-07-01 19:47:50 +00003857 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003858 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003859
cristy117ff172010-08-15 21:35:32 +00003860 register ssize_t
3861 x;
3862
cristy3ed852e2009-09-05 21:47:34 +00003863 if (status == MagickFalse)
3864 continue;
3865 p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception);
3866 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
3867 exception);
cristy4c08aed2011-07-01 19:47:50 +00003868 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003869 {
3870 status=MagickFalse;
3871 continue;
3872 }
3873 /*
3874 Shade this row of pixels.
3875 */
3876 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
cristyed231572011-07-14 02:18:59 +00003877 s0=p+GetPixelChannels(image);
3878 s1=s0+(image->columns+2)*GetPixelChannels(image);
3879 s2=s1+(image->columns+2)*GetPixelChannels(image);
cristybb503372010-05-27 20:51:26 +00003880 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003881 {
3882 /*
3883 Determine the surface normal and compute shading.
3884 */
cristyed231572011-07-14 02:18:59 +00003885 normal.x=(double) (GetPixelIntensity(image,s0-GetPixelChannels(image))+
3886 GetPixelIntensity(image,s1-GetPixelChannels(image))+
3887 GetPixelIntensity(image,s2-GetPixelChannels(image))-
3888 GetPixelIntensity(image,s0+GetPixelChannels(image))-
3889 GetPixelIntensity(image,s1+GetPixelChannels(image))-
3890 GetPixelIntensity(image,s2+GetPixelChannels(image)));
3891 normal.y=(double) (GetPixelIntensity(image,s2-GetPixelChannels(image))+
cristy4c08aed2011-07-01 19:47:50 +00003892 GetPixelIntensity(image,s2)+
cristyed231572011-07-14 02:18:59 +00003893 GetPixelIntensity(image,s2+GetPixelChannels(image))-
3894 GetPixelIntensity(image,s0-GetPixelChannels(image))-
cristy4c08aed2011-07-01 19:47:50 +00003895 GetPixelIntensity(image,s0)-
cristyed231572011-07-14 02:18:59 +00003896 GetPixelIntensity(image,s0+GetPixelChannels(image)));
cristy3ed852e2009-09-05 21:47:34 +00003897 if ((normal.x == 0.0) && (normal.y == 0.0))
3898 shade=light.z;
3899 else
3900 {
3901 shade=0.0;
3902 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
3903 if (distance > MagickEpsilon)
3904 {
3905 normal_distance=
3906 normal.x*normal.x+normal.y*normal.y+normal.z*normal.z;
3907 if (normal_distance > (MagickEpsilon*MagickEpsilon))
3908 shade=distance/sqrt((double) normal_distance);
3909 }
3910 }
3911 if (gray != MagickFalse)
3912 {
cristy4c08aed2011-07-01 19:47:50 +00003913 SetPixelRed(shade_image,ClampToQuantum(shade),q);
3914 SetPixelGreen(shade_image,ClampToQuantum(shade),q);
3915 SetPixelBlue(shade_image,ClampToQuantum(shade),q);
cristy3ed852e2009-09-05 21:47:34 +00003916 }
3917 else
3918 {
cristy4c08aed2011-07-01 19:47:50 +00003919 SetPixelRed(shade_image,ClampToQuantum(QuantumScale*shade*
3920 GetPixelRed(image,s1)),q);
3921 SetPixelGreen(shade_image,ClampToQuantum(QuantumScale*shade*
3922 GetPixelGreen(image,s1)),q);
3923 SetPixelBlue(shade_image,ClampToQuantum(QuantumScale*shade*
3924 GetPixelBlue(image,s1)),q);
cristy3ed852e2009-09-05 21:47:34 +00003925 }
cristy4c08aed2011-07-01 19:47:50 +00003926 SetPixelAlpha(shade_image,GetPixelAlpha(image,s1),q);
cristyed231572011-07-14 02:18:59 +00003927 s0+=GetPixelChannels(image);
3928 s1+=GetPixelChannels(image);
3929 s2+=GetPixelChannels(image);
3930 q+=GetPixelChannels(shade_image);
cristy3ed852e2009-09-05 21:47:34 +00003931 }
3932 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
3933 status=MagickFalse;
3934 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3935 {
3936 MagickBooleanType
3937 proceed;
3938
cristyb5d5f722009-11-04 03:03:49 +00003939#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003940 #pragma omp critical (MagickCore_ShadeImage)
3941#endif
3942 proceed=SetImageProgress(image,ShadeImageTag,progress++,image->rows);
3943 if (proceed == MagickFalse)
3944 status=MagickFalse;
3945 }
3946 }
3947 shade_view=DestroyCacheView(shade_view);
3948 image_view=DestroyCacheView(image_view);
3949 if (status == MagickFalse)
3950 shade_image=DestroyImage(shade_image);
3951 return(shade_image);
3952}
3953
3954/*
3955%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3956% %
3957% %
3958% %
3959% S h a r p e n I m a g e %
3960% %
3961% %
3962% %
3963%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3964%
3965% SharpenImage() sharpens the image. We convolve the image with a Gaussian
3966% operator of the given radius and standard deviation (sigma). For
3967% reasonable results, radius should be larger than sigma. Use a radius of 0
3968% and SharpenImage() selects a suitable radius for you.
3969%
3970% Using a separable kernel would be faster, but the negative weights cancel
3971% out on the corners of the kernel producing often undesirable ringing in the
3972% filtered result; this can be avoided by using a 2D gaussian shaped image
3973% sharpening kernel instead.
3974%
3975% The format of the SharpenImage method is:
3976%
3977% Image *SharpenImage(const Image *image,const double radius,
3978% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003979%
3980% A description of each parameter follows:
3981%
3982% o image: the image.
3983%
cristy3ed852e2009-09-05 21:47:34 +00003984% o radius: the radius of the Gaussian, in pixels, not counting the center
3985% pixel.
3986%
3987% o sigma: the standard deviation of the Laplacian, in pixels.
3988%
3989% o exception: return any errors or warnings in this structure.
3990%
3991*/
cristy3ed852e2009-09-05 21:47:34 +00003992MagickExport Image *SharpenImage(const Image *image,const double radius,
3993 const double sigma,ExceptionInfo *exception)
3994{
cristy3ed852e2009-09-05 21:47:34 +00003995 double
cristy47e00502009-12-17 19:19:57 +00003996 *kernel,
3997 normalize;
cristy3ed852e2009-09-05 21:47:34 +00003998
3999 Image
4000 *sharp_image;
4001
cristybb503372010-05-27 20:51:26 +00004002 register ssize_t
cristy47e00502009-12-17 19:19:57 +00004003 i;
4004
cristybb503372010-05-27 20:51:26 +00004005 size_t
cristy3ed852e2009-09-05 21:47:34 +00004006 width;
4007
cristy117ff172010-08-15 21:35:32 +00004008 ssize_t
4009 j,
4010 u,
4011 v;
4012
cristy3ed852e2009-09-05 21:47:34 +00004013 assert(image != (const Image *) NULL);
4014 assert(image->signature == MagickSignature);
4015 if (image->debug != MagickFalse)
4016 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4017 assert(exception != (ExceptionInfo *) NULL);
4018 assert(exception->signature == MagickSignature);
4019 width=GetOptimalKernelWidth2D(radius,sigma);
4020 kernel=(double *) AcquireQuantumMemory((size_t) width*width,sizeof(*kernel));
4021 if (kernel == (double *) NULL)
4022 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy3ed852e2009-09-05 21:47:34 +00004023 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00004024 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +00004025 i=0;
4026 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00004027 {
cristy47e00502009-12-17 19:19:57 +00004028 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00004029 {
cristy4205a3c2010-09-12 20:19:59 +00004030 kernel[i]=(double) (-exp(-((double) u*u+v*v)/(2.0*MagickSigma*
4031 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00004032 normalize+=kernel[i];
4033 i++;
4034 }
4035 }
4036 kernel[i/2]=(double) ((-2.0)*normalize);
cristyf4ad9df2011-07-08 16:49:03 +00004037 sharp_image=ConvolveImage(image,width,kernel,exception);
cristy3ed852e2009-09-05 21:47:34 +00004038 kernel=(double *) RelinquishMagickMemory(kernel);
4039 return(sharp_image);
4040}
4041
4042/*
4043%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4044% %
4045% %
4046% %
4047% S p r e a d I m a g e %
4048% %
4049% %
4050% %
4051%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4052%
4053% SpreadImage() is a special effects method that randomly displaces each
4054% pixel in a block defined by the radius parameter.
4055%
4056% The format of the SpreadImage method is:
4057%
4058% Image *SpreadImage(const Image *image,const double radius,
4059% ExceptionInfo *exception)
4060%
4061% A description of each parameter follows:
4062%
4063% o image: the image.
4064%
4065% o radius: Choose a random pixel in a neighborhood of this extent.
4066%
4067% o exception: return any errors or warnings in this structure.
4068%
4069*/
4070MagickExport Image *SpreadImage(const Image *image,const double radius,
4071 ExceptionInfo *exception)
4072{
4073#define SpreadImageTag "Spread/Image"
4074
cristyfa112112010-01-04 17:48:07 +00004075 CacheView
cristy9f7e7cb2011-03-26 00:49:57 +00004076 *image_view,
4077 *spread_view;
cristyfa112112010-01-04 17:48:07 +00004078
cristy3ed852e2009-09-05 21:47:34 +00004079 Image
4080 *spread_image;
4081
cristy3ed852e2009-09-05 21:47:34 +00004082 MagickBooleanType
4083 status;
4084
cristybb503372010-05-27 20:51:26 +00004085 MagickOffsetType
4086 progress;
4087
cristy4c08aed2011-07-01 19:47:50 +00004088 PixelInfo
cristyddd82202009-11-03 20:14:50 +00004089 bias;
cristy3ed852e2009-09-05 21:47:34 +00004090
4091 RandomInfo
cristyfa112112010-01-04 17:48:07 +00004092 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00004093
cristybb503372010-05-27 20:51:26 +00004094 size_t
cristy3ed852e2009-09-05 21:47:34 +00004095 width;
4096
cristybb503372010-05-27 20:51:26 +00004097 ssize_t
4098 y;
4099
cristy3ed852e2009-09-05 21:47:34 +00004100 /*
4101 Initialize spread image attributes.
4102 */
4103 assert(image != (Image *) NULL);
4104 assert(image->signature == MagickSignature);
4105 if (image->debug != MagickFalse)
4106 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4107 assert(exception != (ExceptionInfo *) NULL);
4108 assert(exception->signature == MagickSignature);
4109 spread_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4110 exception);
4111 if (spread_image == (Image *) NULL)
4112 return((Image *) NULL);
4113 if (SetImageStorageClass(spread_image,DirectClass) == MagickFalse)
4114 {
4115 InheritException(exception,&spread_image->exception);
4116 spread_image=DestroyImage(spread_image);
4117 return((Image *) NULL);
4118 }
4119 /*
4120 Spread image.
4121 */
4122 status=MagickTrue;
4123 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00004124 GetPixelInfo(spread_image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00004125 width=GetOptimalKernelWidth1D(radius,0.5);
cristy3ed852e2009-09-05 21:47:34 +00004126 random_info=AcquireRandomInfoThreadSet();
cristy9f7e7cb2011-03-26 00:49:57 +00004127 image_view=AcquireCacheView(image);
4128 spread_view=AcquireCacheView(spread_image);
cristyb557a152011-02-22 12:14:30 +00004129#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy09d81172010-10-21 16:15:05 +00004130 #pragma omp parallel for schedule(dynamic,4) shared(progress,status) omp_throttle(1)
cristy3ed852e2009-09-05 21:47:34 +00004131#endif
cristybb503372010-05-27 20:51:26 +00004132 for (y=0; y < (ssize_t) spread_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00004133 {
cristy5c9e6f22010-09-17 17:31:01 +00004134 const int
4135 id = GetOpenMPThreadId();
cristy6ebe97c2010-07-03 01:17:28 +00004136
cristy4c08aed2011-07-01 19:47:50 +00004137 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00004138 pixel;
4139
cristy4c08aed2011-07-01 19:47:50 +00004140 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00004141 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004142
cristy117ff172010-08-15 21:35:32 +00004143 register ssize_t
4144 x;
4145
cristy3ed852e2009-09-05 21:47:34 +00004146 if (status == MagickFalse)
4147 continue;
cristy9f7e7cb2011-03-26 00:49:57 +00004148 q=QueueCacheViewAuthenticPixels(spread_view,0,y,spread_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +00004149 exception);
cristy4c08aed2011-07-01 19:47:50 +00004150 if (q == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00004151 {
4152 status=MagickFalse;
4153 continue;
4154 }
cristyddd82202009-11-03 20:14:50 +00004155 pixel=bias;
cristybb503372010-05-27 20:51:26 +00004156 for (x=0; x < (ssize_t) spread_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00004157 {
cristy4c08aed2011-07-01 19:47:50 +00004158 (void) InterpolatePixelInfo(image,image_view,
cristy8a7c3e82011-03-26 02:10:53 +00004159 UndefinedInterpolatePixel,(double) x+width*(GetPseudoRandomValue(
4160 random_info[id])-0.5),(double) y+width*(GetPseudoRandomValue(
4161 random_info[id])-0.5),&pixel,exception);
cristy4c08aed2011-07-01 19:47:50 +00004162 SetPixelPixelInfo(spread_image,&pixel,q);
cristyed231572011-07-14 02:18:59 +00004163 q+=GetPixelChannels(spread_image);
cristy3ed852e2009-09-05 21:47:34 +00004164 }
cristy9f7e7cb2011-03-26 00:49:57 +00004165 if (SyncCacheViewAuthenticPixels(spread_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00004166 status=MagickFalse;
4167 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4168 {
4169 MagickBooleanType
4170 proceed;
4171
cristyb557a152011-02-22 12:14:30 +00004172#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004173 #pragma omp critical (MagickCore_SpreadImage)
4174#endif
4175 proceed=SetImageProgress(image,SpreadImageTag,progress++,image->rows);
4176 if (proceed == MagickFalse)
4177 status=MagickFalse;
4178 }
4179 }
cristy9f7e7cb2011-03-26 00:49:57 +00004180 spread_view=DestroyCacheView(spread_view);
cristy3ed852e2009-09-05 21:47:34 +00004181 image_view=DestroyCacheView(image_view);
4182 random_info=DestroyRandomInfoThreadSet(random_info);
cristy3ed852e2009-09-05 21:47:34 +00004183 return(spread_image);
4184}
4185
4186/*
4187%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4188% %
4189% %
4190% %
cristy0834d642011-03-18 18:26:08 +00004191% S t a t i s t i c I m a g e %
4192% %
4193% %
4194% %
4195%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4196%
4197% StatisticImage() makes each pixel the min / max / median / mode / etc. of
cristy8d752042011-03-19 01:00:36 +00004198% the neighborhood of the specified width and height.
cristy0834d642011-03-18 18:26:08 +00004199%
4200% The format of the StatisticImage method is:
4201%
4202% Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00004203% const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00004204%
4205% A description of each parameter follows:
4206%
4207% o image: the image.
4208%
cristy0834d642011-03-18 18:26:08 +00004209% o type: the statistic type (median, mode, etc.).
4210%
cristy95c38342011-03-18 22:39:51 +00004211% o width: the width of the pixel neighborhood.
4212%
4213% o height: the height of the pixel neighborhood.
cristy0834d642011-03-18 18:26:08 +00004214%
4215% o exception: return any errors or warnings in this structure.
4216%
4217*/
4218
cristy733678d2011-03-18 21:29:28 +00004219#define ListChannels 5
4220
4221typedef struct _ListNode
4222{
4223 size_t
4224 next[9],
4225 count,
4226 signature;
4227} ListNode;
4228
4229typedef struct _SkipList
4230{
4231 ssize_t
4232 level;
4233
4234 ListNode
4235 *nodes;
4236} SkipList;
4237
4238typedef struct _PixelList
4239{
4240 size_t
cristy6fc86bb2011-03-18 23:45:16 +00004241 length,
cristy733678d2011-03-18 21:29:28 +00004242 seed,
4243 signature;
4244
4245 SkipList
4246 lists[ListChannels];
4247} PixelList;
4248
4249static PixelList *DestroyPixelList(PixelList *pixel_list)
4250{
4251 register ssize_t
4252 i;
4253
4254 if (pixel_list == (PixelList *) NULL)
4255 return((PixelList *) NULL);
4256 for (i=0; i < ListChannels; i++)
4257 if (pixel_list->lists[i].nodes != (ListNode *) NULL)
4258 pixel_list->lists[i].nodes=(ListNode *) RelinquishMagickMemory(
4259 pixel_list->lists[i].nodes);
4260 pixel_list=(PixelList *) RelinquishMagickMemory(pixel_list);
4261 return(pixel_list);
4262}
4263
4264static PixelList **DestroyPixelListThreadSet(PixelList **pixel_list)
4265{
4266 register ssize_t
4267 i;
4268
4269 assert(pixel_list != (PixelList **) NULL);
4270 for (i=0; i < (ssize_t) GetOpenMPMaximumThreads(); i++)
4271 if (pixel_list[i] != (PixelList *) NULL)
4272 pixel_list[i]=DestroyPixelList(pixel_list[i]);
4273 pixel_list=(PixelList **) RelinquishMagickMemory(pixel_list);
4274 return(pixel_list);
4275}
4276
cristy6fc86bb2011-03-18 23:45:16 +00004277static PixelList *AcquirePixelList(const size_t width,const size_t height)
cristy733678d2011-03-18 21:29:28 +00004278{
4279 PixelList
4280 *pixel_list;
4281
4282 register ssize_t
4283 i;
4284
4285 pixel_list=(PixelList *) AcquireMagickMemory(sizeof(*pixel_list));
4286 if (pixel_list == (PixelList *) NULL)
4287 return(pixel_list);
4288 (void) ResetMagickMemory((void *) pixel_list,0,sizeof(*pixel_list));
cristy6fc86bb2011-03-18 23:45:16 +00004289 pixel_list->length=width*height;
cristy733678d2011-03-18 21:29:28 +00004290 for (i=0; i < ListChannels; i++)
4291 {
4292 pixel_list->lists[i].nodes=(ListNode *) AcquireQuantumMemory(65537UL,
4293 sizeof(*pixel_list->lists[i].nodes));
4294 if (pixel_list->lists[i].nodes == (ListNode *) NULL)
4295 return(DestroyPixelList(pixel_list));
4296 (void) ResetMagickMemory(pixel_list->lists[i].nodes,0,65537UL*
4297 sizeof(*pixel_list->lists[i].nodes));
4298 }
4299 pixel_list->signature=MagickSignature;
4300 return(pixel_list);
4301}
4302
cristy6fc86bb2011-03-18 23:45:16 +00004303static PixelList **AcquirePixelListThreadSet(const size_t width,
4304 const size_t height)
cristy733678d2011-03-18 21:29:28 +00004305{
4306 PixelList
4307 **pixel_list;
4308
4309 register ssize_t
4310 i;
4311
4312 size_t
4313 number_threads;
4314
4315 number_threads=GetOpenMPMaximumThreads();
4316 pixel_list=(PixelList **) AcquireQuantumMemory(number_threads,
4317 sizeof(*pixel_list));
4318 if (pixel_list == (PixelList **) NULL)
4319 return((PixelList **) NULL);
4320 (void) ResetMagickMemory(pixel_list,0,number_threads*sizeof(*pixel_list));
4321 for (i=0; i < (ssize_t) number_threads; i++)
4322 {
cristy6fc86bb2011-03-18 23:45:16 +00004323 pixel_list[i]=AcquirePixelList(width,height);
cristy733678d2011-03-18 21:29:28 +00004324 if (pixel_list[i] == (PixelList *) NULL)
4325 return(DestroyPixelListThreadSet(pixel_list));
4326 }
4327 return(pixel_list);
4328}
4329
4330static void AddNodePixelList(PixelList *pixel_list,const ssize_t channel,
4331 const size_t color)
4332{
4333 register SkipList
4334 *list;
4335
4336 register ssize_t
4337 level;
4338
4339 size_t
4340 search,
4341 update[9];
4342
4343 /*
4344 Initialize the node.
4345 */
4346 list=pixel_list->lists+channel;
4347 list->nodes[color].signature=pixel_list->signature;
4348 list->nodes[color].count=1;
4349 /*
4350 Determine where it belongs in the list.
4351 */
4352 search=65536UL;
4353 for (level=list->level; level >= 0; level--)
4354 {
4355 while (list->nodes[search].next[level] < color)
4356 search=list->nodes[search].next[level];
4357 update[level]=search;
4358 }
4359 /*
4360 Generate a pseudo-random level for this node.
4361 */
4362 for (level=0; ; level++)
4363 {
4364 pixel_list->seed=(pixel_list->seed*42893621L)+1L;
4365 if ((pixel_list->seed & 0x300) != 0x300)
4366 break;
4367 }
4368 if (level > 8)
4369 level=8;
4370 if (level > (list->level+2))
4371 level=list->level+2;
4372 /*
4373 If we're raising the list's level, link back to the root node.
4374 */
4375 while (level > list->level)
4376 {
4377 list->level++;
4378 update[list->level]=65536UL;
4379 }
4380 /*
4381 Link the node into the skip-list.
4382 */
4383 do
4384 {
4385 list->nodes[color].next[level]=list->nodes[update[level]].next[level];
4386 list->nodes[update[level]].next[level]=color;
cristy3cba8ca2011-03-19 01:29:12 +00004387 } while (level-- > 0);
cristy733678d2011-03-18 21:29:28 +00004388}
4389
cristy4c08aed2011-07-01 19:47:50 +00004390static PixelInfo GetMaximumPixelList(PixelList *pixel_list)
cristy6fc86bb2011-03-18 23:45:16 +00004391{
cristy4c08aed2011-07-01 19:47:50 +00004392 PixelInfo
cristy6fc86bb2011-03-18 23:45:16 +00004393 pixel;
4394
4395 register SkipList
4396 *list;
4397
4398 register ssize_t
4399 channel;
4400
4401 size_t
cristyd76c51e2011-03-26 00:21:26 +00004402 color,
4403 maximum;
cristy49f37242011-03-22 18:18:23 +00004404
4405 ssize_t
cristy6fc86bb2011-03-18 23:45:16 +00004406 count;
4407
4408 unsigned short
cristyd76c51e2011-03-26 00:21:26 +00004409 channels[ListChannels];
cristy6fc86bb2011-03-18 23:45:16 +00004410
4411 /*
4412 Find the maximum value for each of the color.
4413 */
4414 for (channel=0; channel < 5; channel++)
4415 {
4416 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004417 color=65536L;
cristy6fc86bb2011-03-18 23:45:16 +00004418 count=0;
cristy49f37242011-03-22 18:18:23 +00004419 maximum=list->nodes[color].next[0];
cristy6fc86bb2011-03-18 23:45:16 +00004420 do
4421 {
4422 color=list->nodes[color].next[0];
cristy49f37242011-03-22 18:18:23 +00004423 if (color > maximum)
4424 maximum=color;
cristy6fc86bb2011-03-18 23:45:16 +00004425 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004426 } while (count < (ssize_t) pixel_list->length);
cristy49f37242011-03-22 18:18:23 +00004427 channels[channel]=(unsigned short) maximum;
4428 }
cristy4c08aed2011-07-01 19:47:50 +00004429 GetPixelInfo((const Image *) NULL,&pixel);
cristy49f37242011-03-22 18:18:23 +00004430 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4431 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4432 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004433 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4434 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy49f37242011-03-22 18:18:23 +00004435 return(pixel);
4436}
4437
cristy4c08aed2011-07-01 19:47:50 +00004438static PixelInfo GetMeanPixelList(PixelList *pixel_list)
cristy49f37242011-03-22 18:18:23 +00004439{
cristy4c08aed2011-07-01 19:47:50 +00004440 PixelInfo
cristy49f37242011-03-22 18:18:23 +00004441 pixel;
4442
cristy80a99a32011-03-30 01:30:23 +00004443 MagickRealType
4444 sum;
4445
cristy49f37242011-03-22 18:18:23 +00004446 register SkipList
4447 *list;
4448
4449 register ssize_t
4450 channel;
4451
4452 size_t
cristy80a99a32011-03-30 01:30:23 +00004453 color;
cristy49f37242011-03-22 18:18:23 +00004454
4455 ssize_t
4456 count;
4457
4458 unsigned short
4459 channels[ListChannels];
4460
4461 /*
4462 Find the mean value for each of the color.
4463 */
4464 for (channel=0; channel < 5; channel++)
4465 {
4466 list=pixel_list->lists+channel;
4467 color=65536L;
4468 count=0;
cristy80a99a32011-03-30 01:30:23 +00004469 sum=0.0;
cristy49f37242011-03-22 18:18:23 +00004470 do
4471 {
4472 color=list->nodes[color].next[0];
cristy80a99a32011-03-30 01:30:23 +00004473 sum+=(MagickRealType) list->nodes[color].count*color;
cristy49f37242011-03-22 18:18:23 +00004474 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004475 } while (count < (ssize_t) pixel_list->length);
cristy80a99a32011-03-30 01:30:23 +00004476 sum/=pixel_list->length;
4477 channels[channel]=(unsigned short) sum;
cristy6fc86bb2011-03-18 23:45:16 +00004478 }
cristy4c08aed2011-07-01 19:47:50 +00004479 GetPixelInfo((const Image *) NULL,&pixel);
cristy6fc86bb2011-03-18 23:45:16 +00004480 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4481 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4482 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004483 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4484 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy6fc86bb2011-03-18 23:45:16 +00004485 return(pixel);
4486}
4487
cristy4c08aed2011-07-01 19:47:50 +00004488static PixelInfo GetMedianPixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004489{
cristy4c08aed2011-07-01 19:47:50 +00004490 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004491 pixel;
4492
4493 register SkipList
4494 *list;
4495
4496 register ssize_t
4497 channel;
4498
4499 size_t
cristy49f37242011-03-22 18:18:23 +00004500 color;
4501
4502 ssize_t
cristy733678d2011-03-18 21:29:28 +00004503 count;
4504
4505 unsigned short
4506 channels[ListChannels];
4507
4508 /*
4509 Find the median value for each of the color.
4510 */
cristy733678d2011-03-18 21:29:28 +00004511 for (channel=0; channel < 5; channel++)
4512 {
4513 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004514 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004515 count=0;
4516 do
4517 {
4518 color=list->nodes[color].next[0];
4519 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004520 } while (count <= (ssize_t) (pixel_list->length >> 1));
cristy6fc86bb2011-03-18 23:45:16 +00004521 channels[channel]=(unsigned short) color;
4522 }
cristy4c08aed2011-07-01 19:47:50 +00004523 GetPixelInfo((const Image *) NULL,&pixel);
cristy6fc86bb2011-03-18 23:45:16 +00004524 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4525 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4526 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004527 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4528 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy6fc86bb2011-03-18 23:45:16 +00004529 return(pixel);
4530}
4531
cristy4c08aed2011-07-01 19:47:50 +00004532static PixelInfo GetMinimumPixelList(PixelList *pixel_list)
cristy6fc86bb2011-03-18 23:45:16 +00004533{
cristy4c08aed2011-07-01 19:47:50 +00004534 PixelInfo
cristy6fc86bb2011-03-18 23:45:16 +00004535 pixel;
4536
4537 register SkipList
4538 *list;
4539
4540 register ssize_t
4541 channel;
4542
4543 size_t
cristyd76c51e2011-03-26 00:21:26 +00004544 color,
4545 minimum;
cristy6fc86bb2011-03-18 23:45:16 +00004546
cristy49f37242011-03-22 18:18:23 +00004547 ssize_t
4548 count;
4549
cristy6fc86bb2011-03-18 23:45:16 +00004550 unsigned short
cristyd76c51e2011-03-26 00:21:26 +00004551 channels[ListChannels];
cristy6fc86bb2011-03-18 23:45:16 +00004552
4553 /*
4554 Find the minimum value for each of the color.
4555 */
4556 for (channel=0; channel < 5; channel++)
4557 {
4558 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004559 count=0;
cristy6fc86bb2011-03-18 23:45:16 +00004560 color=65536UL;
cristy49f37242011-03-22 18:18:23 +00004561 minimum=list->nodes[color].next[0];
4562 do
4563 {
4564 color=list->nodes[color].next[0];
4565 if (color < minimum)
4566 minimum=color;
4567 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004568 } while (count < (ssize_t) pixel_list->length);
cristy49f37242011-03-22 18:18:23 +00004569 channels[channel]=(unsigned short) minimum;
cristy733678d2011-03-18 21:29:28 +00004570 }
cristy4c08aed2011-07-01 19:47:50 +00004571 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004572 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4573 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4574 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004575 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4576 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy733678d2011-03-18 21:29:28 +00004577 return(pixel);
4578}
4579
cristy4c08aed2011-07-01 19:47:50 +00004580static PixelInfo GetModePixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004581{
cristy4c08aed2011-07-01 19:47:50 +00004582 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004583 pixel;
4584
4585 register SkipList
4586 *list;
4587
4588 register ssize_t
4589 channel;
4590
4591 size_t
4592 color,
cristy733678d2011-03-18 21:29:28 +00004593 max_count,
cristy6fc86bb2011-03-18 23:45:16 +00004594 mode;
cristy733678d2011-03-18 21:29:28 +00004595
cristy49f37242011-03-22 18:18:23 +00004596 ssize_t
4597 count;
4598
cristy733678d2011-03-18 21:29:28 +00004599 unsigned short
4600 channels[5];
4601
4602 /*
glennrp30d2dc62011-06-25 03:17:16 +00004603 Make each pixel the 'predominant color' of the specified neighborhood.
cristy733678d2011-03-18 21:29:28 +00004604 */
cristy733678d2011-03-18 21:29:28 +00004605 for (channel=0; channel < 5; channel++)
4606 {
4607 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004608 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004609 mode=color;
4610 max_count=list->nodes[mode].count;
4611 count=0;
4612 do
4613 {
4614 color=list->nodes[color].next[0];
4615 if (list->nodes[color].count > max_count)
4616 {
4617 mode=color;
4618 max_count=list->nodes[mode].count;
4619 }
4620 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004621 } while (count < (ssize_t) pixel_list->length);
cristy733678d2011-03-18 21:29:28 +00004622 channels[channel]=(unsigned short) mode;
4623 }
cristy4c08aed2011-07-01 19:47:50 +00004624 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004625 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4626 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4627 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004628 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
4629 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
cristy733678d2011-03-18 21:29:28 +00004630 return(pixel);
4631}
4632
cristy4c08aed2011-07-01 19:47:50 +00004633static PixelInfo GetNonpeakPixelList(PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004634{
cristy4c08aed2011-07-01 19:47:50 +00004635 PixelInfo
cristy733678d2011-03-18 21:29:28 +00004636 pixel;
4637
4638 register SkipList
4639 *list;
4640
4641 register ssize_t
4642 channel;
4643
4644 size_t
cristy733678d2011-03-18 21:29:28 +00004645 color,
cristy733678d2011-03-18 21:29:28 +00004646 next,
4647 previous;
4648
cristy49f37242011-03-22 18:18:23 +00004649 ssize_t
4650 count;
4651
cristy733678d2011-03-18 21:29:28 +00004652 unsigned short
4653 channels[5];
4654
4655 /*
cristy49f37242011-03-22 18:18:23 +00004656 Finds the non peak value for each of the colors.
cristy733678d2011-03-18 21:29:28 +00004657 */
cristy733678d2011-03-18 21:29:28 +00004658 for (channel=0; channel < 5; channel++)
4659 {
4660 list=pixel_list->lists+channel;
cristy49f37242011-03-22 18:18:23 +00004661 color=65536L;
cristy733678d2011-03-18 21:29:28 +00004662 next=list->nodes[color].next[0];
4663 count=0;
4664 do
4665 {
4666 previous=color;
4667 color=next;
4668 next=list->nodes[color].next[0];
4669 count+=list->nodes[color].count;
cristyd76c51e2011-03-26 00:21:26 +00004670 } while (count <= (ssize_t) (pixel_list->length >> 1));
cristy733678d2011-03-18 21:29:28 +00004671 if ((previous == 65536UL) && (next != 65536UL))
4672 color=next;
4673 else
4674 if ((previous != 65536UL) && (next == 65536UL))
4675 color=previous;
4676 channels[channel]=(unsigned short) color;
4677 }
cristy4c08aed2011-07-01 19:47:50 +00004678 GetPixelInfo((const Image *) NULL,&pixel);
cristy733678d2011-03-18 21:29:28 +00004679 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4680 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4681 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004682 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4683 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy733678d2011-03-18 21:29:28 +00004684 return(pixel);
4685}
4686
cristy4c08aed2011-07-01 19:47:50 +00004687static PixelInfo GetStandardDeviationPixelList(PixelList *pixel_list)
cristy9a68cbb2011-03-29 00:51:23 +00004688{
cristy4c08aed2011-07-01 19:47:50 +00004689 PixelInfo
cristy9a68cbb2011-03-29 00:51:23 +00004690 pixel;
4691
cristy80a99a32011-03-30 01:30:23 +00004692 MagickRealType
4693 sum,
4694 sum_squared;
4695
cristy9a68cbb2011-03-29 00:51:23 +00004696 register SkipList
4697 *list;
4698
4699 register ssize_t
4700 channel;
4701
4702 size_t
cristy80a99a32011-03-30 01:30:23 +00004703 color;
cristy9a68cbb2011-03-29 00:51:23 +00004704
4705 ssize_t
4706 count;
4707
4708 unsigned short
4709 channels[ListChannels];
4710
4711 /*
cristy80a99a32011-03-30 01:30:23 +00004712 Find the standard-deviation value for each of the color.
cristy9a68cbb2011-03-29 00:51:23 +00004713 */
4714 for (channel=0; channel < 5; channel++)
4715 {
4716 list=pixel_list->lists+channel;
4717 color=65536L;
4718 count=0;
cristy80a99a32011-03-30 01:30:23 +00004719 sum=0.0;
4720 sum_squared=0.0;
cristy9a68cbb2011-03-29 00:51:23 +00004721 do
4722 {
cristy80a99a32011-03-30 01:30:23 +00004723 register ssize_t
4724 i;
4725
cristy9a68cbb2011-03-29 00:51:23 +00004726 color=list->nodes[color].next[0];
cristy80a99a32011-03-30 01:30:23 +00004727 sum+=(MagickRealType) list->nodes[color].count*color;
4728 for (i=0; i < (ssize_t) list->nodes[color].count; i++)
4729 sum_squared+=((MagickRealType) color)*((MagickRealType) color);
cristy9a68cbb2011-03-29 00:51:23 +00004730 count+=list->nodes[color].count;
4731 } while (count < (ssize_t) pixel_list->length);
cristy80a99a32011-03-30 01:30:23 +00004732 sum/=pixel_list->length;
4733 sum_squared/=pixel_list->length;
4734 channels[channel]=(unsigned short) sqrt(sum_squared-(sum*sum));
cristy9a68cbb2011-03-29 00:51:23 +00004735 }
cristy4c08aed2011-07-01 19:47:50 +00004736 GetPixelInfo((const Image *) NULL,&pixel);
cristy9a68cbb2011-03-29 00:51:23 +00004737 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4738 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4739 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
cristy4c08aed2011-07-01 19:47:50 +00004740 pixel.alpha=(MagickRealType) ScaleShortToQuantum(channels[3]);
4741 pixel.black=(MagickRealType) ScaleShortToQuantum(channels[4]);
cristy9a68cbb2011-03-29 00:51:23 +00004742 return(pixel);
4743}
4744
cristy4c08aed2011-07-01 19:47:50 +00004745static inline void InsertPixelList(const Image *image,const Quantum *pixel,
4746 PixelList *pixel_list)
cristy733678d2011-03-18 21:29:28 +00004747{
4748 size_t
4749 signature;
4750
4751 unsigned short
4752 index;
4753
cristy4c08aed2011-07-01 19:47:50 +00004754 index=ScaleQuantumToShort(GetPixelRed(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004755 signature=pixel_list->lists[0].nodes[index].signature;
4756 if (signature == pixel_list->signature)
4757 pixel_list->lists[0].nodes[index].count++;
4758 else
4759 AddNodePixelList(pixel_list,0,index);
cristy4c08aed2011-07-01 19:47:50 +00004760 index=ScaleQuantumToShort(GetPixelGreen(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004761 signature=pixel_list->lists[1].nodes[index].signature;
4762 if (signature == pixel_list->signature)
4763 pixel_list->lists[1].nodes[index].count++;
4764 else
4765 AddNodePixelList(pixel_list,1,index);
cristy4c08aed2011-07-01 19:47:50 +00004766 index=ScaleQuantumToShort(GetPixelBlue(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004767 signature=pixel_list->lists[2].nodes[index].signature;
4768 if (signature == pixel_list->signature)
4769 pixel_list->lists[2].nodes[index].count++;
4770 else
4771 AddNodePixelList(pixel_list,2,index);
cristy4c08aed2011-07-01 19:47:50 +00004772 index=ScaleQuantumToShort(GetPixelAlpha(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004773 signature=pixel_list->lists[3].nodes[index].signature;
4774 if (signature == pixel_list->signature)
4775 pixel_list->lists[3].nodes[index].count++;
4776 else
4777 AddNodePixelList(pixel_list,3,index);
4778 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00004779 index=ScaleQuantumToShort(GetPixelBlack(image,pixel));
cristy733678d2011-03-18 21:29:28 +00004780 signature=pixel_list->lists[4].nodes[index].signature;
4781 if (signature == pixel_list->signature)
4782 pixel_list->lists[4].nodes[index].count++;
4783 else
4784 AddNodePixelList(pixel_list,4,index);
4785}
4786
cristy80c99742011-04-04 14:46:39 +00004787static inline MagickRealType MagickAbsoluteValue(const MagickRealType x)
4788{
4789 if (x < 0)
4790 return(-x);
4791 return(x);
4792}
4793
cristy733678d2011-03-18 21:29:28 +00004794static void ResetPixelList(PixelList *pixel_list)
4795{
4796 int
4797 level;
4798
4799 register ListNode
4800 *root;
4801
4802 register SkipList
4803 *list;
4804
4805 register ssize_t
4806 channel;
4807
4808 /*
4809 Reset the skip-list.
4810 */
4811 for (channel=0; channel < 5; channel++)
4812 {
4813 list=pixel_list->lists+channel;
4814 root=list->nodes+65536UL;
4815 list->level=0;
4816 for (level=0; level < 9; level++)
4817 root->next[level]=65536UL;
4818 }
4819 pixel_list->seed=pixel_list->signature++;
4820}
4821
cristy0834d642011-03-18 18:26:08 +00004822MagickExport Image *StatisticImage(const Image *image,const StatisticType type,
cristy95c38342011-03-18 22:39:51 +00004823 const size_t width,const size_t height,ExceptionInfo *exception)
cristy0834d642011-03-18 18:26:08 +00004824{
cristy3cba8ca2011-03-19 01:29:12 +00004825#define StatisticWidth \
cristyd76c51e2011-03-26 00:21:26 +00004826 (width == 0 ? GetOptimalKernelWidth2D((double) width,0.5) : width)
cristy3cba8ca2011-03-19 01:29:12 +00004827#define StatisticHeight \
cristyd76c51e2011-03-26 00:21:26 +00004828 (height == 0 ? GetOptimalKernelWidth2D((double) height,0.5) : height)
cristy0834d642011-03-18 18:26:08 +00004829#define StatisticImageTag "Statistic/Image"
4830
4831 CacheView
4832 *image_view,
4833 *statistic_view;
4834
4835 Image
4836 *statistic_image;
4837
4838 MagickBooleanType
4839 status;
4840
4841 MagickOffsetType
4842 progress;
4843
4844 PixelList
4845 **restrict pixel_list;
4846
cristy0834d642011-03-18 18:26:08 +00004847 ssize_t
4848 y;
4849
4850 /*
4851 Initialize statistics image attributes.
4852 */
4853 assert(image != (Image *) NULL);
4854 assert(image->signature == MagickSignature);
4855 if (image->debug != MagickFalse)
4856 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4857 assert(exception != (ExceptionInfo *) NULL);
4858 assert(exception->signature == MagickSignature);
cristy0834d642011-03-18 18:26:08 +00004859 statistic_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4860 exception);
4861 if (statistic_image == (Image *) NULL)
4862 return((Image *) NULL);
4863 if (SetImageStorageClass(statistic_image,DirectClass) == MagickFalse)
4864 {
4865 InheritException(exception,&statistic_image->exception);
4866 statistic_image=DestroyImage(statistic_image);
4867 return((Image *) NULL);
4868 }
cristy6fc86bb2011-03-18 23:45:16 +00004869 pixel_list=AcquirePixelListThreadSet(StatisticWidth,StatisticHeight);
cristy0834d642011-03-18 18:26:08 +00004870 if (pixel_list == (PixelList **) NULL)
4871 {
4872 statistic_image=DestroyImage(statistic_image);
4873 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
4874 }
4875 /*
cristy8d752042011-03-19 01:00:36 +00004876 Make each pixel the min / max / median / mode / etc. of the neighborhood.
cristy0834d642011-03-18 18:26:08 +00004877 */
4878 status=MagickTrue;
4879 progress=0;
4880 image_view=AcquireCacheView(image);
4881 statistic_view=AcquireCacheView(statistic_image);
4882#if defined(MAGICKCORE_OPENMP_SUPPORT)
4883 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
4884#endif
4885 for (y=0; y < (ssize_t) statistic_image->rows; y++)
4886 {
4887 const int
4888 id = GetOpenMPThreadId();
4889
cristy4c08aed2011-07-01 19:47:50 +00004890 register const Quantum
cristy0834d642011-03-18 18:26:08 +00004891 *restrict p;
4892
cristy4c08aed2011-07-01 19:47:50 +00004893 register Quantum
cristy0834d642011-03-18 18:26:08 +00004894 *restrict q;
4895
4896 register ssize_t
4897 x;
4898
4899 if (status == MagickFalse)
4900 continue;
cristy6fc86bb2011-03-18 23:45:16 +00004901 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) StatisticWidth/2L),y-
4902 (ssize_t) (StatisticHeight/2L),image->columns+StatisticWidth,
4903 StatisticHeight,exception);
cristy3cba8ca2011-03-19 01:29:12 +00004904 q=QueueCacheViewAuthenticPixels(statistic_view,0,y,statistic_image->columns, 1,exception);
cristy4c08aed2011-07-01 19:47:50 +00004905 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy0834d642011-03-18 18:26:08 +00004906 {
4907 status=MagickFalse;
4908 continue;
4909 }
cristy0834d642011-03-18 18:26:08 +00004910 for (x=0; x < (ssize_t) statistic_image->columns; x++)
4911 {
cristy4c08aed2011-07-01 19:47:50 +00004912 PixelInfo
cristy0834d642011-03-18 18:26:08 +00004913 pixel;
4914
cristy4c08aed2011-07-01 19:47:50 +00004915 register const Quantum
cristy6e3026a2011-03-19 00:54:38 +00004916 *restrict r;
4917
cristy0834d642011-03-18 18:26:08 +00004918 register ssize_t
4919 u,
4920 v;
4921
4922 r=p;
cristy0834d642011-03-18 18:26:08 +00004923 ResetPixelList(pixel_list[id]);
cristy6e4c3292011-03-19 00:53:55 +00004924 for (v=0; v < (ssize_t) StatisticHeight; v++)
cristy0834d642011-03-18 18:26:08 +00004925 {
cristy6e4c3292011-03-19 00:53:55 +00004926 for (u=0; u < (ssize_t) StatisticWidth; u++)
cristyed231572011-07-14 02:18:59 +00004927 InsertPixelList(image,r+u*GetPixelChannels(image),pixel_list[id]);
4928 r+=(image->columns+StatisticWidth)*GetPixelChannels(image);
cristy0834d642011-03-18 18:26:08 +00004929 }
cristy4c08aed2011-07-01 19:47:50 +00004930 GetPixelInfo(image,&pixel);
cristy490408a2011-07-07 14:42:05 +00004931 SetPixelInfo(image,p+(StatisticWidth*StatisticHeight/2)*
cristyed231572011-07-14 02:18:59 +00004932 GetPixelChannels(image),&pixel);
cristy0834d642011-03-18 18:26:08 +00004933 switch (type)
4934 {
cristy80c99742011-04-04 14:46:39 +00004935 case GradientStatistic:
4936 {
cristy4c08aed2011-07-01 19:47:50 +00004937 PixelInfo
cristy80c99742011-04-04 14:46:39 +00004938 maximum,
4939 minimum;
4940
4941 minimum=GetMinimumPixelList(pixel_list[id]);
4942 maximum=GetMaximumPixelList(pixel_list[id]);
4943 pixel.red=MagickAbsoluteValue(maximum.red-minimum.red);
4944 pixel.green=MagickAbsoluteValue(maximum.green-minimum.green);
4945 pixel.blue=MagickAbsoluteValue(maximum.blue-minimum.blue);
cristy4c08aed2011-07-01 19:47:50 +00004946 pixel.alpha=MagickAbsoluteValue(maximum.alpha-minimum.alpha);
cristy80c99742011-04-04 14:46:39 +00004947 if (image->colorspace == CMYKColorspace)
cristy4c08aed2011-07-01 19:47:50 +00004948 pixel.black=MagickAbsoluteValue(maximum.black-minimum.black);
cristy80c99742011-04-04 14:46:39 +00004949 break;
4950 }
cristy6fc86bb2011-03-18 23:45:16 +00004951 case MaximumStatistic:
4952 {
4953 pixel=GetMaximumPixelList(pixel_list[id]);
4954 break;
4955 }
cristy49f37242011-03-22 18:18:23 +00004956 case MeanStatistic:
4957 {
4958 pixel=GetMeanPixelList(pixel_list[id]);
4959 break;
4960 }
cristyf2ad14a2011-03-18 18:57:25 +00004961 case MedianStatistic:
cristy6fc86bb2011-03-18 23:45:16 +00004962 default:
cristyf2ad14a2011-03-18 18:57:25 +00004963 {
4964 pixel=GetMedianPixelList(pixel_list[id]);
4965 break;
4966 }
cristy6fc86bb2011-03-18 23:45:16 +00004967 case MinimumStatistic:
4968 {
4969 pixel=GetMinimumPixelList(pixel_list[id]);
4970 break;
4971 }
cristyf2ad14a2011-03-18 18:57:25 +00004972 case ModeStatistic:
4973 {
4974 pixel=GetModePixelList(pixel_list[id]);
4975 break;
4976 }
4977 case NonpeakStatistic:
4978 {
4979 pixel=GetNonpeakPixelList(pixel_list[id]);
4980 break;
4981 }
cristy9a68cbb2011-03-29 00:51:23 +00004982 case StandardDeviationStatistic:
4983 {
4984 pixel=GetStandardDeviationPixelList(pixel_list[id]);
4985 break;
4986 }
cristy0834d642011-03-18 18:26:08 +00004987 }
cristyed231572011-07-14 02:18:59 +00004988 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00004989 SetPixelRed(statistic_image,ClampToQuantum(pixel.red),q);
cristyed231572011-07-14 02:18:59 +00004990 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00004991 SetPixelGreen(statistic_image,ClampToQuantum(pixel.green),q);
cristyed231572011-07-14 02:18:59 +00004992 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy490408a2011-07-07 14:42:05 +00004993 SetPixelBlue(statistic_image,ClampToQuantum(pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00004994 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy0834d642011-03-18 18:26:08 +00004995 (image->colorspace == CMYKColorspace))
cristy490408a2011-07-07 14:42:05 +00004996 SetPixelBlack(statistic_image,ClampToQuantum(pixel.black),q);
cristyed231572011-07-14 02:18:59 +00004997 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00004998 (image->matte != MagickFalse))
cristy490408a2011-07-07 14:42:05 +00004999 SetPixelAlpha(statistic_image,ClampToQuantum(pixel.alpha),q);
cristyed231572011-07-14 02:18:59 +00005000 p+=GetPixelChannels(image);
5001 q+=GetPixelChannels(statistic_image);
cristy0834d642011-03-18 18:26:08 +00005002 }
5003 if (SyncCacheViewAuthenticPixels(statistic_view,exception) == MagickFalse)
5004 status=MagickFalse;
5005 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5006 {
5007 MagickBooleanType
5008 proceed;
5009
5010#if defined(MAGICKCORE_OPENMP_SUPPORT)
5011 #pragma omp critical (MagickCore_StatisticImage)
5012#endif
5013 proceed=SetImageProgress(image,StatisticImageTag,progress++,
5014 image->rows);
5015 if (proceed == MagickFalse)
5016 status=MagickFalse;
5017 }
5018 }
5019 statistic_view=DestroyCacheView(statistic_view);
5020 image_view=DestroyCacheView(image_view);
5021 pixel_list=DestroyPixelListThreadSet(pixel_list);
5022 return(statistic_image);
5023}
5024
5025/*
5026%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5027% %
5028% %
5029% %
cristy3ed852e2009-09-05 21:47:34 +00005030% U n s h a r p M a s k I m a g e %
5031% %
5032% %
5033% %
5034%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5035%
5036% UnsharpMaskImage() sharpens one or more image channels. We convolve the
5037% image with a Gaussian operator of the given radius and standard deviation
5038% (sigma). For reasonable results, radius should be larger than sigma. Use a
5039% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
5040%
5041% The format of the UnsharpMaskImage method is:
5042%
5043% Image *UnsharpMaskImage(const Image *image,const double radius,
5044% const double sigma,const double amount,const double threshold,
5045% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00005046%
5047% A description of each parameter follows:
5048%
5049% o image: the image.
5050%
cristy3ed852e2009-09-05 21:47:34 +00005051% o radius: the radius of the Gaussian, in pixels, not counting the center
5052% pixel.
5053%
5054% o sigma: the standard deviation of the Gaussian, in pixels.
5055%
5056% o amount: the percentage of the difference between the original and the
5057% blur image that is added back into the original.
5058%
5059% o threshold: the threshold in pixels needed to apply the diffence amount.
5060%
5061% o exception: return any errors or warnings in this structure.
5062%
5063*/
cristyf4ad9df2011-07-08 16:49:03 +00005064MagickExport Image *UnsharpMaskImage(const Image *image,
5065 const double radius,const double sigma,const double amount,
5066 const double threshold,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00005067{
5068#define SharpenImageTag "Sharpen/Image"
5069
cristyc4c8d132010-01-07 01:58:38 +00005070 CacheView
5071 *image_view,
5072 *unsharp_view;
5073
cristy3ed852e2009-09-05 21:47:34 +00005074 Image
5075 *unsharp_image;
5076
cristy3ed852e2009-09-05 21:47:34 +00005077 MagickBooleanType
5078 status;
5079
cristybb503372010-05-27 20:51:26 +00005080 MagickOffsetType
5081 progress;
5082
cristy4c08aed2011-07-01 19:47:50 +00005083 PixelInfo
cristyddd82202009-11-03 20:14:50 +00005084 bias;
cristy3ed852e2009-09-05 21:47:34 +00005085
5086 MagickRealType
5087 quantum_threshold;
5088
cristybb503372010-05-27 20:51:26 +00005089 ssize_t
5090 y;
5091
cristy3ed852e2009-09-05 21:47:34 +00005092 assert(image != (const Image *) NULL);
5093 assert(image->signature == MagickSignature);
5094 if (image->debug != MagickFalse)
5095 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5096 assert(exception != (ExceptionInfo *) NULL);
cristyf4ad9df2011-07-08 16:49:03 +00005097 unsharp_image=BlurImage(image,radius,sigma,exception);
cristy3ed852e2009-09-05 21:47:34 +00005098 if (unsharp_image == (Image *) NULL)
5099 return((Image *) NULL);
5100 quantum_threshold=(MagickRealType) QuantumRange*threshold;
5101 /*
5102 Unsharp-mask image.
5103 */
5104 status=MagickTrue;
5105 progress=0;
cristy4c08aed2011-07-01 19:47:50 +00005106 GetPixelInfo(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00005107 image_view=AcquireCacheView(image);
5108 unsharp_view=AcquireCacheView(unsharp_image);
cristyb5d5f722009-11-04 03:03:49 +00005109#if defined(MAGICKCORE_OPENMP_SUPPORT)
5110 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00005111#endif
cristybb503372010-05-27 20:51:26 +00005112 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00005113 {
cristy4c08aed2011-07-01 19:47:50 +00005114 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00005115 pixel;
5116
cristy4c08aed2011-07-01 19:47:50 +00005117 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00005118 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00005119
cristy4c08aed2011-07-01 19:47:50 +00005120 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00005121 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00005122
cristy117ff172010-08-15 21:35:32 +00005123 register ssize_t
5124 x;
5125
cristy3ed852e2009-09-05 21:47:34 +00005126 if (status == MagickFalse)
5127 continue;
5128 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
5129 q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
5130 exception);
cristy4c08aed2011-07-01 19:47:50 +00005131 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00005132 {
5133 status=MagickFalse;
5134 continue;
5135 }
cristyddd82202009-11-03 20:14:50 +00005136 pixel=bias;
cristybb503372010-05-27 20:51:26 +00005137 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00005138 {
cristyed231572011-07-14 02:18:59 +00005139 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00005140 {
cristy4c08aed2011-07-01 19:47:50 +00005141 pixel.red=GetPixelRed(image,p)-(MagickRealType) GetPixelRed(image,q);
cristy3ed852e2009-09-05 21:47:34 +00005142 if (fabs(2.0*pixel.red) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00005143 pixel.red=(MagickRealType) GetPixelRed(image,p);
cristy3ed852e2009-09-05 21:47:34 +00005144 else
cristy4c08aed2011-07-01 19:47:50 +00005145 pixel.red=(MagickRealType) GetPixelRed(image,p)+(pixel.red*amount);
5146 SetPixelRed(unsharp_image,ClampToQuantum(pixel.red),q);
cristy3ed852e2009-09-05 21:47:34 +00005147 }
cristyed231572011-07-14 02:18:59 +00005148 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00005149 {
cristy4c08aed2011-07-01 19:47:50 +00005150 pixel.green=GetPixelGreen(image,p)-
5151 (MagickRealType) GetPixelGreen(image,q);
cristy3ed852e2009-09-05 21:47:34 +00005152 if (fabs(2.0*pixel.green) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00005153 pixel.green=(MagickRealType)
5154 GetPixelGreen(image,p);
cristy3ed852e2009-09-05 21:47:34 +00005155 else
cristy4c08aed2011-07-01 19:47:50 +00005156 pixel.green=(MagickRealType)
5157 GetPixelGreen(image,p)+
5158 (pixel.green*amount);
5159 SetPixelGreen(unsharp_image,
5160 ClampToQuantum(pixel.green),q);
cristy3ed852e2009-09-05 21:47:34 +00005161 }
cristyed231572011-07-14 02:18:59 +00005162 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00005163 {
cristy4c08aed2011-07-01 19:47:50 +00005164 pixel.blue=GetPixelBlue(image,p)-
5165 (MagickRealType) GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00005166 if (fabs(2.0*pixel.blue) < quantum_threshold)
cristy4c08aed2011-07-01 19:47:50 +00005167 pixel.blue=(MagickRealType)
5168 GetPixelBlue(image,p);
cristy3ed852e2009-09-05 21:47:34 +00005169 else
cristy4c08aed2011-07-01 19:47:50 +00005170 pixel.blue=(MagickRealType)
5171 GetPixelBlue(image,p)+(pixel.blue*amount);
5172 SetPixelBlue(unsharp_image,
5173 ClampToQuantum(pixel.blue),q);
cristy3ed852e2009-09-05 21:47:34 +00005174 }
cristyed231572011-07-14 02:18:59 +00005175 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00005176 (image->colorspace == CMYKColorspace))
5177 {
cristy4c08aed2011-07-01 19:47:50 +00005178 pixel.black=GetPixelBlack(image,p)-
5179 (MagickRealType) GetPixelBlack(image,q);
5180 if (fabs(2.0*pixel.black) < quantum_threshold)
5181 pixel.black=(MagickRealType)
5182 GetPixelBlack(image,p);
cristy3ed852e2009-09-05 21:47:34 +00005183 else
cristy4c08aed2011-07-01 19:47:50 +00005184 pixel.black=(MagickRealType)
5185 GetPixelBlack(image,p)+(pixel.black*
5186 amount);
5187 SetPixelBlack(unsharp_image,
5188 ClampToQuantum(pixel.black),q);
cristy3ed852e2009-09-05 21:47:34 +00005189 }
cristyed231572011-07-14 02:18:59 +00005190 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy4c08aed2011-07-01 19:47:50 +00005191 {
5192 pixel.alpha=GetPixelAlpha(image,p)-
5193 (MagickRealType) GetPixelAlpha(image,q);
5194 if (fabs(2.0*pixel.alpha) < quantum_threshold)
5195 pixel.alpha=(MagickRealType)
5196 GetPixelAlpha(image,p);
5197 else
5198 pixel.alpha=GetPixelAlpha(image,p)+
5199 (pixel.alpha*amount);
5200 SetPixelAlpha(unsharp_image,
5201 ClampToQuantum(pixel.alpha),q);
5202 }
cristyed231572011-07-14 02:18:59 +00005203 p+=GetPixelChannels(image);
5204 q+=GetPixelChannels(unsharp_image);
cristy3ed852e2009-09-05 21:47:34 +00005205 }
5206 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
5207 status=MagickFalse;
5208 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5209 {
5210 MagickBooleanType
5211 proceed;
5212
cristyb5d5f722009-11-04 03:03:49 +00005213#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf4ad9df2011-07-08 16:49:03 +00005214 #pragma omp critical (MagickCore_UnsharpMaskImage)
cristy3ed852e2009-09-05 21:47:34 +00005215#endif
5216 proceed=SetImageProgress(image,SharpenImageTag,progress++,image->rows);
5217 if (proceed == MagickFalse)
5218 status=MagickFalse;
5219 }
5220 }
5221 unsharp_image->type=image->type;
5222 unsharp_view=DestroyCacheView(unsharp_view);
5223 image_view=DestroyCacheView(image_view);
5224 if (status == MagickFalse)
5225 unsharp_image=DestroyImage(unsharp_image);
5226 return(unsharp_image);
5227}