blob: c4c37ffc9272e1f107dffbca7f8e4af7d2d9c759 [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% %
cristy16af1cb2009-12-11 21:38:29 +000020% Copyright 1999-2010 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*/
43#include "magick/studio.h"
cristyd43a46b2010-01-21 02:13:41 +000044#include "magick/accelerate.h"
cristy3ed852e2009-09-05 21:47:34 +000045#include "magick/blob.h"
46#include "magick/cache-view.h"
47#include "magick/color.h"
48#include "magick/color-private.h"
49#include "magick/colorspace.h"
50#include "magick/constitute.h"
51#include "magick/decorate.h"
52#include "magick/draw.h"
53#include "magick/enhance.h"
54#include "magick/exception.h"
55#include "magick/exception-private.h"
56#include "magick/effect.h"
57#include "magick/fx.h"
58#include "magick/gem.h"
59#include "magick/geometry.h"
60#include "magick/image-private.h"
61#include "magick/list.h"
62#include "magick/log.h"
63#include "magick/memory_.h"
64#include "magick/monitor.h"
65#include "magick/monitor-private.h"
66#include "magick/montage.h"
cristy6771f1e2010-03-05 19:43:39 +000067#include "magick/morphology.h"
cristy3ed852e2009-09-05 21:47:34 +000068#include "magick/paint.h"
69#include "magick/pixel-private.h"
70#include "magick/property.h"
71#include "magick/quantize.h"
72#include "magick/quantum.h"
73#include "magick/random_.h"
74#include "magick/random-private.h"
75#include "magick/resample.h"
76#include "magick/resample-private.h"
77#include "magick/resize.h"
78#include "magick/resource_.h"
79#include "magick/segment.h"
80#include "magick/shear.h"
81#include "magick/signature-private.h"
82#include "magick/string_.h"
83#include "magick/thread-private.h"
84#include "magick/transform.h"
85#include "magick/threshold.h"
86
87/*
88%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
89% %
90% %
91% %
92% A d a p t i v e B l u r I m a g e %
93% %
94% %
95% %
96%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
97%
98% AdaptiveBlurImage() adaptively blurs the image by blurring less
99% intensely near image edges and more intensely far from edges. We blur the
100% image with a Gaussian operator of the given radius and standard deviation
101% (sigma). For reasonable results, radius should be larger than sigma. Use a
102% radius of 0 and AdaptiveBlurImage() selects a suitable radius for you.
103%
104% The format of the AdaptiveBlurImage method is:
105%
106% Image *AdaptiveBlurImage(const Image *image,const double radius,
107% const double sigma,ExceptionInfo *exception)
108% Image *AdaptiveBlurImageChannel(const Image *image,
109% const ChannelType channel,double radius,const double sigma,
110% ExceptionInfo *exception)
111%
112% A description of each parameter follows:
113%
114% o image: the image.
115%
116% o channel: the channel type.
117%
118% o radius: the radius of the Gaussian, in pixels, not counting the center
119% pixel.
120%
121% o sigma: the standard deviation of the Laplacian, in pixels.
122%
123% o exception: return any errors or warnings in this structure.
124%
125*/
126
127MagickExport Image *AdaptiveBlurImage(const Image *image,const double radius,
128 const double sigma,ExceptionInfo *exception)
129{
130 Image
131 *blur_image;
132
133 blur_image=AdaptiveBlurImageChannel(image,DefaultChannels,radius,sigma,
134 exception);
135 return(blur_image);
136}
137
138MagickExport Image *AdaptiveBlurImageChannel(const Image *image,
139 const ChannelType channel,const double radius,const double sigma,
140 ExceptionInfo *exception)
141{
142#define AdaptiveBlurImageTag "Convolve/Image"
143#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
144
cristyc4c8d132010-01-07 01:58:38 +0000145 CacheView
146 *blur_view,
147 *edge_view,
148 *image_view;
149
cristy3ed852e2009-09-05 21:47:34 +0000150 double
cristy47e00502009-12-17 19:19:57 +0000151 **kernel,
152 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000153
154 Image
155 *blur_image,
156 *edge_image,
157 *gaussian_image;
158
159 long
160 j,
cristy47e00502009-12-17 19:19:57 +0000161 k,
cristy3ed852e2009-09-05 21:47:34 +0000162 progress,
cristy47e00502009-12-17 19:19:57 +0000163 u,
164 v,
cristy3ed852e2009-09-05 21:47:34 +0000165 y;
166
167 MagickBooleanType
168 status;
169
170 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +0000171 bias;
cristy3ed852e2009-09-05 21:47:34 +0000172
cristy3ed852e2009-09-05 21:47:34 +0000173 register long
cristy47e00502009-12-17 19:19:57 +0000174 i;
cristy3ed852e2009-09-05 21:47:34 +0000175
176 unsigned long
177 width;
178
cristy3ed852e2009-09-05 21:47:34 +0000179 assert(image != (const Image *) NULL);
180 assert(image->signature == MagickSignature);
181 if (image->debug != MagickFalse)
182 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
183 assert(exception != (ExceptionInfo *) NULL);
184 assert(exception->signature == MagickSignature);
185 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
186 if (blur_image == (Image *) NULL)
187 return((Image *) NULL);
188 if (fabs(sigma) <= MagickEpsilon)
189 return(blur_image);
190 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
191 {
192 InheritException(exception,&blur_image->exception);
193 blur_image=DestroyImage(blur_image);
194 return((Image *) NULL);
195 }
196 /*
197 Edge detect the image brighness channel, level, blur, and level again.
198 */
199 edge_image=EdgeImage(image,radius,exception);
200 if (edge_image == (Image *) NULL)
201 {
202 blur_image=DestroyImage(blur_image);
203 return((Image *) NULL);
204 }
205 (void) LevelImage(edge_image,"20%,95%");
206 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,exception);
207 if (gaussian_image != (Image *) NULL)
208 {
209 edge_image=DestroyImage(edge_image);
210 edge_image=gaussian_image;
211 }
212 (void) LevelImage(edge_image,"10%,95%");
213 /*
214 Create a set of kernels from maximum (radius,sigma) to minimum.
215 */
216 width=GetOptimalKernelWidth2D(radius,sigma);
217 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
218 if (kernel == (double **) NULL)
219 {
220 edge_image=DestroyImage(edge_image);
221 blur_image=DestroyImage(blur_image);
222 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
223 }
224 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
225 for (i=0; i < (long) width; i+=2)
226 {
227 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
228 sizeof(**kernel));
229 if (kernel[i] == (double *) NULL)
230 break;
cristy47e00502009-12-17 19:19:57 +0000231 normalize=0.0;
232 j=(long) (width-i)/2;
233 k=0;
234 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000235 {
cristy47e00502009-12-17 19:19:57 +0000236 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000237 {
cristy47e00502009-12-17 19:19:57 +0000238 kernel[i][k]=exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
239 (2.0*MagickPI*MagickSigma*MagickSigma);
240 normalize+=kernel[i][k];
241 k++;
cristy3ed852e2009-09-05 21:47:34 +0000242 }
243 }
cristy3ed852e2009-09-05 21:47:34 +0000244 if (fabs(normalize) <= MagickEpsilon)
245 normalize=1.0;
246 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000247 for (k=0; k < (j*j); k++)
248 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000249 }
250 if (i < (long) width)
251 {
252 for (i-=2; i >= 0; i-=2)
253 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
254 kernel=(double **) RelinquishMagickMemory(kernel);
255 edge_image=DestroyImage(edge_image);
256 blur_image=DestroyImage(blur_image);
257 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
258 }
259 /*
260 Adaptively blur image.
261 */
262 status=MagickTrue;
263 progress=0;
cristyddd82202009-11-03 20:14:50 +0000264 GetMagickPixelPacket(image,&bias);
265 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000266 image_view=AcquireCacheView(image);
267 edge_view=AcquireCacheView(edge_image);
268 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000269#if defined(MAGICKCORE_OPENMP_SUPPORT)
270 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000271#endif
272 for (y=0; y < (long) blur_image->rows; y++)
273 {
274 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000275 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000276
277 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000278 *restrict p,
279 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000280
281 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000282 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000283
284 register long
285 x;
286
287 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000288 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000289
290 if (status == MagickFalse)
291 continue;
292 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
293 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
294 exception);
295 if ((r == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
296 {
297 status=MagickFalse;
298 continue;
299 }
300 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
301 for (x=0; x < (long) blur_image->columns; x++)
302 {
303 MagickPixelPacket
304 pixel;
305
306 MagickRealType
307 alpha,
308 gamma;
309
310 register const double
cristyc47d1f82009-11-26 01:44:43 +0000311 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000312
313 register long
314 i,
315 u,
316 v;
317
318 gamma=0.0;
cristy1f9ce9f2010-04-28 11:55:12 +0000319 i=(long) ceil((double) width*QuantumScale*PixelIntensity(r)-0.5);
cristy3ed852e2009-09-05 21:47:34 +0000320 if (i < 0)
321 i=0;
322 else
323 if (i > (long) width)
324 i=(long) width;
325 if ((i & 0x01) != 0)
326 i--;
327 p=GetCacheViewVirtualPixels(image_view,x-((long) (width-i)/2L),y-(long)
328 ((width-i)/2L),width-i,width-i,exception);
329 if (p == (const PixelPacket *) NULL)
330 break;
331 indexes=GetCacheViewVirtualIndexQueue(image_view);
cristyddd82202009-11-03 20:14:50 +0000332 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +0000333 k=kernel[i];
334 for (v=0; v < (long) (width-i); v++)
335 {
336 for (u=0; u < (long) (width-i); u++)
337 {
338 alpha=1.0;
339 if (((channel & OpacityChannel) != 0) &&
340 (image->matte != MagickFalse))
cristy46f08202010-01-10 04:04:21 +0000341 alpha=(MagickRealType) (QuantumScale*GetAlphaPixelComponent(p));
cristy3ed852e2009-09-05 21:47:34 +0000342 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000343 pixel.red+=(*k)*alpha*GetRedPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000344 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000345 pixel.green+=(*k)*alpha*GetGreenPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000346 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000347 pixel.blue+=(*k)*alpha*GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000348 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000349 pixel.opacity+=(*k)*GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000350 if (((channel & IndexChannel) != 0) &&
351 (image->colorspace == CMYKColorspace))
352 pixel.index+=(*k)*alpha*indexes[x+(width-i)*v+u];
353 gamma+=(*k)*alpha;
354 k++;
355 p++;
356 }
357 }
358 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
359 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000360 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000361 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000362 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000363 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000364 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000365 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000366 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000367 if (((channel & IndexChannel) != 0) &&
368 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +0000369 blur_indexes[x]=ClampToQuantum(gamma*GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000370 q++;
371 r++;
372 }
373 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
374 status=MagickFalse;
375 if (image->progress_monitor != (MagickProgressMonitor) NULL)
376 {
377 MagickBooleanType
378 proceed;
379
cristyb5d5f722009-11-04 03:03:49 +0000380#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000381 #pragma omp critical (MagickCore_AdaptiveBlurImageChannel)
382#endif
383 proceed=SetImageProgress(image,AdaptiveBlurImageTag,progress++,
384 image->rows);
385 if (proceed == MagickFalse)
386 status=MagickFalse;
387 }
388 }
389 blur_image->type=image->type;
390 blur_view=DestroyCacheView(blur_view);
391 edge_view=DestroyCacheView(edge_view);
392 image_view=DestroyCacheView(image_view);
393 edge_image=DestroyImage(edge_image);
394 for (i=0; i < (long) width; i+=2)
395 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
396 kernel=(double **) RelinquishMagickMemory(kernel);
397 if (status == MagickFalse)
398 blur_image=DestroyImage(blur_image);
399 return(blur_image);
400}
401
402/*
403%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
404% %
405% %
406% %
407% A d a p t i v e S h a r p e n I m a g e %
408% %
409% %
410% %
411%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
412%
413% AdaptiveSharpenImage() adaptively sharpens the image by sharpening more
414% intensely near image edges and less intensely far from edges. We sharpen the
415% image with a Gaussian operator of the given radius and standard deviation
416% (sigma). For reasonable results, radius should be larger than sigma. Use a
417% radius of 0 and AdaptiveSharpenImage() selects a suitable radius for you.
418%
419% The format of the AdaptiveSharpenImage method is:
420%
421% Image *AdaptiveSharpenImage(const Image *image,const double radius,
422% const double sigma,ExceptionInfo *exception)
423% Image *AdaptiveSharpenImageChannel(const Image *image,
424% const ChannelType channel,double radius,const double sigma,
425% ExceptionInfo *exception)
426%
427% A description of each parameter follows:
428%
429% o image: the image.
430%
431% o channel: the channel type.
432%
433% o radius: the radius of the Gaussian, in pixels, not counting the center
434% pixel.
435%
436% o sigma: the standard deviation of the Laplacian, in pixels.
437%
438% o exception: return any errors or warnings in this structure.
439%
440*/
441
442MagickExport Image *AdaptiveSharpenImage(const Image *image,const double radius,
443 const double sigma,ExceptionInfo *exception)
444{
445 Image
446 *sharp_image;
447
448 sharp_image=AdaptiveSharpenImageChannel(image,DefaultChannels,radius,sigma,
449 exception);
450 return(sharp_image);
451}
452
453MagickExport Image *AdaptiveSharpenImageChannel(const Image *image,
454 const ChannelType channel,const double radius,const double sigma,
455 ExceptionInfo *exception)
456{
457#define AdaptiveSharpenImageTag "Convolve/Image"
458#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
459
cristyc4c8d132010-01-07 01:58:38 +0000460 CacheView
461 *sharp_view,
462 *edge_view,
463 *image_view;
464
cristy3ed852e2009-09-05 21:47:34 +0000465 double
cristy47e00502009-12-17 19:19:57 +0000466 **kernel,
467 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000468
469 Image
470 *sharp_image,
471 *edge_image,
472 *gaussian_image;
473
474 long
475 j,
cristy47e00502009-12-17 19:19:57 +0000476 k,
cristy3ed852e2009-09-05 21:47:34 +0000477 progress,
cristy47e00502009-12-17 19:19:57 +0000478 u,
479 v,
cristy3ed852e2009-09-05 21:47:34 +0000480 y;
481
482 MagickBooleanType
483 status;
484
485 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +0000486 bias;
cristy3ed852e2009-09-05 21:47:34 +0000487
cristy3ed852e2009-09-05 21:47:34 +0000488 register long
cristy47e00502009-12-17 19:19:57 +0000489 i;
cristy3ed852e2009-09-05 21:47:34 +0000490
491 unsigned long
492 width;
493
cristy3ed852e2009-09-05 21:47:34 +0000494 assert(image != (const Image *) NULL);
495 assert(image->signature == MagickSignature);
496 if (image->debug != MagickFalse)
497 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
498 assert(exception != (ExceptionInfo *) NULL);
499 assert(exception->signature == MagickSignature);
500 sharp_image=CloneImage(image,0,0,MagickTrue,exception);
501 if (sharp_image == (Image *) NULL)
502 return((Image *) NULL);
503 if (fabs(sigma) <= MagickEpsilon)
504 return(sharp_image);
505 if (SetImageStorageClass(sharp_image,DirectClass) == MagickFalse)
506 {
507 InheritException(exception,&sharp_image->exception);
508 sharp_image=DestroyImage(sharp_image);
509 return((Image *) NULL);
510 }
511 /*
512 Edge detect the image brighness channel, level, sharp, and level again.
513 */
514 edge_image=EdgeImage(image,radius,exception);
515 if (edge_image == (Image *) NULL)
516 {
517 sharp_image=DestroyImage(sharp_image);
518 return((Image *) NULL);
519 }
520 (void) LevelImage(edge_image,"20%,95%");
521 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,exception);
522 if (gaussian_image != (Image *) NULL)
523 {
524 edge_image=DestroyImage(edge_image);
525 edge_image=gaussian_image;
526 }
527 (void) LevelImage(edge_image,"10%,95%");
528 /*
529 Create a set of kernels from maximum (radius,sigma) to minimum.
530 */
531 width=GetOptimalKernelWidth2D(radius,sigma);
532 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
533 if (kernel == (double **) NULL)
534 {
535 edge_image=DestroyImage(edge_image);
536 sharp_image=DestroyImage(sharp_image);
537 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
538 }
539 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
540 for (i=0; i < (long) width; i+=2)
541 {
542 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
543 sizeof(**kernel));
544 if (kernel[i] == (double *) NULL)
545 break;
cristy47e00502009-12-17 19:19:57 +0000546 normalize=0.0;
547 j=(long) (width-i)/2;
548 k=0;
549 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000550 {
cristy47e00502009-12-17 19:19:57 +0000551 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000552 {
cristy47e00502009-12-17 19:19:57 +0000553 kernel[i][k]=(-exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
554 (2.0*MagickPI*MagickSigma*MagickSigma));
555 normalize+=kernel[i][k];
556 k++;
cristy3ed852e2009-09-05 21:47:34 +0000557 }
558 }
cristy3ed852e2009-09-05 21:47:34 +0000559 if (fabs(normalize) <= MagickEpsilon)
560 normalize=1.0;
561 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000562 for (k=0; k < (j*j); k++)
563 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000564 }
565 if (i < (long) width)
566 {
567 for (i-=2; i >= 0; i-=2)
568 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
569 kernel=(double **) RelinquishMagickMemory(kernel);
570 edge_image=DestroyImage(edge_image);
571 sharp_image=DestroyImage(sharp_image);
572 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
573 }
574 /*
575 Adaptively sharpen image.
576 */
577 status=MagickTrue;
578 progress=0;
cristyddd82202009-11-03 20:14:50 +0000579 GetMagickPixelPacket(image,&bias);
580 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000581 image_view=AcquireCacheView(image);
582 edge_view=AcquireCacheView(edge_image);
583 sharp_view=AcquireCacheView(sharp_image);
cristyb5d5f722009-11-04 03:03:49 +0000584#if defined(MAGICKCORE_OPENMP_SUPPORT)
585 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000586#endif
587 for (y=0; y < (long) sharp_image->rows; y++)
588 {
589 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000590 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000591
592 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000593 *restrict p,
594 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000595
596 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000597 *restrict sharp_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000598
599 register long
600 x;
601
602 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000603 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000604
605 if (status == MagickFalse)
606 continue;
607 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
608 q=QueueCacheViewAuthenticPixels(sharp_view,0,y,sharp_image->columns,1,
609 exception);
610 if ((r == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
611 {
612 status=MagickFalse;
613 continue;
614 }
615 sharp_indexes=GetCacheViewAuthenticIndexQueue(sharp_view);
616 for (x=0; x < (long) sharp_image->columns; x++)
617 {
618 MagickPixelPacket
619 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
628 register long
629 i,
630 u,
631 v;
632
633 gamma=0.0;
cristy1f9ce9f2010-04-28 11:55:12 +0000634 i=(long) ceil((double) width*(QuantumRange-QuantumScale*
635 PixelIntensity(r))-0.5);
cristy3ed852e2009-09-05 21:47:34 +0000636 if (i < 0)
637 i=0;
638 else
639 if (i > (long) width)
640 i=(long) width;
641 if ((i & 0x01) != 0)
642 i--;
643 p=GetCacheViewVirtualPixels(image_view,x-((long) (width-i)/2L),y-(long)
644 ((width-i)/2L),width-i,width-i,exception);
645 if (p == (const PixelPacket *) NULL)
646 break;
647 indexes=GetCacheViewVirtualIndexQueue(image_view);
648 k=kernel[i];
cristyddd82202009-11-03 20:14:50 +0000649 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +0000650 for (v=0; v < (long) (width-i); v++)
651 {
652 for (u=0; u < (long) (width-i); u++)
653 {
654 alpha=1.0;
655 if (((channel & OpacityChannel) != 0) &&
656 (image->matte != MagickFalse))
cristy46f08202010-01-10 04:04:21 +0000657 alpha=(MagickRealType) (QuantumScale*GetAlphaPixelComponent(p));
cristy3ed852e2009-09-05 21:47:34 +0000658 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000659 pixel.red+=(*k)*alpha*GetRedPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000660 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000661 pixel.green+=(*k)*alpha*GetGreenPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000662 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000663 pixel.blue+=(*k)*alpha*GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000664 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000665 pixel.opacity+=(*k)*GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000666 if (((channel & IndexChannel) != 0) &&
667 (image->colorspace == CMYKColorspace))
668 pixel.index+=(*k)*alpha*indexes[x+(width-i)*v+u];
669 gamma+=(*k)*alpha;
670 k++;
671 p++;
672 }
673 }
674 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
675 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000676 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000677 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000678 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000679 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000680 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000681 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000682 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000683 if (((channel & IndexChannel) != 0) &&
684 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +0000685 sharp_indexes[x]=ClampToQuantum(gamma*GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000686 q++;
687 r++;
688 }
689 if (SyncCacheViewAuthenticPixels(sharp_view,exception) == MagickFalse)
690 status=MagickFalse;
691 if (image->progress_monitor != (MagickProgressMonitor) NULL)
692 {
693 MagickBooleanType
694 proceed;
695
cristyb5d5f722009-11-04 03:03:49 +0000696#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000697 #pragma omp critical (MagickCore_AdaptiveSharpenImageChannel)
698#endif
699 proceed=SetImageProgress(image,AdaptiveSharpenImageTag,progress++,
700 image->rows);
701 if (proceed == MagickFalse)
702 status=MagickFalse;
703 }
704 }
705 sharp_image->type=image->type;
706 sharp_view=DestroyCacheView(sharp_view);
707 edge_view=DestroyCacheView(edge_view);
708 image_view=DestroyCacheView(image_view);
709 edge_image=DestroyImage(edge_image);
710 for (i=0; i < (long) width; i+=2)
711 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
712 kernel=(double **) RelinquishMagickMemory(kernel);
713 if (status == MagickFalse)
714 sharp_image=DestroyImage(sharp_image);
715 return(sharp_image);
716}
717
718/*
719%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
720% %
721% %
722% %
723% B l u r I m a g e %
724% %
725% %
726% %
727%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
728%
729% BlurImage() blurs an image. We convolve the image with a Gaussian operator
730% of the given radius and standard deviation (sigma). For reasonable results,
731% the radius should be larger than sigma. Use a radius of 0 and BlurImage()
732% selects a suitable radius for you.
733%
734% BlurImage() differs from GaussianBlurImage() in that it uses a separable
735% kernel which is faster but mathematically equivalent to the non-separable
736% kernel.
737%
738% The format of the BlurImage method is:
739%
740% Image *BlurImage(const Image *image,const double radius,
741% const double sigma,ExceptionInfo *exception)
742% Image *BlurImageChannel(const Image *image,const ChannelType channel,
743% const double radius,const double sigma,ExceptionInfo *exception)
744%
745% A description of each parameter follows:
746%
747% o image: the image.
748%
749% o channel: the channel type.
750%
751% o radius: the radius of the Gaussian, in pixels, not counting the center
752% pixel.
753%
754% o sigma: the standard deviation of the Gaussian, in pixels.
755%
756% o exception: return any errors or warnings in this structure.
757%
758*/
759
760MagickExport Image *BlurImage(const Image *image,const double radius,
761 const double sigma,ExceptionInfo *exception)
762{
763 Image
764 *blur_image;
765
766 blur_image=BlurImageChannel(image,DefaultChannels,radius,sigma,exception);
767 return(blur_image);
768}
769
cristy47e00502009-12-17 19:19:57 +0000770static double *GetBlurKernel(const unsigned long width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +0000771{
cristy3ed852e2009-09-05 21:47:34 +0000772 double
cristy47e00502009-12-17 19:19:57 +0000773 *kernel,
774 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000775
776 long
cristy47e00502009-12-17 19:19:57 +0000777 j,
778 k;
cristy3ed852e2009-09-05 21:47:34 +0000779
780 register long
781 i;
782
783 /*
784 Generate a 1-D convolution kernel.
785 */
786 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
787 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
788 if (kernel == (double *) NULL)
789 return(0);
cristy3ed852e2009-09-05 21:47:34 +0000790 normalize=0.0;
cristy47e00502009-12-17 19:19:57 +0000791 j=(long) width/2;
792 i=0;
793 for (k=(-j); k <= j; k++)
794 {
cristyf267c722009-12-18 00:07:22 +0000795 kernel[i]=exp(-((double) k*k)/(2.0*MagickSigma*MagickSigma))/
cristy47e00502009-12-17 19:19:57 +0000796 (MagickSQ2PI*MagickSigma);
cristy3ed852e2009-09-05 21:47:34 +0000797 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +0000798 i++;
799 }
cristy3ed852e2009-09-05 21:47:34 +0000800 for (i=0; i < (long) width; i++)
801 kernel[i]/=normalize;
802 return(kernel);
803}
804
805MagickExport Image *BlurImageChannel(const Image *image,
806 const ChannelType channel,const double radius,const double sigma,
807 ExceptionInfo *exception)
808{
809#define BlurImageTag "Blur/Image"
810
cristyc4c8d132010-01-07 01:58:38 +0000811 CacheView
812 *blur_view,
813 *image_view;
814
cristy3ed852e2009-09-05 21:47:34 +0000815 double
816 *kernel;
817
818 Image
819 *blur_image;
820
821 long
822 progress,
823 x,
824 y;
825
826 MagickBooleanType
827 status;
828
829 MagickPixelPacket
cristy3ed852e2009-09-05 21:47:34 +0000830 bias;
831
832 register long
833 i;
834
835 unsigned long
836 width;
837
cristy3ed852e2009-09-05 21:47:34 +0000838 /*
839 Initialize blur image attributes.
840 */
841 assert(image != (Image *) NULL);
842 assert(image->signature == MagickSignature);
843 if (image->debug != MagickFalse)
844 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
845 assert(exception != (ExceptionInfo *) NULL);
846 assert(exception->signature == MagickSignature);
847 blur_image=CloneImage(image,0,0,MagickTrue,exception);
848 if (blur_image == (Image *) NULL)
849 return((Image *) NULL);
850 if (fabs(sigma) <= MagickEpsilon)
851 return(blur_image);
852 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
853 {
854 InheritException(exception,&blur_image->exception);
855 blur_image=DestroyImage(blur_image);
856 return((Image *) NULL);
857 }
858 width=GetOptimalKernelWidth1D(radius,sigma);
859 kernel=GetBlurKernel(width,sigma);
860 if (kernel == (double *) NULL)
861 {
862 blur_image=DestroyImage(blur_image);
863 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
864 }
865 if (image->debug != MagickFalse)
866 {
867 char
868 format[MaxTextExtent],
869 *message;
870
871 register const double
872 *k;
873
874 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
875 " BlurImage with %ld kernel:",width);
876 message=AcquireString("");
877 k=kernel;
878 for (i=0; i < (long) width; i++)
879 {
880 *message='\0';
881 (void) FormatMagickString(format,MaxTextExtent,"%ld: ",i);
882 (void) ConcatenateString(&message,format);
cristye7f51092010-01-17 00:39:37 +0000883 (void) FormatMagickString(format,MaxTextExtent,"%g ",*k++);
cristy3ed852e2009-09-05 21:47:34 +0000884 (void) ConcatenateString(&message,format);
885 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
886 }
887 message=DestroyString(message);
888 }
889 /*
890 Blur rows.
891 */
892 status=MagickTrue;
893 progress=0;
cristyddd82202009-11-03 20:14:50 +0000894 GetMagickPixelPacket(image,&bias);
895 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000896 image_view=AcquireCacheView(image);
897 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000898#if defined(MAGICKCORE_OPENMP_SUPPORT)
899 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000900#endif
901 for (y=0; y < (long) blur_image->rows; y++)
902 {
903 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000904 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000905
906 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000907 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000908
909 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000910 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000911
912 register long
913 x;
914
915 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000916 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000917
918 if (status == MagickFalse)
919 continue;
920 p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y,image->columns+
921 width,1,exception);
922 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
923 exception);
924 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
925 {
926 status=MagickFalse;
927 continue;
928 }
929 indexes=GetCacheViewVirtualIndexQueue(image_view);
930 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
931 for (x=0; x < (long) blur_image->columns; x++)
932 {
933 MagickPixelPacket
934 pixel;
935
936 register const double
cristyc47d1f82009-11-26 01:44:43 +0000937 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000938
939 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000940 *restrict kernel_pixels;
cristy3ed852e2009-09-05 21:47:34 +0000941
942 register long
943 i;
944
cristyddd82202009-11-03 20:14:50 +0000945 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +0000946 k=kernel;
947 kernel_pixels=p;
948 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
949 {
950 for (i=0; i < (long) width; i++)
951 {
952 pixel.red+=(*k)*kernel_pixels->red;
953 pixel.green+=(*k)*kernel_pixels->green;
954 pixel.blue+=(*k)*kernel_pixels->blue;
955 k++;
956 kernel_pixels++;
957 }
958 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000959 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000960 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000961 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000962 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000963 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000964 if ((channel & OpacityChannel) != 0)
965 {
966 k=kernel;
967 kernel_pixels=p;
968 for (i=0; i < (long) width; i++)
969 {
970 pixel.opacity+=(*k)*kernel_pixels->opacity;
971 k++;
972 kernel_pixels++;
973 }
cristyce70c172010-01-07 17:15:30 +0000974 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000975 }
976 if (((channel & IndexChannel) != 0) &&
977 (image->colorspace == CMYKColorspace))
978 {
979 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000980 *restrict kernel_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000981
982 k=kernel;
983 kernel_indexes=indexes;
984 for (i=0; i < (long) width; i++)
985 {
986 pixel.index+=(*k)*(*kernel_indexes);
987 k++;
988 kernel_indexes++;
989 }
cristyce70c172010-01-07 17:15:30 +0000990 blur_indexes[x]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +0000991 }
992 }
993 else
994 {
995 MagickRealType
996 alpha,
997 gamma;
998
999 gamma=0.0;
1000 for (i=0; i < (long) width; i++)
1001 {
cristy46f08202010-01-10 04:04:21 +00001002 alpha=(MagickRealType) (QuantumScale*
1003 GetAlphaPixelComponent(kernel_pixels));
cristy3ed852e2009-09-05 21:47:34 +00001004 pixel.red+=(*k)*alpha*kernel_pixels->red;
1005 pixel.green+=(*k)*alpha*kernel_pixels->green;
1006 pixel.blue+=(*k)*alpha*kernel_pixels->blue;
1007 gamma+=(*k)*alpha;
1008 k++;
1009 kernel_pixels++;
1010 }
1011 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1012 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001013 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001014 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001015 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001016 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001017 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001018 if ((channel & OpacityChannel) != 0)
1019 {
1020 k=kernel;
1021 kernel_pixels=p;
1022 for (i=0; i < (long) width; i++)
1023 {
1024 pixel.opacity+=(*k)*kernel_pixels->opacity;
1025 k++;
1026 kernel_pixels++;
1027 }
cristyce70c172010-01-07 17:15:30 +00001028 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001029 }
1030 if (((channel & IndexChannel) != 0) &&
1031 (image->colorspace == CMYKColorspace))
1032 {
1033 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001034 *restrict kernel_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001035
1036 k=kernel;
1037 kernel_pixels=p;
1038 kernel_indexes=indexes;
1039 for (i=0; i < (long) width; i++)
1040 {
cristy46f08202010-01-10 04:04:21 +00001041 alpha=(MagickRealType) (QuantumScale*
1042 GetAlphaPixelComponent(kernel_pixels));
cristy3ed852e2009-09-05 21:47:34 +00001043 pixel.index+=(*k)*alpha*(*kernel_indexes);
1044 k++;
1045 kernel_pixels++;
1046 kernel_indexes++;
1047 }
cristy46f08202010-01-10 04:04:21 +00001048 blur_indexes[x]=ClampToQuantum(gamma*
1049 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001050 }
1051 }
1052 p++;
1053 q++;
1054 }
1055 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1056 status=MagickFalse;
1057 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1058 {
1059 MagickBooleanType
1060 proceed;
1061
cristyb5d5f722009-11-04 03:03:49 +00001062#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001063 #pragma omp critical (MagickCore_BlurImageChannel)
1064#endif
1065 proceed=SetImageProgress(image,BlurImageTag,progress++,blur_image->rows+
1066 blur_image->columns);
1067 if (proceed == MagickFalse)
1068 status=MagickFalse;
1069 }
1070 }
1071 blur_view=DestroyCacheView(blur_view);
1072 image_view=DestroyCacheView(image_view);
1073 /*
1074 Blur columns.
1075 */
1076 image_view=AcquireCacheView(blur_image);
1077 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00001078#if defined(MAGICKCORE_OPENMP_SUPPORT)
1079 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001080#endif
1081 for (x=0; x < (long) blur_image->columns; x++)
1082 {
1083 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001084 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001085
1086 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001087 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001088
1089 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001090 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001091
1092 register long
1093 y;
1094
1095 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001096 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001097
1098 if (status == MagickFalse)
1099 continue;
1100 p=GetCacheViewVirtualPixels(image_view,x,-((long) width/2L),1,image->rows+
1101 width,exception);
1102 q=GetCacheViewAuthenticPixels(blur_view,x,0,1,blur_image->rows,exception);
1103 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1104 {
1105 status=MagickFalse;
1106 continue;
1107 }
1108 indexes=GetCacheViewVirtualIndexQueue(image_view);
1109 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
1110 for (y=0; y < (long) blur_image->rows; y++)
1111 {
1112 MagickPixelPacket
1113 pixel;
1114
1115 register const double
cristyc47d1f82009-11-26 01:44:43 +00001116 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00001117
1118 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001119 *restrict kernel_pixels;
cristy3ed852e2009-09-05 21:47:34 +00001120
1121 register long
1122 i;
1123
cristyddd82202009-11-03 20:14:50 +00001124 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00001125 k=kernel;
1126 kernel_pixels=p;
1127 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
1128 {
1129 for (i=0; i < (long) width; i++)
1130 {
1131 pixel.red+=(*k)*kernel_pixels->red;
1132 pixel.green+=(*k)*kernel_pixels->green;
1133 pixel.blue+=(*k)*kernel_pixels->blue;
1134 k++;
1135 kernel_pixels++;
1136 }
1137 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001138 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001139 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001140 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001141 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001142 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001143 if ((channel & OpacityChannel) != 0)
1144 {
1145 k=kernel;
1146 kernel_pixels=p;
1147 for (i=0; i < (long) width; i++)
1148 {
1149 pixel.opacity+=(*k)*kernel_pixels->opacity;
1150 k++;
1151 kernel_pixels++;
1152 }
cristyce70c172010-01-07 17:15:30 +00001153 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001154 }
1155 if (((channel & IndexChannel) != 0) &&
1156 (image->colorspace == CMYKColorspace))
1157 {
1158 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001159 *restrict kernel_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001160
1161 k=kernel;
1162 kernel_indexes=indexes;
1163 for (i=0; i < (long) width; i++)
1164 {
1165 pixel.index+=(*k)*(*kernel_indexes);
1166 k++;
1167 kernel_indexes++;
1168 }
cristyce70c172010-01-07 17:15:30 +00001169 blur_indexes[y]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00001170 }
1171 }
1172 else
1173 {
1174 MagickRealType
1175 alpha,
1176 gamma;
1177
1178 gamma=0.0;
1179 for (i=0; i < (long) width; i++)
1180 {
cristy46f08202010-01-10 04:04:21 +00001181 alpha=(MagickRealType) (QuantumScale*
1182 GetAlphaPixelComponent(kernel_pixels));
cristy3ed852e2009-09-05 21:47:34 +00001183 pixel.red+=(*k)*alpha*kernel_pixels->red;
1184 pixel.green+=(*k)*alpha*kernel_pixels->green;
1185 pixel.blue+=(*k)*alpha*kernel_pixels->blue;
1186 gamma+=(*k)*alpha;
1187 k++;
1188 kernel_pixels++;
1189 }
1190 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1191 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001192 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001193 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001194 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001195 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001196 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001197 if ((channel & OpacityChannel) != 0)
1198 {
1199 k=kernel;
1200 kernel_pixels=p;
1201 for (i=0; i < (long) width; i++)
1202 {
1203 pixel.opacity+=(*k)*kernel_pixels->opacity;
1204 k++;
1205 kernel_pixels++;
1206 }
cristyce70c172010-01-07 17:15:30 +00001207 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001208 }
1209 if (((channel & IndexChannel) != 0) &&
1210 (image->colorspace == CMYKColorspace))
1211 {
1212 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001213 *restrict kernel_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001214
1215 k=kernel;
1216 kernel_pixels=p;
1217 kernel_indexes=indexes;
1218 for (i=0; i < (long) width; i++)
1219 {
cristy46f08202010-01-10 04:04:21 +00001220 alpha=(MagickRealType) (QuantumScale*
1221 GetAlphaPixelComponent(kernel_pixels));
cristy3ed852e2009-09-05 21:47:34 +00001222 pixel.index+=(*k)*alpha*(*kernel_indexes);
1223 k++;
1224 kernel_pixels++;
1225 kernel_indexes++;
1226 }
cristy46f08202010-01-10 04:04:21 +00001227 blur_indexes[y]=ClampToQuantum(gamma*
1228 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001229 }
1230 }
1231 p++;
1232 q++;
1233 }
1234 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1235 status=MagickFalse;
1236 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1237 {
1238 MagickBooleanType
1239 proceed;
1240
cristyb5d5f722009-11-04 03:03:49 +00001241#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001242 #pragma omp critical (MagickCore_BlurImageChannel)
1243#endif
1244 proceed=SetImageProgress(image,BlurImageTag,progress++,blur_image->rows+
1245 blur_image->columns);
1246 if (proceed == MagickFalse)
1247 status=MagickFalse;
1248 }
1249 }
1250 blur_view=DestroyCacheView(blur_view);
1251 image_view=DestroyCacheView(image_view);
1252 kernel=(double *) RelinquishMagickMemory(kernel);
1253 if (status == MagickFalse)
1254 blur_image=DestroyImage(blur_image);
1255 blur_image->type=image->type;
1256 return(blur_image);
1257}
1258
1259/*
1260%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1261% %
1262% %
1263% %
cristyfccdab92009-11-30 16:43:57 +00001264% C o n v o l v e I m a g e %
1265% %
1266% %
1267% %
1268%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1269%
1270% ConvolveImage() applies a custom convolution kernel to the image.
1271%
1272% The format of the ConvolveImage method is:
1273%
1274% Image *ConvolveImage(const Image *image,const unsigned long order,
1275% const double *kernel,ExceptionInfo *exception)
1276% Image *ConvolveImageChannel(const Image *image,const ChannelType channel,
1277% const unsigned long order,const double *kernel,
1278% ExceptionInfo *exception)
1279%
1280% A description of each parameter follows:
1281%
1282% o image: the image.
1283%
1284% o channel: the channel type.
1285%
1286% o order: the number of columns and rows in the filter kernel.
1287%
1288% o kernel: An array of double representing the convolution kernel.
1289%
1290% o exception: return any errors or warnings in this structure.
1291%
1292*/
1293
1294MagickExport Image *ConvolveImage(const Image *image,const unsigned long order,
1295 const double *kernel,ExceptionInfo *exception)
1296{
1297 Image
1298 *convolve_image;
1299
1300 convolve_image=ConvolveImageChannel(image,DefaultChannels,order,kernel,
1301 exception);
1302 return(convolve_image);
1303}
1304
1305MagickExport Image *ConvolveImageChannel(const Image *image,
1306 const ChannelType channel,const unsigned long order,const double *kernel,
1307 ExceptionInfo *exception)
1308{
1309#define ConvolveImageTag "Convolve/Image"
1310
cristyc4c8d132010-01-07 01:58:38 +00001311 CacheView
1312 *convolve_view,
1313 *image_view;
1314
cristyfccdab92009-11-30 16:43:57 +00001315 double
1316 *normal_kernel;
1317
1318 Image
1319 *convolve_image;
1320
1321 long
1322 progress,
1323 y;
1324
1325 MagickBooleanType
1326 status;
1327
1328 MagickPixelPacket
1329 bias;
1330
1331 MagickRealType
1332 gamma;
1333
1334 register long
1335 i;
1336
1337 unsigned long
1338 width;
1339
cristyfccdab92009-11-30 16:43:57 +00001340 /*
1341 Initialize convolve image attributes.
1342 */
1343 assert(image != (Image *) NULL);
1344 assert(image->signature == MagickSignature);
1345 if (image->debug != MagickFalse)
1346 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1347 assert(exception != (ExceptionInfo *) NULL);
1348 assert(exception->signature == MagickSignature);
1349 width=order;
1350 if ((width % 2) == 0)
1351 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
1352 convolve_image=CloneImage(image,0,0,MagickTrue,exception);
1353 if (convolve_image == (Image *) NULL)
1354 return((Image *) NULL);
1355 if (SetImageStorageClass(convolve_image,DirectClass) == MagickFalse)
1356 {
1357 InheritException(exception,&convolve_image->exception);
1358 convolve_image=DestroyImage(convolve_image);
1359 return((Image *) NULL);
1360 }
1361 if (image->debug != MagickFalse)
1362 {
1363 char
1364 format[MaxTextExtent],
1365 *message;
1366
1367 long
1368 u,
1369 v;
1370
1371 register const double
1372 *k;
1373
1374 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
1375 " ConvolveImage with %ldx%ld kernel:",width,width);
1376 message=AcquireString("");
1377 k=kernel;
1378 for (v=0; v < (long) width; v++)
1379 {
1380 *message='\0';
1381 (void) FormatMagickString(format,MaxTextExtent,"%ld: ",v);
1382 (void) ConcatenateString(&message,format);
1383 for (u=0; u < (long) width; u++)
1384 {
cristye7f51092010-01-17 00:39:37 +00001385 (void) FormatMagickString(format,MaxTextExtent,"%g ",*k++);
cristyfccdab92009-11-30 16:43:57 +00001386 (void) ConcatenateString(&message,format);
1387 }
1388 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
1389 }
1390 message=DestroyString(message);
1391 }
1392 /*
1393 Normalize kernel.
1394 */
1395 normal_kernel=(double *) AcquireQuantumMemory(width*width,
1396 sizeof(*normal_kernel));
1397 if (normal_kernel == (double *) NULL)
1398 {
1399 convolve_image=DestroyImage(convolve_image);
1400 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1401 }
1402 gamma=0.0;
1403 for (i=0; i < (long) (width*width); i++)
1404 gamma+=kernel[i];
1405 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1406 for (i=0; i < (long) (width*width); i++)
1407 normal_kernel[i]=gamma*kernel[i];
1408 /*
1409 Convolve image.
1410 */
1411 status=MagickTrue;
1412 progress=0;
1413 GetMagickPixelPacket(image,&bias);
1414 SetMagickPixelPacketBias(image,&bias);
1415 image_view=AcquireCacheView(image);
1416 convolve_view=AcquireCacheView(convolve_image);
1417#if defined(MAGICKCORE_OPENMP_SUPPORT)
1418 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1419#endif
1420 for (y=0; y < (long) image->rows; y++)
1421 {
1422 MagickBooleanType
1423 sync;
1424
1425 register const IndexPacket
1426 *restrict indexes;
1427
1428 register const PixelPacket
1429 *restrict p;
1430
1431 register IndexPacket
1432 *restrict convolve_indexes;
1433
1434 register long
1435 x;
1436
1437 register PixelPacket
1438 *restrict q;
1439
1440 if (status == MagickFalse)
1441 continue;
1442 p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y-(long) (width/
1443 2L),image->columns+width,width,exception);
1444 q=GetCacheViewAuthenticPixels(convolve_view,0,y,convolve_image->columns,1,
1445 exception);
1446 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1447 {
1448 status=MagickFalse;
1449 continue;
1450 }
1451 indexes=GetCacheViewVirtualIndexQueue(image_view);
1452 convolve_indexes=GetCacheViewAuthenticIndexQueue(convolve_view);
1453 for (x=0; x < (long) image->columns; x++)
1454 {
1455 long
1456 v;
1457
1458 MagickPixelPacket
1459 pixel;
1460
1461 register const double
1462 *restrict k;
1463
1464 register const PixelPacket
1465 *restrict kernel_pixels;
1466
1467 register long
1468 u;
1469
1470 pixel=bias;
1471 k=normal_kernel;
1472 kernel_pixels=p;
1473 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
1474 {
1475 for (v=0; v < (long) width; v++)
1476 {
1477 for (u=0; u < (long) width; u++)
1478 {
1479 pixel.red+=(*k)*kernel_pixels[u].red;
1480 pixel.green+=(*k)*kernel_pixels[u].green;
1481 pixel.blue+=(*k)*kernel_pixels[u].blue;
1482 k++;
1483 }
1484 kernel_pixels+=image->columns+width;
1485 }
1486 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001487 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001488 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001489 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001490 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001491 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001492 if ((channel & OpacityChannel) != 0)
1493 {
1494 k=normal_kernel;
1495 kernel_pixels=p;
1496 for (v=0; v < (long) width; v++)
1497 {
1498 for (u=0; u < (long) width; u++)
1499 {
1500 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
1501 k++;
1502 }
1503 kernel_pixels+=image->columns+width;
1504 }
cristyce70c172010-01-07 17:15:30 +00001505 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001506 }
1507 if (((channel & IndexChannel) != 0) &&
1508 (image->colorspace == CMYKColorspace))
1509 {
1510 register const IndexPacket
1511 *restrict kernel_indexes;
1512
1513 k=normal_kernel;
1514 kernel_indexes=indexes;
1515 for (v=0; v < (long) width; v++)
1516 {
1517 for (u=0; u < (long) width; u++)
1518 {
1519 pixel.index+=(*k)*kernel_indexes[u];
1520 k++;
1521 }
1522 kernel_indexes+=image->columns+width;
1523 }
cristyce70c172010-01-07 17:15:30 +00001524 convolve_indexes[x]=ClampToQuantum(pixel.index);
cristyfccdab92009-11-30 16:43:57 +00001525 }
1526 }
1527 else
1528 {
1529 MagickRealType
1530 alpha,
1531 gamma;
1532
1533 gamma=0.0;
1534 for (v=0; v < (long) width; v++)
1535 {
1536 for (u=0; u < (long) width; u++)
1537 {
1538 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
1539 kernel_pixels[u].opacity));
1540 pixel.red+=(*k)*alpha*kernel_pixels[u].red;
1541 pixel.green+=(*k)*alpha*kernel_pixels[u].green;
1542 pixel.blue+=(*k)*alpha*kernel_pixels[u].blue;
cristyfccdab92009-11-30 16:43:57 +00001543 gamma+=(*k)*alpha;
1544 k++;
1545 }
1546 kernel_pixels+=image->columns+width;
1547 }
1548 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1549 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001550 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001551 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001552 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001553 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001554 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001555 if ((channel & OpacityChannel) != 0)
1556 {
1557 k=normal_kernel;
1558 kernel_pixels=p;
1559 for (v=0; v < (long) width; v++)
1560 {
1561 for (u=0; u < (long) width; u++)
1562 {
1563 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
1564 k++;
1565 }
1566 kernel_pixels+=image->columns+width;
1567 }
cristyce70c172010-01-07 17:15:30 +00001568 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001569 }
1570 if (((channel & IndexChannel) != 0) &&
1571 (image->colorspace == CMYKColorspace))
1572 {
1573 register const IndexPacket
1574 *restrict kernel_indexes;
1575
1576 k=normal_kernel;
1577 kernel_pixels=p;
1578 kernel_indexes=indexes;
1579 for (v=0; v < (long) width; v++)
1580 {
1581 for (u=0; u < (long) width; u++)
1582 {
1583 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
1584 kernel_pixels[u].opacity));
1585 pixel.index+=(*k)*alpha*kernel_indexes[u];
1586 k++;
1587 }
1588 kernel_pixels+=image->columns+width;
1589 kernel_indexes+=image->columns+width;
1590 }
cristy24b06da2010-01-09 23:05:56 +00001591 convolve_indexes[x]=ClampToQuantum(gamma*
1592 GetIndexPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001593 }
1594 }
1595 p++;
1596 q++;
1597 }
1598 sync=SyncCacheViewAuthenticPixels(convolve_view,exception);
1599 if (sync == MagickFalse)
1600 status=MagickFalse;
1601 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1602 {
1603 MagickBooleanType
1604 proceed;
1605
1606#if defined(MAGICKCORE_OPENMP_SUPPORT)
1607 #pragma omp critical (MagickCore_ConvolveImageChannel)
1608#endif
1609 proceed=SetImageProgress(image,ConvolveImageTag,progress++,image->rows);
1610 if (proceed == MagickFalse)
1611 status=MagickFalse;
1612 }
1613 }
1614 convolve_image->type=image->type;
1615 convolve_view=DestroyCacheView(convolve_view);
1616 image_view=DestroyCacheView(image_view);
1617 normal_kernel=(double *) RelinquishMagickMemory(normal_kernel);
1618 if (status == MagickFalse)
1619 convolve_image=DestroyImage(convolve_image);
1620 return(convolve_image);
1621}
1622
1623/*
1624%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1625% %
1626% %
1627% %
cristy3ed852e2009-09-05 21:47:34 +00001628% D e s p e c k l e I m a g e %
1629% %
1630% %
1631% %
1632%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1633%
1634% DespeckleImage() reduces the speckle noise in an image while perserving the
1635% edges of the original image.
1636%
1637% The format of the DespeckleImage method is:
1638%
1639% Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1640%
1641% A description of each parameter follows:
1642%
1643% o image: the image.
1644%
1645% o exception: return any errors or warnings in this structure.
1646%
1647*/
1648
1649static Quantum **DestroyPixelThreadSet(Quantum **pixels)
1650{
1651 register long
1652 i;
1653
1654 assert(pixels != (Quantum **) NULL);
1655 for (i=0; i < (long) GetOpenMPMaximumThreads(); i++)
1656 if (pixels[i] != (Quantum *) NULL)
1657 pixels[i]=(Quantum *) RelinquishMagickMemory(pixels[i]);
1658 pixels=(Quantum **) RelinquishAlignedMemory(pixels);
1659 return(pixels);
1660}
1661
1662static Quantum **AcquirePixelThreadSet(const size_t count)
1663{
1664 register long
1665 i;
1666
1667 Quantum
1668 **pixels;
1669
1670 unsigned long
1671 number_threads;
1672
1673 number_threads=GetOpenMPMaximumThreads();
1674 pixels=(Quantum **) AcquireAlignedMemory(number_threads,sizeof(*pixels));
1675 if (pixels == (Quantum **) NULL)
1676 return((Quantum **) NULL);
1677 (void) ResetMagickMemory(pixels,0,number_threads*sizeof(*pixels));
1678 for (i=0; i < (long) number_threads; i++)
1679 {
1680 pixels[i]=(Quantum *) AcquireQuantumMemory(count,sizeof(**pixels));
1681 if (pixels[i] == (Quantum *) NULL)
1682 return(DestroyPixelThreadSet(pixels));
1683 }
1684 return(pixels);
1685}
1686
1687static void Hull(const long x_offset,const long y_offset,
1688 const unsigned long columns,const unsigned long rows,Quantum *f,Quantum *g,
1689 const int polarity)
1690{
1691 long
1692 y;
1693
1694 MagickRealType
1695 v;
1696
1697 register long
1698 x;
1699
1700 register Quantum
1701 *p,
1702 *q,
1703 *r,
1704 *s;
1705
1706 assert(f != (Quantum *) NULL);
1707 assert(g != (Quantum *) NULL);
1708 p=f+(columns+2);
1709 q=g+(columns+2);
1710 r=p+(y_offset*((long) columns+2)+x_offset);
1711 for (y=0; y < (long) rows; y++)
1712 {
1713 p++;
1714 q++;
1715 r++;
1716 if (polarity > 0)
1717 for (x=(long) columns; x != 0; x--)
1718 {
1719 v=(MagickRealType) (*p);
1720 if ((MagickRealType) *r >= (v+(MagickRealType) ScaleCharToQuantum(2)))
1721 v+=ScaleCharToQuantum(1);
1722 *q=(Quantum) v;
1723 p++;
1724 q++;
1725 r++;
1726 }
1727 else
1728 for (x=(long) columns; x != 0; x--)
1729 {
1730 v=(MagickRealType) (*p);
1731 if ((MagickRealType) *r <= (v-(MagickRealType) ScaleCharToQuantum(2)))
1732 v-=(long) ScaleCharToQuantum(1);
1733 *q=(Quantum) v;
1734 p++;
1735 q++;
1736 r++;
1737 }
1738 p++;
1739 q++;
1740 r++;
1741 }
1742 p=f+(columns+2);
1743 q=g+(columns+2);
1744 r=q+(y_offset*((long) columns+2)+x_offset);
1745 s=q-(y_offset*((long) columns+2)+x_offset);
1746 for (y=0; y < (long) rows; y++)
1747 {
1748 p++;
1749 q++;
1750 r++;
1751 s++;
1752 if (polarity > 0)
1753 for (x=(long) columns; x != 0; x--)
1754 {
1755 v=(MagickRealType) (*q);
1756 if (((MagickRealType) *s >=
1757 (v+(MagickRealType) ScaleCharToQuantum(2))) &&
1758 ((MagickRealType) *r > v))
1759 v+=ScaleCharToQuantum(1);
1760 *p=(Quantum) v;
1761 p++;
1762 q++;
1763 r++;
1764 s++;
1765 }
1766 else
1767 for (x=(long) columns; x != 0; x--)
1768 {
1769 v=(MagickRealType) (*q);
1770 if (((MagickRealType) *s <=
1771 (v-(MagickRealType) ScaleCharToQuantum(2))) &&
1772 ((MagickRealType) *r < v))
1773 v-=(MagickRealType) ScaleCharToQuantum(1);
1774 *p=(Quantum) v;
1775 p++;
1776 q++;
1777 r++;
1778 s++;
1779 }
1780 p++;
1781 q++;
1782 r++;
1783 s++;
1784 }
1785}
1786
1787MagickExport Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1788{
1789#define DespeckleImageTag "Despeckle/Image"
1790
cristy2407fc22009-09-11 00:55:25 +00001791 CacheView
1792 *despeckle_view,
1793 *image_view;
1794
cristy3ed852e2009-09-05 21:47:34 +00001795 Image
1796 *despeckle_image;
1797
1798 long
1799 channel;
1800
1801 MagickBooleanType
1802 status;
1803
1804 Quantum
cristyfa112112010-01-04 17:48:07 +00001805 **restrict buffers,
1806 **restrict pixels;
cristy3ed852e2009-09-05 21:47:34 +00001807
1808 size_t
1809 length;
1810
1811 static const int
cristy691a29e2009-09-11 00:44:10 +00001812 X[4] = {0, 1, 1,-1},
1813 Y[4] = {1, 0, 1, 1};
cristy3ed852e2009-09-05 21:47:34 +00001814
cristy3ed852e2009-09-05 21:47:34 +00001815 /*
1816 Allocate despeckled image.
1817 */
1818 assert(image != (const Image *) NULL);
1819 assert(image->signature == MagickSignature);
1820 if (image->debug != MagickFalse)
1821 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1822 assert(exception != (ExceptionInfo *) NULL);
1823 assert(exception->signature == MagickSignature);
1824 despeckle_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1825 exception);
1826 if (despeckle_image == (Image *) NULL)
1827 return((Image *) NULL);
1828 if (SetImageStorageClass(despeckle_image,DirectClass) == MagickFalse)
1829 {
1830 InheritException(exception,&despeckle_image->exception);
1831 despeckle_image=DestroyImage(despeckle_image);
1832 return((Image *) NULL);
1833 }
1834 /*
1835 Allocate image buffers.
1836 */
1837 length=(size_t) ((image->columns+2)*(image->rows+2));
1838 pixels=AcquirePixelThreadSet(length);
1839 buffers=AcquirePixelThreadSet(length);
1840 if ((pixels == (Quantum **) NULL) || (buffers == (Quantum **) NULL))
1841 {
1842 if (buffers != (Quantum **) NULL)
1843 buffers=DestroyPixelThreadSet(buffers);
1844 if (pixels != (Quantum **) NULL)
1845 pixels=DestroyPixelThreadSet(pixels);
1846 despeckle_image=DestroyImage(despeckle_image);
1847 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1848 }
1849 /*
1850 Reduce speckle in the image.
1851 */
1852 status=MagickTrue;
1853 image_view=AcquireCacheView(image);
1854 despeckle_view=AcquireCacheView(despeckle_image);
cristyb5d5f722009-11-04 03:03:49 +00001855#if defined(MAGICKCORE_OPENMP_SUPPORT)
1856 #pragma omp parallel for schedule(dynamic,4) shared(status)
cristy3ed852e2009-09-05 21:47:34 +00001857#endif
1858 for (channel=0; channel <= 3; channel++)
1859 {
1860 long
1861 j,
1862 y;
1863
1864 register long
1865 i,
cristy691a29e2009-09-11 00:44:10 +00001866 id,
cristy3ed852e2009-09-05 21:47:34 +00001867 x;
1868
1869 register Quantum
1870 *buffer,
1871 *pixel;
1872
1873 if (status == MagickFalse)
1874 continue;
cristy691a29e2009-09-11 00:44:10 +00001875 id=GetOpenMPThreadId();
1876 pixel=pixels[id];
cristy3ed852e2009-09-05 21:47:34 +00001877 (void) ResetMagickMemory(pixel,0,length*sizeof(*pixel));
cristy691a29e2009-09-11 00:44:10 +00001878 buffer=buffers[id];
cristy3ed852e2009-09-05 21:47:34 +00001879 j=(long) image->columns+2;
1880 for (y=0; y < (long) image->rows; y++)
1881 {
1882 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001883 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001884
1885 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1886 if (p == (const PixelPacket *) NULL)
1887 break;
1888 j++;
1889 for (x=0; x < (long) image->columns; x++)
1890 {
1891 switch (channel)
1892 {
cristyce70c172010-01-07 17:15:30 +00001893 case 0: pixel[j]=GetRedPixelComponent(p); break;
1894 case 1: pixel[j]=GetGreenPixelComponent(p); break;
1895 case 2: pixel[j]=GetBluePixelComponent(p); break;
1896 case 3: pixel[j]=GetOpacityPixelComponent(p); break;
cristy3ed852e2009-09-05 21:47:34 +00001897 default: break;
1898 }
1899 p++;
1900 j++;
1901 }
1902 j++;
1903 }
cristy3ed852e2009-09-05 21:47:34 +00001904 (void) ResetMagickMemory(buffer,0,length*sizeof(*buffer));
1905 for (i=0; i < 4; i++)
1906 {
1907 Hull(X[i],Y[i],image->columns,image->rows,pixel,buffer,1);
1908 Hull(-X[i],-Y[i],image->columns,image->rows,pixel,buffer,1);
1909 Hull(-X[i],-Y[i],image->columns,image->rows,pixel,buffer,-1);
1910 Hull(X[i],Y[i],image->columns,image->rows,pixel,buffer,-1);
1911 }
1912 j=(long) image->columns+2;
1913 for (y=0; y < (long) image->rows; y++)
1914 {
1915 MagickBooleanType
1916 sync;
1917
1918 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001919 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001920
1921 q=GetCacheViewAuthenticPixels(despeckle_view,0,y,despeckle_image->columns,
1922 1,exception);
1923 if (q == (PixelPacket *) NULL)
1924 break;
1925 j++;
1926 for (x=0; x < (long) image->columns; x++)
1927 {
1928 switch (channel)
1929 {
1930 case 0: q->red=pixel[j]; break;
1931 case 1: q->green=pixel[j]; break;
1932 case 2: q->blue=pixel[j]; break;
1933 case 3: q->opacity=pixel[j]; break;
1934 default: break;
1935 }
1936 q++;
1937 j++;
1938 }
1939 sync=SyncCacheViewAuthenticPixels(despeckle_view,exception);
1940 if (sync == MagickFalse)
1941 {
1942 status=MagickFalse;
1943 break;
1944 }
1945 j++;
1946 }
1947 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1948 {
1949 MagickBooleanType
1950 proceed;
1951
cristyb5d5f722009-11-04 03:03:49 +00001952#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001953 #pragma omp critical (MagickCore_DespeckleImage)
1954#endif
1955 proceed=SetImageProgress(image,DespeckleImageTag,channel,3);
1956 if (proceed == MagickFalse)
1957 status=MagickFalse;
1958 }
1959 }
1960 despeckle_view=DestroyCacheView(despeckle_view);
1961 image_view=DestroyCacheView(image_view);
1962 buffers=DestroyPixelThreadSet(buffers);
1963 pixels=DestroyPixelThreadSet(pixels);
1964 despeckle_image->type=image->type;
1965 if (status == MagickFalse)
1966 despeckle_image=DestroyImage(despeckle_image);
1967 return(despeckle_image);
1968}
1969
1970/*
1971%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1972% %
1973% %
1974% %
1975% E d g e I m a g e %
1976% %
1977% %
1978% %
1979%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1980%
1981% EdgeImage() finds edges in an image. Radius defines the radius of the
1982% convolution filter. Use a radius of 0 and EdgeImage() selects a suitable
1983% radius for you.
1984%
1985% The format of the EdgeImage method is:
1986%
1987% Image *EdgeImage(const Image *image,const double radius,
1988% ExceptionInfo *exception)
1989%
1990% A description of each parameter follows:
1991%
1992% o image: the image.
1993%
1994% o radius: the radius of the pixel neighborhood.
1995%
1996% o exception: return any errors or warnings in this structure.
1997%
1998*/
1999MagickExport Image *EdgeImage(const Image *image,const double radius,
2000 ExceptionInfo *exception)
2001{
2002 Image
2003 *edge_image;
2004
2005 double
2006 *kernel;
2007
2008 register long
2009 i;
2010
2011 unsigned long
2012 width;
2013
2014 assert(image != (const Image *) NULL);
2015 assert(image->signature == MagickSignature);
2016 if (image->debug != MagickFalse)
2017 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2018 assert(exception != (ExceptionInfo *) NULL);
2019 assert(exception->signature == MagickSignature);
2020 width=GetOptimalKernelWidth1D(radius,0.5);
2021 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2022 if (kernel == (double *) NULL)
2023 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2024 for (i=0; i < (long) (width*width); i++)
2025 kernel[i]=(-1.0);
2026 kernel[i/2]=(double) (width*width-1.0);
2027 edge_image=ConvolveImage(image,width,kernel,exception);
2028 kernel=(double *) RelinquishMagickMemory(kernel);
2029 return(edge_image);
2030}
2031
2032/*
2033%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2034% %
2035% %
2036% %
2037% E m b o s s I m a g e %
2038% %
2039% %
2040% %
2041%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2042%
2043% EmbossImage() returns a grayscale image with a three-dimensional effect.
2044% We convolve the image with a Gaussian operator of the given radius and
2045% standard deviation (sigma). For reasonable results, radius should be
2046% larger than sigma. Use a radius of 0 and Emboss() selects a suitable
2047% radius for you.
2048%
2049% The format of the EmbossImage method is:
2050%
2051% Image *EmbossImage(const Image *image,const double radius,
2052% const double sigma,ExceptionInfo *exception)
2053%
2054% A description of each parameter follows:
2055%
2056% o image: the image.
2057%
2058% o radius: the radius of the pixel neighborhood.
2059%
2060% o sigma: the standard deviation of the Gaussian, in pixels.
2061%
2062% o exception: return any errors or warnings in this structure.
2063%
2064*/
2065MagickExport Image *EmbossImage(const Image *image,const double radius,
2066 const double sigma,ExceptionInfo *exception)
2067{
2068 double
2069 *kernel;
2070
2071 Image
2072 *emboss_image;
2073
2074 long
cristy47e00502009-12-17 19:19:57 +00002075 j,
2076 k,
cristy3ed852e2009-09-05 21:47:34 +00002077 u,
2078 v;
2079
cristy47e00502009-12-17 19:19:57 +00002080 register long
2081 i;
2082
cristy3ed852e2009-09-05 21:47:34 +00002083 unsigned long
2084 width;
2085
2086 assert(image != (Image *) NULL);
2087 assert(image->signature == MagickSignature);
2088 if (image->debug != MagickFalse)
2089 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2090 assert(exception != (ExceptionInfo *) NULL);
2091 assert(exception->signature == MagickSignature);
2092 width=GetOptimalKernelWidth2D(radius,sigma);
2093 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2094 if (kernel == (double *) NULL)
2095 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy3ed852e2009-09-05 21:47:34 +00002096 j=(long) width/2;
cristy47e00502009-12-17 19:19:57 +00002097 k=j;
2098 i=0;
2099 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002100 {
cristy47e00502009-12-17 19:19:57 +00002101 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00002102 {
cristy47e00502009-12-17 19:19:57 +00002103 kernel[i]=((u < 0) || (v < 0) ? -8.0 : 8.0)*
2104 exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
2105 (2.0*MagickPI*MagickSigma*MagickSigma);
2106 if (u != k)
cristy3ed852e2009-09-05 21:47:34 +00002107 kernel[i]=0.0;
2108 i++;
2109 }
cristy47e00502009-12-17 19:19:57 +00002110 k--;
cristy3ed852e2009-09-05 21:47:34 +00002111 }
2112 emboss_image=ConvolveImage(image,width,kernel,exception);
2113 if (emboss_image != (Image *) NULL)
2114 (void) EqualizeImage(emboss_image);
2115 kernel=(double *) RelinquishMagickMemory(kernel);
2116 return(emboss_image);
2117}
2118
2119/*
2120%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2121% %
2122% %
2123% %
cristy56a9e512010-01-06 18:18:55 +00002124% F i l t e r I m a g e %
2125% %
2126% %
2127% %
2128%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2129%
2130% FilterImage() applies a custom convolution kernel to the image.
2131%
2132% The format of the FilterImage method is:
2133%
cristy2be15382010-01-21 02:38:03 +00002134% Image *FilterImage(const Image *image,const KernelInfo *kernel,
cristy56a9e512010-01-06 18:18:55 +00002135% ExceptionInfo *exception)
2136% Image *FilterImageChannel(const Image *image,const ChannelType channel,
cristy2be15382010-01-21 02:38:03 +00002137% const KernelInfo *kernel,ExceptionInfo *exception)
cristy56a9e512010-01-06 18:18:55 +00002138%
2139% A description of each parameter follows:
2140%
2141% o image: the image.
2142%
2143% o channel: the channel type.
2144%
2145% o kernel: the filtering kernel.
2146%
2147% o exception: return any errors or warnings in this structure.
2148%
2149*/
2150
cristy2be15382010-01-21 02:38:03 +00002151MagickExport Image *FilterImage(const Image *image,const KernelInfo *kernel,
cristy56a9e512010-01-06 18:18:55 +00002152 ExceptionInfo *exception)
2153{
2154 Image
2155 *filter_image;
2156
2157 filter_image=FilterImageChannel(image,DefaultChannels,kernel,exception);
2158 return(filter_image);
2159}
2160
2161MagickExport Image *FilterImageChannel(const Image *image,
cristy2be15382010-01-21 02:38:03 +00002162 const ChannelType channel,const KernelInfo *kernel,ExceptionInfo *exception)
cristy56a9e512010-01-06 18:18:55 +00002163{
2164#define FilterImageTag "Filter/Image"
2165
2166 CacheView
2167 *filter_view,
2168 *image_view;
2169
cristy56a9e512010-01-06 18:18:55 +00002170 Image
2171 *filter_image;
2172
2173 long
2174 progress,
2175 y;
2176
2177 MagickBooleanType
2178 status;
2179
2180 MagickPixelPacket
2181 bias;
2182
cristy56a9e512010-01-06 18:18:55 +00002183 /*
2184 Initialize filter image attributes.
2185 */
2186 assert(image != (Image *) NULL);
2187 assert(image->signature == MagickSignature);
2188 if (image->debug != MagickFalse)
2189 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2190 assert(exception != (ExceptionInfo *) NULL);
2191 assert(exception->signature == MagickSignature);
2192 if ((kernel->width % 2) == 0)
2193 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
2194 filter_image=CloneImage(image,0,0,MagickTrue,exception);
2195 if (filter_image == (Image *) NULL)
2196 return((Image *) NULL);
2197 if (SetImageStorageClass(filter_image,DirectClass) == MagickFalse)
2198 {
2199 InheritException(exception,&filter_image->exception);
2200 filter_image=DestroyImage(filter_image);
2201 return((Image *) NULL);
2202 }
2203 if (image->debug != MagickFalse)
2204 {
2205 char
2206 format[MaxTextExtent],
2207 *message;
2208
2209 long
2210 u,
2211 v;
2212
2213 register const double
2214 *k;
2215
2216 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
2217 " FilterImage with %ldx%ld kernel:",kernel->width,kernel->height);
2218 message=AcquireString("");
2219 k=kernel->values;
2220 for (v=0; v < (long) kernel->height; v++)
2221 {
2222 *message='\0';
2223 (void) FormatMagickString(format,MaxTextExtent,"%ld: ",v);
2224 (void) ConcatenateString(&message,format);
2225 for (u=0; u < (long) kernel->width; u++)
2226 {
cristye7f51092010-01-17 00:39:37 +00002227 (void) FormatMagickString(format,MaxTextExtent,"%g ",*k++);
cristy56a9e512010-01-06 18:18:55 +00002228 (void) ConcatenateString(&message,format);
2229 }
2230 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
2231 }
2232 message=DestroyString(message);
2233 }
cristy36826ab2010-03-06 01:29:30 +00002234 status=AccelerateConvolveImage(image,kernel,filter_image,exception);
cristyd43a46b2010-01-21 02:13:41 +00002235 if (status == MagickTrue)
2236 return(filter_image);
cristy56a9e512010-01-06 18:18:55 +00002237 /*
2238 Filter image.
2239 */
2240 status=MagickTrue;
2241 progress=0;
2242 GetMagickPixelPacket(image,&bias);
2243 SetMagickPixelPacketBias(image,&bias);
2244 image_view=AcquireCacheView(image);
2245 filter_view=AcquireCacheView(filter_image);
2246#if defined(MAGICKCORE_OPENMP_SUPPORT)
2247 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2248#endif
2249 for (y=0; y < (long) image->rows; y++)
2250 {
2251 MagickBooleanType
2252 sync;
2253
2254 register const IndexPacket
2255 *restrict indexes;
2256
2257 register const PixelPacket
2258 *restrict p;
2259
2260 register IndexPacket
2261 *restrict filter_indexes;
2262
2263 register long
2264 x;
2265
2266 register PixelPacket
2267 *restrict q;
2268
2269 if (status == MagickFalse)
2270 continue;
cristy36826ab2010-03-06 01:29:30 +00002271 p=GetCacheViewVirtualPixels(image_view,-((long) kernel->width/2L),
2272 y-(long) (kernel->height/2L),image->columns+kernel->width,kernel->height,
2273 exception);
cristy56a9e512010-01-06 18:18:55 +00002274 q=GetCacheViewAuthenticPixels(filter_view,0,y,filter_image->columns,1,
2275 exception);
2276 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2277 {
2278 status=MagickFalse;
2279 continue;
2280 }
2281 indexes=GetCacheViewVirtualIndexQueue(image_view);
2282 filter_indexes=GetCacheViewAuthenticIndexQueue(filter_view);
2283 for (x=0; x < (long) image->columns; x++)
2284 {
2285 long
2286 v;
2287
2288 MagickPixelPacket
2289 pixel;
2290
2291 register const double
2292 *restrict k;
2293
2294 register const PixelPacket
2295 *restrict kernel_pixels;
2296
2297 register long
2298 u;
2299
2300 pixel=bias;
cristy36826ab2010-03-06 01:29:30 +00002301 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002302 kernel_pixels=p;
2303 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
2304 {
cristy36826ab2010-03-06 01:29:30 +00002305 for (v=0; v < (long) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002306 {
cristy36826ab2010-03-06 01:29:30 +00002307 for (u=0; u < (long) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002308 {
2309 pixel.red+=(*k)*kernel_pixels[u].red;
2310 pixel.green+=(*k)*kernel_pixels[u].green;
2311 pixel.blue+=(*k)*kernel_pixels[u].blue;
2312 k++;
2313 }
cristy36826ab2010-03-06 01:29:30 +00002314 kernel_pixels+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002315 }
2316 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002317 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002318 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002319 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002320 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002321 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002322 if ((channel & OpacityChannel) != 0)
2323 {
cristy36826ab2010-03-06 01:29:30 +00002324 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002325 kernel_pixels=p;
cristy36826ab2010-03-06 01:29:30 +00002326 for (v=0; v < (long) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002327 {
cristy36826ab2010-03-06 01:29:30 +00002328 for (u=0; u < (long) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002329 {
2330 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
2331 k++;
2332 }
cristy36826ab2010-03-06 01:29:30 +00002333 kernel_pixels+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002334 }
cristyce70c172010-01-07 17:15:30 +00002335 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002336 }
2337 if (((channel & IndexChannel) != 0) &&
2338 (image->colorspace == CMYKColorspace))
2339 {
2340 register const IndexPacket
2341 *restrict kernel_indexes;
2342
cristy36826ab2010-03-06 01:29:30 +00002343 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002344 kernel_indexes=indexes;
cristy36826ab2010-03-06 01:29:30 +00002345 for (v=0; v < (long) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002346 {
cristy36826ab2010-03-06 01:29:30 +00002347 for (u=0; u < (long) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002348 {
2349 pixel.index+=(*k)*kernel_indexes[u];
2350 k++;
2351 }
cristy36826ab2010-03-06 01:29:30 +00002352 kernel_indexes+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002353 }
cristyce70c172010-01-07 17:15:30 +00002354 filter_indexes[x]=ClampToQuantum(pixel.index);
cristy56a9e512010-01-06 18:18:55 +00002355 }
2356 }
2357 else
2358 {
2359 MagickRealType
2360 alpha,
2361 gamma;
2362
2363 gamma=0.0;
cristy36826ab2010-03-06 01:29:30 +00002364 for (v=0; v < (long) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002365 {
cristy36826ab2010-03-06 01:29:30 +00002366 for (u=0; u < (long) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002367 {
2368 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
2369 kernel_pixels[u].opacity));
2370 pixel.red+=(*k)*alpha*kernel_pixels[u].red;
2371 pixel.green+=(*k)*alpha*kernel_pixels[u].green;
2372 pixel.blue+=(*k)*alpha*kernel_pixels[u].blue;
2373 gamma+=(*k)*alpha;
2374 k++;
2375 }
cristy36826ab2010-03-06 01:29:30 +00002376 kernel_pixels+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002377 }
2378 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
2379 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002380 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002381 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002382 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002383 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002384 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002385 if ((channel & OpacityChannel) != 0)
2386 {
cristy36826ab2010-03-06 01:29:30 +00002387 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002388 kernel_pixels=p;
cristy36826ab2010-03-06 01:29:30 +00002389 for (v=0; v < (long) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002390 {
cristy36826ab2010-03-06 01:29:30 +00002391 for (u=0; u < (long) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002392 {
2393 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
2394 k++;
2395 }
cristy36826ab2010-03-06 01:29:30 +00002396 kernel_pixels+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002397 }
cristyce70c172010-01-07 17:15:30 +00002398 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002399 }
2400 if (((channel & IndexChannel) != 0) &&
2401 (image->colorspace == CMYKColorspace))
2402 {
2403 register const IndexPacket
2404 *restrict kernel_indexes;
2405
cristy36826ab2010-03-06 01:29:30 +00002406 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002407 kernel_pixels=p;
2408 kernel_indexes=indexes;
cristy36826ab2010-03-06 01:29:30 +00002409 for (v=0; v < (long) kernel->width; v++)
cristy56a9e512010-01-06 18:18:55 +00002410 {
cristy36826ab2010-03-06 01:29:30 +00002411 for (u=0; u < (long) kernel->height; u++)
cristy56a9e512010-01-06 18:18:55 +00002412 {
2413 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
2414 kernel_pixels[u].opacity));
2415 pixel.index+=(*k)*alpha*kernel_indexes[u];
2416 k++;
2417 }
cristy36826ab2010-03-06 01:29:30 +00002418 kernel_pixels+=image->columns+kernel->width;
2419 kernel_indexes+=image->columns+kernel->width;
cristy56a9e512010-01-06 18:18:55 +00002420 }
cristy2115aea2010-01-09 23:16:08 +00002421 filter_indexes[x]=ClampToQuantum(gamma*
2422 GetIndexPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002423 }
2424 }
2425 p++;
2426 q++;
2427 }
2428 sync=SyncCacheViewAuthenticPixels(filter_view,exception);
2429 if (sync == MagickFalse)
2430 status=MagickFalse;
2431 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2432 {
2433 MagickBooleanType
2434 proceed;
2435
2436#if defined(MAGICKCORE_OPENMP_SUPPORT)
2437 #pragma omp critical (MagickCore_FilterImageChannel)
2438#endif
2439 proceed=SetImageProgress(image,FilterImageTag,progress++,image->rows);
2440 if (proceed == MagickFalse)
2441 status=MagickFalse;
2442 }
2443 }
2444 filter_image->type=image->type;
2445 filter_view=DestroyCacheView(filter_view);
2446 image_view=DestroyCacheView(image_view);
cristy56a9e512010-01-06 18:18:55 +00002447 if (status == MagickFalse)
2448 filter_image=DestroyImage(filter_image);
2449 return(filter_image);
2450}
2451
2452/*
2453%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2454% %
2455% %
2456% %
cristy3ed852e2009-09-05 21:47:34 +00002457% G a u s s i a n B l u r I m a g e %
2458% %
2459% %
2460% %
2461%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2462%
2463% GaussianBlurImage() blurs an image. We convolve the image with a
2464% Gaussian operator of the given radius and standard deviation (sigma).
2465% For reasonable results, the radius should be larger than sigma. Use a
2466% radius of 0 and GaussianBlurImage() selects a suitable radius for you
2467%
2468% The format of the GaussianBlurImage method is:
2469%
2470% Image *GaussianBlurImage(const Image *image,onst double radius,
2471% const double sigma,ExceptionInfo *exception)
2472% Image *GaussianBlurImageChannel(const Image *image,
2473% const ChannelType channel,const double radius,const double sigma,
2474% ExceptionInfo *exception)
2475%
2476% A description of each parameter follows:
2477%
2478% o image: the image.
2479%
2480% o channel: the channel type.
2481%
2482% o radius: the radius of the Gaussian, in pixels, not counting the center
2483% pixel.
2484%
2485% o sigma: the standard deviation of the Gaussian, in pixels.
2486%
2487% o exception: return any errors or warnings in this structure.
2488%
2489*/
2490
2491MagickExport Image *GaussianBlurImage(const Image *image,const double radius,
2492 const double sigma,ExceptionInfo *exception)
2493{
2494 Image
2495 *blur_image;
2496
2497 blur_image=GaussianBlurImageChannel(image,DefaultChannels,radius,sigma,
2498 exception);
2499 return(blur_image);
2500}
2501
2502MagickExport Image *GaussianBlurImageChannel(const Image *image,
2503 const ChannelType channel,const double radius,const double sigma,
2504 ExceptionInfo *exception)
2505{
2506 double
2507 *kernel;
2508
2509 Image
2510 *blur_image;
2511
cristy47e00502009-12-17 19:19:57 +00002512 long
2513 j,
cristy3ed852e2009-09-05 21:47:34 +00002514 u,
2515 v;
2516
cristy47e00502009-12-17 19:19:57 +00002517 register long
2518 i;
2519
cristy3ed852e2009-09-05 21:47:34 +00002520 unsigned long
2521 width;
2522
2523 assert(image != (const Image *) NULL);
2524 assert(image->signature == MagickSignature);
2525 if (image->debug != MagickFalse)
2526 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2527 assert(exception != (ExceptionInfo *) NULL);
2528 assert(exception->signature == MagickSignature);
2529 width=GetOptimalKernelWidth2D(radius,sigma);
2530 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2531 if (kernel == (double *) NULL)
2532 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy47e00502009-12-17 19:19:57 +00002533 j=(long) width/2;
cristy3ed852e2009-09-05 21:47:34 +00002534 i=0;
cristy47e00502009-12-17 19:19:57 +00002535 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002536 {
cristy47e00502009-12-17 19:19:57 +00002537 for (u=(-j); u <= j; u++)
2538 kernel[i++]=exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
2539 (2.0*MagickPI*MagickSigma*MagickSigma);
cristy3ed852e2009-09-05 21:47:34 +00002540 }
2541 blur_image=ConvolveImageChannel(image,channel,width,kernel,exception);
2542 kernel=(double *) RelinquishMagickMemory(kernel);
2543 return(blur_image);
2544}
2545
2546/*
2547%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2548% %
2549% %
2550% %
2551% M e d i a n F i l t e r I m a g e %
2552% %
2553% %
2554% %
2555%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2556%
2557% MedianFilterImage() applies a digital filter that improves the quality
2558% of a noisy image. Each pixel is replaced by the median in a set of
2559% neighboring pixels as defined by radius.
2560%
2561% The algorithm was contributed by Mike Edmonds and implements an insertion
2562% sort for selecting median color-channel values. For more on this algorithm
2563% see "Skip Lists: A probabilistic Alternative to Balanced Trees" by William
2564% Pugh in the June 1990 of Communications of the ACM.
2565%
2566% The format of the MedianFilterImage method is:
2567%
2568% Image *MedianFilterImage(const Image *image,const double radius,
2569% ExceptionInfo *exception)
2570%
2571% A description of each parameter follows:
2572%
2573% o image: the image.
2574%
2575% o radius: the radius of the pixel neighborhood.
2576%
2577% o exception: return any errors or warnings in this structure.
2578%
2579*/
2580
2581#define MedianListChannels 5
2582
2583typedef struct _MedianListNode
2584{
2585 unsigned long
2586 next[9],
2587 count,
2588 signature;
2589} MedianListNode;
2590
2591typedef struct _MedianSkipList
2592{
2593 long
2594 level;
2595
2596 MedianListNode
2597 *nodes;
2598} MedianSkipList;
2599
2600typedef struct _MedianPixelList
2601{
2602 unsigned long
2603 center,
2604 seed,
2605 signature;
2606
2607 MedianSkipList
2608 lists[MedianListChannels];
2609} MedianPixelList;
2610
2611static MedianPixelList *DestroyMedianPixelList(MedianPixelList *pixel_list)
2612{
2613 register long
2614 i;
2615
2616 if (pixel_list == (MedianPixelList *) NULL)
2617 return((MedianPixelList *) NULL);
2618 for (i=0; i < MedianListChannels; i++)
2619 if (pixel_list->lists[i].nodes != (MedianListNode *) NULL)
2620 pixel_list->lists[i].nodes=(MedianListNode *) RelinquishMagickMemory(
2621 pixel_list->lists[i].nodes);
2622 pixel_list=(MedianPixelList *) RelinquishAlignedMemory(pixel_list);
2623 return(pixel_list);
2624}
2625
2626static MedianPixelList **DestroyMedianPixelListThreadSet(
2627 MedianPixelList **pixel_list)
2628{
2629 register long
2630 i;
2631
2632 assert(pixel_list != (MedianPixelList **) NULL);
2633 for (i=0; i < (long) GetOpenMPMaximumThreads(); i++)
2634 if (pixel_list[i] != (MedianPixelList *) NULL)
2635 pixel_list[i]=DestroyMedianPixelList(pixel_list[i]);
2636 pixel_list=(MedianPixelList **) RelinquishAlignedMemory(pixel_list);
2637 return(pixel_list);
2638}
2639
2640static MedianPixelList *AcquireMedianPixelList(const unsigned long width)
2641{
2642 MedianPixelList
2643 *pixel_list;
2644
2645 register long
2646 i;
2647
2648 pixel_list=(MedianPixelList *) AcquireAlignedMemory(1,sizeof(*pixel_list));
2649 if (pixel_list == (MedianPixelList *) NULL)
2650 return(pixel_list);
2651 (void) ResetMagickMemory((void *) pixel_list,0,sizeof(*pixel_list));
2652 pixel_list->center=width*width/2;
2653 for (i=0; i < MedianListChannels; i++)
2654 {
2655 pixel_list->lists[i].nodes=(MedianListNode *) AcquireQuantumMemory(65537UL,
2656 sizeof(*pixel_list->lists[i].nodes));
2657 if (pixel_list->lists[i].nodes == (MedianListNode *) NULL)
2658 return(DestroyMedianPixelList(pixel_list));
2659 (void) ResetMagickMemory(pixel_list->lists[i].nodes,0,65537UL*
2660 sizeof(*pixel_list->lists[i].nodes));
2661 }
2662 pixel_list->signature=MagickSignature;
2663 return(pixel_list);
2664}
2665
2666static MedianPixelList **AcquireMedianPixelListThreadSet(
2667 const unsigned long width)
2668{
2669 register long
2670 i;
2671
2672 MedianPixelList
2673 **pixel_list;
2674
2675 unsigned long
2676 number_threads;
2677
2678 number_threads=GetOpenMPMaximumThreads();
2679 pixel_list=(MedianPixelList **) AcquireAlignedMemory(number_threads,
2680 sizeof(*pixel_list));
2681 if (pixel_list == (MedianPixelList **) NULL)
2682 return((MedianPixelList **) NULL);
2683 (void) ResetMagickMemory(pixel_list,0,number_threads*sizeof(*pixel_list));
2684 for (i=0; i < (long) number_threads; i++)
2685 {
2686 pixel_list[i]=AcquireMedianPixelList(width);
2687 if (pixel_list[i] == (MedianPixelList *) NULL)
2688 return(DestroyMedianPixelListThreadSet(pixel_list));
2689 }
2690 return(pixel_list);
2691}
2692
2693static void AddNodeMedianPixelList(MedianPixelList *pixel_list,
2694 const long channel,const unsigned long color)
2695{
2696 register long
2697 level;
2698
2699 register MedianSkipList
2700 *list;
2701
2702 unsigned long
2703 search,
2704 update[9];
2705
2706 /*
2707 Initialize the node.
2708 */
2709 list=pixel_list->lists+channel;
2710 list->nodes[color].signature=pixel_list->signature;
2711 list->nodes[color].count=1;
2712 /*
2713 Determine where it belongs in the list.
2714 */
2715 search=65536UL;
2716 for (level=list->level; level >= 0; level--)
2717 {
2718 while (list->nodes[search].next[level] < color)
2719 search=list->nodes[search].next[level];
2720 update[level]=search;
2721 }
2722 /*
2723 Generate a pseudo-random level for this node.
2724 */
2725 for (level=0; ; level++)
2726 {
2727 pixel_list->seed=(pixel_list->seed*42893621L)+1L;
2728 if ((pixel_list->seed & 0x300) != 0x300)
2729 break;
2730 }
2731 if (level > 8)
2732 level=8;
2733 if (level > (list->level+2))
2734 level=list->level+2;
2735 /*
2736 If we're raising the list's level, link back to the root node.
2737 */
2738 while (level > list->level)
2739 {
2740 list->level++;
2741 update[list->level]=65536UL;
2742 }
2743 /*
2744 Link the node into the skip-list.
2745 */
2746 do
2747 {
2748 list->nodes[color].next[level]=list->nodes[update[level]].next[level];
2749 list->nodes[update[level]].next[level]=color;
2750 }
2751 while (level-- > 0);
2752}
2753
2754static MagickPixelPacket GetMedianPixelList(MedianPixelList *pixel_list)
2755{
2756 MagickPixelPacket
2757 pixel;
2758
2759 register long
2760 channel;
2761
2762 register MedianSkipList
2763 *list;
2764
2765 unsigned long
2766 center,
2767 color,
2768 count;
2769
2770 unsigned short
2771 channels[MedianListChannels];
2772
2773 /*
2774 Find the median value for each of the color.
2775 */
2776 center=pixel_list->center;
2777 for (channel=0; channel < 5; channel++)
2778 {
2779 list=pixel_list->lists+channel;
2780 color=65536UL;
2781 count=0;
2782 do
2783 {
2784 color=list->nodes[color].next[0];
2785 count+=list->nodes[color].count;
2786 }
2787 while (count <= center);
2788 channels[channel]=(unsigned short) color;
2789 }
2790 GetMagickPixelPacket((const Image *) NULL,&pixel);
2791 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
2792 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
2793 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
2794 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
2795 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
2796 return(pixel);
2797}
2798
2799static inline void InsertMedianPixelList(const Image *image,
2800 const PixelPacket *pixel,const IndexPacket *indexes,
2801 MedianPixelList *pixel_list)
2802{
2803 unsigned long
2804 signature;
2805
2806 unsigned short
2807 index;
2808
2809 index=ScaleQuantumToShort(pixel->red);
2810 signature=pixel_list->lists[0].nodes[index].signature;
2811 if (signature == pixel_list->signature)
2812 pixel_list->lists[0].nodes[index].count++;
2813 else
2814 AddNodeMedianPixelList(pixel_list,0,index);
2815 index=ScaleQuantumToShort(pixel->green);
2816 signature=pixel_list->lists[1].nodes[index].signature;
2817 if (signature == pixel_list->signature)
2818 pixel_list->lists[1].nodes[index].count++;
2819 else
2820 AddNodeMedianPixelList(pixel_list,1,index);
2821 index=ScaleQuantumToShort(pixel->blue);
2822 signature=pixel_list->lists[2].nodes[index].signature;
2823 if (signature == pixel_list->signature)
2824 pixel_list->lists[2].nodes[index].count++;
2825 else
2826 AddNodeMedianPixelList(pixel_list,2,index);
2827 index=ScaleQuantumToShort(pixel->opacity);
2828 signature=pixel_list->lists[3].nodes[index].signature;
2829 if (signature == pixel_list->signature)
2830 pixel_list->lists[3].nodes[index].count++;
2831 else
2832 AddNodeMedianPixelList(pixel_list,3,index);
2833 if (image->colorspace == CMYKColorspace)
2834 index=ScaleQuantumToShort(*indexes);
2835 signature=pixel_list->lists[4].nodes[index].signature;
2836 if (signature == pixel_list->signature)
2837 pixel_list->lists[4].nodes[index].count++;
2838 else
2839 AddNodeMedianPixelList(pixel_list,4,index);
2840}
2841
2842static void ResetMedianPixelList(MedianPixelList *pixel_list)
2843{
2844 int
2845 level;
2846
2847 register long
2848 channel;
2849
2850 register MedianListNode
2851 *root;
2852
2853 register MedianSkipList
2854 *list;
2855
2856 /*
2857 Reset the skip-list.
2858 */
2859 for (channel=0; channel < 5; channel++)
2860 {
2861 list=pixel_list->lists+channel;
2862 root=list->nodes+65536UL;
2863 list->level=0;
2864 for (level=0; level < 9; level++)
2865 root->next[level]=65536UL;
2866 }
2867 pixel_list->seed=pixel_list->signature++;
2868}
2869
2870MagickExport Image *MedianFilterImage(const Image *image,const double radius,
2871 ExceptionInfo *exception)
2872{
2873#define MedianFilterImageTag "MedianFilter/Image"
2874
cristyc4c8d132010-01-07 01:58:38 +00002875 CacheView
2876 *image_view,
2877 *median_view;
2878
cristy3ed852e2009-09-05 21:47:34 +00002879 Image
2880 *median_image;
2881
2882 long
2883 progress,
2884 y;
2885
2886 MagickBooleanType
2887 status;
2888
2889 MedianPixelList
cristyfa112112010-01-04 17:48:07 +00002890 **restrict pixel_list;
cristy3ed852e2009-09-05 21:47:34 +00002891
2892 unsigned long
2893 width;
2894
cristy3ed852e2009-09-05 21:47:34 +00002895 /*
2896 Initialize median image attributes.
2897 */
2898 assert(image != (Image *) NULL);
2899 assert(image->signature == MagickSignature);
2900 if (image->debug != MagickFalse)
2901 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2902 assert(exception != (ExceptionInfo *) NULL);
2903 assert(exception->signature == MagickSignature);
2904 width=GetOptimalKernelWidth2D(radius,0.5);
2905 if ((image->columns < width) || (image->rows < width))
2906 ThrowImageException(OptionError,"ImageSmallerThanKernelRadius");
2907 median_image=CloneImage(image,image->columns,image->rows,MagickTrue,
2908 exception);
2909 if (median_image == (Image *) NULL)
2910 return((Image *) NULL);
2911 if (SetImageStorageClass(median_image,DirectClass) == MagickFalse)
2912 {
2913 InheritException(exception,&median_image->exception);
2914 median_image=DestroyImage(median_image);
2915 return((Image *) NULL);
2916 }
2917 pixel_list=AcquireMedianPixelListThreadSet(width);
2918 if (pixel_list == (MedianPixelList **) NULL)
2919 {
2920 median_image=DestroyImage(median_image);
2921 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2922 }
2923 /*
2924 Median filter each image row.
2925 */
2926 status=MagickTrue;
2927 progress=0;
2928 image_view=AcquireCacheView(image);
2929 median_view=AcquireCacheView(median_image);
cristyb5d5f722009-11-04 03:03:49 +00002930#if defined(MAGICKCORE_OPENMP_SUPPORT)
2931 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002932#endif
2933 for (y=0; y < (long) median_image->rows; y++)
2934 {
2935 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002936 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002937
2938 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002939 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002940
2941 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002942 *restrict median_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002943
2944 register long
2945 id,
2946 x;
2947
2948 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002949 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002950
2951 if (status == MagickFalse)
2952 continue;
2953 p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y-(long) (width/
2954 2L),image->columns+width,width,exception);
2955 q=QueueCacheViewAuthenticPixels(median_view,0,y,median_image->columns,1,
2956 exception);
2957 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2958 {
2959 status=MagickFalse;
2960 continue;
2961 }
2962 indexes=GetCacheViewVirtualIndexQueue(image_view);
2963 median_indexes=GetCacheViewAuthenticIndexQueue(median_view);
2964 id=GetOpenMPThreadId();
2965 for (x=0; x < (long) median_image->columns; x++)
2966 {
2967 MagickPixelPacket
2968 pixel;
2969
2970 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002971 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +00002972
2973 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002974 *restrict s;
cristy3ed852e2009-09-05 21:47:34 +00002975
2976 register long
2977 u,
2978 v;
2979
2980 r=p;
2981 s=indexes+x;
2982 ResetMedianPixelList(pixel_list[id]);
2983 for (v=0; v < (long) width; v++)
2984 {
2985 for (u=0; u < (long) width; u++)
2986 InsertMedianPixelList(image,r+u,s+u,pixel_list[id]);
2987 r+=image->columns+width;
2988 s+=image->columns+width;
2989 }
2990 pixel=GetMedianPixelList(pixel_list[id]);
2991 SetPixelPacket(median_image,&pixel,q,median_indexes+x);
2992 p++;
2993 q++;
2994 }
2995 if (SyncCacheViewAuthenticPixels(median_view,exception) == MagickFalse)
2996 status=MagickFalse;
2997 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2998 {
2999 MagickBooleanType
3000 proceed;
3001
cristyb5d5f722009-11-04 03:03:49 +00003002#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003003 #pragma omp critical (MagickCore_MedianFilterImage)
3004#endif
3005 proceed=SetImageProgress(image,MedianFilterImageTag,progress++,
3006 image->rows);
3007 if (proceed == MagickFalse)
3008 status=MagickFalse;
3009 }
3010 }
3011 median_view=DestroyCacheView(median_view);
3012 image_view=DestroyCacheView(image_view);
3013 pixel_list=DestroyMedianPixelListThreadSet(pixel_list);
3014 return(median_image);
3015}
3016
3017/*
3018%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3019% %
3020% %
3021% %
3022% M o t i o n B l u r I m a g e %
3023% %
3024% %
3025% %
3026%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3027%
3028% MotionBlurImage() simulates motion blur. We convolve the image with a
3029% Gaussian operator of the given radius and standard deviation (sigma).
3030% For reasonable results, radius should be larger than sigma. Use a
3031% radius of 0 and MotionBlurImage() selects a suitable radius for you.
3032% Angle gives the angle of the blurring motion.
3033%
3034% Andrew Protano contributed this effect.
3035%
3036% The format of the MotionBlurImage method is:
3037%
3038% Image *MotionBlurImage(const Image *image,const double radius,
3039% const double sigma,const double angle,ExceptionInfo *exception)
3040% Image *MotionBlurImageChannel(const Image *image,const ChannelType channel,
3041% const double radius,const double sigma,const double angle,
3042% ExceptionInfo *exception)
3043%
3044% A description of each parameter follows:
3045%
3046% o image: the image.
3047%
3048% o channel: the channel type.
3049%
3050% o radius: the radius of the Gaussian, in pixels, not counting the center
3051% o radius: the radius of the Gaussian, in pixels, not counting
3052% the center pixel.
3053%
3054% o sigma: the standard deviation of the Gaussian, in pixels.
3055%
3056% o angle: Apply the effect along this angle.
3057%
3058% o exception: return any errors or warnings in this structure.
3059%
3060*/
3061
cristy47e00502009-12-17 19:19:57 +00003062static double *GetMotionBlurKernel(const unsigned long width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +00003063{
cristy3ed852e2009-09-05 21:47:34 +00003064 double
cristy47e00502009-12-17 19:19:57 +00003065 *kernel,
cristy3ed852e2009-09-05 21:47:34 +00003066 normalize;
3067
3068 register long
3069 i;
3070
3071 /*
cristy47e00502009-12-17 19:19:57 +00003072 Generate a 1-D convolution kernel.
cristy3ed852e2009-09-05 21:47:34 +00003073 */
3074 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
3075 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
3076 if (kernel == (double *) NULL)
3077 return(kernel);
cristy3ed852e2009-09-05 21:47:34 +00003078 normalize=0.0;
3079 for (i=0; i < (long) width; i++)
cristy47e00502009-12-17 19:19:57 +00003080 {
3081 kernel[i]=exp((-((double) i*i)/(double) (2.0*MagickSigma*MagickSigma)))/
3082 (MagickSQ2PI*MagickSigma);
cristy3ed852e2009-09-05 21:47:34 +00003083 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +00003084 }
cristy3ed852e2009-09-05 21:47:34 +00003085 for (i=0; i < (long) width; i++)
3086 kernel[i]/=normalize;
3087 return(kernel);
3088}
3089
3090MagickExport Image *MotionBlurImage(const Image *image,const double radius,
3091 const double sigma,const double angle,ExceptionInfo *exception)
3092{
3093 Image
3094 *motion_blur;
3095
3096 motion_blur=MotionBlurImageChannel(image,DefaultChannels,radius,sigma,angle,
3097 exception);
3098 return(motion_blur);
3099}
3100
3101MagickExport Image *MotionBlurImageChannel(const Image *image,
3102 const ChannelType channel,const double radius,const double sigma,
3103 const double angle,ExceptionInfo *exception)
3104{
cristyc4c8d132010-01-07 01:58:38 +00003105 CacheView
3106 *blur_view,
3107 *image_view;
3108
cristy3ed852e2009-09-05 21:47:34 +00003109 double
3110 *kernel;
3111
3112 Image
3113 *blur_image;
3114
3115 long
3116 progress,
3117 y;
3118
3119 MagickBooleanType
3120 status;
3121
3122 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00003123 bias;
cristy3ed852e2009-09-05 21:47:34 +00003124
3125 OffsetInfo
3126 *offset;
3127
3128 PointInfo
3129 point;
3130
3131 register long
3132 i;
3133
3134 unsigned long
3135 width;
3136
cristy3ed852e2009-09-05 21:47:34 +00003137 assert(image != (Image *) NULL);
3138 assert(image->signature == MagickSignature);
3139 if (image->debug != MagickFalse)
3140 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3141 assert(exception != (ExceptionInfo *) NULL);
3142 width=GetOptimalKernelWidth1D(radius,sigma);
3143 kernel=GetMotionBlurKernel(width,sigma);
3144 if (kernel == (double *) NULL)
3145 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3146 offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset));
3147 if (offset == (OffsetInfo *) NULL)
3148 {
3149 kernel=(double *) RelinquishMagickMemory(kernel);
3150 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3151 }
3152 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3153 if (blur_image == (Image *) NULL)
3154 {
3155 kernel=(double *) RelinquishMagickMemory(kernel);
3156 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
3157 return((Image *) NULL);
3158 }
3159 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3160 {
3161 kernel=(double *) RelinquishMagickMemory(kernel);
3162 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
3163 InheritException(exception,&blur_image->exception);
3164 blur_image=DestroyImage(blur_image);
3165 return((Image *) NULL);
3166 }
3167 point.x=(double) width*sin(DegreesToRadians(angle));
3168 point.y=(double) width*cos(DegreesToRadians(angle));
3169 for (i=0; i < (long) width; i++)
3170 {
cristy1f9ce9f2010-04-28 11:55:12 +00003171 offset[i].x=(long) ceil((double) (i*point.y)/hypot(point.x,point.y)-0.5);
3172 offset[i].y=(long) ceil((double) (i*point.x)/hypot(point.x,point.y)-0.5);
cristy3ed852e2009-09-05 21:47:34 +00003173 }
3174 /*
3175 Motion blur image.
3176 */
3177 status=MagickTrue;
3178 progress=0;
cristyddd82202009-11-03 20:14:50 +00003179 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003180 image_view=AcquireCacheView(image);
3181 blur_view=AcquireCacheView(blur_image);
cristyd3dd0502010-04-26 00:57:20 +00003182#if defined(MAGICKCORE_OPENMP_SUPPORT) && defined(MAGICKCORE_FUTURE)
cristy8a7ea362010-03-10 20:31:43 +00003183 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003184#endif
3185 for (y=0; y < (long) image->rows; y++)
3186 {
3187 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003188 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00003189
3190 register long
3191 x;
3192
3193 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003194 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003195
3196 if (status == MagickFalse)
3197 continue;
3198 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3199 exception);
3200 if (q == (PixelPacket *) NULL)
3201 {
3202 status=MagickFalse;
3203 continue;
3204 }
3205 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
3206 for (x=0; x < (long) image->columns; x++)
3207 {
3208 MagickPixelPacket
3209 qixel;
3210
3211 PixelPacket
3212 pixel;
3213
3214 register double
cristyc47d1f82009-11-26 01:44:43 +00003215 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00003216
3217 register long
3218 i;
3219
3220 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003221 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003222
3223 k=kernel;
cristyddd82202009-11-03 20:14:50 +00003224 qixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00003225 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
3226 {
3227 for (i=0; i < (long) width; i++)
3228 {
3229 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
3230 offset[i].y,&pixel,exception);
3231 qixel.red+=(*k)*pixel.red;
3232 qixel.green+=(*k)*pixel.green;
3233 qixel.blue+=(*k)*pixel.blue;
3234 qixel.opacity+=(*k)*pixel.opacity;
3235 if (image->colorspace == CMYKColorspace)
3236 {
3237 indexes=GetCacheViewVirtualIndexQueue(image_view);
3238 qixel.index+=(*k)*(*indexes);
3239 }
3240 k++;
3241 }
3242 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003243 q->red=ClampToQuantum(qixel.red);
cristy3ed852e2009-09-05 21:47:34 +00003244 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003245 q->green=ClampToQuantum(qixel.green);
cristy3ed852e2009-09-05 21:47:34 +00003246 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003247 q->blue=ClampToQuantum(qixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00003248 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003249 q->opacity=ClampToQuantum(qixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00003250 if (((channel & IndexChannel) != 0) &&
3251 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00003252 blur_indexes[x]=(IndexPacket) ClampToQuantum(qixel.index);
cristy3ed852e2009-09-05 21:47:34 +00003253 }
3254 else
3255 {
3256 MagickRealType
3257 alpha,
3258 gamma;
3259
3260 alpha=0.0;
3261 gamma=0.0;
3262 for (i=0; i < (long) width; i++)
3263 {
3264 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
3265 offset[i].y,&pixel,exception);
cristy8a7ea362010-03-10 20:31:43 +00003266 alpha=(MagickRealType) (QuantumScale*
3267 GetAlphaPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00003268 qixel.red+=(*k)*alpha*pixel.red;
3269 qixel.green+=(*k)*alpha*pixel.green;
3270 qixel.blue+=(*k)*alpha*pixel.blue;
3271 qixel.opacity+=(*k)*pixel.opacity;
3272 if (image->colorspace == CMYKColorspace)
3273 {
3274 indexes=GetCacheViewVirtualIndexQueue(image_view);
3275 qixel.index+=(*k)*alpha*(*indexes);
3276 }
3277 gamma+=(*k)*alpha;
3278 k++;
3279 }
3280 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3281 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003282 q->red=ClampToQuantum(gamma*qixel.red);
cristy3ed852e2009-09-05 21:47:34 +00003283 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003284 q->green=ClampToQuantum(gamma*qixel.green);
cristy3ed852e2009-09-05 21:47:34 +00003285 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003286 q->blue=ClampToQuantum(gamma*qixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00003287 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003288 q->opacity=ClampToQuantum(qixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00003289 if (((channel & IndexChannel) != 0) &&
3290 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00003291 blur_indexes[x]=(IndexPacket) ClampToQuantum(gamma*qixel.index);
cristy3ed852e2009-09-05 21:47:34 +00003292 }
3293 q++;
3294 }
3295 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
3296 status=MagickFalse;
3297 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3298 {
3299 MagickBooleanType
3300 proceed;
3301
cristyd3dd0502010-04-26 00:57:20 +00003302#if defined(MAGICKCORE_OPENMP_SUPPORT) && defined(MAGICKCORE_FUTURE)
cristy3ed852e2009-09-05 21:47:34 +00003303 #pragma omp critical (MagickCore_MotionBlurImageChannel)
3304#endif
3305 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
3306 if (proceed == MagickFalse)
3307 status=MagickFalse;
3308 }
3309 }
3310 blur_view=DestroyCacheView(blur_view);
3311 image_view=DestroyCacheView(image_view);
3312 kernel=(double *) RelinquishMagickMemory(kernel);
3313 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
3314 if (status == MagickFalse)
3315 blur_image=DestroyImage(blur_image);
3316 return(blur_image);
3317}
3318
3319/*
3320%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3321% %
3322% %
3323% %
3324% P r e v i e w I m a g e %
3325% %
3326% %
3327% %
3328%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3329%
3330% PreviewImage() tiles 9 thumbnails of the specified image with an image
3331% processing operation applied with varying parameters. This may be helpful
3332% pin-pointing an appropriate parameter for a particular image processing
3333% operation.
3334%
3335% The format of the PreviewImages method is:
3336%
3337% Image *PreviewImages(const Image *image,const PreviewType preview,
3338% ExceptionInfo *exception)
3339%
3340% A description of each parameter follows:
3341%
3342% o image: the image.
3343%
3344% o preview: the image processing operation.
3345%
3346% o exception: return any errors or warnings in this structure.
3347%
3348*/
3349MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
3350 ExceptionInfo *exception)
3351{
3352#define NumberTiles 9
3353#define PreviewImageTag "Preview/Image"
3354#define DefaultPreviewGeometry "204x204+10+10"
3355
3356 char
3357 factor[MaxTextExtent],
3358 label[MaxTextExtent];
3359
3360 double
3361 degrees,
3362 gamma,
3363 percentage,
3364 radius,
3365 sigma,
3366 threshold;
3367
3368 Image
3369 *images,
3370 *montage_image,
3371 *preview_image,
3372 *thumbnail;
3373
3374 ImageInfo
3375 *preview_info;
3376
3377 long
3378 y;
3379
3380 MagickBooleanType
3381 proceed;
3382
3383 MontageInfo
3384 *montage_info;
3385
3386 QuantizeInfo
3387 quantize_info;
3388
3389 RectangleInfo
3390 geometry;
3391
3392 register long
3393 i,
3394 x;
3395
3396 unsigned long
3397 colors;
3398
3399 /*
3400 Open output image file.
3401 */
3402 assert(image != (Image *) NULL);
3403 assert(image->signature == MagickSignature);
3404 if (image->debug != MagickFalse)
3405 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3406 colors=2;
3407 degrees=0.0;
3408 gamma=(-0.2f);
3409 preview_info=AcquireImageInfo();
3410 SetGeometry(image,&geometry);
3411 (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
3412 &geometry.width,&geometry.height);
3413 images=NewImageList();
3414 percentage=12.5;
3415 GetQuantizeInfo(&quantize_info);
3416 radius=0.0;
3417 sigma=1.0;
3418 threshold=0.0;
3419 x=0;
3420 y=0;
3421 for (i=0; i < NumberTiles; i++)
3422 {
3423 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
3424 if (thumbnail == (Image *) NULL)
3425 break;
3426 (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
3427 (void *) NULL);
3428 (void) SetImageProperty(thumbnail,"label",DefaultTileLabel);
3429 if (i == (NumberTiles/2))
3430 {
3431 (void) QueryColorDatabase("#dfdfdf",&thumbnail->matte_color,exception);
3432 AppendImageToList(&images,thumbnail);
3433 continue;
3434 }
3435 switch (preview)
3436 {
3437 case RotatePreview:
3438 {
3439 degrees+=45.0;
3440 preview_image=RotateImage(thumbnail,degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003441 (void) FormatMagickString(label,MaxTextExtent,"rotate %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003442 break;
3443 }
3444 case ShearPreview:
3445 {
3446 degrees+=5.0;
3447 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003448 (void) FormatMagickString(label,MaxTextExtent,"shear %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00003449 degrees,2.0*degrees);
3450 break;
3451 }
3452 case RollPreview:
3453 {
3454 x=(long) ((i+1)*thumbnail->columns)/NumberTiles;
3455 y=(long) ((i+1)*thumbnail->rows)/NumberTiles;
3456 preview_image=RollImage(thumbnail,x,y,exception);
3457 (void) FormatMagickString(label,MaxTextExtent,"roll %ldx%ld",x,y);
3458 break;
3459 }
3460 case HuePreview:
3461 {
3462 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3463 if (preview_image == (Image *) NULL)
3464 break;
cristye7f51092010-01-17 00:39:37 +00003465 (void) FormatMagickString(factor,MaxTextExtent,"100,100,%g",
cristy3ed852e2009-09-05 21:47:34 +00003466 2.0*percentage);
3467 (void) ModulateImage(preview_image,factor);
3468 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
3469 break;
3470 }
3471 case SaturationPreview:
3472 {
3473 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3474 if (preview_image == (Image *) NULL)
3475 break;
cristye7f51092010-01-17 00:39:37 +00003476 (void) FormatMagickString(factor,MaxTextExtent,"100,%g",
cristy8cd5b312010-01-07 01:10:24 +00003477 2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00003478 (void) ModulateImage(preview_image,factor);
3479 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
3480 break;
3481 }
3482 case BrightnessPreview:
3483 {
3484 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3485 if (preview_image == (Image *) NULL)
3486 break;
cristye7f51092010-01-17 00:39:37 +00003487 (void) FormatMagickString(factor,MaxTextExtent,"%g",2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00003488 (void) ModulateImage(preview_image,factor);
3489 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
3490 break;
3491 }
3492 case GammaPreview:
3493 default:
3494 {
3495 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3496 if (preview_image == (Image *) NULL)
3497 break;
3498 gamma+=0.4f;
3499 (void) GammaImageChannel(preview_image,DefaultChannels,gamma);
cristye7f51092010-01-17 00:39:37 +00003500 (void) FormatMagickString(label,MaxTextExtent,"gamma %g",gamma);
cristy3ed852e2009-09-05 21:47:34 +00003501 break;
3502 }
3503 case SpiffPreview:
3504 {
3505 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3506 if (preview_image != (Image *) NULL)
3507 for (x=0; x < i; x++)
3508 (void) ContrastImage(preview_image,MagickTrue);
3509 (void) FormatMagickString(label,MaxTextExtent,"contrast (%ld)",i+1);
3510 break;
3511 }
3512 case DullPreview:
3513 {
3514 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3515 if (preview_image == (Image *) NULL)
3516 break;
3517 for (x=0; x < i; x++)
3518 (void) ContrastImage(preview_image,MagickFalse);
3519 (void) FormatMagickString(label,MaxTextExtent,"+contrast (%ld)",i+1);
3520 break;
3521 }
3522 case GrayscalePreview:
3523 {
3524 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3525 if (preview_image == (Image *) NULL)
3526 break;
3527 colors<<=1;
3528 quantize_info.number_colors=colors;
3529 quantize_info.colorspace=GRAYColorspace;
3530 (void) QuantizeImage(&quantize_info,preview_image);
3531 (void) FormatMagickString(label,MaxTextExtent,
3532 "-colorspace gray -colors %ld",colors);
3533 break;
3534 }
3535 case QuantizePreview:
3536 {
3537 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3538 if (preview_image == (Image *) NULL)
3539 break;
3540 colors<<=1;
3541 quantize_info.number_colors=colors;
3542 (void) QuantizeImage(&quantize_info,preview_image);
3543 (void) FormatMagickString(label,MaxTextExtent,"colors %ld",colors);
3544 break;
3545 }
3546 case DespecklePreview:
3547 {
3548 for (x=0; x < (i-1); x++)
3549 {
3550 preview_image=DespeckleImage(thumbnail,exception);
3551 if (preview_image == (Image *) NULL)
3552 break;
3553 thumbnail=DestroyImage(thumbnail);
3554 thumbnail=preview_image;
3555 }
3556 preview_image=DespeckleImage(thumbnail,exception);
3557 if (preview_image == (Image *) NULL)
3558 break;
3559 (void) FormatMagickString(label,MaxTextExtent,"despeckle (%ld)",i+1);
3560 break;
3561 }
3562 case ReduceNoisePreview:
3563 {
3564 preview_image=ReduceNoiseImage(thumbnail,radius,exception);
cristye7f51092010-01-17 00:39:37 +00003565 (void) FormatMagickString(label,MaxTextExtent,"noise %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003566 break;
3567 }
3568 case AddNoisePreview:
3569 {
3570 switch ((int) i)
3571 {
3572 case 0:
3573 {
3574 (void) CopyMagickString(factor,"uniform",MaxTextExtent);
3575 break;
3576 }
3577 case 1:
3578 {
3579 (void) CopyMagickString(factor,"gaussian",MaxTextExtent);
3580 break;
3581 }
3582 case 2:
3583 {
3584 (void) CopyMagickString(factor,"multiplicative",MaxTextExtent);
3585 break;
3586 }
3587 case 3:
3588 {
3589 (void) CopyMagickString(factor,"impulse",MaxTextExtent);
3590 break;
3591 }
3592 case 4:
3593 {
3594 (void) CopyMagickString(factor,"laplacian",MaxTextExtent);
3595 break;
3596 }
3597 case 5:
3598 {
3599 (void) CopyMagickString(factor,"Poisson",MaxTextExtent);
3600 break;
3601 }
3602 default:
3603 {
3604 (void) CopyMagickString(thumbnail->magick,"NULL",MaxTextExtent);
3605 break;
3606 }
3607 }
3608 preview_image=ReduceNoiseImage(thumbnail,(double) i,exception);
3609 (void) FormatMagickString(label,MaxTextExtent,"+noise %s",factor);
3610 break;
3611 }
3612 case SharpenPreview:
3613 {
3614 preview_image=SharpenImage(thumbnail,radius,sigma,exception);
cristye7f51092010-01-17 00:39:37 +00003615 (void) FormatMagickString(label,MaxTextExtent,"sharpen %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003616 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00003617 break;
3618 }
3619 case BlurPreview:
3620 {
3621 preview_image=BlurImage(thumbnail,radius,sigma,exception);
cristye7f51092010-01-17 00:39:37 +00003622 (void) FormatMagickString(label,MaxTextExtent,"blur %gx%g",radius,
cristy3ed852e2009-09-05 21:47:34 +00003623 sigma);
3624 break;
3625 }
3626 case ThresholdPreview:
3627 {
3628 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3629 if (preview_image == (Image *) NULL)
3630 break;
3631 (void) BilevelImage(thumbnail,
3632 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
cristye7f51092010-01-17 00:39:37 +00003633 (void) FormatMagickString(label,MaxTextExtent,"threshold %g",
cristy3ed852e2009-09-05 21:47:34 +00003634 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
3635 break;
3636 }
3637 case EdgeDetectPreview:
3638 {
3639 preview_image=EdgeImage(thumbnail,radius,exception);
cristye7f51092010-01-17 00:39:37 +00003640 (void) FormatMagickString(label,MaxTextExtent,"edge %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003641 break;
3642 }
3643 case SpreadPreview:
3644 {
3645 preview_image=SpreadImage(thumbnail,radius,exception);
cristye7f51092010-01-17 00:39:37 +00003646 (void) FormatMagickString(label,MaxTextExtent,"spread %g",
cristy8cd5b312010-01-07 01:10:24 +00003647 radius+0.5);
cristy3ed852e2009-09-05 21:47:34 +00003648 break;
3649 }
3650 case SolarizePreview:
3651 {
3652 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3653 if (preview_image == (Image *) NULL)
3654 break;
3655 (void) SolarizeImage(preview_image,(double) QuantumRange*
3656 percentage/100.0);
cristye7f51092010-01-17 00:39:37 +00003657 (void) FormatMagickString(label,MaxTextExtent,"solarize %g",
cristy3ed852e2009-09-05 21:47:34 +00003658 (QuantumRange*percentage)/100.0);
3659 break;
3660 }
3661 case ShadePreview:
3662 {
3663 degrees+=10.0;
3664 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
3665 exception);
cristye7f51092010-01-17 00:39:37 +00003666 (void) FormatMagickString(label,MaxTextExtent,"shade %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003667 degrees,degrees);
cristy3ed852e2009-09-05 21:47:34 +00003668 break;
3669 }
3670 case RaisePreview:
3671 {
3672 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3673 if (preview_image == (Image *) NULL)
3674 break;
3675 geometry.width=(unsigned long) (2*i+2);
3676 geometry.height=(unsigned long) (2*i+2);
3677 geometry.x=i/2;
3678 geometry.y=i/2;
3679 (void) RaiseImage(preview_image,&geometry,MagickTrue);
3680 (void) FormatMagickString(label,MaxTextExtent,"raise %lux%lu%+ld%+ld",
3681 geometry.width,geometry.height,geometry.x,geometry.y);
3682 break;
3683 }
3684 case SegmentPreview:
3685 {
3686 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3687 if (preview_image == (Image *) NULL)
3688 break;
3689 threshold+=0.4f;
3690 (void) SegmentImage(preview_image,RGBColorspace,MagickFalse,threshold,
3691 threshold);
cristye7f51092010-01-17 00:39:37 +00003692 (void) FormatMagickString(label,MaxTextExtent,"segment %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00003693 threshold,threshold);
3694 break;
3695 }
3696 case SwirlPreview:
3697 {
3698 preview_image=SwirlImage(thumbnail,degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003699 (void) FormatMagickString(label,MaxTextExtent,"swirl %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003700 degrees+=45.0;
3701 break;
3702 }
3703 case ImplodePreview:
3704 {
3705 degrees+=0.1f;
3706 preview_image=ImplodeImage(thumbnail,degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003707 (void) FormatMagickString(label,MaxTextExtent,"implode %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003708 break;
3709 }
3710 case WavePreview:
3711 {
3712 degrees+=5.0f;
3713 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003714 (void) FormatMagickString(label,MaxTextExtent,"wave %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003715 0.5*degrees,2.0*degrees);
cristy3ed852e2009-09-05 21:47:34 +00003716 break;
3717 }
3718 case OilPaintPreview:
3719 {
3720 preview_image=OilPaintImage(thumbnail,(double) radius,exception);
cristye7f51092010-01-17 00:39:37 +00003721 (void) FormatMagickString(label,MaxTextExtent,"paint %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003722 break;
3723 }
3724 case CharcoalDrawingPreview:
3725 {
3726 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
3727 exception);
cristye7f51092010-01-17 00:39:37 +00003728 (void) FormatMagickString(label,MaxTextExtent,"charcoal %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003729 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00003730 break;
3731 }
3732 case JPEGPreview:
3733 {
3734 char
3735 filename[MaxTextExtent];
3736
3737 int
3738 file;
3739
3740 MagickBooleanType
3741 status;
3742
3743 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3744 if (preview_image == (Image *) NULL)
3745 break;
3746 preview_info->quality=(unsigned long) percentage;
3747 (void) FormatMagickString(factor,MaxTextExtent,"%lu",
3748 preview_info->quality);
3749 file=AcquireUniqueFileResource(filename);
3750 if (file != -1)
3751 file=close(file)-1;
3752 (void) FormatMagickString(preview_image->filename,MaxTextExtent,
3753 "jpeg:%s",filename);
3754 status=WriteImage(preview_info,preview_image);
3755 if (status != MagickFalse)
3756 {
3757 Image
3758 *quality_image;
3759
3760 (void) CopyMagickString(preview_info->filename,
3761 preview_image->filename,MaxTextExtent);
3762 quality_image=ReadImage(preview_info,exception);
3763 if (quality_image != (Image *) NULL)
3764 {
3765 preview_image=DestroyImage(preview_image);
3766 preview_image=quality_image;
3767 }
3768 }
3769 (void) RelinquishUniqueFileResource(preview_image->filename);
3770 if ((GetBlobSize(preview_image)/1024) >= 1024)
cristye7f51092010-01-17 00:39:37 +00003771 (void) FormatMagickString(label,MaxTextExtent,"quality %s\n%gmb ",
cristy3ed852e2009-09-05 21:47:34 +00003772 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
3773 1024.0/1024.0);
3774 else
3775 if (GetBlobSize(preview_image) >= 1024)
cristy8cd5b312010-01-07 01:10:24 +00003776 (void) FormatMagickString(label,MaxTextExtent,
cristye7f51092010-01-17 00:39:37 +00003777 "quality %s\n%gkb ",factor,(double) ((MagickOffsetType)
cristy8cd5b312010-01-07 01:10:24 +00003778 GetBlobSize(preview_image))/1024.0);
cristy3ed852e2009-09-05 21:47:34 +00003779 else
3780 (void) FormatMagickString(label,MaxTextExtent,"quality %s\n%lub ",
3781 factor,(unsigned long) GetBlobSize(thumbnail));
3782 break;
3783 }
3784 }
3785 thumbnail=DestroyImage(thumbnail);
3786 percentage+=12.5;
3787 radius+=0.5;
3788 sigma+=0.25;
3789 if (preview_image == (Image *) NULL)
3790 break;
3791 (void) DeleteImageProperty(preview_image,"label");
3792 (void) SetImageProperty(preview_image,"label",label);
3793 AppendImageToList(&images,preview_image);
3794 proceed=SetImageProgress(image,PreviewImageTag,i,NumberTiles);
3795 if (proceed == MagickFalse)
3796 break;
3797 }
3798 if (images == (Image *) NULL)
3799 {
3800 preview_info=DestroyImageInfo(preview_info);
3801 return((Image *) NULL);
3802 }
3803 /*
3804 Create the montage.
3805 */
3806 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
3807 (void) CopyMagickString(montage_info->filename,image->filename,MaxTextExtent);
3808 montage_info->shadow=MagickTrue;
3809 (void) CloneString(&montage_info->tile,"3x3");
3810 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
3811 (void) CloneString(&montage_info->frame,DefaultTileFrame);
3812 montage_image=MontageImages(images,montage_info,exception);
3813 montage_info=DestroyMontageInfo(montage_info);
3814 images=DestroyImageList(images);
3815 if (montage_image == (Image *) NULL)
3816 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3817 if (montage_image->montage != (char *) NULL)
3818 {
3819 /*
3820 Free image directory.
3821 */
3822 montage_image->montage=(char *) RelinquishMagickMemory(
3823 montage_image->montage);
3824 if (image->directory != (char *) NULL)
3825 montage_image->directory=(char *) RelinquishMagickMemory(
3826 montage_image->directory);
3827 }
3828 preview_info=DestroyImageInfo(preview_info);
3829 return(montage_image);
3830}
3831
3832/*
3833%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3834% %
3835% %
3836% %
3837% R a d i a l B l u r I m a g e %
3838% %
3839% %
3840% %
3841%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3842%
3843% RadialBlurImage() applies a radial blur to the image.
3844%
3845% Andrew Protano contributed this effect.
3846%
3847% The format of the RadialBlurImage method is:
3848%
3849% Image *RadialBlurImage(const Image *image,const double angle,
3850% ExceptionInfo *exception)
3851% Image *RadialBlurImageChannel(const Image *image,const ChannelType channel,
3852% const double angle,ExceptionInfo *exception)
3853%
3854% A description of each parameter follows:
3855%
3856% o image: the image.
3857%
3858% o channel: the channel type.
3859%
3860% o angle: the angle of the radial blur.
3861%
3862% o exception: return any errors or warnings in this structure.
3863%
3864*/
3865
3866MagickExport Image *RadialBlurImage(const Image *image,const double angle,
3867 ExceptionInfo *exception)
3868{
3869 Image
3870 *blur_image;
3871
3872 blur_image=RadialBlurImageChannel(image,DefaultChannels,angle,exception);
3873 return(blur_image);
3874}
3875
3876MagickExport Image *RadialBlurImageChannel(const Image *image,
3877 const ChannelType channel,const double angle,ExceptionInfo *exception)
3878{
cristyc4c8d132010-01-07 01:58:38 +00003879 CacheView
3880 *blur_view,
3881 *image_view;
3882
cristy3ed852e2009-09-05 21:47:34 +00003883 Image
3884 *blur_image;
3885
3886 long
3887 progress,
3888 y;
3889
3890 MagickBooleanType
3891 status;
3892
3893 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00003894 bias;
cristy3ed852e2009-09-05 21:47:34 +00003895
3896 MagickRealType
3897 blur_radius,
3898 *cos_theta,
3899 offset,
3900 *sin_theta,
3901 theta;
3902
3903 PointInfo
3904 blur_center;
3905
3906 register long
3907 i;
3908
3909 unsigned long
3910 n;
3911
cristy3ed852e2009-09-05 21:47:34 +00003912 /*
3913 Allocate blur image.
3914 */
3915 assert(image != (Image *) NULL);
3916 assert(image->signature == MagickSignature);
3917 if (image->debug != MagickFalse)
3918 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3919 assert(exception != (ExceptionInfo *) NULL);
3920 assert(exception->signature == MagickSignature);
3921 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3922 if (blur_image == (Image *) NULL)
3923 return((Image *) NULL);
3924 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3925 {
3926 InheritException(exception,&blur_image->exception);
3927 blur_image=DestroyImage(blur_image);
3928 return((Image *) NULL);
3929 }
3930 blur_center.x=(double) image->columns/2.0;
3931 blur_center.y=(double) image->rows/2.0;
3932 blur_radius=hypot(blur_center.x,blur_center.y);
3933 n=(unsigned long) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+
3934 2UL);
3935 theta=DegreesToRadians(angle)/(MagickRealType) (n-1);
3936 cos_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3937 sizeof(*cos_theta));
3938 sin_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3939 sizeof(*sin_theta));
3940 if ((cos_theta == (MagickRealType *) NULL) ||
3941 (sin_theta == (MagickRealType *) NULL))
3942 {
3943 blur_image=DestroyImage(blur_image);
3944 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3945 }
3946 offset=theta*(MagickRealType) (n-1)/2.0;
3947 for (i=0; i < (long) n; i++)
3948 {
3949 cos_theta[i]=cos((double) (theta*i-offset));
3950 sin_theta[i]=sin((double) (theta*i-offset));
3951 }
3952 /*
3953 Radial blur image.
3954 */
3955 status=MagickTrue;
3956 progress=0;
cristyddd82202009-11-03 20:14:50 +00003957 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003958 image_view=AcquireCacheView(image);
3959 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003960#if defined(MAGICKCORE_OPENMP_SUPPORT)
3961 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003962#endif
3963 for (y=0; y < (long) blur_image->rows; y++)
3964 {
3965 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003966 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003967
3968 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003969 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00003970
3971 register long
3972 x;
3973
3974 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003975 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003976
3977 if (status == MagickFalse)
3978 continue;
3979 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3980 exception);
3981 if (q == (PixelPacket *) NULL)
3982 {
3983 status=MagickFalse;
3984 continue;
3985 }
3986 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
3987 for (x=0; x < (long) blur_image->columns; x++)
3988 {
3989 MagickPixelPacket
3990 qixel;
3991
3992 MagickRealType
3993 normalize,
3994 radius;
3995
3996 PixelPacket
3997 pixel;
3998
3999 PointInfo
4000 center;
4001
4002 register long
4003 i;
4004
4005 unsigned long
4006 step;
4007
4008 center.x=(double) x-blur_center.x;
4009 center.y=(double) y-blur_center.y;
4010 radius=hypot((double) center.x,center.y);
4011 if (radius == 0)
4012 step=1;
4013 else
4014 {
4015 step=(unsigned long) (blur_radius/radius);
4016 if (step == 0)
4017 step=1;
4018 else
4019 if (step >= n)
4020 step=n-1;
4021 }
4022 normalize=0.0;
cristyddd82202009-11-03 20:14:50 +00004023 qixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00004024 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
4025 {
4026 for (i=0; i < (long) n; i+=step)
4027 {
4028 (void) GetOneCacheViewVirtualPixel(image_view,(long) (blur_center.x+
4029 center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),(long) (
4030 blur_center.y+center.x*sin_theta[i]+center.y*cos_theta[i]+0.5),
4031 &pixel,exception);
4032 qixel.red+=pixel.red;
4033 qixel.green+=pixel.green;
4034 qixel.blue+=pixel.blue;
4035 qixel.opacity+=pixel.opacity;
4036 if (image->colorspace == CMYKColorspace)
4037 {
4038 indexes=GetCacheViewVirtualIndexQueue(image_view);
4039 qixel.index+=(*indexes);
4040 }
4041 normalize+=1.0;
4042 }
4043 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
4044 normalize);
4045 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004046 q->red=ClampToQuantum(normalize*qixel.red);
cristy3ed852e2009-09-05 21:47:34 +00004047 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004048 q->green=ClampToQuantum(normalize*qixel.green);
cristy3ed852e2009-09-05 21:47:34 +00004049 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004050 q->blue=ClampToQuantum(normalize*qixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00004051 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004052 q->opacity=ClampToQuantum(normalize*qixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00004053 if (((channel & IndexChannel) != 0) &&
4054 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00004055 blur_indexes[x]=(IndexPacket) ClampToQuantum(normalize*qixel.index);
cristy3ed852e2009-09-05 21:47:34 +00004056 }
4057 else
4058 {
4059 MagickRealType
4060 alpha,
4061 gamma;
4062
4063 alpha=1.0;
4064 gamma=0.0;
4065 for (i=0; i < (long) n; i+=step)
4066 {
4067 (void) GetOneCacheViewVirtualPixel(image_view,(long) (blur_center.x+
4068 center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),(long) (
4069 blur_center.y+center.x*sin_theta[i]+center.y*cos_theta[i]+0.5),
4070 &pixel,exception);
cristy46f08202010-01-10 04:04:21 +00004071 alpha=(MagickRealType) (QuantumScale*
4072 GetAlphaPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004073 qixel.red+=alpha*pixel.red;
4074 qixel.green+=alpha*pixel.green;
4075 qixel.blue+=alpha*pixel.blue;
4076 qixel.opacity+=pixel.opacity;
4077 if (image->colorspace == CMYKColorspace)
4078 {
4079 indexes=GetCacheViewVirtualIndexQueue(image_view);
4080 qixel.index+=alpha*(*indexes);
4081 }
4082 gamma+=alpha;
4083 normalize+=1.0;
4084 }
4085 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
4086 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
4087 normalize);
4088 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004089 q->red=ClampToQuantum(gamma*qixel.red);
cristy3ed852e2009-09-05 21:47:34 +00004090 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004091 q->green=ClampToQuantum(gamma*qixel.green);
cristy3ed852e2009-09-05 21:47:34 +00004092 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004093 q->blue=ClampToQuantum(gamma*qixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00004094 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004095 q->opacity=ClampToQuantum(normalize*qixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00004096 if (((channel & IndexChannel) != 0) &&
4097 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00004098 blur_indexes[x]=(IndexPacket) ClampToQuantum(gamma*qixel.index);
cristy3ed852e2009-09-05 21:47:34 +00004099 }
4100 q++;
4101 }
4102 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
4103 status=MagickFalse;
4104 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4105 {
4106 MagickBooleanType
4107 proceed;
4108
cristyb5d5f722009-11-04 03:03:49 +00004109#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004110 #pragma omp critical (MagickCore_RadialBlurImageChannel)
4111#endif
4112 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
4113 if (proceed == MagickFalse)
4114 status=MagickFalse;
4115 }
4116 }
4117 blur_view=DestroyCacheView(blur_view);
4118 image_view=DestroyCacheView(image_view);
4119 cos_theta=(MagickRealType *) RelinquishMagickMemory(cos_theta);
4120 sin_theta=(MagickRealType *) RelinquishMagickMemory(sin_theta);
4121 if (status == MagickFalse)
4122 blur_image=DestroyImage(blur_image);
4123 return(blur_image);
4124}
4125
4126/*
4127%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4128% %
4129% %
4130% %
4131% R e d u c e N o i s e I m a g e %
4132% %
4133% %
4134% %
4135%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4136%
4137% ReduceNoiseImage() smooths the contours of an image while still preserving
4138% edge information. The algorithm works by replacing each pixel with its
4139% neighbor closest in value. A neighbor is defined by radius. Use a radius
4140% of 0 and ReduceNoise() selects a suitable radius for you.
4141%
4142% The format of the ReduceNoiseImage method is:
4143%
4144% Image *ReduceNoiseImage(const Image *image,const double radius,
4145% ExceptionInfo *exception)
4146%
4147% A description of each parameter follows:
4148%
4149% o image: the image.
4150%
4151% o radius: the radius of the pixel neighborhood.
4152%
4153% o exception: return any errors or warnings in this structure.
4154%
4155*/
4156
4157static MagickPixelPacket GetNonpeakMedianPixelList(MedianPixelList *pixel_list)
4158{
4159 MagickPixelPacket
4160 pixel;
4161
4162 register long
4163 channel;
4164
4165 register MedianSkipList
4166 *list;
4167
4168 unsigned long
4169 center,
4170 color,
4171 count,
4172 previous,
4173 next;
4174
4175 unsigned short
4176 channels[5];
4177
4178 /*
4179 Finds the median value for each of the color.
4180 */
4181 center=pixel_list->center;
4182 for (channel=0; channel < 5; channel++)
4183 {
4184 list=pixel_list->lists+channel;
4185 color=65536UL;
4186 next=list->nodes[color].next[0];
4187 count=0;
4188 do
4189 {
4190 previous=color;
4191 color=next;
4192 next=list->nodes[color].next[0];
4193 count+=list->nodes[color].count;
4194 }
4195 while (count <= center);
4196 if ((previous == 65536UL) && (next != 65536UL))
4197 color=next;
4198 else
4199 if ((previous != 65536UL) && (next == 65536UL))
4200 color=previous;
4201 channels[channel]=(unsigned short) color;
4202 }
4203 GetMagickPixelPacket((const Image *) NULL,&pixel);
4204 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4205 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4206 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
4207 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
4208 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
4209 return(pixel);
4210}
4211
4212MagickExport Image *ReduceNoiseImage(const Image *image,const double radius,
4213 ExceptionInfo *exception)
4214{
4215#define ReduceNoiseImageTag "ReduceNoise/Image"
4216
cristyfa112112010-01-04 17:48:07 +00004217 CacheView
4218 *image_view,
4219 *noise_view;
4220
cristy3ed852e2009-09-05 21:47:34 +00004221 Image
4222 *noise_image;
4223
4224 long
4225 progress,
4226 y;
4227
4228 MagickBooleanType
4229 status;
4230
4231 MedianPixelList
cristyfa112112010-01-04 17:48:07 +00004232 **restrict pixel_list;
cristy3ed852e2009-09-05 21:47:34 +00004233
4234 unsigned long
4235 width;
4236
cristy3ed852e2009-09-05 21:47:34 +00004237 /*
4238 Initialize noise image attributes.
4239 */
4240 assert(image != (Image *) NULL);
4241 assert(image->signature == MagickSignature);
4242 if (image->debug != MagickFalse)
4243 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4244 assert(exception != (ExceptionInfo *) NULL);
4245 assert(exception->signature == MagickSignature);
4246 width=GetOptimalKernelWidth2D(radius,0.5);
4247 if ((image->columns < width) || (image->rows < width))
4248 ThrowImageException(OptionError,"ImageSmallerThanKernelRadius");
4249 noise_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4250 exception);
4251 if (noise_image == (Image *) NULL)
4252 return((Image *) NULL);
4253 if (SetImageStorageClass(noise_image,DirectClass) == MagickFalse)
4254 {
4255 InheritException(exception,&noise_image->exception);
4256 noise_image=DestroyImage(noise_image);
4257 return((Image *) NULL);
4258 }
4259 pixel_list=AcquireMedianPixelListThreadSet(width);
4260 if (pixel_list == (MedianPixelList **) NULL)
4261 {
4262 noise_image=DestroyImage(noise_image);
4263 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
4264 }
4265 /*
4266 Reduce noise image.
4267 */
4268 status=MagickTrue;
4269 progress=0;
4270 image_view=AcquireCacheView(image);
4271 noise_view=AcquireCacheView(noise_image);
cristyb5d5f722009-11-04 03:03:49 +00004272#if defined(MAGICKCORE_OPENMP_SUPPORT)
4273 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004274#endif
4275 for (y=0; y < (long) noise_image->rows; y++)
4276 {
4277 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004278 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00004279
4280 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004281 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00004282
4283 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004284 *restrict noise_indexes;
cristy3ed852e2009-09-05 21:47:34 +00004285
4286 register long
4287 id,
4288 x;
4289
4290 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004291 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004292
4293 if (status == MagickFalse)
4294 continue;
4295 p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y-(long) (width/
4296 2L),image->columns+width,width,exception);
4297 q=QueueCacheViewAuthenticPixels(noise_view,0,y,noise_image->columns,1,
4298 exception);
4299 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
4300 {
4301 status=MagickFalse;
4302 continue;
4303 }
4304 indexes=GetCacheViewVirtualIndexQueue(image_view);
4305 noise_indexes=GetCacheViewAuthenticIndexQueue(noise_view);
4306 id=GetOpenMPThreadId();
4307 for (x=0; x < (long) noise_image->columns; x++)
4308 {
4309 MagickPixelPacket
4310 pixel;
4311
4312 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004313 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +00004314
4315 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004316 *restrict s;
cristy3ed852e2009-09-05 21:47:34 +00004317
4318 register long
4319 u,
4320 v;
4321
4322 r=p;
4323 s=indexes+x;
4324 ResetMedianPixelList(pixel_list[id]);
4325 for (v=0; v < (long) width; v++)
4326 {
4327 for (u=0; u < (long) width; u++)
4328 InsertMedianPixelList(image,r+u,s+u,pixel_list[id]);
4329 r+=image->columns+width;
4330 s+=image->columns+width;
4331 }
4332 pixel=GetNonpeakMedianPixelList(pixel_list[id]);
4333 SetPixelPacket(noise_image,&pixel,q,noise_indexes+x);
4334 p++;
4335 q++;
4336 }
4337 if (SyncCacheViewAuthenticPixels(noise_view,exception) == MagickFalse)
4338 status=MagickFalse;
4339 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4340 {
4341 MagickBooleanType
4342 proceed;
4343
cristyb5d5f722009-11-04 03:03:49 +00004344#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004345 #pragma omp critical (MagickCore_ReduceNoiseImage)
4346#endif
4347 proceed=SetImageProgress(image,ReduceNoiseImageTag,progress++,
4348 image->rows);
4349 if (proceed == MagickFalse)
4350 status=MagickFalse;
4351 }
4352 }
4353 noise_view=DestroyCacheView(noise_view);
4354 image_view=DestroyCacheView(image_view);
4355 pixel_list=DestroyMedianPixelListThreadSet(pixel_list);
4356 return(noise_image);
4357}
4358
4359/*
4360%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4361% %
4362% %
4363% %
4364% S e l e c t i v e B l u r I m a g e %
4365% %
4366% %
4367% %
4368%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4369%
4370% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
4371% It is similar to the unsharpen mask that sharpens everything with contrast
4372% above a certain threshold.
4373%
4374% The format of the SelectiveBlurImage method is:
4375%
4376% Image *SelectiveBlurImage(const Image *image,const double radius,
4377% const double sigma,const double threshold,ExceptionInfo *exception)
4378% Image *SelectiveBlurImageChannel(const Image *image,
4379% const ChannelType channel,const double radius,const double sigma,
4380% const double threshold,ExceptionInfo *exception)
4381%
4382% A description of each parameter follows:
4383%
4384% o image: the image.
4385%
4386% o channel: the channel type.
4387%
4388% o radius: the radius of the Gaussian, in pixels, not counting the center
4389% pixel.
4390%
4391% o sigma: the standard deviation of the Gaussian, in pixels.
4392%
4393% o threshold: only pixels within this contrast threshold are included
4394% in the blur operation.
4395%
4396% o exception: return any errors or warnings in this structure.
4397%
4398*/
4399
4400static inline MagickBooleanType SelectiveContrast(const PixelPacket *p,
4401 const PixelPacket *q,const double threshold)
4402{
4403 if (fabs(PixelIntensity(p)-PixelIntensity(q)) < threshold)
4404 return(MagickTrue);
4405 return(MagickFalse);
4406}
4407
4408MagickExport Image *SelectiveBlurImage(const Image *image,const double radius,
4409 const double sigma,const double threshold,ExceptionInfo *exception)
4410{
4411 Image
4412 *blur_image;
4413
4414 blur_image=SelectiveBlurImageChannel(image,DefaultChannels,radius,sigma,
4415 threshold,exception);
4416 return(blur_image);
4417}
4418
4419MagickExport Image *SelectiveBlurImageChannel(const Image *image,
4420 const ChannelType channel,const double radius,const double sigma,
4421 const double threshold,ExceptionInfo *exception)
4422{
4423#define SelectiveBlurImageTag "SelectiveBlur/Image"
4424
cristy47e00502009-12-17 19:19:57 +00004425 CacheView
4426 *blur_view,
4427 *image_view;
4428
cristy3ed852e2009-09-05 21:47:34 +00004429 double
cristy3ed852e2009-09-05 21:47:34 +00004430 *kernel;
4431
4432 Image
4433 *blur_image;
4434
4435 long
cristy47e00502009-12-17 19:19:57 +00004436 j,
cristy3ed852e2009-09-05 21:47:34 +00004437 progress,
cristy47e00502009-12-17 19:19:57 +00004438 u,
cristy3ed852e2009-09-05 21:47:34 +00004439 v,
4440 y;
4441
4442 MagickBooleanType
4443 status;
4444
4445 MagickPixelPacket
cristy3ed852e2009-09-05 21:47:34 +00004446 bias;
4447
4448 register long
cristy47e00502009-12-17 19:19:57 +00004449 i;
cristy3ed852e2009-09-05 21:47:34 +00004450
4451 unsigned long
4452 width;
4453
cristy3ed852e2009-09-05 21:47:34 +00004454 /*
4455 Initialize blur image attributes.
4456 */
4457 assert(image != (Image *) NULL);
4458 assert(image->signature == MagickSignature);
4459 if (image->debug != MagickFalse)
4460 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4461 assert(exception != (ExceptionInfo *) NULL);
4462 assert(exception->signature == MagickSignature);
4463 width=GetOptimalKernelWidth1D(radius,sigma);
4464 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
4465 if (kernel == (double *) NULL)
4466 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy47e00502009-12-17 19:19:57 +00004467 j=(long) width/2;
cristy3ed852e2009-09-05 21:47:34 +00004468 i=0;
cristy47e00502009-12-17 19:19:57 +00004469 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00004470 {
cristy47e00502009-12-17 19:19:57 +00004471 for (u=(-j); u <= j; u++)
4472 kernel[i++]=exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
4473 (2.0*MagickPI*MagickSigma*MagickSigma);
cristy3ed852e2009-09-05 21:47:34 +00004474 }
4475 if (image->debug != MagickFalse)
4476 {
4477 char
4478 format[MaxTextExtent],
4479 *message;
4480
4481 long
4482 u,
4483 v;
4484
4485 register const double
4486 *k;
4487
4488 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
4489 " SelectiveBlurImage with %ldx%ld kernel:",width,width);
4490 message=AcquireString("");
4491 k=kernel;
4492 for (v=0; v < (long) width; v++)
4493 {
4494 *message='\0';
4495 (void) FormatMagickString(format,MaxTextExtent,"%ld: ",v);
4496 (void) ConcatenateString(&message,format);
4497 for (u=0; u < (long) width; u++)
4498 {
4499 (void) FormatMagickString(format,MaxTextExtent,"%+f ",*k++);
4500 (void) ConcatenateString(&message,format);
4501 }
4502 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
4503 }
4504 message=DestroyString(message);
4505 }
4506 blur_image=CloneImage(image,0,0,MagickTrue,exception);
4507 if (blur_image == (Image *) NULL)
4508 return((Image *) NULL);
4509 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
4510 {
4511 InheritException(exception,&blur_image->exception);
4512 blur_image=DestroyImage(blur_image);
4513 return((Image *) NULL);
4514 }
4515 /*
4516 Threshold blur image.
4517 */
4518 status=MagickTrue;
4519 progress=0;
cristyddd82202009-11-03 20:14:50 +00004520 GetMagickPixelPacket(image,&bias);
4521 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00004522 image_view=AcquireCacheView(image);
4523 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00004524#if defined(MAGICKCORE_OPENMP_SUPPORT)
4525 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004526#endif
4527 for (y=0; y < (long) image->rows; y++)
4528 {
4529 MagickBooleanType
4530 sync;
4531
4532 MagickRealType
4533 gamma;
4534
4535 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004536 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00004537
4538 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004539 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00004540
4541 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004542 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00004543
4544 register long
4545 x;
4546
4547 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004548 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004549
4550 if (status == MagickFalse)
4551 continue;
4552 p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y-(long) (width/
4553 2L),image->columns+width,width,exception);
4554 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
4555 exception);
4556 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
4557 {
4558 status=MagickFalse;
4559 continue;
4560 }
4561 indexes=GetCacheViewVirtualIndexQueue(image_view);
4562 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
4563 for (x=0; x < (long) image->columns; x++)
4564 {
4565 long
4566 j,
4567 v;
4568
4569 MagickPixelPacket
4570 pixel;
4571
4572 register const double
cristyc47d1f82009-11-26 01:44:43 +00004573 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00004574
4575 register long
4576 u;
4577
cristyddd82202009-11-03 20:14:50 +00004578 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00004579 k=kernel;
4580 gamma=0.0;
4581 j=0;
4582 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
4583 {
4584 for (v=0; v < (long) width; v++)
4585 {
4586 for (u=0; u < (long) width; u++)
4587 {
4588 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4589 {
4590 pixel.red+=(*k)*(p+u+j)->red;
4591 pixel.green+=(*k)*(p+u+j)->green;
4592 pixel.blue+=(*k)*(p+u+j)->blue;
4593 gamma+=(*k);
4594 k++;
4595 }
4596 }
4597 j+=image->columns+width;
4598 }
4599 if (gamma != 0.0)
4600 {
4601 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
4602 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004603 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004604 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004605 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004606 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004607 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004608 }
4609 if ((channel & OpacityChannel) != 0)
4610 {
4611 gamma=0.0;
4612 j=0;
4613 for (v=0; v < (long) width; v++)
4614 {
4615 for (u=0; u < (long) width; u++)
4616 {
4617 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4618 {
4619 pixel.opacity+=(*k)*(p+u+j)->opacity;
4620 gamma+=(*k);
4621 k++;
4622 }
4623 }
4624 j+=image->columns+width;
4625 }
4626 if (gamma != 0.0)
4627 {
4628 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4629 gamma);
cristyce70c172010-01-07 17:15:30 +00004630 SetOpacityPixelComponent(q,ClampToQuantum(gamma*
4631 GetOpacityPixelComponent(&pixel)));
cristy3ed852e2009-09-05 21:47:34 +00004632 }
4633 }
4634 if (((channel & IndexChannel) != 0) &&
4635 (image->colorspace == CMYKColorspace))
4636 {
4637 gamma=0.0;
4638 j=0;
4639 for (v=0; v < (long) width; v++)
4640 {
4641 for (u=0; u < (long) width; u++)
4642 {
4643 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4644 {
4645 pixel.index+=(*k)*indexes[x+u+j];
4646 gamma+=(*k);
4647 k++;
4648 }
4649 }
4650 j+=image->columns+width;
4651 }
4652 if (gamma != 0.0)
4653 {
4654 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4655 gamma);
cristy6db48122010-01-11 00:18:07 +00004656 blur_indexes[x]=ClampToQuantum(gamma*
4657 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004658 }
4659 }
4660 }
4661 else
4662 {
4663 MagickRealType
4664 alpha;
4665
4666 for (v=0; v < (long) width; v++)
4667 {
4668 for (u=0; u < (long) width; u++)
4669 {
4670 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4671 {
cristy46f08202010-01-10 04:04:21 +00004672 alpha=(MagickRealType) (QuantumScale*
4673 GetAlphaPixelComponent(p+u+j));
cristy3ed852e2009-09-05 21:47:34 +00004674 pixel.red+=(*k)*alpha*(p+u+j)->red;
4675 pixel.green+=(*k)*alpha*(p+u+j)->green;
4676 pixel.blue+=(*k)*alpha*(p+u+j)->blue;
4677 pixel.opacity+=(*k)*(p+u+j)->opacity;
4678 gamma+=(*k)*alpha;
4679 k++;
4680 }
4681 }
4682 j+=image->columns+width;
4683 }
4684 if (gamma != 0.0)
4685 {
4686 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
4687 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004688 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004689 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004690 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004691 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004692 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004693 }
4694 if ((channel & OpacityChannel) != 0)
4695 {
4696 gamma=0.0;
4697 j=0;
4698 for (v=0; v < (long) width; v++)
4699 {
4700 for (u=0; u < (long) width; u++)
4701 {
4702 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4703 {
4704 pixel.opacity+=(*k)*(p+u+j)->opacity;
4705 gamma+=(*k);
4706 k++;
4707 }
4708 }
4709 j+=image->columns+width;
4710 }
4711 if (gamma != 0.0)
4712 {
4713 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4714 gamma);
cristy6db48122010-01-11 00:18:07 +00004715 SetOpacityPixelComponent(q,
4716 ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004717 }
4718 }
4719 if (((channel & IndexChannel) != 0) &&
4720 (image->colorspace == CMYKColorspace))
4721 {
4722 gamma=0.0;
4723 j=0;
4724 for (v=0; v < (long) width; v++)
4725 {
4726 for (u=0; u < (long) width; u++)
4727 {
4728 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4729 {
cristy46f08202010-01-10 04:04:21 +00004730 alpha=(MagickRealType) (QuantumScale*
4731 GetAlphaPixelComponent(p+u+j));
cristy3ed852e2009-09-05 21:47:34 +00004732 pixel.index+=(*k)*alpha*indexes[x+u+j];
4733 gamma+=(*k);
4734 k++;
4735 }
4736 }
4737 j+=image->columns+width;
4738 }
4739 if (gamma != 0.0)
4740 {
4741 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4742 gamma);
cristy6db48122010-01-11 00:18:07 +00004743 blur_indexes[x]=ClampToQuantum(gamma*
4744 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004745 }
4746 }
4747 }
4748 p++;
4749 q++;
4750 }
4751 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
4752 if (sync == MagickFalse)
4753 status=MagickFalse;
4754 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4755 {
4756 MagickBooleanType
4757 proceed;
4758
cristyb5d5f722009-11-04 03:03:49 +00004759#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004760 #pragma omp critical (MagickCore_SelectiveBlurImageChannel)
4761#endif
4762 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress++,
4763 image->rows);
4764 if (proceed == MagickFalse)
4765 status=MagickFalse;
4766 }
4767 }
4768 blur_image->type=image->type;
4769 blur_view=DestroyCacheView(blur_view);
4770 image_view=DestroyCacheView(image_view);
4771 kernel=(double *) RelinquishMagickMemory(kernel);
4772 if (status == MagickFalse)
4773 blur_image=DestroyImage(blur_image);
4774 return(blur_image);
4775}
4776
4777/*
4778%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4779% %
4780% %
4781% %
4782% S h a d e I m a g e %
4783% %
4784% %
4785% %
4786%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4787%
4788% ShadeImage() shines a distant light on an image to create a
4789% three-dimensional effect. You control the positioning of the light with
4790% azimuth and elevation; azimuth is measured in degrees off the x axis
4791% and elevation is measured in pixels above the Z axis.
4792%
4793% The format of the ShadeImage method is:
4794%
4795% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
4796% const double azimuth,const double elevation,ExceptionInfo *exception)
4797%
4798% A description of each parameter follows:
4799%
4800% o image: the image.
4801%
4802% o gray: A value other than zero shades the intensity of each pixel.
4803%
4804% o azimuth, elevation: Define the light source direction.
4805%
4806% o exception: return any errors or warnings in this structure.
4807%
4808*/
4809MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
4810 const double azimuth,const double elevation,ExceptionInfo *exception)
4811{
4812#define ShadeImageTag "Shade/Image"
4813
cristyc4c8d132010-01-07 01:58:38 +00004814 CacheView
4815 *image_view,
4816 *shade_view;
4817
cristy3ed852e2009-09-05 21:47:34 +00004818 Image
4819 *shade_image;
4820
4821 long
4822 progress,
4823 y;
4824
4825 MagickBooleanType
4826 status;
4827
4828 PrimaryInfo
4829 light;
4830
cristy3ed852e2009-09-05 21:47:34 +00004831 /*
4832 Initialize shaded image attributes.
4833 */
4834 assert(image != (const Image *) NULL);
4835 assert(image->signature == MagickSignature);
4836 if (image->debug != MagickFalse)
4837 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4838 assert(exception != (ExceptionInfo *) NULL);
4839 assert(exception->signature == MagickSignature);
4840 shade_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
4841 if (shade_image == (Image *) NULL)
4842 return((Image *) NULL);
4843 if (SetImageStorageClass(shade_image,DirectClass) == MagickFalse)
4844 {
4845 InheritException(exception,&shade_image->exception);
4846 shade_image=DestroyImage(shade_image);
4847 return((Image *) NULL);
4848 }
4849 /*
4850 Compute the light vector.
4851 */
4852 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
4853 cos(DegreesToRadians(elevation));
4854 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
4855 cos(DegreesToRadians(elevation));
4856 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
4857 /*
4858 Shade image.
4859 */
4860 status=MagickTrue;
4861 progress=0;
4862 image_view=AcquireCacheView(image);
4863 shade_view=AcquireCacheView(shade_image);
cristyb5d5f722009-11-04 03:03:49 +00004864#if defined(MAGICKCORE_OPENMP_SUPPORT)
4865 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004866#endif
4867 for (y=0; y < (long) image->rows; y++)
4868 {
4869 MagickRealType
4870 distance,
4871 normal_distance,
4872 shade;
4873
4874 PrimaryInfo
4875 normal;
4876
4877 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004878 *restrict p,
4879 *restrict s0,
4880 *restrict s1,
4881 *restrict s2;
cristy3ed852e2009-09-05 21:47:34 +00004882
4883 register long
4884 x;
4885
4886 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004887 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004888
4889 if (status == MagickFalse)
4890 continue;
4891 p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception);
4892 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
4893 exception);
4894 if ((p == (PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
4895 {
4896 status=MagickFalse;
4897 continue;
4898 }
4899 /*
4900 Shade this row of pixels.
4901 */
4902 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
4903 s0=p+1;
4904 s1=s0+image->columns+2;
4905 s2=s1+image->columns+2;
4906 for (x=0; x < (long) image->columns; x++)
4907 {
4908 /*
4909 Determine the surface normal and compute shading.
4910 */
4911 normal.x=(double) (PixelIntensity(s0-1)+PixelIntensity(s1-1)+
4912 PixelIntensity(s2-1)-PixelIntensity(s0+1)-PixelIntensity(s1+1)-
4913 PixelIntensity(s2+1));
4914 normal.y=(double) (PixelIntensity(s2-1)+PixelIntensity(s2)+
4915 PixelIntensity(s2+1)-PixelIntensity(s0-1)-PixelIntensity(s0)-
4916 PixelIntensity(s0+1));
4917 if ((normal.x == 0.0) && (normal.y == 0.0))
4918 shade=light.z;
4919 else
4920 {
4921 shade=0.0;
4922 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
4923 if (distance > MagickEpsilon)
4924 {
4925 normal_distance=
4926 normal.x*normal.x+normal.y*normal.y+normal.z*normal.z;
4927 if (normal_distance > (MagickEpsilon*MagickEpsilon))
4928 shade=distance/sqrt((double) normal_distance);
4929 }
4930 }
4931 if (gray != MagickFalse)
4932 {
4933 q->red=(Quantum) shade;
4934 q->green=(Quantum) shade;
4935 q->blue=(Quantum) shade;
4936 }
4937 else
4938 {
cristyce70c172010-01-07 17:15:30 +00004939 q->red=ClampToQuantum(QuantumScale*shade*s1->red);
4940 q->green=ClampToQuantum(QuantumScale*shade*s1->green);
4941 q->blue=ClampToQuantum(QuantumScale*shade*s1->blue);
cristy3ed852e2009-09-05 21:47:34 +00004942 }
4943 q->opacity=s1->opacity;
4944 s0++;
4945 s1++;
4946 s2++;
4947 q++;
4948 }
4949 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
4950 status=MagickFalse;
4951 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4952 {
4953 MagickBooleanType
4954 proceed;
4955
cristyb5d5f722009-11-04 03:03:49 +00004956#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004957 #pragma omp critical (MagickCore_ShadeImage)
4958#endif
4959 proceed=SetImageProgress(image,ShadeImageTag,progress++,image->rows);
4960 if (proceed == MagickFalse)
4961 status=MagickFalse;
4962 }
4963 }
4964 shade_view=DestroyCacheView(shade_view);
4965 image_view=DestroyCacheView(image_view);
4966 if (status == MagickFalse)
4967 shade_image=DestroyImage(shade_image);
4968 return(shade_image);
4969}
4970
4971/*
4972%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4973% %
4974% %
4975% %
4976% S h a r p e n I m a g e %
4977% %
4978% %
4979% %
4980%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4981%
4982% SharpenImage() sharpens the image. We convolve the image with a Gaussian
4983% operator of the given radius and standard deviation (sigma). For
4984% reasonable results, radius should be larger than sigma. Use a radius of 0
4985% and SharpenImage() selects a suitable radius for you.
4986%
4987% Using a separable kernel would be faster, but the negative weights cancel
4988% out on the corners of the kernel producing often undesirable ringing in the
4989% filtered result; this can be avoided by using a 2D gaussian shaped image
4990% sharpening kernel instead.
4991%
4992% The format of the SharpenImage method is:
4993%
4994% Image *SharpenImage(const Image *image,const double radius,
4995% const double sigma,ExceptionInfo *exception)
4996% Image *SharpenImageChannel(const Image *image,const ChannelType channel,
4997% const double radius,const double sigma,ExceptionInfo *exception)
4998%
4999% A description of each parameter follows:
5000%
5001% o image: the image.
5002%
5003% o channel: the channel type.
5004%
5005% o radius: the radius of the Gaussian, in pixels, not counting the center
5006% pixel.
5007%
5008% o sigma: the standard deviation of the Laplacian, in pixels.
5009%
5010% o exception: return any errors or warnings in this structure.
5011%
5012*/
5013
5014MagickExport Image *SharpenImage(const Image *image,const double radius,
5015 const double sigma,ExceptionInfo *exception)
5016{
5017 Image
5018 *sharp_image;
5019
5020 sharp_image=SharpenImageChannel(image,DefaultChannels,radius,sigma,exception);
5021 return(sharp_image);
5022}
5023
5024MagickExport Image *SharpenImageChannel(const Image *image,
5025 const ChannelType channel,const double radius,const double sigma,
5026 ExceptionInfo *exception)
5027{
5028 double
cristy47e00502009-12-17 19:19:57 +00005029 *kernel,
5030 normalize;
cristy3ed852e2009-09-05 21:47:34 +00005031
5032 Image
5033 *sharp_image;
5034
cristy47e00502009-12-17 19:19:57 +00005035 long
5036 j,
cristy3ed852e2009-09-05 21:47:34 +00005037 u,
5038 v;
5039
cristy47e00502009-12-17 19:19:57 +00005040 register long
5041 i;
5042
cristy3ed852e2009-09-05 21:47:34 +00005043 unsigned long
5044 width;
5045
5046 assert(image != (const Image *) NULL);
5047 assert(image->signature == MagickSignature);
5048 if (image->debug != MagickFalse)
5049 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5050 assert(exception != (ExceptionInfo *) NULL);
5051 assert(exception->signature == MagickSignature);
5052 width=GetOptimalKernelWidth2D(radius,sigma);
5053 kernel=(double *) AcquireQuantumMemory((size_t) width*width,sizeof(*kernel));
5054 if (kernel == (double *) NULL)
5055 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy3ed852e2009-09-05 21:47:34 +00005056 normalize=0.0;
cristy47e00502009-12-17 19:19:57 +00005057 j=(long) width/2;
5058 i=0;
5059 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00005060 {
cristy47e00502009-12-17 19:19:57 +00005061 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00005062 {
cristy47e00502009-12-17 19:19:57 +00005063 kernel[i]=(-exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
5064 (2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00005065 normalize+=kernel[i];
5066 i++;
5067 }
5068 }
5069 kernel[i/2]=(double) ((-2.0)*normalize);
5070 sharp_image=ConvolveImageChannel(image,channel,width,kernel,exception);
5071 kernel=(double *) RelinquishMagickMemory(kernel);
5072 return(sharp_image);
5073}
5074
5075/*
5076%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5077% %
5078% %
5079% %
5080% S p r e a d I m a g e %
5081% %
5082% %
5083% %
5084%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5085%
5086% SpreadImage() is a special effects method that randomly displaces each
5087% pixel in a block defined by the radius parameter.
5088%
5089% The format of the SpreadImage method is:
5090%
5091% Image *SpreadImage(const Image *image,const double radius,
5092% ExceptionInfo *exception)
5093%
5094% A description of each parameter follows:
5095%
5096% o image: the image.
5097%
5098% o radius: Choose a random pixel in a neighborhood of this extent.
5099%
5100% o exception: return any errors or warnings in this structure.
5101%
5102*/
5103MagickExport Image *SpreadImage(const Image *image,const double radius,
5104 ExceptionInfo *exception)
5105{
5106#define SpreadImageTag "Spread/Image"
5107
cristyfa112112010-01-04 17:48:07 +00005108 CacheView
5109 *image_view;
5110
cristy3ed852e2009-09-05 21:47:34 +00005111 Image
5112 *spread_image;
5113
5114 long
5115 progress,
5116 y;
5117
5118 MagickBooleanType
5119 status;
5120
5121 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00005122 bias;
cristy3ed852e2009-09-05 21:47:34 +00005123
5124 RandomInfo
cristyfa112112010-01-04 17:48:07 +00005125 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00005126
5127 ResampleFilter
cristyfa112112010-01-04 17:48:07 +00005128 **restrict resample_filter;
cristy3ed852e2009-09-05 21:47:34 +00005129
5130 unsigned long
5131 width;
5132
cristy3ed852e2009-09-05 21:47:34 +00005133 /*
5134 Initialize spread image attributes.
5135 */
5136 assert(image != (Image *) NULL);
5137 assert(image->signature == MagickSignature);
5138 if (image->debug != MagickFalse)
5139 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5140 assert(exception != (ExceptionInfo *) NULL);
5141 assert(exception->signature == MagickSignature);
5142 spread_image=CloneImage(image,image->columns,image->rows,MagickTrue,
5143 exception);
5144 if (spread_image == (Image *) NULL)
5145 return((Image *) NULL);
5146 if (SetImageStorageClass(spread_image,DirectClass) == MagickFalse)
5147 {
5148 InheritException(exception,&spread_image->exception);
5149 spread_image=DestroyImage(spread_image);
5150 return((Image *) NULL);
5151 }
5152 /*
5153 Spread image.
5154 */
5155 status=MagickTrue;
5156 progress=0;
cristyddd82202009-11-03 20:14:50 +00005157 GetMagickPixelPacket(spread_image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00005158 width=GetOptimalKernelWidth1D(radius,0.5);
cristyb2a11ae2010-02-22 00:53:36 +00005159 resample_filter=AcquireResampleFilterThreadSet(image,
5160 UndefinedVirtualPixelMethod,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00005161 random_info=AcquireRandomInfoThreadSet();
5162 image_view=AcquireCacheView(spread_image);
cristyd3dd0502010-04-26 00:57:20 +00005163#if defined(MAGICKCORE_OPENMP_SUPPORT) && defined(MAGICKCORE_FUTURE)
cristy8a7ea362010-03-10 20:31:43 +00005164 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00005165#endif
5166 for (y=0; y < (long) spread_image->rows; y++)
5167 {
5168 MagickPixelPacket
5169 pixel;
5170
5171 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00005172 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00005173
5174 register long
5175 id,
5176 x;
5177
5178 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00005179 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00005180
5181 if (status == MagickFalse)
5182 continue;
5183 q=QueueCacheViewAuthenticPixels(image_view,0,y,spread_image->columns,1,
5184 exception);
5185 if (q == (PixelPacket *) NULL)
5186 {
5187 status=MagickFalse;
5188 continue;
5189 }
5190 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristyddd82202009-11-03 20:14:50 +00005191 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00005192 id=GetOpenMPThreadId();
5193 for (x=0; x < (long) spread_image->columns; x++)
5194 {
5195 (void) ResamplePixelColor(resample_filter[id],(double) x+width*
5196 (GetPseudoRandomValue(random_info[id])-0.5),(double) y+width*
5197 (GetPseudoRandomValue(random_info[id])-0.5),&pixel);
5198 SetPixelPacket(spread_image,&pixel,q,indexes+x);
5199 q++;
5200 }
5201 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
5202 status=MagickFalse;
5203 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5204 {
5205 MagickBooleanType
5206 proceed;
5207
cristyd3dd0502010-04-26 00:57:20 +00005208#if defined(MAGICKCORE_OPENMP_SUPPORT) && defined(MAGICKCORE_FUTURE)
cristy3ed852e2009-09-05 21:47:34 +00005209 #pragma omp critical (MagickCore_SpreadImage)
5210#endif
5211 proceed=SetImageProgress(image,SpreadImageTag,progress++,image->rows);
5212 if (proceed == MagickFalse)
5213 status=MagickFalse;
5214 }
5215 }
5216 image_view=DestroyCacheView(image_view);
5217 random_info=DestroyRandomInfoThreadSet(random_info);
5218 resample_filter=DestroyResampleFilterThreadSet(resample_filter);
5219 return(spread_image);
5220}
5221
5222/*
5223%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5224% %
5225% %
5226% %
5227% U n s h a r p M a s k I m a g e %
5228% %
5229% %
5230% %
5231%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5232%
5233% UnsharpMaskImage() sharpens one or more image channels. We convolve the
5234% image with a Gaussian operator of the given radius and standard deviation
5235% (sigma). For reasonable results, radius should be larger than sigma. Use a
5236% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
5237%
5238% The format of the UnsharpMaskImage method is:
5239%
5240% Image *UnsharpMaskImage(const Image *image,const double radius,
5241% const double sigma,const double amount,const double threshold,
5242% ExceptionInfo *exception)
5243% Image *UnsharpMaskImageChannel(const Image *image,
5244% const ChannelType channel,const double radius,const double sigma,
5245% const double amount,const double threshold,ExceptionInfo *exception)
5246%
5247% A description of each parameter follows:
5248%
5249% o image: the image.
5250%
5251% o channel: the channel type.
5252%
5253% o radius: the radius of the Gaussian, in pixels, not counting the center
5254% pixel.
5255%
5256% o sigma: the standard deviation of the Gaussian, in pixels.
5257%
5258% o amount: the percentage of the difference between the original and the
5259% blur image that is added back into the original.
5260%
5261% o threshold: the threshold in pixels needed to apply the diffence amount.
5262%
5263% o exception: return any errors or warnings in this structure.
5264%
5265*/
5266
5267MagickExport Image *UnsharpMaskImage(const Image *image,const double radius,
5268 const double sigma,const double amount,const double threshold,
5269 ExceptionInfo *exception)
5270{
5271 Image
5272 *sharp_image;
5273
5274 sharp_image=UnsharpMaskImageChannel(image,DefaultChannels,radius,sigma,amount,
5275 threshold,exception);
5276 return(sharp_image);
5277}
5278
5279MagickExport Image *UnsharpMaskImageChannel(const Image *image,
5280 const ChannelType channel,const double radius,const double sigma,
5281 const double amount,const double threshold,ExceptionInfo *exception)
5282{
5283#define SharpenImageTag "Sharpen/Image"
5284
cristyc4c8d132010-01-07 01:58:38 +00005285 CacheView
5286 *image_view,
5287 *unsharp_view;
5288
cristy3ed852e2009-09-05 21:47:34 +00005289 Image
5290 *unsharp_image;
5291
5292 long
5293 progress,
5294 y;
5295
5296 MagickBooleanType
5297 status;
5298
5299 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00005300 bias;
cristy3ed852e2009-09-05 21:47:34 +00005301
5302 MagickRealType
5303 quantum_threshold;
5304
cristy3ed852e2009-09-05 21:47:34 +00005305 assert(image != (const Image *) NULL);
5306 assert(image->signature == MagickSignature);
5307 if (image->debug != MagickFalse)
5308 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5309 assert(exception != (ExceptionInfo *) NULL);
5310 unsharp_image=BlurImageChannel(image,channel,radius,sigma,exception);
5311 if (unsharp_image == (Image *) NULL)
5312 return((Image *) NULL);
5313 quantum_threshold=(MagickRealType) QuantumRange*threshold;
5314 /*
5315 Unsharp-mask image.
5316 */
5317 status=MagickTrue;
5318 progress=0;
cristyddd82202009-11-03 20:14:50 +00005319 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00005320 image_view=AcquireCacheView(image);
5321 unsharp_view=AcquireCacheView(unsharp_image);
cristyb5d5f722009-11-04 03:03:49 +00005322#if defined(MAGICKCORE_OPENMP_SUPPORT)
5323 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00005324#endif
5325 for (y=0; y < (long) image->rows; y++)
5326 {
5327 MagickPixelPacket
5328 pixel;
5329
5330 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00005331 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00005332
5333 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00005334 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00005335
5336 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00005337 *restrict unsharp_indexes;
cristy3ed852e2009-09-05 21:47:34 +00005338
5339 register long
5340 x;
5341
5342 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00005343 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00005344
5345 if (status == MagickFalse)
5346 continue;
5347 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
5348 q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
5349 exception);
5350 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
5351 {
5352 status=MagickFalse;
5353 continue;
5354 }
5355 indexes=GetCacheViewVirtualIndexQueue(image_view);
5356 unsharp_indexes=GetCacheViewAuthenticIndexQueue(unsharp_view);
cristyddd82202009-11-03 20:14:50 +00005357 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00005358 for (x=0; x < (long) image->columns; x++)
5359 {
5360 if ((channel & RedChannel) != 0)
5361 {
5362 pixel.red=p->red-(MagickRealType) q->red;
5363 if (fabs(2.0*pixel.red) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005364 pixel.red=(MagickRealType) GetRedPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005365 else
5366 pixel.red=(MagickRealType) p->red+(pixel.red*amount);
cristyce70c172010-01-07 17:15:30 +00005367 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00005368 }
5369 if ((channel & GreenChannel) != 0)
5370 {
5371 pixel.green=p->green-(MagickRealType) q->green;
5372 if (fabs(2.0*pixel.green) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005373 pixel.green=(MagickRealType) GetGreenPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005374 else
5375 pixel.green=(MagickRealType) p->green+(pixel.green*amount);
cristyce70c172010-01-07 17:15:30 +00005376 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00005377 }
5378 if ((channel & BlueChannel) != 0)
5379 {
5380 pixel.blue=p->blue-(MagickRealType) q->blue;
5381 if (fabs(2.0*pixel.blue) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005382 pixel.blue=(MagickRealType) GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005383 else
5384 pixel.blue=(MagickRealType) p->blue+(pixel.blue*amount);
cristyce70c172010-01-07 17:15:30 +00005385 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00005386 }
5387 if ((channel & OpacityChannel) != 0)
5388 {
5389 pixel.opacity=p->opacity-(MagickRealType) q->opacity;
5390 if (fabs(2.0*pixel.opacity) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005391 pixel.opacity=(MagickRealType) GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005392 else
5393 pixel.opacity=p->opacity+(pixel.opacity*amount);
cristyce70c172010-01-07 17:15:30 +00005394 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00005395 }
5396 if (((channel & IndexChannel) != 0) &&
5397 (image->colorspace == CMYKColorspace))
5398 {
5399 pixel.index=unsharp_indexes[x]-(MagickRealType) indexes[x];
5400 if (fabs(2.0*pixel.index) < quantum_threshold)
5401 pixel.index=(MagickRealType) unsharp_indexes[x];
5402 else
5403 pixel.index=(MagickRealType) unsharp_indexes[x]+(pixel.index*
5404 amount);
cristyce70c172010-01-07 17:15:30 +00005405 unsharp_indexes[x]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00005406 }
5407 p++;
5408 q++;
5409 }
5410 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
5411 status=MagickFalse;
5412 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5413 {
5414 MagickBooleanType
5415 proceed;
5416
cristyb5d5f722009-11-04 03:03:49 +00005417#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00005418 #pragma omp critical (MagickCore_UnsharpMaskImageChannel)
5419#endif
5420 proceed=SetImageProgress(image,SharpenImageTag,progress++,image->rows);
5421 if (proceed == MagickFalse)
5422 status=MagickFalse;
5423 }
5424 }
5425 unsharp_image->type=image->type;
5426 unsharp_view=DestroyCacheView(unsharp_view);
5427 image_view=DestroyCacheView(image_view);
5428 if (status == MagickFalse)
5429 unsharp_image=DestroyImage(unsharp_image);
5430 return(unsharp_image);
5431}