blob: 21aefe805281904f50c2aa3c729a977ce959fd59 [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"
67#include "magick/paint.h"
68#include "magick/pixel-private.h"
69#include "magick/property.h"
70#include "magick/quantize.h"
71#include "magick/quantum.h"
72#include "magick/random_.h"
73#include "magick/random-private.h"
74#include "magick/resample.h"
75#include "magick/resample-private.h"
76#include "magick/resize.h"
77#include "magick/resource_.h"
78#include "magick/segment.h"
79#include "magick/shear.h"
80#include "magick/signature-private.h"
81#include "magick/string_.h"
82#include "magick/thread-private.h"
83#include "magick/transform.h"
84#include "magick/threshold.h"
85
86/*
87%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
88% %
89% %
90% %
91% A d a p t i v e B l u r I m a g e %
92% %
93% %
94% %
95%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
96%
97% AdaptiveBlurImage() adaptively blurs the image by blurring less
98% intensely near image edges and more intensely far from edges. We blur the
99% image with a Gaussian operator of the given radius and standard deviation
100% (sigma). For reasonable results, radius should be larger than sigma. Use a
101% radius of 0 and AdaptiveBlurImage() selects a suitable radius for you.
102%
103% The format of the AdaptiveBlurImage method is:
104%
105% Image *AdaptiveBlurImage(const Image *image,const double radius,
106% const double sigma,ExceptionInfo *exception)
107% Image *AdaptiveBlurImageChannel(const Image *image,
108% const ChannelType channel,double radius,const double sigma,
109% ExceptionInfo *exception)
110%
111% A description of each parameter follows:
112%
113% o image: the image.
114%
115% o channel: the channel type.
116%
117% o radius: the radius of the Gaussian, in pixels, not counting the center
118% pixel.
119%
120% o sigma: the standard deviation of the Laplacian, in pixels.
121%
122% o exception: return any errors or warnings in this structure.
123%
124*/
125
126MagickExport Image *AdaptiveBlurImage(const Image *image,const double radius,
127 const double sigma,ExceptionInfo *exception)
128{
129 Image
130 *blur_image;
131
132 blur_image=AdaptiveBlurImageChannel(image,DefaultChannels,radius,sigma,
133 exception);
134 return(blur_image);
135}
136
137MagickExport Image *AdaptiveBlurImageChannel(const Image *image,
138 const ChannelType channel,const double radius,const double sigma,
139 ExceptionInfo *exception)
140{
141#define AdaptiveBlurImageTag "Convolve/Image"
142#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
143
cristyc4c8d132010-01-07 01:58:38 +0000144 CacheView
145 *blur_view,
146 *edge_view,
147 *image_view;
148
cristy3ed852e2009-09-05 21:47:34 +0000149 double
cristy47e00502009-12-17 19:19:57 +0000150 **kernel,
151 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000152
153 Image
154 *blur_image,
155 *edge_image,
156 *gaussian_image;
157
158 long
159 j,
cristy47e00502009-12-17 19:19:57 +0000160 k,
cristy3ed852e2009-09-05 21:47:34 +0000161 progress,
cristy47e00502009-12-17 19:19:57 +0000162 u,
163 v,
cristy3ed852e2009-09-05 21:47:34 +0000164 y;
165
166 MagickBooleanType
167 status;
168
169 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +0000170 bias;
cristy3ed852e2009-09-05 21:47:34 +0000171
cristy3ed852e2009-09-05 21:47:34 +0000172 register long
cristy47e00502009-12-17 19:19:57 +0000173 i;
cristy3ed852e2009-09-05 21:47:34 +0000174
175 unsigned long
176 width;
177
cristy3ed852e2009-09-05 21:47:34 +0000178 assert(image != (const Image *) NULL);
179 assert(image->signature == MagickSignature);
180 if (image->debug != MagickFalse)
181 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
182 assert(exception != (ExceptionInfo *) NULL);
183 assert(exception->signature == MagickSignature);
184 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
185 if (blur_image == (Image *) NULL)
186 return((Image *) NULL);
187 if (fabs(sigma) <= MagickEpsilon)
188 return(blur_image);
189 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
190 {
191 InheritException(exception,&blur_image->exception);
192 blur_image=DestroyImage(blur_image);
193 return((Image *) NULL);
194 }
195 /*
196 Edge detect the image brighness channel, level, blur, and level again.
197 */
198 edge_image=EdgeImage(image,radius,exception);
199 if (edge_image == (Image *) NULL)
200 {
201 blur_image=DestroyImage(blur_image);
202 return((Image *) NULL);
203 }
204 (void) LevelImage(edge_image,"20%,95%");
205 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,exception);
206 if (gaussian_image != (Image *) NULL)
207 {
208 edge_image=DestroyImage(edge_image);
209 edge_image=gaussian_image;
210 }
211 (void) LevelImage(edge_image,"10%,95%");
212 /*
213 Create a set of kernels from maximum (radius,sigma) to minimum.
214 */
215 width=GetOptimalKernelWidth2D(radius,sigma);
216 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
217 if (kernel == (double **) NULL)
218 {
219 edge_image=DestroyImage(edge_image);
220 blur_image=DestroyImage(blur_image);
221 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
222 }
223 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
224 for (i=0; i < (long) width; i+=2)
225 {
226 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
227 sizeof(**kernel));
228 if (kernel[i] == (double *) NULL)
229 break;
cristy47e00502009-12-17 19:19:57 +0000230 normalize=0.0;
231 j=(long) (width-i)/2;
232 k=0;
233 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000234 {
cristy47e00502009-12-17 19:19:57 +0000235 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000236 {
cristy47e00502009-12-17 19:19:57 +0000237 kernel[i][k]=exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
238 (2.0*MagickPI*MagickSigma*MagickSigma);
239 normalize+=kernel[i][k];
240 k++;
cristy3ed852e2009-09-05 21:47:34 +0000241 }
242 }
cristy3ed852e2009-09-05 21:47:34 +0000243 if (fabs(normalize) <= MagickEpsilon)
244 normalize=1.0;
245 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000246 for (k=0; k < (j*j); k++)
247 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000248 }
249 if (i < (long) width)
250 {
251 for (i-=2; i >= 0; i-=2)
252 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
253 kernel=(double **) RelinquishMagickMemory(kernel);
254 edge_image=DestroyImage(edge_image);
255 blur_image=DestroyImage(blur_image);
256 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
257 }
258 /*
259 Adaptively blur image.
260 */
261 status=MagickTrue;
262 progress=0;
cristyddd82202009-11-03 20:14:50 +0000263 GetMagickPixelPacket(image,&bias);
264 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000265 image_view=AcquireCacheView(image);
266 edge_view=AcquireCacheView(edge_image);
267 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000268#if defined(MAGICKCORE_OPENMP_SUPPORT)
269 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000270#endif
271 for (y=0; y < (long) blur_image->rows; y++)
272 {
273 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000274 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000275
276 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000277 *restrict p,
278 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000279
280 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000281 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000282
283 register long
284 x;
285
286 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000287 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000288
289 if (status == MagickFalse)
290 continue;
291 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
292 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
293 exception);
294 if ((r == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
295 {
296 status=MagickFalse;
297 continue;
298 }
299 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
300 for (x=0; x < (long) blur_image->columns; x++)
301 {
302 MagickPixelPacket
303 pixel;
304
305 MagickRealType
306 alpha,
307 gamma;
308
309 register const double
cristyc47d1f82009-11-26 01:44:43 +0000310 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000311
312 register long
313 i,
314 u,
315 v;
316
317 gamma=0.0;
318 i=(long) (width*QuantumScale*PixelIntensity(r)+0.5);
319 if (i < 0)
320 i=0;
321 else
322 if (i > (long) width)
323 i=(long) width;
324 if ((i & 0x01) != 0)
325 i--;
326 p=GetCacheViewVirtualPixels(image_view,x-((long) (width-i)/2L),y-(long)
327 ((width-i)/2L),width-i,width-i,exception);
328 if (p == (const PixelPacket *) NULL)
329 break;
330 indexes=GetCacheViewVirtualIndexQueue(image_view);
cristyddd82202009-11-03 20:14:50 +0000331 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +0000332 k=kernel[i];
333 for (v=0; v < (long) (width-i); v++)
334 {
335 for (u=0; u < (long) (width-i); u++)
336 {
337 alpha=1.0;
338 if (((channel & OpacityChannel) != 0) &&
339 (image->matte != MagickFalse))
cristy46f08202010-01-10 04:04:21 +0000340 alpha=(MagickRealType) (QuantumScale*GetAlphaPixelComponent(p));
cristy3ed852e2009-09-05 21:47:34 +0000341 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000342 pixel.red+=(*k)*alpha*GetRedPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000343 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000344 pixel.green+=(*k)*alpha*GetGreenPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000345 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000346 pixel.blue+=(*k)*alpha*GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000347 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000348 pixel.opacity+=(*k)*GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000349 if (((channel & IndexChannel) != 0) &&
350 (image->colorspace == CMYKColorspace))
351 pixel.index+=(*k)*alpha*indexes[x+(width-i)*v+u];
352 gamma+=(*k)*alpha;
353 k++;
354 p++;
355 }
356 }
357 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
358 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000359 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000360 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000361 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000362 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000363 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000364 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000365 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000366 if (((channel & IndexChannel) != 0) &&
367 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +0000368 blur_indexes[x]=ClampToQuantum(gamma*GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000369 q++;
370 r++;
371 }
372 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
373 status=MagickFalse;
374 if (image->progress_monitor != (MagickProgressMonitor) NULL)
375 {
376 MagickBooleanType
377 proceed;
378
cristyb5d5f722009-11-04 03:03:49 +0000379#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000380 #pragma omp critical (MagickCore_AdaptiveBlurImageChannel)
381#endif
382 proceed=SetImageProgress(image,AdaptiveBlurImageTag,progress++,
383 image->rows);
384 if (proceed == MagickFalse)
385 status=MagickFalse;
386 }
387 }
388 blur_image->type=image->type;
389 blur_view=DestroyCacheView(blur_view);
390 edge_view=DestroyCacheView(edge_view);
391 image_view=DestroyCacheView(image_view);
392 edge_image=DestroyImage(edge_image);
393 for (i=0; i < (long) width; i+=2)
394 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
395 kernel=(double **) RelinquishMagickMemory(kernel);
396 if (status == MagickFalse)
397 blur_image=DestroyImage(blur_image);
398 return(blur_image);
399}
400
401/*
402%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
403% %
404% %
405% %
406% A d a p t i v e S h a r p e n I m a g e %
407% %
408% %
409% %
410%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
411%
412% AdaptiveSharpenImage() adaptively sharpens the image by sharpening more
413% intensely near image edges and less intensely far from edges. We sharpen the
414% image with a Gaussian operator of the given radius and standard deviation
415% (sigma). For reasonable results, radius should be larger than sigma. Use a
416% radius of 0 and AdaptiveSharpenImage() selects a suitable radius for you.
417%
418% The format of the AdaptiveSharpenImage method is:
419%
420% Image *AdaptiveSharpenImage(const Image *image,const double radius,
421% const double sigma,ExceptionInfo *exception)
422% Image *AdaptiveSharpenImageChannel(const Image *image,
423% const ChannelType channel,double radius,const double sigma,
424% ExceptionInfo *exception)
425%
426% A description of each parameter follows:
427%
428% o image: the image.
429%
430% o channel: the channel type.
431%
432% o radius: the radius of the Gaussian, in pixels, not counting the center
433% pixel.
434%
435% o sigma: the standard deviation of the Laplacian, in pixels.
436%
437% o exception: return any errors or warnings in this structure.
438%
439*/
440
441MagickExport Image *AdaptiveSharpenImage(const Image *image,const double radius,
442 const double sigma,ExceptionInfo *exception)
443{
444 Image
445 *sharp_image;
446
447 sharp_image=AdaptiveSharpenImageChannel(image,DefaultChannels,radius,sigma,
448 exception);
449 return(sharp_image);
450}
451
452MagickExport Image *AdaptiveSharpenImageChannel(const Image *image,
453 const ChannelType channel,const double radius,const double sigma,
454 ExceptionInfo *exception)
455{
456#define AdaptiveSharpenImageTag "Convolve/Image"
457#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
458
cristyc4c8d132010-01-07 01:58:38 +0000459 CacheView
460 *sharp_view,
461 *edge_view,
462 *image_view;
463
cristy3ed852e2009-09-05 21:47:34 +0000464 double
cristy47e00502009-12-17 19:19:57 +0000465 **kernel,
466 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000467
468 Image
469 *sharp_image,
470 *edge_image,
471 *gaussian_image;
472
473 long
474 j,
cristy47e00502009-12-17 19:19:57 +0000475 k,
cristy3ed852e2009-09-05 21:47:34 +0000476 progress,
cristy47e00502009-12-17 19:19:57 +0000477 u,
478 v,
cristy3ed852e2009-09-05 21:47:34 +0000479 y;
480
481 MagickBooleanType
482 status;
483
484 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +0000485 bias;
cristy3ed852e2009-09-05 21:47:34 +0000486
cristy3ed852e2009-09-05 21:47:34 +0000487 register long
cristy47e00502009-12-17 19:19:57 +0000488 i;
cristy3ed852e2009-09-05 21:47:34 +0000489
490 unsigned long
491 width;
492
cristy3ed852e2009-09-05 21:47:34 +0000493 assert(image != (const Image *) NULL);
494 assert(image->signature == MagickSignature);
495 if (image->debug != MagickFalse)
496 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
497 assert(exception != (ExceptionInfo *) NULL);
498 assert(exception->signature == MagickSignature);
499 sharp_image=CloneImage(image,0,0,MagickTrue,exception);
500 if (sharp_image == (Image *) NULL)
501 return((Image *) NULL);
502 if (fabs(sigma) <= MagickEpsilon)
503 return(sharp_image);
504 if (SetImageStorageClass(sharp_image,DirectClass) == MagickFalse)
505 {
506 InheritException(exception,&sharp_image->exception);
507 sharp_image=DestroyImage(sharp_image);
508 return((Image *) NULL);
509 }
510 /*
511 Edge detect the image brighness channel, level, sharp, and level again.
512 */
513 edge_image=EdgeImage(image,radius,exception);
514 if (edge_image == (Image *) NULL)
515 {
516 sharp_image=DestroyImage(sharp_image);
517 return((Image *) NULL);
518 }
519 (void) LevelImage(edge_image,"20%,95%");
520 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,exception);
521 if (gaussian_image != (Image *) NULL)
522 {
523 edge_image=DestroyImage(edge_image);
524 edge_image=gaussian_image;
525 }
526 (void) LevelImage(edge_image,"10%,95%");
527 /*
528 Create a set of kernels from maximum (radius,sigma) to minimum.
529 */
530 width=GetOptimalKernelWidth2D(radius,sigma);
531 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
532 if (kernel == (double **) NULL)
533 {
534 edge_image=DestroyImage(edge_image);
535 sharp_image=DestroyImage(sharp_image);
536 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
537 }
538 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
539 for (i=0; i < (long) width; i+=2)
540 {
541 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
542 sizeof(**kernel));
543 if (kernel[i] == (double *) NULL)
544 break;
cristy47e00502009-12-17 19:19:57 +0000545 normalize=0.0;
546 j=(long) (width-i)/2;
547 k=0;
548 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000549 {
cristy47e00502009-12-17 19:19:57 +0000550 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000551 {
cristy47e00502009-12-17 19:19:57 +0000552 kernel[i][k]=(-exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
553 (2.0*MagickPI*MagickSigma*MagickSigma));
554 normalize+=kernel[i][k];
555 k++;
cristy3ed852e2009-09-05 21:47:34 +0000556 }
557 }
cristy3ed852e2009-09-05 21:47:34 +0000558 if (fabs(normalize) <= MagickEpsilon)
559 normalize=1.0;
560 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000561 for (k=0; k < (j*j); k++)
562 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000563 }
564 if (i < (long) width)
565 {
566 for (i-=2; i >= 0; i-=2)
567 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
568 kernel=(double **) RelinquishMagickMemory(kernel);
569 edge_image=DestroyImage(edge_image);
570 sharp_image=DestroyImage(sharp_image);
571 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
572 }
573 /*
574 Adaptively sharpen image.
575 */
576 status=MagickTrue;
577 progress=0;
cristyddd82202009-11-03 20:14:50 +0000578 GetMagickPixelPacket(image,&bias);
579 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000580 image_view=AcquireCacheView(image);
581 edge_view=AcquireCacheView(edge_image);
582 sharp_view=AcquireCacheView(sharp_image);
cristyb5d5f722009-11-04 03:03:49 +0000583#if defined(MAGICKCORE_OPENMP_SUPPORT)
584 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000585#endif
586 for (y=0; y < (long) sharp_image->rows; y++)
587 {
588 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000589 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000590
591 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000592 *restrict p,
593 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000594
595 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000596 *restrict sharp_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000597
598 register long
599 x;
600
601 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000602 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000603
604 if (status == MagickFalse)
605 continue;
606 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
607 q=QueueCacheViewAuthenticPixels(sharp_view,0,y,sharp_image->columns,1,
608 exception);
609 if ((r == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
610 {
611 status=MagickFalse;
612 continue;
613 }
614 sharp_indexes=GetCacheViewAuthenticIndexQueue(sharp_view);
615 for (x=0; x < (long) sharp_image->columns; x++)
616 {
617 MagickPixelPacket
618 pixel;
619
620 MagickRealType
621 alpha,
622 gamma;
623
624 register const double
cristyc47d1f82009-11-26 01:44:43 +0000625 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000626
627 register long
628 i,
629 u,
630 v;
631
632 gamma=0.0;
633 i=(long) (width*(QuantumRange-QuantumScale*PixelIntensity(r))+0.5);
634 if (i < 0)
635 i=0;
636 else
637 if (i > (long) width)
638 i=(long) width;
639 if ((i & 0x01) != 0)
640 i--;
641 p=GetCacheViewVirtualPixels(image_view,x-((long) (width-i)/2L),y-(long)
642 ((width-i)/2L),width-i,width-i,exception);
643 if (p == (const PixelPacket *) NULL)
644 break;
645 indexes=GetCacheViewVirtualIndexQueue(image_view);
646 k=kernel[i];
cristyddd82202009-11-03 20:14:50 +0000647 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +0000648 for (v=0; v < (long) (width-i); v++)
649 {
650 for (u=0; u < (long) (width-i); u++)
651 {
652 alpha=1.0;
653 if (((channel & OpacityChannel) != 0) &&
654 (image->matte != MagickFalse))
cristy46f08202010-01-10 04:04:21 +0000655 alpha=(MagickRealType) (QuantumScale*GetAlphaPixelComponent(p));
cristy3ed852e2009-09-05 21:47:34 +0000656 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000657 pixel.red+=(*k)*alpha*GetRedPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000658 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000659 pixel.green+=(*k)*alpha*GetGreenPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000660 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000661 pixel.blue+=(*k)*alpha*GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000662 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000663 pixel.opacity+=(*k)*GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000664 if (((channel & IndexChannel) != 0) &&
665 (image->colorspace == CMYKColorspace))
666 pixel.index+=(*k)*alpha*indexes[x+(width-i)*v+u];
667 gamma+=(*k)*alpha;
668 k++;
669 p++;
670 }
671 }
672 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
673 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000674 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000675 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000676 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000677 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000678 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000679 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000680 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000681 if (((channel & IndexChannel) != 0) &&
682 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +0000683 sharp_indexes[x]=ClampToQuantum(gamma*GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000684 q++;
685 r++;
686 }
687 if (SyncCacheViewAuthenticPixels(sharp_view,exception) == MagickFalse)
688 status=MagickFalse;
689 if (image->progress_monitor != (MagickProgressMonitor) NULL)
690 {
691 MagickBooleanType
692 proceed;
693
cristyb5d5f722009-11-04 03:03:49 +0000694#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000695 #pragma omp critical (MagickCore_AdaptiveSharpenImageChannel)
696#endif
697 proceed=SetImageProgress(image,AdaptiveSharpenImageTag,progress++,
698 image->rows);
699 if (proceed == MagickFalse)
700 status=MagickFalse;
701 }
702 }
703 sharp_image->type=image->type;
704 sharp_view=DestroyCacheView(sharp_view);
705 edge_view=DestroyCacheView(edge_view);
706 image_view=DestroyCacheView(image_view);
707 edge_image=DestroyImage(edge_image);
708 for (i=0; i < (long) width; i+=2)
709 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
710 kernel=(double **) RelinquishMagickMemory(kernel);
711 if (status == MagickFalse)
712 sharp_image=DestroyImage(sharp_image);
713 return(sharp_image);
714}
715
716/*
717%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
718% %
719% %
720% %
721% B l u r I m a g e %
722% %
723% %
724% %
725%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
726%
727% BlurImage() blurs an image. We convolve the image with a Gaussian operator
728% of the given radius and standard deviation (sigma). For reasonable results,
729% the radius should be larger than sigma. Use a radius of 0 and BlurImage()
730% selects a suitable radius for you.
731%
732% BlurImage() differs from GaussianBlurImage() in that it uses a separable
733% kernel which is faster but mathematically equivalent to the non-separable
734% kernel.
735%
736% The format of the BlurImage method is:
737%
738% Image *BlurImage(const Image *image,const double radius,
739% const double sigma,ExceptionInfo *exception)
740% Image *BlurImageChannel(const Image *image,const ChannelType channel,
741% const double radius,const double sigma,ExceptionInfo *exception)
742%
743% A description of each parameter follows:
744%
745% o image: the image.
746%
747% o channel: the channel type.
748%
749% o radius: the radius of the Gaussian, in pixels, not counting the center
750% pixel.
751%
752% o sigma: the standard deviation of the Gaussian, in pixels.
753%
754% o exception: return any errors or warnings in this structure.
755%
756*/
757
758MagickExport Image *BlurImage(const Image *image,const double radius,
759 const double sigma,ExceptionInfo *exception)
760{
761 Image
762 *blur_image;
763
764 blur_image=BlurImageChannel(image,DefaultChannels,radius,sigma,exception);
765 return(blur_image);
766}
767
cristy47e00502009-12-17 19:19:57 +0000768static double *GetBlurKernel(const unsigned long width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +0000769{
cristy3ed852e2009-09-05 21:47:34 +0000770 double
cristy47e00502009-12-17 19:19:57 +0000771 *kernel,
772 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000773
774 long
cristy47e00502009-12-17 19:19:57 +0000775 j,
776 k;
cristy3ed852e2009-09-05 21:47:34 +0000777
778 register long
779 i;
780
781 /*
782 Generate a 1-D convolution kernel.
783 */
784 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
785 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
786 if (kernel == (double *) NULL)
787 return(0);
cristy3ed852e2009-09-05 21:47:34 +0000788 normalize=0.0;
cristy47e00502009-12-17 19:19:57 +0000789 j=(long) width/2;
790 i=0;
791 for (k=(-j); k <= j; k++)
792 {
cristyf267c722009-12-18 00:07:22 +0000793 kernel[i]=exp(-((double) k*k)/(2.0*MagickSigma*MagickSigma))/
cristy47e00502009-12-17 19:19:57 +0000794 (MagickSQ2PI*MagickSigma);
cristy3ed852e2009-09-05 21:47:34 +0000795 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +0000796 i++;
797 }
cristy3ed852e2009-09-05 21:47:34 +0000798 for (i=0; i < (long) width; i++)
799 kernel[i]/=normalize;
800 return(kernel);
801}
802
803MagickExport Image *BlurImageChannel(const Image *image,
804 const ChannelType channel,const double radius,const double sigma,
805 ExceptionInfo *exception)
806{
807#define BlurImageTag "Blur/Image"
808
cristyc4c8d132010-01-07 01:58:38 +0000809 CacheView
810 *blur_view,
811 *image_view;
812
cristy3ed852e2009-09-05 21:47:34 +0000813 double
814 *kernel;
815
816 Image
817 *blur_image;
818
819 long
820 progress,
821 x,
822 y;
823
824 MagickBooleanType
825 status;
826
827 MagickPixelPacket
cristy3ed852e2009-09-05 21:47:34 +0000828 bias;
829
830 register long
831 i;
832
833 unsigned long
834 width;
835
cristy3ed852e2009-09-05 21:47:34 +0000836 /*
837 Initialize blur image attributes.
838 */
839 assert(image != (Image *) NULL);
840 assert(image->signature == MagickSignature);
841 if (image->debug != MagickFalse)
842 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
843 assert(exception != (ExceptionInfo *) NULL);
844 assert(exception->signature == MagickSignature);
845 blur_image=CloneImage(image,0,0,MagickTrue,exception);
846 if (blur_image == (Image *) NULL)
847 return((Image *) NULL);
848 if (fabs(sigma) <= MagickEpsilon)
849 return(blur_image);
850 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
851 {
852 InheritException(exception,&blur_image->exception);
853 blur_image=DestroyImage(blur_image);
854 return((Image *) NULL);
855 }
856 width=GetOptimalKernelWidth1D(radius,sigma);
857 kernel=GetBlurKernel(width,sigma);
858 if (kernel == (double *) NULL)
859 {
860 blur_image=DestroyImage(blur_image);
861 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
862 }
863 if (image->debug != MagickFalse)
864 {
865 char
866 format[MaxTextExtent],
867 *message;
868
869 register const double
870 *k;
871
872 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
873 " BlurImage with %ld kernel:",width);
874 message=AcquireString("");
875 k=kernel;
876 for (i=0; i < (long) width; i++)
877 {
878 *message='\0';
879 (void) FormatMagickString(format,MaxTextExtent,"%ld: ",i);
880 (void) ConcatenateString(&message,format);
cristye7f51092010-01-17 00:39:37 +0000881 (void) FormatMagickString(format,MaxTextExtent,"%g ",*k++);
cristy3ed852e2009-09-05 21:47:34 +0000882 (void) ConcatenateString(&message,format);
883 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
884 }
885 message=DestroyString(message);
886 }
887 /*
888 Blur rows.
889 */
890 status=MagickTrue;
891 progress=0;
cristyddd82202009-11-03 20:14:50 +0000892 GetMagickPixelPacket(image,&bias);
893 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000894 image_view=AcquireCacheView(image);
895 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000896#if defined(MAGICKCORE_OPENMP_SUPPORT)
897 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000898#endif
899 for (y=0; y < (long) blur_image->rows; y++)
900 {
901 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000902 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000903
904 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000905 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000906
907 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000908 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000909
910 register long
911 x;
912
913 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000914 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000915
916 if (status == MagickFalse)
917 continue;
918 p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y,image->columns+
919 width,1,exception);
920 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
921 exception);
922 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
923 {
924 status=MagickFalse;
925 continue;
926 }
927 indexes=GetCacheViewVirtualIndexQueue(image_view);
928 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
929 for (x=0; x < (long) blur_image->columns; x++)
930 {
931 MagickPixelPacket
932 pixel;
933
934 register const double
cristyc47d1f82009-11-26 01:44:43 +0000935 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000936
937 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000938 *restrict kernel_pixels;
cristy3ed852e2009-09-05 21:47:34 +0000939
940 register long
941 i;
942
cristyddd82202009-11-03 20:14:50 +0000943 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +0000944 k=kernel;
945 kernel_pixels=p;
946 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
947 {
948 for (i=0; i < (long) width; i++)
949 {
950 pixel.red+=(*k)*kernel_pixels->red;
951 pixel.green+=(*k)*kernel_pixels->green;
952 pixel.blue+=(*k)*kernel_pixels->blue;
953 k++;
954 kernel_pixels++;
955 }
956 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000957 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000958 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000959 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000960 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000961 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000962 if ((channel & OpacityChannel) != 0)
963 {
964 k=kernel;
965 kernel_pixels=p;
966 for (i=0; i < (long) width; i++)
967 {
968 pixel.opacity+=(*k)*kernel_pixels->opacity;
969 k++;
970 kernel_pixels++;
971 }
cristyce70c172010-01-07 17:15:30 +0000972 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000973 }
974 if (((channel & IndexChannel) != 0) &&
975 (image->colorspace == CMYKColorspace))
976 {
977 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000978 *restrict kernel_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000979
980 k=kernel;
981 kernel_indexes=indexes;
982 for (i=0; i < (long) width; i++)
983 {
984 pixel.index+=(*k)*(*kernel_indexes);
985 k++;
986 kernel_indexes++;
987 }
cristyce70c172010-01-07 17:15:30 +0000988 blur_indexes[x]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +0000989 }
990 }
991 else
992 {
993 MagickRealType
994 alpha,
995 gamma;
996
997 gamma=0.0;
998 for (i=0; i < (long) width; i++)
999 {
cristy46f08202010-01-10 04:04:21 +00001000 alpha=(MagickRealType) (QuantumScale*
1001 GetAlphaPixelComponent(kernel_pixels));
cristy3ed852e2009-09-05 21:47:34 +00001002 pixel.red+=(*k)*alpha*kernel_pixels->red;
1003 pixel.green+=(*k)*alpha*kernel_pixels->green;
1004 pixel.blue+=(*k)*alpha*kernel_pixels->blue;
1005 gamma+=(*k)*alpha;
1006 k++;
1007 kernel_pixels++;
1008 }
1009 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1010 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001011 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001012 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001013 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001014 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001015 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001016 if ((channel & OpacityChannel) != 0)
1017 {
1018 k=kernel;
1019 kernel_pixels=p;
1020 for (i=0; i < (long) width; i++)
1021 {
1022 pixel.opacity+=(*k)*kernel_pixels->opacity;
1023 k++;
1024 kernel_pixels++;
1025 }
cristyce70c172010-01-07 17:15:30 +00001026 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001027 }
1028 if (((channel & IndexChannel) != 0) &&
1029 (image->colorspace == CMYKColorspace))
1030 {
1031 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001032 *restrict kernel_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001033
1034 k=kernel;
1035 kernel_pixels=p;
1036 kernel_indexes=indexes;
1037 for (i=0; i < (long) width; i++)
1038 {
cristy46f08202010-01-10 04:04:21 +00001039 alpha=(MagickRealType) (QuantumScale*
1040 GetAlphaPixelComponent(kernel_pixels));
cristy3ed852e2009-09-05 21:47:34 +00001041 pixel.index+=(*k)*alpha*(*kernel_indexes);
1042 k++;
1043 kernel_pixels++;
1044 kernel_indexes++;
1045 }
cristy46f08202010-01-10 04:04:21 +00001046 blur_indexes[x]=ClampToQuantum(gamma*
1047 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001048 }
1049 }
1050 p++;
1051 q++;
1052 }
1053 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1054 status=MagickFalse;
1055 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1056 {
1057 MagickBooleanType
1058 proceed;
1059
cristyb5d5f722009-11-04 03:03:49 +00001060#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001061 #pragma omp critical (MagickCore_BlurImageChannel)
1062#endif
1063 proceed=SetImageProgress(image,BlurImageTag,progress++,blur_image->rows+
1064 blur_image->columns);
1065 if (proceed == MagickFalse)
1066 status=MagickFalse;
1067 }
1068 }
1069 blur_view=DestroyCacheView(blur_view);
1070 image_view=DestroyCacheView(image_view);
1071 /*
1072 Blur columns.
1073 */
1074 image_view=AcquireCacheView(blur_image);
1075 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00001076#if defined(MAGICKCORE_OPENMP_SUPPORT)
1077 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001078#endif
1079 for (x=0; x < (long) blur_image->columns; x++)
1080 {
1081 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001082 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001083
1084 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001085 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001086
1087 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001088 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001089
1090 register long
1091 y;
1092
1093 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001094 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001095
1096 if (status == MagickFalse)
1097 continue;
1098 p=GetCacheViewVirtualPixels(image_view,x,-((long) width/2L),1,image->rows+
1099 width,exception);
1100 q=GetCacheViewAuthenticPixels(blur_view,x,0,1,blur_image->rows,exception);
1101 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1102 {
1103 status=MagickFalse;
1104 continue;
1105 }
1106 indexes=GetCacheViewVirtualIndexQueue(image_view);
1107 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
1108 for (y=0; y < (long) blur_image->rows; y++)
1109 {
1110 MagickPixelPacket
1111 pixel;
1112
1113 register const double
cristyc47d1f82009-11-26 01:44:43 +00001114 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00001115
1116 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001117 *restrict kernel_pixels;
cristy3ed852e2009-09-05 21:47:34 +00001118
1119 register long
1120 i;
1121
cristyddd82202009-11-03 20:14:50 +00001122 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00001123 k=kernel;
1124 kernel_pixels=p;
1125 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
1126 {
1127 for (i=0; i < (long) width; i++)
1128 {
1129 pixel.red+=(*k)*kernel_pixels->red;
1130 pixel.green+=(*k)*kernel_pixels->green;
1131 pixel.blue+=(*k)*kernel_pixels->blue;
1132 k++;
1133 kernel_pixels++;
1134 }
1135 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001136 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001137 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001138 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001139 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001140 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001141 if ((channel & OpacityChannel) != 0)
1142 {
1143 k=kernel;
1144 kernel_pixels=p;
1145 for (i=0; i < (long) width; i++)
1146 {
1147 pixel.opacity+=(*k)*kernel_pixels->opacity;
1148 k++;
1149 kernel_pixels++;
1150 }
cristyce70c172010-01-07 17:15:30 +00001151 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001152 }
1153 if (((channel & IndexChannel) != 0) &&
1154 (image->colorspace == CMYKColorspace))
1155 {
1156 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001157 *restrict kernel_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001158
1159 k=kernel;
1160 kernel_indexes=indexes;
1161 for (i=0; i < (long) width; i++)
1162 {
1163 pixel.index+=(*k)*(*kernel_indexes);
1164 k++;
1165 kernel_indexes++;
1166 }
cristyce70c172010-01-07 17:15:30 +00001167 blur_indexes[y]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00001168 }
1169 }
1170 else
1171 {
1172 MagickRealType
1173 alpha,
1174 gamma;
1175
1176 gamma=0.0;
1177 for (i=0; i < (long) width; i++)
1178 {
cristy46f08202010-01-10 04:04:21 +00001179 alpha=(MagickRealType) (QuantumScale*
1180 GetAlphaPixelComponent(kernel_pixels));
cristy3ed852e2009-09-05 21:47:34 +00001181 pixel.red+=(*k)*alpha*kernel_pixels->red;
1182 pixel.green+=(*k)*alpha*kernel_pixels->green;
1183 pixel.blue+=(*k)*alpha*kernel_pixels->blue;
1184 gamma+=(*k)*alpha;
1185 k++;
1186 kernel_pixels++;
1187 }
1188 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1189 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001190 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001191 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001192 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001193 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001194 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001195 if ((channel & OpacityChannel) != 0)
1196 {
1197 k=kernel;
1198 kernel_pixels=p;
1199 for (i=0; i < (long) width; i++)
1200 {
1201 pixel.opacity+=(*k)*kernel_pixels->opacity;
1202 k++;
1203 kernel_pixels++;
1204 }
cristyce70c172010-01-07 17:15:30 +00001205 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001206 }
1207 if (((channel & IndexChannel) != 0) &&
1208 (image->colorspace == CMYKColorspace))
1209 {
1210 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001211 *restrict kernel_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001212
1213 k=kernel;
1214 kernel_pixels=p;
1215 kernel_indexes=indexes;
1216 for (i=0; i < (long) width; i++)
1217 {
cristy46f08202010-01-10 04:04:21 +00001218 alpha=(MagickRealType) (QuantumScale*
1219 GetAlphaPixelComponent(kernel_pixels));
cristy3ed852e2009-09-05 21:47:34 +00001220 pixel.index+=(*k)*alpha*(*kernel_indexes);
1221 k++;
1222 kernel_pixels++;
1223 kernel_indexes++;
1224 }
cristy46f08202010-01-10 04:04:21 +00001225 blur_indexes[y]=ClampToQuantum(gamma*
1226 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001227 }
1228 }
1229 p++;
1230 q++;
1231 }
1232 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1233 status=MagickFalse;
1234 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1235 {
1236 MagickBooleanType
1237 proceed;
1238
cristyb5d5f722009-11-04 03:03:49 +00001239#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001240 #pragma omp critical (MagickCore_BlurImageChannel)
1241#endif
1242 proceed=SetImageProgress(image,BlurImageTag,progress++,blur_image->rows+
1243 blur_image->columns);
1244 if (proceed == MagickFalse)
1245 status=MagickFalse;
1246 }
1247 }
1248 blur_view=DestroyCacheView(blur_view);
1249 image_view=DestroyCacheView(image_view);
1250 kernel=(double *) RelinquishMagickMemory(kernel);
1251 if (status == MagickFalse)
1252 blur_image=DestroyImage(blur_image);
1253 blur_image->type=image->type;
1254 return(blur_image);
1255}
1256
1257/*
1258%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1259% %
1260% %
1261% %
cristyfccdab92009-11-30 16:43:57 +00001262% C o n v o l v e I m a g e %
1263% %
1264% %
1265% %
1266%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1267%
1268% ConvolveImage() applies a custom convolution kernel to the image.
1269%
1270% The format of the ConvolveImage method is:
1271%
1272% Image *ConvolveImage(const Image *image,const unsigned long order,
1273% const double *kernel,ExceptionInfo *exception)
1274% Image *ConvolveImageChannel(const Image *image,const ChannelType channel,
1275% const unsigned long order,const double *kernel,
1276% ExceptionInfo *exception)
1277%
1278% A description of each parameter follows:
1279%
1280% o image: the image.
1281%
1282% o channel: the channel type.
1283%
1284% o order: the number of columns and rows in the filter kernel.
1285%
1286% o kernel: An array of double representing the convolution kernel.
1287%
1288% o exception: return any errors or warnings in this structure.
1289%
1290*/
1291
1292MagickExport Image *ConvolveImage(const Image *image,const unsigned long order,
1293 const double *kernel,ExceptionInfo *exception)
1294{
1295 Image
1296 *convolve_image;
1297
1298 convolve_image=ConvolveImageChannel(image,DefaultChannels,order,kernel,
1299 exception);
1300 return(convolve_image);
1301}
1302
1303MagickExport Image *ConvolveImageChannel(const Image *image,
1304 const ChannelType channel,const unsigned long order,const double *kernel,
1305 ExceptionInfo *exception)
1306{
1307#define ConvolveImageTag "Convolve/Image"
1308
cristyc4c8d132010-01-07 01:58:38 +00001309 CacheView
1310 *convolve_view,
1311 *image_view;
1312
cristyfccdab92009-11-30 16:43:57 +00001313 double
1314 *normal_kernel;
1315
1316 Image
1317 *convolve_image;
1318
1319 long
1320 progress,
1321 y;
1322
1323 MagickBooleanType
1324 status;
1325
1326 MagickPixelPacket
1327 bias;
1328
1329 MagickRealType
1330 gamma;
1331
1332 register long
1333 i;
1334
1335 unsigned long
1336 width;
1337
cristyfccdab92009-11-30 16:43:57 +00001338 /*
1339 Initialize convolve image attributes.
1340 */
1341 assert(image != (Image *) NULL);
1342 assert(image->signature == MagickSignature);
1343 if (image->debug != MagickFalse)
1344 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1345 assert(exception != (ExceptionInfo *) NULL);
1346 assert(exception->signature == MagickSignature);
1347 width=order;
1348 if ((width % 2) == 0)
1349 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
1350 convolve_image=CloneImage(image,0,0,MagickTrue,exception);
1351 if (convolve_image == (Image *) NULL)
1352 return((Image *) NULL);
1353 if (SetImageStorageClass(convolve_image,DirectClass) == MagickFalse)
1354 {
1355 InheritException(exception,&convolve_image->exception);
1356 convolve_image=DestroyImage(convolve_image);
1357 return((Image *) NULL);
1358 }
1359 if (image->debug != MagickFalse)
1360 {
1361 char
1362 format[MaxTextExtent],
1363 *message;
1364
1365 long
1366 u,
1367 v;
1368
1369 register const double
1370 *k;
1371
1372 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
1373 " ConvolveImage with %ldx%ld kernel:",width,width);
1374 message=AcquireString("");
1375 k=kernel;
1376 for (v=0; v < (long) width; v++)
1377 {
1378 *message='\0';
1379 (void) FormatMagickString(format,MaxTextExtent,"%ld: ",v);
1380 (void) ConcatenateString(&message,format);
1381 for (u=0; u < (long) width; u++)
1382 {
cristye7f51092010-01-17 00:39:37 +00001383 (void) FormatMagickString(format,MaxTextExtent,"%g ",*k++);
cristyfccdab92009-11-30 16:43:57 +00001384 (void) ConcatenateString(&message,format);
1385 }
1386 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
1387 }
1388 message=DestroyString(message);
1389 }
1390 /*
1391 Normalize kernel.
1392 */
1393 normal_kernel=(double *) AcquireQuantumMemory(width*width,
1394 sizeof(*normal_kernel));
1395 if (normal_kernel == (double *) NULL)
1396 {
1397 convolve_image=DestroyImage(convolve_image);
1398 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1399 }
1400 gamma=0.0;
1401 for (i=0; i < (long) (width*width); i++)
1402 gamma+=kernel[i];
1403 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1404 for (i=0; i < (long) (width*width); i++)
1405 normal_kernel[i]=gamma*kernel[i];
1406 /*
1407 Convolve image.
1408 */
1409 status=MagickTrue;
1410 progress=0;
1411 GetMagickPixelPacket(image,&bias);
1412 SetMagickPixelPacketBias(image,&bias);
1413 image_view=AcquireCacheView(image);
1414 convolve_view=AcquireCacheView(convolve_image);
1415#if defined(MAGICKCORE_OPENMP_SUPPORT)
1416 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1417#endif
1418 for (y=0; y < (long) image->rows; y++)
1419 {
1420 MagickBooleanType
1421 sync;
1422
1423 register const IndexPacket
1424 *restrict indexes;
1425
1426 register const PixelPacket
1427 *restrict p;
1428
1429 register IndexPacket
1430 *restrict convolve_indexes;
1431
1432 register long
1433 x;
1434
1435 register PixelPacket
1436 *restrict q;
1437
1438 if (status == MagickFalse)
1439 continue;
1440 p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y-(long) (width/
1441 2L),image->columns+width,width,exception);
1442 q=GetCacheViewAuthenticPixels(convolve_view,0,y,convolve_image->columns,1,
1443 exception);
1444 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1445 {
1446 status=MagickFalse;
1447 continue;
1448 }
1449 indexes=GetCacheViewVirtualIndexQueue(image_view);
1450 convolve_indexes=GetCacheViewAuthenticIndexQueue(convolve_view);
1451 for (x=0; x < (long) image->columns; x++)
1452 {
1453 long
1454 v;
1455
1456 MagickPixelPacket
1457 pixel;
1458
1459 register const double
1460 *restrict k;
1461
1462 register const PixelPacket
1463 *restrict kernel_pixels;
1464
1465 register long
1466 u;
1467
1468 pixel=bias;
1469 k=normal_kernel;
1470 kernel_pixels=p;
1471 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
1472 {
1473 for (v=0; v < (long) width; v++)
1474 {
1475 for (u=0; u < (long) width; u++)
1476 {
1477 pixel.red+=(*k)*kernel_pixels[u].red;
1478 pixel.green+=(*k)*kernel_pixels[u].green;
1479 pixel.blue+=(*k)*kernel_pixels[u].blue;
1480 k++;
1481 }
1482 kernel_pixels+=image->columns+width;
1483 }
1484 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001485 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001486 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001487 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001488 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001489 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001490 if ((channel & OpacityChannel) != 0)
1491 {
1492 k=normal_kernel;
1493 kernel_pixels=p;
1494 for (v=0; v < (long) width; v++)
1495 {
1496 for (u=0; u < (long) width; u++)
1497 {
1498 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
1499 k++;
1500 }
1501 kernel_pixels+=image->columns+width;
1502 }
cristyce70c172010-01-07 17:15:30 +00001503 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001504 }
1505 if (((channel & IndexChannel) != 0) &&
1506 (image->colorspace == CMYKColorspace))
1507 {
1508 register const IndexPacket
1509 *restrict kernel_indexes;
1510
1511 k=normal_kernel;
1512 kernel_indexes=indexes;
1513 for (v=0; v < (long) width; v++)
1514 {
1515 for (u=0; u < (long) width; u++)
1516 {
1517 pixel.index+=(*k)*kernel_indexes[u];
1518 k++;
1519 }
1520 kernel_indexes+=image->columns+width;
1521 }
cristyce70c172010-01-07 17:15:30 +00001522 convolve_indexes[x]=ClampToQuantum(pixel.index);
cristyfccdab92009-11-30 16:43:57 +00001523 }
1524 }
1525 else
1526 {
1527 MagickRealType
1528 alpha,
1529 gamma;
1530
1531 gamma=0.0;
1532 for (v=0; v < (long) width; v++)
1533 {
1534 for (u=0; u < (long) width; u++)
1535 {
1536 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
1537 kernel_pixels[u].opacity));
1538 pixel.red+=(*k)*alpha*kernel_pixels[u].red;
1539 pixel.green+=(*k)*alpha*kernel_pixels[u].green;
1540 pixel.blue+=(*k)*alpha*kernel_pixels[u].blue;
cristyfccdab92009-11-30 16:43:57 +00001541 gamma+=(*k)*alpha;
1542 k++;
1543 }
1544 kernel_pixels+=image->columns+width;
1545 }
1546 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1547 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001548 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001549 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001550 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001551 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001552 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001553 if ((channel & OpacityChannel) != 0)
1554 {
1555 k=normal_kernel;
1556 kernel_pixels=p;
1557 for (v=0; v < (long) width; v++)
1558 {
1559 for (u=0; u < (long) width; u++)
1560 {
1561 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
1562 k++;
1563 }
1564 kernel_pixels+=image->columns+width;
1565 }
cristyce70c172010-01-07 17:15:30 +00001566 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001567 }
1568 if (((channel & IndexChannel) != 0) &&
1569 (image->colorspace == CMYKColorspace))
1570 {
1571 register const IndexPacket
1572 *restrict kernel_indexes;
1573
1574 k=normal_kernel;
1575 kernel_pixels=p;
1576 kernel_indexes=indexes;
1577 for (v=0; v < (long) width; v++)
1578 {
1579 for (u=0; u < (long) width; u++)
1580 {
1581 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
1582 kernel_pixels[u].opacity));
1583 pixel.index+=(*k)*alpha*kernel_indexes[u];
1584 k++;
1585 }
1586 kernel_pixels+=image->columns+width;
1587 kernel_indexes+=image->columns+width;
1588 }
cristy24b06da2010-01-09 23:05:56 +00001589 convolve_indexes[x]=ClampToQuantum(gamma*
1590 GetIndexPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001591 }
1592 }
1593 p++;
1594 q++;
1595 }
1596 sync=SyncCacheViewAuthenticPixels(convolve_view,exception);
1597 if (sync == MagickFalse)
1598 status=MagickFalse;
1599 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1600 {
1601 MagickBooleanType
1602 proceed;
1603
1604#if defined(MAGICKCORE_OPENMP_SUPPORT)
1605 #pragma omp critical (MagickCore_ConvolveImageChannel)
1606#endif
1607 proceed=SetImageProgress(image,ConvolveImageTag,progress++,image->rows);
1608 if (proceed == MagickFalse)
1609 status=MagickFalse;
1610 }
1611 }
1612 convolve_image->type=image->type;
1613 convolve_view=DestroyCacheView(convolve_view);
1614 image_view=DestroyCacheView(image_view);
1615 normal_kernel=(double *) RelinquishMagickMemory(normal_kernel);
1616 if (status == MagickFalse)
1617 convolve_image=DestroyImage(convolve_image);
1618 return(convolve_image);
1619}
1620
1621/*
1622%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1623% %
1624% %
1625% %
cristy3ed852e2009-09-05 21:47:34 +00001626% D e s p e c k l e I m a g e %
1627% %
1628% %
1629% %
1630%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1631%
1632% DespeckleImage() reduces the speckle noise in an image while perserving the
1633% edges of the original image.
1634%
1635% The format of the DespeckleImage method is:
1636%
1637% Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1638%
1639% A description of each parameter follows:
1640%
1641% o image: the image.
1642%
1643% o exception: return any errors or warnings in this structure.
1644%
1645*/
1646
1647static Quantum **DestroyPixelThreadSet(Quantum **pixels)
1648{
1649 register long
1650 i;
1651
1652 assert(pixels != (Quantum **) NULL);
1653 for (i=0; i < (long) GetOpenMPMaximumThreads(); i++)
1654 if (pixels[i] != (Quantum *) NULL)
1655 pixels[i]=(Quantum *) RelinquishMagickMemory(pixels[i]);
1656 pixels=(Quantum **) RelinquishAlignedMemory(pixels);
1657 return(pixels);
1658}
1659
1660static Quantum **AcquirePixelThreadSet(const size_t count)
1661{
1662 register long
1663 i;
1664
1665 Quantum
1666 **pixels;
1667
1668 unsigned long
1669 number_threads;
1670
1671 number_threads=GetOpenMPMaximumThreads();
1672 pixels=(Quantum **) AcquireAlignedMemory(number_threads,sizeof(*pixels));
1673 if (pixels == (Quantum **) NULL)
1674 return((Quantum **) NULL);
1675 (void) ResetMagickMemory(pixels,0,number_threads*sizeof(*pixels));
1676 for (i=0; i < (long) number_threads; i++)
1677 {
1678 pixels[i]=(Quantum *) AcquireQuantumMemory(count,sizeof(**pixels));
1679 if (pixels[i] == (Quantum *) NULL)
1680 return(DestroyPixelThreadSet(pixels));
1681 }
1682 return(pixels);
1683}
1684
1685static void Hull(const long x_offset,const long y_offset,
1686 const unsigned long columns,const unsigned long rows,Quantum *f,Quantum *g,
1687 const int polarity)
1688{
1689 long
1690 y;
1691
1692 MagickRealType
1693 v;
1694
1695 register long
1696 x;
1697
1698 register Quantum
1699 *p,
1700 *q,
1701 *r,
1702 *s;
1703
1704 assert(f != (Quantum *) NULL);
1705 assert(g != (Quantum *) NULL);
1706 p=f+(columns+2);
1707 q=g+(columns+2);
1708 r=p+(y_offset*((long) columns+2)+x_offset);
1709 for (y=0; y < (long) rows; y++)
1710 {
1711 p++;
1712 q++;
1713 r++;
1714 if (polarity > 0)
1715 for (x=(long) columns; x != 0; x--)
1716 {
1717 v=(MagickRealType) (*p);
1718 if ((MagickRealType) *r >= (v+(MagickRealType) ScaleCharToQuantum(2)))
1719 v+=ScaleCharToQuantum(1);
1720 *q=(Quantum) v;
1721 p++;
1722 q++;
1723 r++;
1724 }
1725 else
1726 for (x=(long) columns; x != 0; x--)
1727 {
1728 v=(MagickRealType) (*p);
1729 if ((MagickRealType) *r <= (v-(MagickRealType) ScaleCharToQuantum(2)))
1730 v-=(long) ScaleCharToQuantum(1);
1731 *q=(Quantum) v;
1732 p++;
1733 q++;
1734 r++;
1735 }
1736 p++;
1737 q++;
1738 r++;
1739 }
1740 p=f+(columns+2);
1741 q=g+(columns+2);
1742 r=q+(y_offset*((long) columns+2)+x_offset);
1743 s=q-(y_offset*((long) columns+2)+x_offset);
1744 for (y=0; y < (long) rows; y++)
1745 {
1746 p++;
1747 q++;
1748 r++;
1749 s++;
1750 if (polarity > 0)
1751 for (x=(long) columns; x != 0; x--)
1752 {
1753 v=(MagickRealType) (*q);
1754 if (((MagickRealType) *s >=
1755 (v+(MagickRealType) ScaleCharToQuantum(2))) &&
1756 ((MagickRealType) *r > v))
1757 v+=ScaleCharToQuantum(1);
1758 *p=(Quantum) v;
1759 p++;
1760 q++;
1761 r++;
1762 s++;
1763 }
1764 else
1765 for (x=(long) columns; x != 0; x--)
1766 {
1767 v=(MagickRealType) (*q);
1768 if (((MagickRealType) *s <=
1769 (v-(MagickRealType) ScaleCharToQuantum(2))) &&
1770 ((MagickRealType) *r < v))
1771 v-=(MagickRealType) ScaleCharToQuantum(1);
1772 *p=(Quantum) v;
1773 p++;
1774 q++;
1775 r++;
1776 s++;
1777 }
1778 p++;
1779 q++;
1780 r++;
1781 s++;
1782 }
1783}
1784
1785MagickExport Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1786{
1787#define DespeckleImageTag "Despeckle/Image"
1788
cristy2407fc22009-09-11 00:55:25 +00001789 CacheView
1790 *despeckle_view,
1791 *image_view;
1792
cristy3ed852e2009-09-05 21:47:34 +00001793 Image
1794 *despeckle_image;
1795
1796 long
1797 channel;
1798
1799 MagickBooleanType
1800 status;
1801
1802 Quantum
cristyfa112112010-01-04 17:48:07 +00001803 **restrict buffers,
1804 **restrict pixels;
cristy3ed852e2009-09-05 21:47:34 +00001805
1806 size_t
1807 length;
1808
1809 static const int
cristy691a29e2009-09-11 00:44:10 +00001810 X[4] = {0, 1, 1,-1},
1811 Y[4] = {1, 0, 1, 1};
cristy3ed852e2009-09-05 21:47:34 +00001812
cristy3ed852e2009-09-05 21:47:34 +00001813 /*
1814 Allocate despeckled image.
1815 */
1816 assert(image != (const Image *) NULL);
1817 assert(image->signature == MagickSignature);
1818 if (image->debug != MagickFalse)
1819 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1820 assert(exception != (ExceptionInfo *) NULL);
1821 assert(exception->signature == MagickSignature);
1822 despeckle_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1823 exception);
1824 if (despeckle_image == (Image *) NULL)
1825 return((Image *) NULL);
1826 if (SetImageStorageClass(despeckle_image,DirectClass) == MagickFalse)
1827 {
1828 InheritException(exception,&despeckle_image->exception);
1829 despeckle_image=DestroyImage(despeckle_image);
1830 return((Image *) NULL);
1831 }
1832 /*
1833 Allocate image buffers.
1834 */
1835 length=(size_t) ((image->columns+2)*(image->rows+2));
1836 pixels=AcquirePixelThreadSet(length);
1837 buffers=AcquirePixelThreadSet(length);
1838 if ((pixels == (Quantum **) NULL) || (buffers == (Quantum **) NULL))
1839 {
1840 if (buffers != (Quantum **) NULL)
1841 buffers=DestroyPixelThreadSet(buffers);
1842 if (pixels != (Quantum **) NULL)
1843 pixels=DestroyPixelThreadSet(pixels);
1844 despeckle_image=DestroyImage(despeckle_image);
1845 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1846 }
1847 /*
1848 Reduce speckle in the image.
1849 */
1850 status=MagickTrue;
1851 image_view=AcquireCacheView(image);
1852 despeckle_view=AcquireCacheView(despeckle_image);
cristyb5d5f722009-11-04 03:03:49 +00001853#if defined(MAGICKCORE_OPENMP_SUPPORT)
1854 #pragma omp parallel for schedule(dynamic,4) shared(status)
cristy3ed852e2009-09-05 21:47:34 +00001855#endif
1856 for (channel=0; channel <= 3; channel++)
1857 {
1858 long
1859 j,
1860 y;
1861
1862 register long
1863 i,
cristy691a29e2009-09-11 00:44:10 +00001864 id,
cristy3ed852e2009-09-05 21:47:34 +00001865 x;
1866
1867 register Quantum
1868 *buffer,
1869 *pixel;
1870
1871 if (status == MagickFalse)
1872 continue;
cristy691a29e2009-09-11 00:44:10 +00001873 id=GetOpenMPThreadId();
1874 pixel=pixels[id];
cristy3ed852e2009-09-05 21:47:34 +00001875 (void) ResetMagickMemory(pixel,0,length*sizeof(*pixel));
cristy691a29e2009-09-11 00:44:10 +00001876 buffer=buffers[id];
cristy3ed852e2009-09-05 21:47:34 +00001877 j=(long) image->columns+2;
1878 for (y=0; y < (long) image->rows; y++)
1879 {
1880 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001881 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001882
1883 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1884 if (p == (const PixelPacket *) NULL)
1885 break;
1886 j++;
1887 for (x=0; x < (long) image->columns; x++)
1888 {
1889 switch (channel)
1890 {
cristyce70c172010-01-07 17:15:30 +00001891 case 0: pixel[j]=GetRedPixelComponent(p); break;
1892 case 1: pixel[j]=GetGreenPixelComponent(p); break;
1893 case 2: pixel[j]=GetBluePixelComponent(p); break;
1894 case 3: pixel[j]=GetOpacityPixelComponent(p); break;
cristy3ed852e2009-09-05 21:47:34 +00001895 default: break;
1896 }
1897 p++;
1898 j++;
1899 }
1900 j++;
1901 }
cristy3ed852e2009-09-05 21:47:34 +00001902 (void) ResetMagickMemory(buffer,0,length*sizeof(*buffer));
1903 for (i=0; i < 4; i++)
1904 {
1905 Hull(X[i],Y[i],image->columns,image->rows,pixel,buffer,1);
1906 Hull(-X[i],-Y[i],image->columns,image->rows,pixel,buffer,1);
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 }
1910 j=(long) image->columns+2;
1911 for (y=0; y < (long) image->rows; y++)
1912 {
1913 MagickBooleanType
1914 sync;
1915
1916 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001917 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001918
1919 q=GetCacheViewAuthenticPixels(despeckle_view,0,y,despeckle_image->columns,
1920 1,exception);
1921 if (q == (PixelPacket *) NULL)
1922 break;
1923 j++;
1924 for (x=0; x < (long) image->columns; x++)
1925 {
1926 switch (channel)
1927 {
1928 case 0: q->red=pixel[j]; break;
1929 case 1: q->green=pixel[j]; break;
1930 case 2: q->blue=pixel[j]; break;
1931 case 3: q->opacity=pixel[j]; break;
1932 default: break;
1933 }
1934 q++;
1935 j++;
1936 }
1937 sync=SyncCacheViewAuthenticPixels(despeckle_view,exception);
1938 if (sync == MagickFalse)
1939 {
1940 status=MagickFalse;
1941 break;
1942 }
1943 j++;
1944 }
1945 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1946 {
1947 MagickBooleanType
1948 proceed;
1949
cristyb5d5f722009-11-04 03:03:49 +00001950#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001951 #pragma omp critical (MagickCore_DespeckleImage)
1952#endif
1953 proceed=SetImageProgress(image,DespeckleImageTag,channel,3);
1954 if (proceed == MagickFalse)
1955 status=MagickFalse;
1956 }
1957 }
1958 despeckle_view=DestroyCacheView(despeckle_view);
1959 image_view=DestroyCacheView(image_view);
1960 buffers=DestroyPixelThreadSet(buffers);
1961 pixels=DestroyPixelThreadSet(pixels);
1962 despeckle_image->type=image->type;
1963 if (status == MagickFalse)
1964 despeckle_image=DestroyImage(despeckle_image);
1965 return(despeckle_image);
1966}
1967
1968/*
1969%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1970% %
1971% %
1972% %
1973% E d g e I m a g e %
1974% %
1975% %
1976% %
1977%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1978%
1979% EdgeImage() finds edges in an image. Radius defines the radius of the
1980% convolution filter. Use a radius of 0 and EdgeImage() selects a suitable
1981% radius for you.
1982%
1983% The format of the EdgeImage method is:
1984%
1985% Image *EdgeImage(const Image *image,const double radius,
1986% ExceptionInfo *exception)
1987%
1988% A description of each parameter follows:
1989%
1990% o image: the image.
1991%
1992% o radius: the radius of the pixel neighborhood.
1993%
1994% o exception: return any errors or warnings in this structure.
1995%
1996*/
1997MagickExport Image *EdgeImage(const Image *image,const double radius,
1998 ExceptionInfo *exception)
1999{
2000 Image
2001 *edge_image;
2002
2003 double
2004 *kernel;
2005
2006 register long
2007 i;
2008
2009 unsigned long
2010 width;
2011
2012 assert(image != (const Image *) NULL);
2013 assert(image->signature == MagickSignature);
2014 if (image->debug != MagickFalse)
2015 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2016 assert(exception != (ExceptionInfo *) NULL);
2017 assert(exception->signature == MagickSignature);
2018 width=GetOptimalKernelWidth1D(radius,0.5);
2019 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2020 if (kernel == (double *) NULL)
2021 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2022 for (i=0; i < (long) (width*width); i++)
2023 kernel[i]=(-1.0);
2024 kernel[i/2]=(double) (width*width-1.0);
2025 edge_image=ConvolveImage(image,width,kernel,exception);
2026 kernel=(double *) RelinquishMagickMemory(kernel);
2027 return(edge_image);
2028}
2029
2030/*
2031%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2032% %
2033% %
2034% %
2035% E m b o s s I m a g e %
2036% %
2037% %
2038% %
2039%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2040%
2041% EmbossImage() returns a grayscale image with a three-dimensional effect.
2042% We convolve the image with a Gaussian operator of the given radius and
2043% standard deviation (sigma). For reasonable results, radius should be
2044% larger than sigma. Use a radius of 0 and Emboss() selects a suitable
2045% radius for you.
2046%
2047% The format of the EmbossImage method is:
2048%
2049% Image *EmbossImage(const Image *image,const double radius,
2050% const double sigma,ExceptionInfo *exception)
2051%
2052% A description of each parameter follows:
2053%
2054% o image: the image.
2055%
2056% o radius: the radius of the pixel neighborhood.
2057%
2058% o sigma: the standard deviation of the Gaussian, in pixels.
2059%
2060% o exception: return any errors or warnings in this structure.
2061%
2062*/
2063MagickExport Image *EmbossImage(const Image *image,const double radius,
2064 const double sigma,ExceptionInfo *exception)
2065{
2066 double
2067 *kernel;
2068
2069 Image
2070 *emboss_image;
2071
2072 long
cristy47e00502009-12-17 19:19:57 +00002073 j,
2074 k,
cristy3ed852e2009-09-05 21:47:34 +00002075 u,
2076 v;
2077
cristy47e00502009-12-17 19:19:57 +00002078 register long
2079 i;
2080
cristy3ed852e2009-09-05 21:47:34 +00002081 unsigned long
2082 width;
2083
2084 assert(image != (Image *) NULL);
2085 assert(image->signature == MagickSignature);
2086 if (image->debug != MagickFalse)
2087 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2088 assert(exception != (ExceptionInfo *) NULL);
2089 assert(exception->signature == MagickSignature);
2090 width=GetOptimalKernelWidth2D(radius,sigma);
2091 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2092 if (kernel == (double *) NULL)
2093 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy3ed852e2009-09-05 21:47:34 +00002094 j=(long) width/2;
cristy47e00502009-12-17 19:19:57 +00002095 k=j;
2096 i=0;
2097 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002098 {
cristy47e00502009-12-17 19:19:57 +00002099 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00002100 {
cristy47e00502009-12-17 19:19:57 +00002101 kernel[i]=((u < 0) || (v < 0) ? -8.0 : 8.0)*
2102 exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
2103 (2.0*MagickPI*MagickSigma*MagickSigma);
2104 if (u != k)
cristy3ed852e2009-09-05 21:47:34 +00002105 kernel[i]=0.0;
2106 i++;
2107 }
cristy47e00502009-12-17 19:19:57 +00002108 k--;
cristy3ed852e2009-09-05 21:47:34 +00002109 }
2110 emboss_image=ConvolveImage(image,width,kernel,exception);
2111 if (emboss_image != (Image *) NULL)
2112 (void) EqualizeImage(emboss_image);
2113 kernel=(double *) RelinquishMagickMemory(kernel);
2114 return(emboss_image);
2115}
2116
2117/*
2118%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2119% %
2120% %
2121% %
cristy56a9e512010-01-06 18:18:55 +00002122% F i l t e r I m a g e %
2123% %
2124% %
2125% %
2126%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2127%
2128% FilterImage() applies a custom convolution kernel to the image.
2129%
2130% The format of the FilterImage method is:
2131%
cristy2be15382010-01-21 02:38:03 +00002132% Image *FilterImage(const Image *image,const KernelInfo *kernel,
cristy56a9e512010-01-06 18:18:55 +00002133% ExceptionInfo *exception)
2134% Image *FilterImageChannel(const Image *image,const ChannelType channel,
cristy2be15382010-01-21 02:38:03 +00002135% const KernelInfo *kernel,ExceptionInfo *exception)
cristy56a9e512010-01-06 18:18:55 +00002136%
2137% A description of each parameter follows:
2138%
2139% o image: the image.
2140%
2141% o channel: the channel type.
2142%
2143% o kernel: the filtering kernel.
2144%
2145% o exception: return any errors or warnings in this structure.
2146%
2147*/
2148
cristy2be15382010-01-21 02:38:03 +00002149MagickExport Image *FilterImage(const Image *image,const KernelInfo *kernel,
cristy56a9e512010-01-06 18:18:55 +00002150 ExceptionInfo *exception)
2151{
2152 Image
2153 *filter_image;
2154
2155 filter_image=FilterImageChannel(image,DefaultChannels,kernel,exception);
2156 return(filter_image);
2157}
2158
2159MagickExport Image *FilterImageChannel(const Image *image,
cristy2be15382010-01-21 02:38:03 +00002160 const ChannelType channel,const KernelInfo *kernel,ExceptionInfo *exception)
cristy56a9e512010-01-06 18:18:55 +00002161{
2162#define FilterImageTag "Filter/Image"
2163
2164 CacheView
2165 *filter_view,
2166 *image_view;
2167
cristy56a9e512010-01-06 18:18:55 +00002168 Image
2169 *filter_image;
2170
2171 long
2172 progress,
2173 y;
2174
2175 MagickBooleanType
2176 status;
2177
2178 MagickPixelPacket
2179 bias;
2180
cristy56a9e512010-01-06 18:18:55 +00002181 /*
2182 Initialize filter image attributes.
2183 */
2184 assert(image != (Image *) NULL);
2185 assert(image->signature == MagickSignature);
2186 if (image->debug != MagickFalse)
2187 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2188 assert(exception != (ExceptionInfo *) NULL);
2189 assert(exception->signature == MagickSignature);
2190 if ((kernel->width % 2) == 0)
2191 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
2192 filter_image=CloneImage(image,0,0,MagickTrue,exception);
2193 if (filter_image == (Image *) NULL)
2194 return((Image *) NULL);
2195 if (SetImageStorageClass(filter_image,DirectClass) == MagickFalse)
2196 {
2197 InheritException(exception,&filter_image->exception);
2198 filter_image=DestroyImage(filter_image);
2199 return((Image *) NULL);
2200 }
2201 if (image->debug != MagickFalse)
2202 {
2203 char
2204 format[MaxTextExtent],
2205 *message;
2206
2207 long
2208 u,
2209 v;
2210
2211 register const double
2212 *k;
2213
2214 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
2215 " FilterImage with %ldx%ld kernel:",kernel->width,kernel->height);
2216 message=AcquireString("");
2217 k=kernel->values;
2218 for (v=0; v < (long) kernel->height; v++)
2219 {
2220 *message='\0';
2221 (void) FormatMagickString(format,MaxTextExtent,"%ld: ",v);
2222 (void) ConcatenateString(&message,format);
2223 for (u=0; u < (long) kernel->width; u++)
2224 {
cristye7f51092010-01-17 00:39:37 +00002225 (void) FormatMagickString(format,MaxTextExtent,"%g ",*k++);
cristy56a9e512010-01-06 18:18:55 +00002226 (void) ConcatenateString(&message,format);
2227 }
2228 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
2229 }
2230 message=DestroyString(message);
2231 }
cristyd43a46b2010-01-21 02:13:41 +00002232 status=AccelerateConvolveImage(image,kernel,filter_image,exception);
2233 if (status == MagickTrue)
2234 return(filter_image);
cristy56a9e512010-01-06 18:18:55 +00002235 /*
2236 Filter image.
2237 */
2238 status=MagickTrue;
2239 progress=0;
2240 GetMagickPixelPacket(image,&bias);
2241 SetMagickPixelPacketBias(image,&bias);
2242 image_view=AcquireCacheView(image);
2243 filter_view=AcquireCacheView(filter_image);
2244#if defined(MAGICKCORE_OPENMP_SUPPORT)
2245 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2246#endif
2247 for (y=0; y < (long) image->rows; y++)
2248 {
2249 MagickBooleanType
2250 sync;
2251
2252 register const IndexPacket
2253 *restrict indexes;
2254
2255 register const PixelPacket
2256 *restrict p;
2257
2258 register IndexPacket
2259 *restrict filter_indexes;
2260
2261 register long
2262 x;
2263
2264 register PixelPacket
2265 *restrict q;
2266
2267 if (status == MagickFalse)
2268 continue;
2269 p=GetCacheViewVirtualPixels(image_view,-((long) kernel->width/2L),
2270 y-(long) (kernel->height/2L),image->columns+kernel->width,kernel->height,
2271 exception);
2272 q=GetCacheViewAuthenticPixels(filter_view,0,y,filter_image->columns,1,
2273 exception);
2274 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2275 {
2276 status=MagickFalse;
2277 continue;
2278 }
2279 indexes=GetCacheViewVirtualIndexQueue(image_view);
2280 filter_indexes=GetCacheViewAuthenticIndexQueue(filter_view);
2281 for (x=0; x < (long) image->columns; x++)
2282 {
2283 long
2284 v;
2285
2286 MagickPixelPacket
2287 pixel;
2288
2289 register const double
2290 *restrict k;
2291
2292 register const PixelPacket
2293 *restrict kernel_pixels;
2294
2295 register long
2296 u;
2297
2298 pixel=bias;
cristyd43a46b2010-01-21 02:13:41 +00002299 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002300 kernel_pixels=p;
2301 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
2302 {
2303 for (v=0; v < (long) kernel->width; v++)
2304 {
2305 for (u=0; u < (long) kernel->height; u++)
2306 {
2307 pixel.red+=(*k)*kernel_pixels[u].red;
2308 pixel.green+=(*k)*kernel_pixels[u].green;
2309 pixel.blue+=(*k)*kernel_pixels[u].blue;
2310 k++;
2311 }
2312 kernel_pixels+=image->columns+kernel->width;
2313 }
2314 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002315 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002316 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002317 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002318 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002319 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002320 if ((channel & OpacityChannel) != 0)
2321 {
cristyd43a46b2010-01-21 02:13:41 +00002322 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002323 kernel_pixels=p;
2324 for (v=0; v < (long) kernel->width; v++)
2325 {
2326 for (u=0; u < (long) kernel->height; u++)
2327 {
2328 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
2329 k++;
2330 }
2331 kernel_pixels+=image->columns+kernel->width;
2332 }
cristyce70c172010-01-07 17:15:30 +00002333 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002334 }
2335 if (((channel & IndexChannel) != 0) &&
2336 (image->colorspace == CMYKColorspace))
2337 {
2338 register const IndexPacket
2339 *restrict kernel_indexes;
2340
cristyd43a46b2010-01-21 02:13:41 +00002341 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002342 kernel_indexes=indexes;
2343 for (v=0; v < (long) kernel->width; v++)
2344 {
2345 for (u=0; u < (long) kernel->height; u++)
2346 {
2347 pixel.index+=(*k)*kernel_indexes[u];
2348 k++;
2349 }
2350 kernel_indexes+=image->columns+kernel->width;
2351 }
cristyce70c172010-01-07 17:15:30 +00002352 filter_indexes[x]=ClampToQuantum(pixel.index);
cristy56a9e512010-01-06 18:18:55 +00002353 }
2354 }
2355 else
2356 {
2357 MagickRealType
2358 alpha,
2359 gamma;
2360
2361 gamma=0.0;
2362 for (v=0; v < (long) kernel->width; v++)
2363 {
2364 for (u=0; u < (long) kernel->height; u++)
2365 {
2366 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
2367 kernel_pixels[u].opacity));
2368 pixel.red+=(*k)*alpha*kernel_pixels[u].red;
2369 pixel.green+=(*k)*alpha*kernel_pixels[u].green;
2370 pixel.blue+=(*k)*alpha*kernel_pixels[u].blue;
2371 gamma+=(*k)*alpha;
2372 k++;
2373 }
2374 kernel_pixels+=image->columns+kernel->width;
2375 }
2376 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
2377 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002378 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002379 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002380 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002381 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002382 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002383 if ((channel & OpacityChannel) != 0)
2384 {
cristyd43a46b2010-01-21 02:13:41 +00002385 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002386 kernel_pixels=p;
2387 for (v=0; v < (long) kernel->width; v++)
2388 {
2389 for (u=0; u < (long) kernel->height; u++)
2390 {
2391 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
2392 k++;
2393 }
2394 kernel_pixels+=image->columns+kernel->width;
2395 }
cristyce70c172010-01-07 17:15:30 +00002396 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002397 }
2398 if (((channel & IndexChannel) != 0) &&
2399 (image->colorspace == CMYKColorspace))
2400 {
2401 register const IndexPacket
2402 *restrict kernel_indexes;
2403
cristyd43a46b2010-01-21 02:13:41 +00002404 k=kernel->values;
cristy56a9e512010-01-06 18:18:55 +00002405 kernel_pixels=p;
2406 kernel_indexes=indexes;
2407 for (v=0; v < (long) kernel->width; v++)
2408 {
2409 for (u=0; u < (long) kernel->height; u++)
2410 {
2411 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
2412 kernel_pixels[u].opacity));
2413 pixel.index+=(*k)*alpha*kernel_indexes[u];
2414 k++;
2415 }
2416 kernel_pixels+=image->columns+kernel->width;
2417 kernel_indexes+=image->columns+kernel->width;
2418 }
cristy2115aea2010-01-09 23:16:08 +00002419 filter_indexes[x]=ClampToQuantum(gamma*
2420 GetIndexPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002421 }
2422 }
2423 p++;
2424 q++;
2425 }
2426 sync=SyncCacheViewAuthenticPixels(filter_view,exception);
2427 if (sync == MagickFalse)
2428 status=MagickFalse;
2429 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2430 {
2431 MagickBooleanType
2432 proceed;
2433
2434#if defined(MAGICKCORE_OPENMP_SUPPORT)
2435 #pragma omp critical (MagickCore_FilterImageChannel)
2436#endif
2437 proceed=SetImageProgress(image,FilterImageTag,progress++,image->rows);
2438 if (proceed == MagickFalse)
2439 status=MagickFalse;
2440 }
2441 }
2442 filter_image->type=image->type;
2443 filter_view=DestroyCacheView(filter_view);
2444 image_view=DestroyCacheView(image_view);
cristy56a9e512010-01-06 18:18:55 +00002445 if (status == MagickFalse)
2446 filter_image=DestroyImage(filter_image);
2447 return(filter_image);
2448}
2449
2450/*
2451%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2452% %
2453% %
2454% %
cristy3ed852e2009-09-05 21:47:34 +00002455% G a u s s i a n B l u r I m a g e %
2456% %
2457% %
2458% %
2459%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2460%
2461% GaussianBlurImage() blurs an image. We convolve the image with a
2462% Gaussian operator of the given radius and standard deviation (sigma).
2463% For reasonable results, the radius should be larger than sigma. Use a
2464% radius of 0 and GaussianBlurImage() selects a suitable radius for you
2465%
2466% The format of the GaussianBlurImage method is:
2467%
2468% Image *GaussianBlurImage(const Image *image,onst double radius,
2469% const double sigma,ExceptionInfo *exception)
2470% Image *GaussianBlurImageChannel(const Image *image,
2471% const ChannelType channel,const double radius,const double sigma,
2472% ExceptionInfo *exception)
2473%
2474% A description of each parameter follows:
2475%
2476% o image: the image.
2477%
2478% o channel: the channel type.
2479%
2480% o radius: the radius of the Gaussian, in pixels, not counting the center
2481% pixel.
2482%
2483% o sigma: the standard deviation of the Gaussian, in pixels.
2484%
2485% o exception: return any errors or warnings in this structure.
2486%
2487*/
2488
2489MagickExport Image *GaussianBlurImage(const Image *image,const double radius,
2490 const double sigma,ExceptionInfo *exception)
2491{
2492 Image
2493 *blur_image;
2494
2495 blur_image=GaussianBlurImageChannel(image,DefaultChannels,radius,sigma,
2496 exception);
2497 return(blur_image);
2498}
2499
2500MagickExport Image *GaussianBlurImageChannel(const Image *image,
2501 const ChannelType channel,const double radius,const double sigma,
2502 ExceptionInfo *exception)
2503{
2504 double
2505 *kernel;
2506
2507 Image
2508 *blur_image;
2509
cristy47e00502009-12-17 19:19:57 +00002510 long
2511 j,
cristy3ed852e2009-09-05 21:47:34 +00002512 u,
2513 v;
2514
cristy47e00502009-12-17 19:19:57 +00002515 register long
2516 i;
2517
cristy3ed852e2009-09-05 21:47:34 +00002518 unsigned long
2519 width;
2520
2521 assert(image != (const Image *) NULL);
2522 assert(image->signature == MagickSignature);
2523 if (image->debug != MagickFalse)
2524 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2525 assert(exception != (ExceptionInfo *) NULL);
2526 assert(exception->signature == MagickSignature);
2527 width=GetOptimalKernelWidth2D(radius,sigma);
2528 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2529 if (kernel == (double *) NULL)
2530 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy47e00502009-12-17 19:19:57 +00002531 j=(long) width/2;
cristy3ed852e2009-09-05 21:47:34 +00002532 i=0;
cristy47e00502009-12-17 19:19:57 +00002533 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002534 {
cristy47e00502009-12-17 19:19:57 +00002535 for (u=(-j); u <= j; u++)
2536 kernel[i++]=exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
2537 (2.0*MagickPI*MagickSigma*MagickSigma);
cristy3ed852e2009-09-05 21:47:34 +00002538 }
2539 blur_image=ConvolveImageChannel(image,channel,width,kernel,exception);
2540 kernel=(double *) RelinquishMagickMemory(kernel);
2541 return(blur_image);
2542}
2543
2544/*
2545%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2546% %
2547% %
2548% %
2549% M e d i a n F i l t e r I m a g e %
2550% %
2551% %
2552% %
2553%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2554%
2555% MedianFilterImage() applies a digital filter that improves the quality
2556% of a noisy image. Each pixel is replaced by the median in a set of
2557% neighboring pixels as defined by radius.
2558%
2559% The algorithm was contributed by Mike Edmonds and implements an insertion
2560% sort for selecting median color-channel values. For more on this algorithm
2561% see "Skip Lists: A probabilistic Alternative to Balanced Trees" by William
2562% Pugh in the June 1990 of Communications of the ACM.
2563%
2564% The format of the MedianFilterImage method is:
2565%
2566% Image *MedianFilterImage(const Image *image,const double radius,
2567% ExceptionInfo *exception)
2568%
2569% A description of each parameter follows:
2570%
2571% o image: the image.
2572%
2573% o radius: the radius of the pixel neighborhood.
2574%
2575% o exception: return any errors or warnings in this structure.
2576%
2577*/
2578
2579#define MedianListChannels 5
2580
2581typedef struct _MedianListNode
2582{
2583 unsigned long
2584 next[9],
2585 count,
2586 signature;
2587} MedianListNode;
2588
2589typedef struct _MedianSkipList
2590{
2591 long
2592 level;
2593
2594 MedianListNode
2595 *nodes;
2596} MedianSkipList;
2597
2598typedef struct _MedianPixelList
2599{
2600 unsigned long
2601 center,
2602 seed,
2603 signature;
2604
2605 MedianSkipList
2606 lists[MedianListChannels];
2607} MedianPixelList;
2608
2609static MedianPixelList *DestroyMedianPixelList(MedianPixelList *pixel_list)
2610{
2611 register long
2612 i;
2613
2614 if (pixel_list == (MedianPixelList *) NULL)
2615 return((MedianPixelList *) NULL);
2616 for (i=0; i < MedianListChannels; i++)
2617 if (pixel_list->lists[i].nodes != (MedianListNode *) NULL)
2618 pixel_list->lists[i].nodes=(MedianListNode *) RelinquishMagickMemory(
2619 pixel_list->lists[i].nodes);
2620 pixel_list=(MedianPixelList *) RelinquishAlignedMemory(pixel_list);
2621 return(pixel_list);
2622}
2623
2624static MedianPixelList **DestroyMedianPixelListThreadSet(
2625 MedianPixelList **pixel_list)
2626{
2627 register long
2628 i;
2629
2630 assert(pixel_list != (MedianPixelList **) NULL);
2631 for (i=0; i < (long) GetOpenMPMaximumThreads(); i++)
2632 if (pixel_list[i] != (MedianPixelList *) NULL)
2633 pixel_list[i]=DestroyMedianPixelList(pixel_list[i]);
2634 pixel_list=(MedianPixelList **) RelinquishAlignedMemory(pixel_list);
2635 return(pixel_list);
2636}
2637
2638static MedianPixelList *AcquireMedianPixelList(const unsigned long width)
2639{
2640 MedianPixelList
2641 *pixel_list;
2642
2643 register long
2644 i;
2645
2646 pixel_list=(MedianPixelList *) AcquireAlignedMemory(1,sizeof(*pixel_list));
2647 if (pixel_list == (MedianPixelList *) NULL)
2648 return(pixel_list);
2649 (void) ResetMagickMemory((void *) pixel_list,0,sizeof(*pixel_list));
2650 pixel_list->center=width*width/2;
2651 for (i=0; i < MedianListChannels; i++)
2652 {
2653 pixel_list->lists[i].nodes=(MedianListNode *) AcquireQuantumMemory(65537UL,
2654 sizeof(*pixel_list->lists[i].nodes));
2655 if (pixel_list->lists[i].nodes == (MedianListNode *) NULL)
2656 return(DestroyMedianPixelList(pixel_list));
2657 (void) ResetMagickMemory(pixel_list->lists[i].nodes,0,65537UL*
2658 sizeof(*pixel_list->lists[i].nodes));
2659 }
2660 pixel_list->signature=MagickSignature;
2661 return(pixel_list);
2662}
2663
2664static MedianPixelList **AcquireMedianPixelListThreadSet(
2665 const unsigned long width)
2666{
2667 register long
2668 i;
2669
2670 MedianPixelList
2671 **pixel_list;
2672
2673 unsigned long
2674 number_threads;
2675
2676 number_threads=GetOpenMPMaximumThreads();
2677 pixel_list=(MedianPixelList **) AcquireAlignedMemory(number_threads,
2678 sizeof(*pixel_list));
2679 if (pixel_list == (MedianPixelList **) NULL)
2680 return((MedianPixelList **) NULL);
2681 (void) ResetMagickMemory(pixel_list,0,number_threads*sizeof(*pixel_list));
2682 for (i=0; i < (long) number_threads; i++)
2683 {
2684 pixel_list[i]=AcquireMedianPixelList(width);
2685 if (pixel_list[i] == (MedianPixelList *) NULL)
2686 return(DestroyMedianPixelListThreadSet(pixel_list));
2687 }
2688 return(pixel_list);
2689}
2690
2691static void AddNodeMedianPixelList(MedianPixelList *pixel_list,
2692 const long channel,const unsigned long color)
2693{
2694 register long
2695 level;
2696
2697 register MedianSkipList
2698 *list;
2699
2700 unsigned long
2701 search,
2702 update[9];
2703
2704 /*
2705 Initialize the node.
2706 */
2707 list=pixel_list->lists+channel;
2708 list->nodes[color].signature=pixel_list->signature;
2709 list->nodes[color].count=1;
2710 /*
2711 Determine where it belongs in the list.
2712 */
2713 search=65536UL;
2714 for (level=list->level; level >= 0; level--)
2715 {
2716 while (list->nodes[search].next[level] < color)
2717 search=list->nodes[search].next[level];
2718 update[level]=search;
2719 }
2720 /*
2721 Generate a pseudo-random level for this node.
2722 */
2723 for (level=0; ; level++)
2724 {
2725 pixel_list->seed=(pixel_list->seed*42893621L)+1L;
2726 if ((pixel_list->seed & 0x300) != 0x300)
2727 break;
2728 }
2729 if (level > 8)
2730 level=8;
2731 if (level > (list->level+2))
2732 level=list->level+2;
2733 /*
2734 If we're raising the list's level, link back to the root node.
2735 */
2736 while (level > list->level)
2737 {
2738 list->level++;
2739 update[list->level]=65536UL;
2740 }
2741 /*
2742 Link the node into the skip-list.
2743 */
2744 do
2745 {
2746 list->nodes[color].next[level]=list->nodes[update[level]].next[level];
2747 list->nodes[update[level]].next[level]=color;
2748 }
2749 while (level-- > 0);
2750}
2751
2752static MagickPixelPacket GetMedianPixelList(MedianPixelList *pixel_list)
2753{
2754 MagickPixelPacket
2755 pixel;
2756
2757 register long
2758 channel;
2759
2760 register MedianSkipList
2761 *list;
2762
2763 unsigned long
2764 center,
2765 color,
2766 count;
2767
2768 unsigned short
2769 channels[MedianListChannels];
2770
2771 /*
2772 Find the median value for each of the color.
2773 */
2774 center=pixel_list->center;
2775 for (channel=0; channel < 5; channel++)
2776 {
2777 list=pixel_list->lists+channel;
2778 color=65536UL;
2779 count=0;
2780 do
2781 {
2782 color=list->nodes[color].next[0];
2783 count+=list->nodes[color].count;
2784 }
2785 while (count <= center);
2786 channels[channel]=(unsigned short) color;
2787 }
2788 GetMagickPixelPacket((const Image *) NULL,&pixel);
2789 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
2790 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
2791 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
2792 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
2793 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
2794 return(pixel);
2795}
2796
2797static inline void InsertMedianPixelList(const Image *image,
2798 const PixelPacket *pixel,const IndexPacket *indexes,
2799 MedianPixelList *pixel_list)
2800{
2801 unsigned long
2802 signature;
2803
2804 unsigned short
2805 index;
2806
2807 index=ScaleQuantumToShort(pixel->red);
2808 signature=pixel_list->lists[0].nodes[index].signature;
2809 if (signature == pixel_list->signature)
2810 pixel_list->lists[0].nodes[index].count++;
2811 else
2812 AddNodeMedianPixelList(pixel_list,0,index);
2813 index=ScaleQuantumToShort(pixel->green);
2814 signature=pixel_list->lists[1].nodes[index].signature;
2815 if (signature == pixel_list->signature)
2816 pixel_list->lists[1].nodes[index].count++;
2817 else
2818 AddNodeMedianPixelList(pixel_list,1,index);
2819 index=ScaleQuantumToShort(pixel->blue);
2820 signature=pixel_list->lists[2].nodes[index].signature;
2821 if (signature == pixel_list->signature)
2822 pixel_list->lists[2].nodes[index].count++;
2823 else
2824 AddNodeMedianPixelList(pixel_list,2,index);
2825 index=ScaleQuantumToShort(pixel->opacity);
2826 signature=pixel_list->lists[3].nodes[index].signature;
2827 if (signature == pixel_list->signature)
2828 pixel_list->lists[3].nodes[index].count++;
2829 else
2830 AddNodeMedianPixelList(pixel_list,3,index);
2831 if (image->colorspace == CMYKColorspace)
2832 index=ScaleQuantumToShort(*indexes);
2833 signature=pixel_list->lists[4].nodes[index].signature;
2834 if (signature == pixel_list->signature)
2835 pixel_list->lists[4].nodes[index].count++;
2836 else
2837 AddNodeMedianPixelList(pixel_list,4,index);
2838}
2839
2840static void ResetMedianPixelList(MedianPixelList *pixel_list)
2841{
2842 int
2843 level;
2844
2845 register long
2846 channel;
2847
2848 register MedianListNode
2849 *root;
2850
2851 register MedianSkipList
2852 *list;
2853
2854 /*
2855 Reset the skip-list.
2856 */
2857 for (channel=0; channel < 5; channel++)
2858 {
2859 list=pixel_list->lists+channel;
2860 root=list->nodes+65536UL;
2861 list->level=0;
2862 for (level=0; level < 9; level++)
2863 root->next[level]=65536UL;
2864 }
2865 pixel_list->seed=pixel_list->signature++;
2866}
2867
2868MagickExport Image *MedianFilterImage(const Image *image,const double radius,
2869 ExceptionInfo *exception)
2870{
2871#define MedianFilterImageTag "MedianFilter/Image"
2872
cristyc4c8d132010-01-07 01:58:38 +00002873 CacheView
2874 *image_view,
2875 *median_view;
2876
cristy3ed852e2009-09-05 21:47:34 +00002877 Image
2878 *median_image;
2879
2880 long
2881 progress,
2882 y;
2883
2884 MagickBooleanType
2885 status;
2886
2887 MedianPixelList
cristyfa112112010-01-04 17:48:07 +00002888 **restrict pixel_list;
cristy3ed852e2009-09-05 21:47:34 +00002889
2890 unsigned long
2891 width;
2892
cristy3ed852e2009-09-05 21:47:34 +00002893 /*
2894 Initialize median image attributes.
2895 */
2896 assert(image != (Image *) NULL);
2897 assert(image->signature == MagickSignature);
2898 if (image->debug != MagickFalse)
2899 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2900 assert(exception != (ExceptionInfo *) NULL);
2901 assert(exception->signature == MagickSignature);
2902 width=GetOptimalKernelWidth2D(radius,0.5);
2903 if ((image->columns < width) || (image->rows < width))
2904 ThrowImageException(OptionError,"ImageSmallerThanKernelRadius");
2905 median_image=CloneImage(image,image->columns,image->rows,MagickTrue,
2906 exception);
2907 if (median_image == (Image *) NULL)
2908 return((Image *) NULL);
2909 if (SetImageStorageClass(median_image,DirectClass) == MagickFalse)
2910 {
2911 InheritException(exception,&median_image->exception);
2912 median_image=DestroyImage(median_image);
2913 return((Image *) NULL);
2914 }
2915 pixel_list=AcquireMedianPixelListThreadSet(width);
2916 if (pixel_list == (MedianPixelList **) NULL)
2917 {
2918 median_image=DestroyImage(median_image);
2919 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2920 }
2921 /*
2922 Median filter each image row.
2923 */
2924 status=MagickTrue;
2925 progress=0;
2926 image_view=AcquireCacheView(image);
2927 median_view=AcquireCacheView(median_image);
cristyb5d5f722009-11-04 03:03:49 +00002928#if defined(MAGICKCORE_OPENMP_SUPPORT)
2929 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002930#endif
2931 for (y=0; y < (long) median_image->rows; y++)
2932 {
2933 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002934 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002935
2936 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002937 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002938
2939 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002940 *restrict median_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002941
2942 register long
2943 id,
2944 x;
2945
2946 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002947 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002948
2949 if (status == MagickFalse)
2950 continue;
2951 p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y-(long) (width/
2952 2L),image->columns+width,width,exception);
2953 q=QueueCacheViewAuthenticPixels(median_view,0,y,median_image->columns,1,
2954 exception);
2955 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2956 {
2957 status=MagickFalse;
2958 continue;
2959 }
2960 indexes=GetCacheViewVirtualIndexQueue(image_view);
2961 median_indexes=GetCacheViewAuthenticIndexQueue(median_view);
2962 id=GetOpenMPThreadId();
2963 for (x=0; x < (long) median_image->columns; x++)
2964 {
2965 MagickPixelPacket
2966 pixel;
2967
2968 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002969 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +00002970
2971 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002972 *restrict s;
cristy3ed852e2009-09-05 21:47:34 +00002973
2974 register long
2975 u,
2976 v;
2977
2978 r=p;
2979 s=indexes+x;
2980 ResetMedianPixelList(pixel_list[id]);
2981 for (v=0; v < (long) width; v++)
2982 {
2983 for (u=0; u < (long) width; u++)
2984 InsertMedianPixelList(image,r+u,s+u,pixel_list[id]);
2985 r+=image->columns+width;
2986 s+=image->columns+width;
2987 }
2988 pixel=GetMedianPixelList(pixel_list[id]);
2989 SetPixelPacket(median_image,&pixel,q,median_indexes+x);
2990 p++;
2991 q++;
2992 }
2993 if (SyncCacheViewAuthenticPixels(median_view,exception) == MagickFalse)
2994 status=MagickFalse;
2995 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2996 {
2997 MagickBooleanType
2998 proceed;
2999
cristyb5d5f722009-11-04 03:03:49 +00003000#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003001 #pragma omp critical (MagickCore_MedianFilterImage)
3002#endif
3003 proceed=SetImageProgress(image,MedianFilterImageTag,progress++,
3004 image->rows);
3005 if (proceed == MagickFalse)
3006 status=MagickFalse;
3007 }
3008 }
3009 median_view=DestroyCacheView(median_view);
3010 image_view=DestroyCacheView(image_view);
3011 pixel_list=DestroyMedianPixelListThreadSet(pixel_list);
3012 return(median_image);
3013}
3014
3015/*
3016%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3017% %
3018% %
3019% %
3020% M o t i o n B l u r I m a g e %
3021% %
3022% %
3023% %
3024%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3025%
3026% MotionBlurImage() simulates motion blur. We convolve the image with a
3027% Gaussian operator of the given radius and standard deviation (sigma).
3028% For reasonable results, radius should be larger than sigma. Use a
3029% radius of 0 and MotionBlurImage() selects a suitable radius for you.
3030% Angle gives the angle of the blurring motion.
3031%
3032% Andrew Protano contributed this effect.
3033%
3034% The format of the MotionBlurImage method is:
3035%
3036% Image *MotionBlurImage(const Image *image,const double radius,
3037% const double sigma,const double angle,ExceptionInfo *exception)
3038% Image *MotionBlurImageChannel(const Image *image,const ChannelType channel,
3039% const double radius,const double sigma,const double angle,
3040% ExceptionInfo *exception)
3041%
3042% A description of each parameter follows:
3043%
3044% o image: the image.
3045%
3046% o channel: the channel type.
3047%
3048% o radius: the radius of the Gaussian, in pixels, not counting the center
3049% o radius: the radius of the Gaussian, in pixels, not counting
3050% the center pixel.
3051%
3052% o sigma: the standard deviation of the Gaussian, in pixels.
3053%
3054% o angle: Apply the effect along this angle.
3055%
3056% o exception: return any errors or warnings in this structure.
3057%
3058*/
3059
cristy47e00502009-12-17 19:19:57 +00003060static double *GetMotionBlurKernel(const unsigned long width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +00003061{
cristy3ed852e2009-09-05 21:47:34 +00003062 double
cristy47e00502009-12-17 19:19:57 +00003063 *kernel,
cristy3ed852e2009-09-05 21:47:34 +00003064 normalize;
3065
3066 register long
3067 i;
3068
3069 /*
cristy47e00502009-12-17 19:19:57 +00003070 Generate a 1-D convolution kernel.
cristy3ed852e2009-09-05 21:47:34 +00003071 */
3072 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
3073 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
3074 if (kernel == (double *) NULL)
3075 return(kernel);
cristy3ed852e2009-09-05 21:47:34 +00003076 normalize=0.0;
3077 for (i=0; i < (long) width; i++)
cristy47e00502009-12-17 19:19:57 +00003078 {
3079 kernel[i]=exp((-((double) i*i)/(double) (2.0*MagickSigma*MagickSigma)))/
3080 (MagickSQ2PI*MagickSigma);
cristy3ed852e2009-09-05 21:47:34 +00003081 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +00003082 }
cristy3ed852e2009-09-05 21:47:34 +00003083 for (i=0; i < (long) width; i++)
3084 kernel[i]/=normalize;
3085 return(kernel);
3086}
3087
3088MagickExport Image *MotionBlurImage(const Image *image,const double radius,
3089 const double sigma,const double angle,ExceptionInfo *exception)
3090{
3091 Image
3092 *motion_blur;
3093
3094 motion_blur=MotionBlurImageChannel(image,DefaultChannels,radius,sigma,angle,
3095 exception);
3096 return(motion_blur);
3097}
3098
3099MagickExport Image *MotionBlurImageChannel(const Image *image,
3100 const ChannelType channel,const double radius,const double sigma,
3101 const double angle,ExceptionInfo *exception)
3102{
cristyc4c8d132010-01-07 01:58:38 +00003103 CacheView
3104 *blur_view,
3105 *image_view;
3106
cristy3ed852e2009-09-05 21:47:34 +00003107 double
3108 *kernel;
3109
3110 Image
3111 *blur_image;
3112
3113 long
3114 progress,
3115 y;
3116
3117 MagickBooleanType
3118 status;
3119
3120 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00003121 bias;
cristy3ed852e2009-09-05 21:47:34 +00003122
3123 OffsetInfo
3124 *offset;
3125
3126 PointInfo
3127 point;
3128
3129 register long
3130 i;
3131
3132 unsigned long
3133 width;
3134
cristy3ed852e2009-09-05 21:47:34 +00003135 assert(image != (Image *) NULL);
3136 assert(image->signature == MagickSignature);
3137 if (image->debug != MagickFalse)
3138 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3139 assert(exception != (ExceptionInfo *) NULL);
3140 width=GetOptimalKernelWidth1D(radius,sigma);
3141 kernel=GetMotionBlurKernel(width,sigma);
3142 if (kernel == (double *) NULL)
3143 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3144 offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset));
3145 if (offset == (OffsetInfo *) NULL)
3146 {
3147 kernel=(double *) RelinquishMagickMemory(kernel);
3148 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3149 }
3150 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3151 if (blur_image == (Image *) NULL)
3152 {
3153 kernel=(double *) RelinquishMagickMemory(kernel);
3154 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
3155 return((Image *) NULL);
3156 }
3157 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3158 {
3159 kernel=(double *) RelinquishMagickMemory(kernel);
3160 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
3161 InheritException(exception,&blur_image->exception);
3162 blur_image=DestroyImage(blur_image);
3163 return((Image *) NULL);
3164 }
3165 point.x=(double) width*sin(DegreesToRadians(angle));
3166 point.y=(double) width*cos(DegreesToRadians(angle));
3167 for (i=0; i < (long) width; i++)
3168 {
3169 offset[i].x=(long) ((i*point.y)/hypot(point.x,point.y)+0.5);
3170 offset[i].y=(long) ((i*point.x)/hypot(point.x,point.y)+0.5);
3171 }
3172 /*
3173 Motion blur image.
3174 */
3175 status=MagickTrue;
3176 progress=0;
cristyddd82202009-11-03 20:14:50 +00003177 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003178 image_view=AcquireCacheView(image);
3179 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003180#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy00f95372010-02-13 16:39:29 +00003181 #pragma omp parallel for schedule(static) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003182#endif
3183 for (y=0; y < (long) image->rows; y++)
3184 {
3185 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003186 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00003187
3188 register long
3189 x;
3190
3191 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003192 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003193
3194 if (status == MagickFalse)
3195 continue;
3196 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3197 exception);
3198 if (q == (PixelPacket *) NULL)
3199 {
3200 status=MagickFalse;
3201 continue;
3202 }
3203 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
3204 for (x=0; x < (long) image->columns; x++)
3205 {
3206 MagickPixelPacket
3207 qixel;
3208
3209 PixelPacket
3210 pixel;
3211
3212 register double
cristyc47d1f82009-11-26 01:44:43 +00003213 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00003214
3215 register long
3216 i;
3217
3218 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003219 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003220
3221 k=kernel;
cristyddd82202009-11-03 20:14:50 +00003222 qixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00003223 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
3224 {
3225 for (i=0; i < (long) width; i++)
3226 {
3227 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
3228 offset[i].y,&pixel,exception);
3229 qixel.red+=(*k)*pixel.red;
3230 qixel.green+=(*k)*pixel.green;
3231 qixel.blue+=(*k)*pixel.blue;
3232 qixel.opacity+=(*k)*pixel.opacity;
3233 if (image->colorspace == CMYKColorspace)
3234 {
3235 indexes=GetCacheViewVirtualIndexQueue(image_view);
3236 qixel.index+=(*k)*(*indexes);
3237 }
3238 k++;
3239 }
3240 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003241 q->red=ClampToQuantum(qixel.red);
cristy3ed852e2009-09-05 21:47:34 +00003242 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003243 q->green=ClampToQuantum(qixel.green);
cristy3ed852e2009-09-05 21:47:34 +00003244 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003245 q->blue=ClampToQuantum(qixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00003246 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003247 q->opacity=ClampToQuantum(qixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00003248 if (((channel & IndexChannel) != 0) &&
3249 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00003250 blur_indexes[x]=(IndexPacket) ClampToQuantum(qixel.index);
cristy3ed852e2009-09-05 21:47:34 +00003251 }
3252 else
3253 {
3254 MagickRealType
3255 alpha,
3256 gamma;
3257
3258 alpha=0.0;
3259 gamma=0.0;
3260 for (i=0; i < (long) width; i++)
3261 {
3262 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
3263 offset[i].y,&pixel,exception);
cristy46f08202010-01-10 04:04:21 +00003264 alpha=(MagickRealType) (QuantumScale*GetAlphaPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00003265 qixel.red+=(*k)*alpha*pixel.red;
3266 qixel.green+=(*k)*alpha*pixel.green;
3267 qixel.blue+=(*k)*alpha*pixel.blue;
3268 qixel.opacity+=(*k)*pixel.opacity;
3269 if (image->colorspace == CMYKColorspace)
3270 {
3271 indexes=GetCacheViewVirtualIndexQueue(image_view);
3272 qixel.index+=(*k)*alpha*(*indexes);
3273 }
3274 gamma+=(*k)*alpha;
3275 k++;
3276 }
3277 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3278 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003279 q->red=ClampToQuantum(gamma*qixel.red);
cristy3ed852e2009-09-05 21:47:34 +00003280 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003281 q->green=ClampToQuantum(gamma*qixel.green);
cristy3ed852e2009-09-05 21:47:34 +00003282 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003283 q->blue=ClampToQuantum(gamma*qixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00003284 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003285 q->opacity=ClampToQuantum(qixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00003286 if (((channel & IndexChannel) != 0) &&
3287 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00003288 blur_indexes[x]=(IndexPacket) ClampToQuantum(gamma*qixel.index);
cristy3ed852e2009-09-05 21:47:34 +00003289 }
3290 q++;
3291 }
3292 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
3293 status=MagickFalse;
3294 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3295 {
3296 MagickBooleanType
3297 proceed;
3298
cristyb5d5f722009-11-04 03:03:49 +00003299#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003300 #pragma omp critical (MagickCore_MotionBlurImageChannel)
3301#endif
3302 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
3303 if (proceed == MagickFalse)
3304 status=MagickFalse;
3305 }
3306 }
3307 blur_view=DestroyCacheView(blur_view);
3308 image_view=DestroyCacheView(image_view);
3309 kernel=(double *) RelinquishMagickMemory(kernel);
3310 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
3311 if (status == MagickFalse)
3312 blur_image=DestroyImage(blur_image);
3313 return(blur_image);
3314}
3315
3316/*
3317%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3318% %
3319% %
3320% %
3321% P r e v i e w I m a g e %
3322% %
3323% %
3324% %
3325%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3326%
3327% PreviewImage() tiles 9 thumbnails of the specified image with an image
3328% processing operation applied with varying parameters. This may be helpful
3329% pin-pointing an appropriate parameter for a particular image processing
3330% operation.
3331%
3332% The format of the PreviewImages method is:
3333%
3334% Image *PreviewImages(const Image *image,const PreviewType preview,
3335% ExceptionInfo *exception)
3336%
3337% A description of each parameter follows:
3338%
3339% o image: the image.
3340%
3341% o preview: the image processing operation.
3342%
3343% o exception: return any errors or warnings in this structure.
3344%
3345*/
3346MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
3347 ExceptionInfo *exception)
3348{
3349#define NumberTiles 9
3350#define PreviewImageTag "Preview/Image"
3351#define DefaultPreviewGeometry "204x204+10+10"
3352
3353 char
3354 factor[MaxTextExtent],
3355 label[MaxTextExtent];
3356
3357 double
3358 degrees,
3359 gamma,
3360 percentage,
3361 radius,
3362 sigma,
3363 threshold;
3364
3365 Image
3366 *images,
3367 *montage_image,
3368 *preview_image,
3369 *thumbnail;
3370
3371 ImageInfo
3372 *preview_info;
3373
3374 long
3375 y;
3376
3377 MagickBooleanType
3378 proceed;
3379
3380 MontageInfo
3381 *montage_info;
3382
3383 QuantizeInfo
3384 quantize_info;
3385
3386 RectangleInfo
3387 geometry;
3388
3389 register long
3390 i,
3391 x;
3392
3393 unsigned long
3394 colors;
3395
3396 /*
3397 Open output image file.
3398 */
3399 assert(image != (Image *) NULL);
3400 assert(image->signature == MagickSignature);
3401 if (image->debug != MagickFalse)
3402 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3403 colors=2;
3404 degrees=0.0;
3405 gamma=(-0.2f);
3406 preview_info=AcquireImageInfo();
3407 SetGeometry(image,&geometry);
3408 (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
3409 &geometry.width,&geometry.height);
3410 images=NewImageList();
3411 percentage=12.5;
3412 GetQuantizeInfo(&quantize_info);
3413 radius=0.0;
3414 sigma=1.0;
3415 threshold=0.0;
3416 x=0;
3417 y=0;
3418 for (i=0; i < NumberTiles; i++)
3419 {
3420 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
3421 if (thumbnail == (Image *) NULL)
3422 break;
3423 (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
3424 (void *) NULL);
3425 (void) SetImageProperty(thumbnail,"label",DefaultTileLabel);
3426 if (i == (NumberTiles/2))
3427 {
3428 (void) QueryColorDatabase("#dfdfdf",&thumbnail->matte_color,exception);
3429 AppendImageToList(&images,thumbnail);
3430 continue;
3431 }
3432 switch (preview)
3433 {
3434 case RotatePreview:
3435 {
3436 degrees+=45.0;
3437 preview_image=RotateImage(thumbnail,degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003438 (void) FormatMagickString(label,MaxTextExtent,"rotate %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003439 break;
3440 }
3441 case ShearPreview:
3442 {
3443 degrees+=5.0;
3444 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003445 (void) FormatMagickString(label,MaxTextExtent,"shear %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00003446 degrees,2.0*degrees);
3447 break;
3448 }
3449 case RollPreview:
3450 {
3451 x=(long) ((i+1)*thumbnail->columns)/NumberTiles;
3452 y=(long) ((i+1)*thumbnail->rows)/NumberTiles;
3453 preview_image=RollImage(thumbnail,x,y,exception);
3454 (void) FormatMagickString(label,MaxTextExtent,"roll %ldx%ld",x,y);
3455 break;
3456 }
3457 case HuePreview:
3458 {
3459 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3460 if (preview_image == (Image *) NULL)
3461 break;
cristye7f51092010-01-17 00:39:37 +00003462 (void) FormatMagickString(factor,MaxTextExtent,"100,100,%g",
cristy3ed852e2009-09-05 21:47:34 +00003463 2.0*percentage);
3464 (void) ModulateImage(preview_image,factor);
3465 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
3466 break;
3467 }
3468 case SaturationPreview:
3469 {
3470 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3471 if (preview_image == (Image *) NULL)
3472 break;
cristye7f51092010-01-17 00:39:37 +00003473 (void) FormatMagickString(factor,MaxTextExtent,"100,%g",
cristy8cd5b312010-01-07 01:10:24 +00003474 2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00003475 (void) ModulateImage(preview_image,factor);
3476 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
3477 break;
3478 }
3479 case BrightnessPreview:
3480 {
3481 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3482 if (preview_image == (Image *) NULL)
3483 break;
cristye7f51092010-01-17 00:39:37 +00003484 (void) FormatMagickString(factor,MaxTextExtent,"%g",2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00003485 (void) ModulateImage(preview_image,factor);
3486 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
3487 break;
3488 }
3489 case GammaPreview:
3490 default:
3491 {
3492 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3493 if (preview_image == (Image *) NULL)
3494 break;
3495 gamma+=0.4f;
3496 (void) GammaImageChannel(preview_image,DefaultChannels,gamma);
cristye7f51092010-01-17 00:39:37 +00003497 (void) FormatMagickString(label,MaxTextExtent,"gamma %g",gamma);
cristy3ed852e2009-09-05 21:47:34 +00003498 break;
3499 }
3500 case SpiffPreview:
3501 {
3502 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3503 if (preview_image != (Image *) NULL)
3504 for (x=0; x < i; x++)
3505 (void) ContrastImage(preview_image,MagickTrue);
3506 (void) FormatMagickString(label,MaxTextExtent,"contrast (%ld)",i+1);
3507 break;
3508 }
3509 case DullPreview:
3510 {
3511 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3512 if (preview_image == (Image *) NULL)
3513 break;
3514 for (x=0; x < i; x++)
3515 (void) ContrastImage(preview_image,MagickFalse);
3516 (void) FormatMagickString(label,MaxTextExtent,"+contrast (%ld)",i+1);
3517 break;
3518 }
3519 case GrayscalePreview:
3520 {
3521 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3522 if (preview_image == (Image *) NULL)
3523 break;
3524 colors<<=1;
3525 quantize_info.number_colors=colors;
3526 quantize_info.colorspace=GRAYColorspace;
3527 (void) QuantizeImage(&quantize_info,preview_image);
3528 (void) FormatMagickString(label,MaxTextExtent,
3529 "-colorspace gray -colors %ld",colors);
3530 break;
3531 }
3532 case QuantizePreview:
3533 {
3534 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3535 if (preview_image == (Image *) NULL)
3536 break;
3537 colors<<=1;
3538 quantize_info.number_colors=colors;
3539 (void) QuantizeImage(&quantize_info,preview_image);
3540 (void) FormatMagickString(label,MaxTextExtent,"colors %ld",colors);
3541 break;
3542 }
3543 case DespecklePreview:
3544 {
3545 for (x=0; x < (i-1); x++)
3546 {
3547 preview_image=DespeckleImage(thumbnail,exception);
3548 if (preview_image == (Image *) NULL)
3549 break;
3550 thumbnail=DestroyImage(thumbnail);
3551 thumbnail=preview_image;
3552 }
3553 preview_image=DespeckleImage(thumbnail,exception);
3554 if (preview_image == (Image *) NULL)
3555 break;
3556 (void) FormatMagickString(label,MaxTextExtent,"despeckle (%ld)",i+1);
3557 break;
3558 }
3559 case ReduceNoisePreview:
3560 {
3561 preview_image=ReduceNoiseImage(thumbnail,radius,exception);
cristye7f51092010-01-17 00:39:37 +00003562 (void) FormatMagickString(label,MaxTextExtent,"noise %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003563 break;
3564 }
3565 case AddNoisePreview:
3566 {
3567 switch ((int) i)
3568 {
3569 case 0:
3570 {
3571 (void) CopyMagickString(factor,"uniform",MaxTextExtent);
3572 break;
3573 }
3574 case 1:
3575 {
3576 (void) CopyMagickString(factor,"gaussian",MaxTextExtent);
3577 break;
3578 }
3579 case 2:
3580 {
3581 (void) CopyMagickString(factor,"multiplicative",MaxTextExtent);
3582 break;
3583 }
3584 case 3:
3585 {
3586 (void) CopyMagickString(factor,"impulse",MaxTextExtent);
3587 break;
3588 }
3589 case 4:
3590 {
3591 (void) CopyMagickString(factor,"laplacian",MaxTextExtent);
3592 break;
3593 }
3594 case 5:
3595 {
3596 (void) CopyMagickString(factor,"Poisson",MaxTextExtent);
3597 break;
3598 }
3599 default:
3600 {
3601 (void) CopyMagickString(thumbnail->magick,"NULL",MaxTextExtent);
3602 break;
3603 }
3604 }
3605 preview_image=ReduceNoiseImage(thumbnail,(double) i,exception);
3606 (void) FormatMagickString(label,MaxTextExtent,"+noise %s",factor);
3607 break;
3608 }
3609 case SharpenPreview:
3610 {
3611 preview_image=SharpenImage(thumbnail,radius,sigma,exception);
cristye7f51092010-01-17 00:39:37 +00003612 (void) FormatMagickString(label,MaxTextExtent,"sharpen %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003613 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00003614 break;
3615 }
3616 case BlurPreview:
3617 {
3618 preview_image=BlurImage(thumbnail,radius,sigma,exception);
cristye7f51092010-01-17 00:39:37 +00003619 (void) FormatMagickString(label,MaxTextExtent,"blur %gx%g",radius,
cristy3ed852e2009-09-05 21:47:34 +00003620 sigma);
3621 break;
3622 }
3623 case ThresholdPreview:
3624 {
3625 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3626 if (preview_image == (Image *) NULL)
3627 break;
3628 (void) BilevelImage(thumbnail,
3629 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
cristye7f51092010-01-17 00:39:37 +00003630 (void) FormatMagickString(label,MaxTextExtent,"threshold %g",
cristy3ed852e2009-09-05 21:47:34 +00003631 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
3632 break;
3633 }
3634 case EdgeDetectPreview:
3635 {
3636 preview_image=EdgeImage(thumbnail,radius,exception);
cristye7f51092010-01-17 00:39:37 +00003637 (void) FormatMagickString(label,MaxTextExtent,"edge %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003638 break;
3639 }
3640 case SpreadPreview:
3641 {
3642 preview_image=SpreadImage(thumbnail,radius,exception);
cristye7f51092010-01-17 00:39:37 +00003643 (void) FormatMagickString(label,MaxTextExtent,"spread %g",
cristy8cd5b312010-01-07 01:10:24 +00003644 radius+0.5);
cristy3ed852e2009-09-05 21:47:34 +00003645 break;
3646 }
3647 case SolarizePreview:
3648 {
3649 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3650 if (preview_image == (Image *) NULL)
3651 break;
3652 (void) SolarizeImage(preview_image,(double) QuantumRange*
3653 percentage/100.0);
cristye7f51092010-01-17 00:39:37 +00003654 (void) FormatMagickString(label,MaxTextExtent,"solarize %g",
cristy3ed852e2009-09-05 21:47:34 +00003655 (QuantumRange*percentage)/100.0);
3656 break;
3657 }
3658 case ShadePreview:
3659 {
3660 degrees+=10.0;
3661 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
3662 exception);
cristye7f51092010-01-17 00:39:37 +00003663 (void) FormatMagickString(label,MaxTextExtent,"shade %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003664 degrees,degrees);
cristy3ed852e2009-09-05 21:47:34 +00003665 break;
3666 }
3667 case RaisePreview:
3668 {
3669 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3670 if (preview_image == (Image *) NULL)
3671 break;
3672 geometry.width=(unsigned long) (2*i+2);
3673 geometry.height=(unsigned long) (2*i+2);
3674 geometry.x=i/2;
3675 geometry.y=i/2;
3676 (void) RaiseImage(preview_image,&geometry,MagickTrue);
3677 (void) FormatMagickString(label,MaxTextExtent,"raise %lux%lu%+ld%+ld",
3678 geometry.width,geometry.height,geometry.x,geometry.y);
3679 break;
3680 }
3681 case SegmentPreview:
3682 {
3683 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3684 if (preview_image == (Image *) NULL)
3685 break;
3686 threshold+=0.4f;
3687 (void) SegmentImage(preview_image,RGBColorspace,MagickFalse,threshold,
3688 threshold);
cristye7f51092010-01-17 00:39:37 +00003689 (void) FormatMagickString(label,MaxTextExtent,"segment %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00003690 threshold,threshold);
3691 break;
3692 }
3693 case SwirlPreview:
3694 {
3695 preview_image=SwirlImage(thumbnail,degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003696 (void) FormatMagickString(label,MaxTextExtent,"swirl %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003697 degrees+=45.0;
3698 break;
3699 }
3700 case ImplodePreview:
3701 {
3702 degrees+=0.1f;
3703 preview_image=ImplodeImage(thumbnail,degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003704 (void) FormatMagickString(label,MaxTextExtent,"implode %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003705 break;
3706 }
3707 case WavePreview:
3708 {
3709 degrees+=5.0f;
3710 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,exception);
cristye7f51092010-01-17 00:39:37 +00003711 (void) FormatMagickString(label,MaxTextExtent,"wave %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003712 0.5*degrees,2.0*degrees);
cristy3ed852e2009-09-05 21:47:34 +00003713 break;
3714 }
3715 case OilPaintPreview:
3716 {
3717 preview_image=OilPaintImage(thumbnail,(double) radius,exception);
cristye7f51092010-01-17 00:39:37 +00003718 (void) FormatMagickString(label,MaxTextExtent,"paint %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003719 break;
3720 }
3721 case CharcoalDrawingPreview:
3722 {
3723 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
3724 exception);
cristye7f51092010-01-17 00:39:37 +00003725 (void) FormatMagickString(label,MaxTextExtent,"charcoal %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00003726 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00003727 break;
3728 }
3729 case JPEGPreview:
3730 {
3731 char
3732 filename[MaxTextExtent];
3733
3734 int
3735 file;
3736
3737 MagickBooleanType
3738 status;
3739
3740 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3741 if (preview_image == (Image *) NULL)
3742 break;
3743 preview_info->quality=(unsigned long) percentage;
3744 (void) FormatMagickString(factor,MaxTextExtent,"%lu",
3745 preview_info->quality);
3746 file=AcquireUniqueFileResource(filename);
3747 if (file != -1)
3748 file=close(file)-1;
3749 (void) FormatMagickString(preview_image->filename,MaxTextExtent,
3750 "jpeg:%s",filename);
3751 status=WriteImage(preview_info,preview_image);
3752 if (status != MagickFalse)
3753 {
3754 Image
3755 *quality_image;
3756
3757 (void) CopyMagickString(preview_info->filename,
3758 preview_image->filename,MaxTextExtent);
3759 quality_image=ReadImage(preview_info,exception);
3760 if (quality_image != (Image *) NULL)
3761 {
3762 preview_image=DestroyImage(preview_image);
3763 preview_image=quality_image;
3764 }
3765 }
3766 (void) RelinquishUniqueFileResource(preview_image->filename);
3767 if ((GetBlobSize(preview_image)/1024) >= 1024)
cristye7f51092010-01-17 00:39:37 +00003768 (void) FormatMagickString(label,MaxTextExtent,"quality %s\n%gmb ",
cristy3ed852e2009-09-05 21:47:34 +00003769 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
3770 1024.0/1024.0);
3771 else
3772 if (GetBlobSize(preview_image) >= 1024)
cristy8cd5b312010-01-07 01:10:24 +00003773 (void) FormatMagickString(label,MaxTextExtent,
cristye7f51092010-01-17 00:39:37 +00003774 "quality %s\n%gkb ",factor,(double) ((MagickOffsetType)
cristy8cd5b312010-01-07 01:10:24 +00003775 GetBlobSize(preview_image))/1024.0);
cristy3ed852e2009-09-05 21:47:34 +00003776 else
3777 (void) FormatMagickString(label,MaxTextExtent,"quality %s\n%lub ",
3778 factor,(unsigned long) GetBlobSize(thumbnail));
3779 break;
3780 }
3781 }
3782 thumbnail=DestroyImage(thumbnail);
3783 percentage+=12.5;
3784 radius+=0.5;
3785 sigma+=0.25;
3786 if (preview_image == (Image *) NULL)
3787 break;
3788 (void) DeleteImageProperty(preview_image,"label");
3789 (void) SetImageProperty(preview_image,"label",label);
3790 AppendImageToList(&images,preview_image);
3791 proceed=SetImageProgress(image,PreviewImageTag,i,NumberTiles);
3792 if (proceed == MagickFalse)
3793 break;
3794 }
3795 if (images == (Image *) NULL)
3796 {
3797 preview_info=DestroyImageInfo(preview_info);
3798 return((Image *) NULL);
3799 }
3800 /*
3801 Create the montage.
3802 */
3803 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
3804 (void) CopyMagickString(montage_info->filename,image->filename,MaxTextExtent);
3805 montage_info->shadow=MagickTrue;
3806 (void) CloneString(&montage_info->tile,"3x3");
3807 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
3808 (void) CloneString(&montage_info->frame,DefaultTileFrame);
3809 montage_image=MontageImages(images,montage_info,exception);
3810 montage_info=DestroyMontageInfo(montage_info);
3811 images=DestroyImageList(images);
3812 if (montage_image == (Image *) NULL)
3813 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3814 if (montage_image->montage != (char *) NULL)
3815 {
3816 /*
3817 Free image directory.
3818 */
3819 montage_image->montage=(char *) RelinquishMagickMemory(
3820 montage_image->montage);
3821 if (image->directory != (char *) NULL)
3822 montage_image->directory=(char *) RelinquishMagickMemory(
3823 montage_image->directory);
3824 }
3825 preview_info=DestroyImageInfo(preview_info);
3826 return(montage_image);
3827}
3828
3829/*
3830%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3831% %
3832% %
3833% %
3834% R a d i a l B l u r I m a g e %
3835% %
3836% %
3837% %
3838%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3839%
3840% RadialBlurImage() applies a radial blur to the image.
3841%
3842% Andrew Protano contributed this effect.
3843%
3844% The format of the RadialBlurImage method is:
3845%
3846% Image *RadialBlurImage(const Image *image,const double angle,
3847% ExceptionInfo *exception)
3848% Image *RadialBlurImageChannel(const Image *image,const ChannelType channel,
3849% const double angle,ExceptionInfo *exception)
3850%
3851% A description of each parameter follows:
3852%
3853% o image: the image.
3854%
3855% o channel: the channel type.
3856%
3857% o angle: the angle of the radial blur.
3858%
3859% o exception: return any errors or warnings in this structure.
3860%
3861*/
3862
3863MagickExport Image *RadialBlurImage(const Image *image,const double angle,
3864 ExceptionInfo *exception)
3865{
3866 Image
3867 *blur_image;
3868
3869 blur_image=RadialBlurImageChannel(image,DefaultChannels,angle,exception);
3870 return(blur_image);
3871}
3872
3873MagickExport Image *RadialBlurImageChannel(const Image *image,
3874 const ChannelType channel,const double angle,ExceptionInfo *exception)
3875{
cristyc4c8d132010-01-07 01:58:38 +00003876 CacheView
3877 *blur_view,
3878 *image_view;
3879
cristy3ed852e2009-09-05 21:47:34 +00003880 Image
3881 *blur_image;
3882
3883 long
3884 progress,
3885 y;
3886
3887 MagickBooleanType
3888 status;
3889
3890 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00003891 bias;
cristy3ed852e2009-09-05 21:47:34 +00003892
3893 MagickRealType
3894 blur_radius,
3895 *cos_theta,
3896 offset,
3897 *sin_theta,
3898 theta;
3899
3900 PointInfo
3901 blur_center;
3902
3903 register long
3904 i;
3905
3906 unsigned long
3907 n;
3908
cristy3ed852e2009-09-05 21:47:34 +00003909 /*
3910 Allocate blur image.
3911 */
3912 assert(image != (Image *) NULL);
3913 assert(image->signature == MagickSignature);
3914 if (image->debug != MagickFalse)
3915 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3916 assert(exception != (ExceptionInfo *) NULL);
3917 assert(exception->signature == MagickSignature);
3918 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3919 if (blur_image == (Image *) NULL)
3920 return((Image *) NULL);
3921 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3922 {
3923 InheritException(exception,&blur_image->exception);
3924 blur_image=DestroyImage(blur_image);
3925 return((Image *) NULL);
3926 }
3927 blur_center.x=(double) image->columns/2.0;
3928 blur_center.y=(double) image->rows/2.0;
3929 blur_radius=hypot(blur_center.x,blur_center.y);
3930 n=(unsigned long) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+
3931 2UL);
3932 theta=DegreesToRadians(angle)/(MagickRealType) (n-1);
3933 cos_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3934 sizeof(*cos_theta));
3935 sin_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3936 sizeof(*sin_theta));
3937 if ((cos_theta == (MagickRealType *) NULL) ||
3938 (sin_theta == (MagickRealType *) NULL))
3939 {
3940 blur_image=DestroyImage(blur_image);
3941 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3942 }
3943 offset=theta*(MagickRealType) (n-1)/2.0;
3944 for (i=0; i < (long) n; i++)
3945 {
3946 cos_theta[i]=cos((double) (theta*i-offset));
3947 sin_theta[i]=sin((double) (theta*i-offset));
3948 }
3949 /*
3950 Radial blur image.
3951 */
3952 status=MagickTrue;
3953 progress=0;
cristyddd82202009-11-03 20:14:50 +00003954 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003955 image_view=AcquireCacheView(image);
3956 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003957#if defined(MAGICKCORE_OPENMP_SUPPORT)
3958 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003959#endif
3960 for (y=0; y < (long) blur_image->rows; y++)
3961 {
3962 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003963 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003964
3965 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003966 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00003967
3968 register long
3969 x;
3970
3971 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003972 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003973
3974 if (status == MagickFalse)
3975 continue;
3976 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3977 exception);
3978 if (q == (PixelPacket *) NULL)
3979 {
3980 status=MagickFalse;
3981 continue;
3982 }
3983 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
3984 for (x=0; x < (long) blur_image->columns; x++)
3985 {
3986 MagickPixelPacket
3987 qixel;
3988
3989 MagickRealType
3990 normalize,
3991 radius;
3992
3993 PixelPacket
3994 pixel;
3995
3996 PointInfo
3997 center;
3998
3999 register long
4000 i;
4001
4002 unsigned long
4003 step;
4004
4005 center.x=(double) x-blur_center.x;
4006 center.y=(double) y-blur_center.y;
4007 radius=hypot((double) center.x,center.y);
4008 if (radius == 0)
4009 step=1;
4010 else
4011 {
4012 step=(unsigned long) (blur_radius/radius);
4013 if (step == 0)
4014 step=1;
4015 else
4016 if (step >= n)
4017 step=n-1;
4018 }
4019 normalize=0.0;
cristyddd82202009-11-03 20:14:50 +00004020 qixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00004021 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
4022 {
4023 for (i=0; i < (long) n; i+=step)
4024 {
4025 (void) GetOneCacheViewVirtualPixel(image_view,(long) (blur_center.x+
4026 center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),(long) (
4027 blur_center.y+center.x*sin_theta[i]+center.y*cos_theta[i]+0.5),
4028 &pixel,exception);
4029 qixel.red+=pixel.red;
4030 qixel.green+=pixel.green;
4031 qixel.blue+=pixel.blue;
4032 qixel.opacity+=pixel.opacity;
4033 if (image->colorspace == CMYKColorspace)
4034 {
4035 indexes=GetCacheViewVirtualIndexQueue(image_view);
4036 qixel.index+=(*indexes);
4037 }
4038 normalize+=1.0;
4039 }
4040 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
4041 normalize);
4042 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004043 q->red=ClampToQuantum(normalize*qixel.red);
cristy3ed852e2009-09-05 21:47:34 +00004044 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004045 q->green=ClampToQuantum(normalize*qixel.green);
cristy3ed852e2009-09-05 21:47:34 +00004046 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004047 q->blue=ClampToQuantum(normalize*qixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00004048 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004049 q->opacity=ClampToQuantum(normalize*qixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00004050 if (((channel & IndexChannel) != 0) &&
4051 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00004052 blur_indexes[x]=(IndexPacket) ClampToQuantum(normalize*qixel.index);
cristy3ed852e2009-09-05 21:47:34 +00004053 }
4054 else
4055 {
4056 MagickRealType
4057 alpha,
4058 gamma;
4059
4060 alpha=1.0;
4061 gamma=0.0;
4062 for (i=0; i < (long) n; i+=step)
4063 {
4064 (void) GetOneCacheViewVirtualPixel(image_view,(long) (blur_center.x+
4065 center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),(long) (
4066 blur_center.y+center.x*sin_theta[i]+center.y*cos_theta[i]+0.5),
4067 &pixel,exception);
cristy46f08202010-01-10 04:04:21 +00004068 alpha=(MagickRealType) (QuantumScale*
4069 GetAlphaPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004070 qixel.red+=alpha*pixel.red;
4071 qixel.green+=alpha*pixel.green;
4072 qixel.blue+=alpha*pixel.blue;
4073 qixel.opacity+=pixel.opacity;
4074 if (image->colorspace == CMYKColorspace)
4075 {
4076 indexes=GetCacheViewVirtualIndexQueue(image_view);
4077 qixel.index+=alpha*(*indexes);
4078 }
4079 gamma+=alpha;
4080 normalize+=1.0;
4081 }
4082 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
4083 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
4084 normalize);
4085 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004086 q->red=ClampToQuantum(gamma*qixel.red);
cristy3ed852e2009-09-05 21:47:34 +00004087 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004088 q->green=ClampToQuantum(gamma*qixel.green);
cristy3ed852e2009-09-05 21:47:34 +00004089 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004090 q->blue=ClampToQuantum(gamma*qixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00004091 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004092 q->opacity=ClampToQuantum(normalize*qixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00004093 if (((channel & IndexChannel) != 0) &&
4094 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00004095 blur_indexes[x]=(IndexPacket) ClampToQuantum(gamma*qixel.index);
cristy3ed852e2009-09-05 21:47:34 +00004096 }
4097 q++;
4098 }
4099 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
4100 status=MagickFalse;
4101 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4102 {
4103 MagickBooleanType
4104 proceed;
4105
cristyb5d5f722009-11-04 03:03:49 +00004106#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004107 #pragma omp critical (MagickCore_RadialBlurImageChannel)
4108#endif
4109 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
4110 if (proceed == MagickFalse)
4111 status=MagickFalse;
4112 }
4113 }
4114 blur_view=DestroyCacheView(blur_view);
4115 image_view=DestroyCacheView(image_view);
4116 cos_theta=(MagickRealType *) RelinquishMagickMemory(cos_theta);
4117 sin_theta=(MagickRealType *) RelinquishMagickMemory(sin_theta);
4118 if (status == MagickFalse)
4119 blur_image=DestroyImage(blur_image);
4120 return(blur_image);
4121}
4122
4123/*
4124%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4125% %
4126% %
4127% %
4128% R e d u c e N o i s e I m a g e %
4129% %
4130% %
4131% %
4132%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4133%
4134% ReduceNoiseImage() smooths the contours of an image while still preserving
4135% edge information. The algorithm works by replacing each pixel with its
4136% neighbor closest in value. A neighbor is defined by radius. Use a radius
4137% of 0 and ReduceNoise() selects a suitable radius for you.
4138%
4139% The format of the ReduceNoiseImage method is:
4140%
4141% Image *ReduceNoiseImage(const Image *image,const double radius,
4142% ExceptionInfo *exception)
4143%
4144% A description of each parameter follows:
4145%
4146% o image: the image.
4147%
4148% o radius: the radius of the pixel neighborhood.
4149%
4150% o exception: return any errors or warnings in this structure.
4151%
4152*/
4153
4154static MagickPixelPacket GetNonpeakMedianPixelList(MedianPixelList *pixel_list)
4155{
4156 MagickPixelPacket
4157 pixel;
4158
4159 register long
4160 channel;
4161
4162 register MedianSkipList
4163 *list;
4164
4165 unsigned long
4166 center,
4167 color,
4168 count,
4169 previous,
4170 next;
4171
4172 unsigned short
4173 channels[5];
4174
4175 /*
4176 Finds the median value for each of the color.
4177 */
4178 center=pixel_list->center;
4179 for (channel=0; channel < 5; channel++)
4180 {
4181 list=pixel_list->lists+channel;
4182 color=65536UL;
4183 next=list->nodes[color].next[0];
4184 count=0;
4185 do
4186 {
4187 previous=color;
4188 color=next;
4189 next=list->nodes[color].next[0];
4190 count+=list->nodes[color].count;
4191 }
4192 while (count <= center);
4193 if ((previous == 65536UL) && (next != 65536UL))
4194 color=next;
4195 else
4196 if ((previous != 65536UL) && (next == 65536UL))
4197 color=previous;
4198 channels[channel]=(unsigned short) color;
4199 }
4200 GetMagickPixelPacket((const Image *) NULL,&pixel);
4201 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4202 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4203 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
4204 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
4205 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
4206 return(pixel);
4207}
4208
4209MagickExport Image *ReduceNoiseImage(const Image *image,const double radius,
4210 ExceptionInfo *exception)
4211{
4212#define ReduceNoiseImageTag "ReduceNoise/Image"
4213
cristyfa112112010-01-04 17:48:07 +00004214 CacheView
4215 *image_view,
4216 *noise_view;
4217
cristy3ed852e2009-09-05 21:47:34 +00004218 Image
4219 *noise_image;
4220
4221 long
4222 progress,
4223 y;
4224
4225 MagickBooleanType
4226 status;
4227
4228 MedianPixelList
cristyfa112112010-01-04 17:48:07 +00004229 **restrict pixel_list;
cristy3ed852e2009-09-05 21:47:34 +00004230
4231 unsigned long
4232 width;
4233
cristy3ed852e2009-09-05 21:47:34 +00004234 /*
4235 Initialize noise image attributes.
4236 */
4237 assert(image != (Image *) NULL);
4238 assert(image->signature == MagickSignature);
4239 if (image->debug != MagickFalse)
4240 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4241 assert(exception != (ExceptionInfo *) NULL);
4242 assert(exception->signature == MagickSignature);
4243 width=GetOptimalKernelWidth2D(radius,0.5);
4244 if ((image->columns < width) || (image->rows < width))
4245 ThrowImageException(OptionError,"ImageSmallerThanKernelRadius");
4246 noise_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4247 exception);
4248 if (noise_image == (Image *) NULL)
4249 return((Image *) NULL);
4250 if (SetImageStorageClass(noise_image,DirectClass) == MagickFalse)
4251 {
4252 InheritException(exception,&noise_image->exception);
4253 noise_image=DestroyImage(noise_image);
4254 return((Image *) NULL);
4255 }
4256 pixel_list=AcquireMedianPixelListThreadSet(width);
4257 if (pixel_list == (MedianPixelList **) NULL)
4258 {
4259 noise_image=DestroyImage(noise_image);
4260 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
4261 }
4262 /*
4263 Reduce noise image.
4264 */
4265 status=MagickTrue;
4266 progress=0;
4267 image_view=AcquireCacheView(image);
4268 noise_view=AcquireCacheView(noise_image);
cristyb5d5f722009-11-04 03:03:49 +00004269#if defined(MAGICKCORE_OPENMP_SUPPORT)
4270 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004271#endif
4272 for (y=0; y < (long) noise_image->rows; y++)
4273 {
4274 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004275 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00004276
4277 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004278 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00004279
4280 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004281 *restrict noise_indexes;
cristy3ed852e2009-09-05 21:47:34 +00004282
4283 register long
4284 id,
4285 x;
4286
4287 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004288 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004289
4290 if (status == MagickFalse)
4291 continue;
4292 p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y-(long) (width/
4293 2L),image->columns+width,width,exception);
4294 q=QueueCacheViewAuthenticPixels(noise_view,0,y,noise_image->columns,1,
4295 exception);
4296 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
4297 {
4298 status=MagickFalse;
4299 continue;
4300 }
4301 indexes=GetCacheViewVirtualIndexQueue(image_view);
4302 noise_indexes=GetCacheViewAuthenticIndexQueue(noise_view);
4303 id=GetOpenMPThreadId();
4304 for (x=0; x < (long) noise_image->columns; x++)
4305 {
4306 MagickPixelPacket
4307 pixel;
4308
4309 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004310 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +00004311
4312 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004313 *restrict s;
cristy3ed852e2009-09-05 21:47:34 +00004314
4315 register long
4316 u,
4317 v;
4318
4319 r=p;
4320 s=indexes+x;
4321 ResetMedianPixelList(pixel_list[id]);
4322 for (v=0; v < (long) width; v++)
4323 {
4324 for (u=0; u < (long) width; u++)
4325 InsertMedianPixelList(image,r+u,s+u,pixel_list[id]);
4326 r+=image->columns+width;
4327 s+=image->columns+width;
4328 }
4329 pixel=GetNonpeakMedianPixelList(pixel_list[id]);
4330 SetPixelPacket(noise_image,&pixel,q,noise_indexes+x);
4331 p++;
4332 q++;
4333 }
4334 if (SyncCacheViewAuthenticPixels(noise_view,exception) == MagickFalse)
4335 status=MagickFalse;
4336 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4337 {
4338 MagickBooleanType
4339 proceed;
4340
cristyb5d5f722009-11-04 03:03:49 +00004341#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004342 #pragma omp critical (MagickCore_ReduceNoiseImage)
4343#endif
4344 proceed=SetImageProgress(image,ReduceNoiseImageTag,progress++,
4345 image->rows);
4346 if (proceed == MagickFalse)
4347 status=MagickFalse;
4348 }
4349 }
4350 noise_view=DestroyCacheView(noise_view);
4351 image_view=DestroyCacheView(image_view);
4352 pixel_list=DestroyMedianPixelListThreadSet(pixel_list);
4353 return(noise_image);
4354}
4355
4356/*
4357%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4358% %
4359% %
4360% %
4361% S e l e c t i v e B l u r I m a g e %
4362% %
4363% %
4364% %
4365%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4366%
4367% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
4368% It is similar to the unsharpen mask that sharpens everything with contrast
4369% above a certain threshold.
4370%
4371% The format of the SelectiveBlurImage method is:
4372%
4373% Image *SelectiveBlurImage(const Image *image,const double radius,
4374% const double sigma,const double threshold,ExceptionInfo *exception)
4375% Image *SelectiveBlurImageChannel(const Image *image,
4376% const ChannelType channel,const double radius,const double sigma,
4377% const double threshold,ExceptionInfo *exception)
4378%
4379% A description of each parameter follows:
4380%
4381% o image: the image.
4382%
4383% o channel: the channel type.
4384%
4385% o radius: the radius of the Gaussian, in pixels, not counting the center
4386% pixel.
4387%
4388% o sigma: the standard deviation of the Gaussian, in pixels.
4389%
4390% o threshold: only pixels within this contrast threshold are included
4391% in the blur operation.
4392%
4393% o exception: return any errors or warnings in this structure.
4394%
4395*/
4396
4397static inline MagickBooleanType SelectiveContrast(const PixelPacket *p,
4398 const PixelPacket *q,const double threshold)
4399{
4400 if (fabs(PixelIntensity(p)-PixelIntensity(q)) < threshold)
4401 return(MagickTrue);
4402 return(MagickFalse);
4403}
4404
4405MagickExport Image *SelectiveBlurImage(const Image *image,const double radius,
4406 const double sigma,const double threshold,ExceptionInfo *exception)
4407{
4408 Image
4409 *blur_image;
4410
4411 blur_image=SelectiveBlurImageChannel(image,DefaultChannels,radius,sigma,
4412 threshold,exception);
4413 return(blur_image);
4414}
4415
4416MagickExport Image *SelectiveBlurImageChannel(const Image *image,
4417 const ChannelType channel,const double radius,const double sigma,
4418 const double threshold,ExceptionInfo *exception)
4419{
4420#define SelectiveBlurImageTag "SelectiveBlur/Image"
4421
cristy47e00502009-12-17 19:19:57 +00004422 CacheView
4423 *blur_view,
4424 *image_view;
4425
cristy3ed852e2009-09-05 21:47:34 +00004426 double
cristy3ed852e2009-09-05 21:47:34 +00004427 *kernel;
4428
4429 Image
4430 *blur_image;
4431
4432 long
cristy47e00502009-12-17 19:19:57 +00004433 j,
cristy3ed852e2009-09-05 21:47:34 +00004434 progress,
cristy47e00502009-12-17 19:19:57 +00004435 u,
cristy3ed852e2009-09-05 21:47:34 +00004436 v,
4437 y;
4438
4439 MagickBooleanType
4440 status;
4441
4442 MagickPixelPacket
cristy3ed852e2009-09-05 21:47:34 +00004443 bias;
4444
4445 register long
cristy47e00502009-12-17 19:19:57 +00004446 i;
cristy3ed852e2009-09-05 21:47:34 +00004447
4448 unsigned long
4449 width;
4450
cristy3ed852e2009-09-05 21:47:34 +00004451 /*
4452 Initialize blur image attributes.
4453 */
4454 assert(image != (Image *) NULL);
4455 assert(image->signature == MagickSignature);
4456 if (image->debug != MagickFalse)
4457 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4458 assert(exception != (ExceptionInfo *) NULL);
4459 assert(exception->signature == MagickSignature);
4460 width=GetOptimalKernelWidth1D(radius,sigma);
4461 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
4462 if (kernel == (double *) NULL)
4463 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy47e00502009-12-17 19:19:57 +00004464 j=(long) width/2;
cristy3ed852e2009-09-05 21:47:34 +00004465 i=0;
cristy47e00502009-12-17 19:19:57 +00004466 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00004467 {
cristy47e00502009-12-17 19:19:57 +00004468 for (u=(-j); u <= j; u++)
4469 kernel[i++]=exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
4470 (2.0*MagickPI*MagickSigma*MagickSigma);
cristy3ed852e2009-09-05 21:47:34 +00004471 }
4472 if (image->debug != MagickFalse)
4473 {
4474 char
4475 format[MaxTextExtent],
4476 *message;
4477
4478 long
4479 u,
4480 v;
4481
4482 register const double
4483 *k;
4484
4485 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
4486 " SelectiveBlurImage with %ldx%ld kernel:",width,width);
4487 message=AcquireString("");
4488 k=kernel;
4489 for (v=0; v < (long) width; v++)
4490 {
4491 *message='\0';
4492 (void) FormatMagickString(format,MaxTextExtent,"%ld: ",v);
4493 (void) ConcatenateString(&message,format);
4494 for (u=0; u < (long) width; u++)
4495 {
4496 (void) FormatMagickString(format,MaxTextExtent,"%+f ",*k++);
4497 (void) ConcatenateString(&message,format);
4498 }
4499 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
4500 }
4501 message=DestroyString(message);
4502 }
4503 blur_image=CloneImage(image,0,0,MagickTrue,exception);
4504 if (blur_image == (Image *) NULL)
4505 return((Image *) NULL);
4506 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
4507 {
4508 InheritException(exception,&blur_image->exception);
4509 blur_image=DestroyImage(blur_image);
4510 return((Image *) NULL);
4511 }
4512 /*
4513 Threshold blur image.
4514 */
4515 status=MagickTrue;
4516 progress=0;
cristyddd82202009-11-03 20:14:50 +00004517 GetMagickPixelPacket(image,&bias);
4518 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00004519 image_view=AcquireCacheView(image);
4520 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00004521#if defined(MAGICKCORE_OPENMP_SUPPORT)
4522 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004523#endif
4524 for (y=0; y < (long) image->rows; y++)
4525 {
4526 MagickBooleanType
4527 sync;
4528
4529 MagickRealType
4530 gamma;
4531
4532 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004533 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00004534
4535 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004536 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00004537
4538 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004539 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00004540
4541 register long
4542 x;
4543
4544 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004545 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004546
4547 if (status == MagickFalse)
4548 continue;
4549 p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y-(long) (width/
4550 2L),image->columns+width,width,exception);
4551 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
4552 exception);
4553 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
4554 {
4555 status=MagickFalse;
4556 continue;
4557 }
4558 indexes=GetCacheViewVirtualIndexQueue(image_view);
4559 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
4560 for (x=0; x < (long) image->columns; x++)
4561 {
4562 long
4563 j,
4564 v;
4565
4566 MagickPixelPacket
4567 pixel;
4568
4569 register const double
cristyc47d1f82009-11-26 01:44:43 +00004570 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00004571
4572 register long
4573 u;
4574
cristyddd82202009-11-03 20:14:50 +00004575 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00004576 k=kernel;
4577 gamma=0.0;
4578 j=0;
4579 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
4580 {
4581 for (v=0; v < (long) width; v++)
4582 {
4583 for (u=0; u < (long) width; u++)
4584 {
4585 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4586 {
4587 pixel.red+=(*k)*(p+u+j)->red;
4588 pixel.green+=(*k)*(p+u+j)->green;
4589 pixel.blue+=(*k)*(p+u+j)->blue;
4590 gamma+=(*k);
4591 k++;
4592 }
4593 }
4594 j+=image->columns+width;
4595 }
4596 if (gamma != 0.0)
4597 {
4598 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
4599 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004600 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004601 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004602 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004603 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004604 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004605 }
4606 if ((channel & OpacityChannel) != 0)
4607 {
4608 gamma=0.0;
4609 j=0;
4610 for (v=0; v < (long) width; v++)
4611 {
4612 for (u=0; u < (long) width; u++)
4613 {
4614 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4615 {
4616 pixel.opacity+=(*k)*(p+u+j)->opacity;
4617 gamma+=(*k);
4618 k++;
4619 }
4620 }
4621 j+=image->columns+width;
4622 }
4623 if (gamma != 0.0)
4624 {
4625 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4626 gamma);
cristyce70c172010-01-07 17:15:30 +00004627 SetOpacityPixelComponent(q,ClampToQuantum(gamma*
4628 GetOpacityPixelComponent(&pixel)));
cristy3ed852e2009-09-05 21:47:34 +00004629 }
4630 }
4631 if (((channel & IndexChannel) != 0) &&
4632 (image->colorspace == CMYKColorspace))
4633 {
4634 gamma=0.0;
4635 j=0;
4636 for (v=0; v < (long) width; v++)
4637 {
4638 for (u=0; u < (long) width; u++)
4639 {
4640 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4641 {
4642 pixel.index+=(*k)*indexes[x+u+j];
4643 gamma+=(*k);
4644 k++;
4645 }
4646 }
4647 j+=image->columns+width;
4648 }
4649 if (gamma != 0.0)
4650 {
4651 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4652 gamma);
cristy6db48122010-01-11 00:18:07 +00004653 blur_indexes[x]=ClampToQuantum(gamma*
4654 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004655 }
4656 }
4657 }
4658 else
4659 {
4660 MagickRealType
4661 alpha;
4662
4663 for (v=0; v < (long) width; v++)
4664 {
4665 for (u=0; u < (long) width; u++)
4666 {
4667 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4668 {
cristy46f08202010-01-10 04:04:21 +00004669 alpha=(MagickRealType) (QuantumScale*
4670 GetAlphaPixelComponent(p+u+j));
cristy3ed852e2009-09-05 21:47:34 +00004671 pixel.red+=(*k)*alpha*(p+u+j)->red;
4672 pixel.green+=(*k)*alpha*(p+u+j)->green;
4673 pixel.blue+=(*k)*alpha*(p+u+j)->blue;
4674 pixel.opacity+=(*k)*(p+u+j)->opacity;
4675 gamma+=(*k)*alpha;
4676 k++;
4677 }
4678 }
4679 j+=image->columns+width;
4680 }
4681 if (gamma != 0.0)
4682 {
4683 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
4684 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004685 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004686 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004687 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004688 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004689 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004690 }
4691 if ((channel & OpacityChannel) != 0)
4692 {
4693 gamma=0.0;
4694 j=0;
4695 for (v=0; v < (long) width; v++)
4696 {
4697 for (u=0; u < (long) width; u++)
4698 {
4699 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4700 {
4701 pixel.opacity+=(*k)*(p+u+j)->opacity;
4702 gamma+=(*k);
4703 k++;
4704 }
4705 }
4706 j+=image->columns+width;
4707 }
4708 if (gamma != 0.0)
4709 {
4710 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4711 gamma);
cristy6db48122010-01-11 00:18:07 +00004712 SetOpacityPixelComponent(q,
4713 ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004714 }
4715 }
4716 if (((channel & IndexChannel) != 0) &&
4717 (image->colorspace == CMYKColorspace))
4718 {
4719 gamma=0.0;
4720 j=0;
4721 for (v=0; v < (long) width; v++)
4722 {
4723 for (u=0; u < (long) width; u++)
4724 {
4725 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4726 {
cristy46f08202010-01-10 04:04:21 +00004727 alpha=(MagickRealType) (QuantumScale*
4728 GetAlphaPixelComponent(p+u+j));
cristy3ed852e2009-09-05 21:47:34 +00004729 pixel.index+=(*k)*alpha*indexes[x+u+j];
4730 gamma+=(*k);
4731 k++;
4732 }
4733 }
4734 j+=image->columns+width;
4735 }
4736 if (gamma != 0.0)
4737 {
4738 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4739 gamma);
cristy6db48122010-01-11 00:18:07 +00004740 blur_indexes[x]=ClampToQuantum(gamma*
4741 GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004742 }
4743 }
4744 }
4745 p++;
4746 q++;
4747 }
4748 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
4749 if (sync == MagickFalse)
4750 status=MagickFalse;
4751 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4752 {
4753 MagickBooleanType
4754 proceed;
4755
cristyb5d5f722009-11-04 03:03:49 +00004756#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004757 #pragma omp critical (MagickCore_SelectiveBlurImageChannel)
4758#endif
4759 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress++,
4760 image->rows);
4761 if (proceed == MagickFalse)
4762 status=MagickFalse;
4763 }
4764 }
4765 blur_image->type=image->type;
4766 blur_view=DestroyCacheView(blur_view);
4767 image_view=DestroyCacheView(image_view);
4768 kernel=(double *) RelinquishMagickMemory(kernel);
4769 if (status == MagickFalse)
4770 blur_image=DestroyImage(blur_image);
4771 return(blur_image);
4772}
4773
4774/*
4775%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4776% %
4777% %
4778% %
4779% S h a d e I m a g e %
4780% %
4781% %
4782% %
4783%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4784%
4785% ShadeImage() shines a distant light on an image to create a
4786% three-dimensional effect. You control the positioning of the light with
4787% azimuth and elevation; azimuth is measured in degrees off the x axis
4788% and elevation is measured in pixels above the Z axis.
4789%
4790% The format of the ShadeImage method is:
4791%
4792% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
4793% const double azimuth,const double elevation,ExceptionInfo *exception)
4794%
4795% A description of each parameter follows:
4796%
4797% o image: the image.
4798%
4799% o gray: A value other than zero shades the intensity of each pixel.
4800%
4801% o azimuth, elevation: Define the light source direction.
4802%
4803% o exception: return any errors or warnings in this structure.
4804%
4805*/
4806MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
4807 const double azimuth,const double elevation,ExceptionInfo *exception)
4808{
4809#define ShadeImageTag "Shade/Image"
4810
cristyc4c8d132010-01-07 01:58:38 +00004811 CacheView
4812 *image_view,
4813 *shade_view;
4814
cristy3ed852e2009-09-05 21:47:34 +00004815 Image
4816 *shade_image;
4817
4818 long
4819 progress,
4820 y;
4821
4822 MagickBooleanType
4823 status;
4824
4825 PrimaryInfo
4826 light;
4827
cristy3ed852e2009-09-05 21:47:34 +00004828 /*
4829 Initialize shaded image attributes.
4830 */
4831 assert(image != (const Image *) NULL);
4832 assert(image->signature == MagickSignature);
4833 if (image->debug != MagickFalse)
4834 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4835 assert(exception != (ExceptionInfo *) NULL);
4836 assert(exception->signature == MagickSignature);
4837 shade_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
4838 if (shade_image == (Image *) NULL)
4839 return((Image *) NULL);
4840 if (SetImageStorageClass(shade_image,DirectClass) == MagickFalse)
4841 {
4842 InheritException(exception,&shade_image->exception);
4843 shade_image=DestroyImage(shade_image);
4844 return((Image *) NULL);
4845 }
4846 /*
4847 Compute the light vector.
4848 */
4849 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
4850 cos(DegreesToRadians(elevation));
4851 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
4852 cos(DegreesToRadians(elevation));
4853 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
4854 /*
4855 Shade image.
4856 */
4857 status=MagickTrue;
4858 progress=0;
4859 image_view=AcquireCacheView(image);
4860 shade_view=AcquireCacheView(shade_image);
cristyb5d5f722009-11-04 03:03:49 +00004861#if defined(MAGICKCORE_OPENMP_SUPPORT)
4862 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004863#endif
4864 for (y=0; y < (long) image->rows; y++)
4865 {
4866 MagickRealType
4867 distance,
4868 normal_distance,
4869 shade;
4870
4871 PrimaryInfo
4872 normal;
4873
4874 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004875 *restrict p,
4876 *restrict s0,
4877 *restrict s1,
4878 *restrict s2;
cristy3ed852e2009-09-05 21:47:34 +00004879
4880 register long
4881 x;
4882
4883 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004884 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004885
4886 if (status == MagickFalse)
4887 continue;
4888 p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception);
4889 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
4890 exception);
4891 if ((p == (PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
4892 {
4893 status=MagickFalse;
4894 continue;
4895 }
4896 /*
4897 Shade this row of pixels.
4898 */
4899 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
4900 s0=p+1;
4901 s1=s0+image->columns+2;
4902 s2=s1+image->columns+2;
4903 for (x=0; x < (long) image->columns; x++)
4904 {
4905 /*
4906 Determine the surface normal and compute shading.
4907 */
4908 normal.x=(double) (PixelIntensity(s0-1)+PixelIntensity(s1-1)+
4909 PixelIntensity(s2-1)-PixelIntensity(s0+1)-PixelIntensity(s1+1)-
4910 PixelIntensity(s2+1));
4911 normal.y=(double) (PixelIntensity(s2-1)+PixelIntensity(s2)+
4912 PixelIntensity(s2+1)-PixelIntensity(s0-1)-PixelIntensity(s0)-
4913 PixelIntensity(s0+1));
4914 if ((normal.x == 0.0) && (normal.y == 0.0))
4915 shade=light.z;
4916 else
4917 {
4918 shade=0.0;
4919 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
4920 if (distance > MagickEpsilon)
4921 {
4922 normal_distance=
4923 normal.x*normal.x+normal.y*normal.y+normal.z*normal.z;
4924 if (normal_distance > (MagickEpsilon*MagickEpsilon))
4925 shade=distance/sqrt((double) normal_distance);
4926 }
4927 }
4928 if (gray != MagickFalse)
4929 {
4930 q->red=(Quantum) shade;
4931 q->green=(Quantum) shade;
4932 q->blue=(Quantum) shade;
4933 }
4934 else
4935 {
cristyce70c172010-01-07 17:15:30 +00004936 q->red=ClampToQuantum(QuantumScale*shade*s1->red);
4937 q->green=ClampToQuantum(QuantumScale*shade*s1->green);
4938 q->blue=ClampToQuantum(QuantumScale*shade*s1->blue);
cristy3ed852e2009-09-05 21:47:34 +00004939 }
4940 q->opacity=s1->opacity;
4941 s0++;
4942 s1++;
4943 s2++;
4944 q++;
4945 }
4946 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
4947 status=MagickFalse;
4948 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4949 {
4950 MagickBooleanType
4951 proceed;
4952
cristyb5d5f722009-11-04 03:03:49 +00004953#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004954 #pragma omp critical (MagickCore_ShadeImage)
4955#endif
4956 proceed=SetImageProgress(image,ShadeImageTag,progress++,image->rows);
4957 if (proceed == MagickFalse)
4958 status=MagickFalse;
4959 }
4960 }
4961 shade_view=DestroyCacheView(shade_view);
4962 image_view=DestroyCacheView(image_view);
4963 if (status == MagickFalse)
4964 shade_image=DestroyImage(shade_image);
4965 return(shade_image);
4966}
4967
4968/*
4969%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4970% %
4971% %
4972% %
4973% S h a r p e n I m a g e %
4974% %
4975% %
4976% %
4977%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4978%
4979% SharpenImage() sharpens the image. We convolve the image with a Gaussian
4980% operator of the given radius and standard deviation (sigma). For
4981% reasonable results, radius should be larger than sigma. Use a radius of 0
4982% and SharpenImage() selects a suitable radius for you.
4983%
4984% Using a separable kernel would be faster, but the negative weights cancel
4985% out on the corners of the kernel producing often undesirable ringing in the
4986% filtered result; this can be avoided by using a 2D gaussian shaped image
4987% sharpening kernel instead.
4988%
4989% The format of the SharpenImage method is:
4990%
4991% Image *SharpenImage(const Image *image,const double radius,
4992% const double sigma,ExceptionInfo *exception)
4993% Image *SharpenImageChannel(const Image *image,const ChannelType channel,
4994% const double radius,const double sigma,ExceptionInfo *exception)
4995%
4996% A description of each parameter follows:
4997%
4998% o image: the image.
4999%
5000% o channel: the channel type.
5001%
5002% o radius: the radius of the Gaussian, in pixels, not counting the center
5003% pixel.
5004%
5005% o sigma: the standard deviation of the Laplacian, in pixels.
5006%
5007% o exception: return any errors or warnings in this structure.
5008%
5009*/
5010
5011MagickExport Image *SharpenImage(const Image *image,const double radius,
5012 const double sigma,ExceptionInfo *exception)
5013{
5014 Image
5015 *sharp_image;
5016
5017 sharp_image=SharpenImageChannel(image,DefaultChannels,radius,sigma,exception);
5018 return(sharp_image);
5019}
5020
5021MagickExport Image *SharpenImageChannel(const Image *image,
5022 const ChannelType channel,const double radius,const double sigma,
5023 ExceptionInfo *exception)
5024{
5025 double
cristy47e00502009-12-17 19:19:57 +00005026 *kernel,
5027 normalize;
cristy3ed852e2009-09-05 21:47:34 +00005028
5029 Image
5030 *sharp_image;
5031
cristy47e00502009-12-17 19:19:57 +00005032 long
5033 j,
cristy3ed852e2009-09-05 21:47:34 +00005034 u,
5035 v;
5036
cristy47e00502009-12-17 19:19:57 +00005037 register long
5038 i;
5039
cristy3ed852e2009-09-05 21:47:34 +00005040 unsigned long
5041 width;
5042
5043 assert(image != (const Image *) NULL);
5044 assert(image->signature == MagickSignature);
5045 if (image->debug != MagickFalse)
5046 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5047 assert(exception != (ExceptionInfo *) NULL);
5048 assert(exception->signature == MagickSignature);
5049 width=GetOptimalKernelWidth2D(radius,sigma);
5050 kernel=(double *) AcquireQuantumMemory((size_t) width*width,sizeof(*kernel));
5051 if (kernel == (double *) NULL)
5052 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy3ed852e2009-09-05 21:47:34 +00005053 normalize=0.0;
cristy47e00502009-12-17 19:19:57 +00005054 j=(long) width/2;
5055 i=0;
5056 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00005057 {
cristy47e00502009-12-17 19:19:57 +00005058 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00005059 {
cristy47e00502009-12-17 19:19:57 +00005060 kernel[i]=(-exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
5061 (2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00005062 normalize+=kernel[i];
5063 i++;
5064 }
5065 }
5066 kernel[i/2]=(double) ((-2.0)*normalize);
5067 sharp_image=ConvolveImageChannel(image,channel,width,kernel,exception);
5068 kernel=(double *) RelinquishMagickMemory(kernel);
5069 return(sharp_image);
5070}
5071
5072/*
5073%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5074% %
5075% %
5076% %
5077% S p r e a d I m a g e %
5078% %
5079% %
5080% %
5081%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5082%
5083% SpreadImage() is a special effects method that randomly displaces each
5084% pixel in a block defined by the radius parameter.
5085%
5086% The format of the SpreadImage method is:
5087%
5088% Image *SpreadImage(const Image *image,const double radius,
5089% ExceptionInfo *exception)
5090%
5091% A description of each parameter follows:
5092%
5093% o image: the image.
5094%
5095% o radius: Choose a random pixel in a neighborhood of this extent.
5096%
5097% o exception: return any errors or warnings in this structure.
5098%
5099*/
5100MagickExport Image *SpreadImage(const Image *image,const double radius,
5101 ExceptionInfo *exception)
5102{
5103#define SpreadImageTag "Spread/Image"
5104
cristyfa112112010-01-04 17:48:07 +00005105 CacheView
5106 *image_view;
5107
cristy3ed852e2009-09-05 21:47:34 +00005108 Image
5109 *spread_image;
5110
5111 long
5112 progress,
5113 y;
5114
5115 MagickBooleanType
5116 status;
5117
5118 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00005119 bias;
cristy3ed852e2009-09-05 21:47:34 +00005120
5121 RandomInfo
cristyfa112112010-01-04 17:48:07 +00005122 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00005123
5124 ResampleFilter
cristyfa112112010-01-04 17:48:07 +00005125 **restrict resample_filter;
cristy3ed852e2009-09-05 21:47:34 +00005126
5127 unsigned long
5128 width;
5129
cristy3ed852e2009-09-05 21:47:34 +00005130 /*
5131 Initialize spread image attributes.
5132 */
5133 assert(image != (Image *) NULL);
5134 assert(image->signature == MagickSignature);
5135 if (image->debug != MagickFalse)
5136 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5137 assert(exception != (ExceptionInfo *) NULL);
5138 assert(exception->signature == MagickSignature);
5139 spread_image=CloneImage(image,image->columns,image->rows,MagickTrue,
5140 exception);
5141 if (spread_image == (Image *) NULL)
5142 return((Image *) NULL);
5143 if (SetImageStorageClass(spread_image,DirectClass) == MagickFalse)
5144 {
5145 InheritException(exception,&spread_image->exception);
5146 spread_image=DestroyImage(spread_image);
5147 return((Image *) NULL);
5148 }
5149 /*
5150 Spread image.
5151 */
5152 status=MagickTrue;
5153 progress=0;
cristyddd82202009-11-03 20:14:50 +00005154 GetMagickPixelPacket(spread_image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00005155 width=GetOptimalKernelWidth1D(radius,0.5);
cristyb2a11ae2010-02-22 00:53:36 +00005156 resample_filter=AcquireResampleFilterThreadSet(image,
5157 UndefinedVirtualPixelMethod,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00005158 random_info=AcquireRandomInfoThreadSet();
5159 image_view=AcquireCacheView(spread_image);
cristyb5d5f722009-11-04 03:03:49 +00005160#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy00f95372010-02-13 16:39:29 +00005161 #pragma omp parallel for schedule(static) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00005162#endif
5163 for (y=0; y < (long) spread_image->rows; y++)
5164 {
5165 MagickPixelPacket
5166 pixel;
5167
5168 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00005169 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00005170
5171 register long
5172 id,
5173 x;
5174
5175 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00005176 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00005177
5178 if (status == MagickFalse)
5179 continue;
5180 q=QueueCacheViewAuthenticPixels(image_view,0,y,spread_image->columns,1,
5181 exception);
5182 if (q == (PixelPacket *) NULL)
5183 {
5184 status=MagickFalse;
5185 continue;
5186 }
5187 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristyddd82202009-11-03 20:14:50 +00005188 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00005189 id=GetOpenMPThreadId();
5190 for (x=0; x < (long) spread_image->columns; x++)
5191 {
5192 (void) ResamplePixelColor(resample_filter[id],(double) x+width*
5193 (GetPseudoRandomValue(random_info[id])-0.5),(double) y+width*
5194 (GetPseudoRandomValue(random_info[id])-0.5),&pixel);
5195 SetPixelPacket(spread_image,&pixel,q,indexes+x);
5196 q++;
5197 }
5198 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
5199 status=MagickFalse;
5200 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5201 {
5202 MagickBooleanType
5203 proceed;
5204
cristyb5d5f722009-11-04 03:03:49 +00005205#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00005206 #pragma omp critical (MagickCore_SpreadImage)
5207#endif
5208 proceed=SetImageProgress(image,SpreadImageTag,progress++,image->rows);
5209 if (proceed == MagickFalse)
5210 status=MagickFalse;
5211 }
5212 }
5213 image_view=DestroyCacheView(image_view);
5214 random_info=DestroyRandomInfoThreadSet(random_info);
5215 resample_filter=DestroyResampleFilterThreadSet(resample_filter);
5216 return(spread_image);
5217}
5218
5219/*
5220%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5221% %
5222% %
5223% %
5224% U n s h a r p M a s k I m a g e %
5225% %
5226% %
5227% %
5228%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5229%
5230% UnsharpMaskImage() sharpens one or more image channels. We convolve the
5231% image with a Gaussian operator of the given radius and standard deviation
5232% (sigma). For reasonable results, radius should be larger than sigma. Use a
5233% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
5234%
5235% The format of the UnsharpMaskImage method is:
5236%
5237% Image *UnsharpMaskImage(const Image *image,const double radius,
5238% const double sigma,const double amount,const double threshold,
5239% ExceptionInfo *exception)
5240% Image *UnsharpMaskImageChannel(const Image *image,
5241% const ChannelType channel,const double radius,const double sigma,
5242% const double amount,const double threshold,ExceptionInfo *exception)
5243%
5244% A description of each parameter follows:
5245%
5246% o image: the image.
5247%
5248% o channel: the channel type.
5249%
5250% o radius: the radius of the Gaussian, in pixels, not counting the center
5251% pixel.
5252%
5253% o sigma: the standard deviation of the Gaussian, in pixels.
5254%
5255% o amount: the percentage of the difference between the original and the
5256% blur image that is added back into the original.
5257%
5258% o threshold: the threshold in pixels needed to apply the diffence amount.
5259%
5260% o exception: return any errors or warnings in this structure.
5261%
5262*/
5263
5264MagickExport Image *UnsharpMaskImage(const Image *image,const double radius,
5265 const double sigma,const double amount,const double threshold,
5266 ExceptionInfo *exception)
5267{
5268 Image
5269 *sharp_image;
5270
5271 sharp_image=UnsharpMaskImageChannel(image,DefaultChannels,radius,sigma,amount,
5272 threshold,exception);
5273 return(sharp_image);
5274}
5275
5276MagickExport Image *UnsharpMaskImageChannel(const Image *image,
5277 const ChannelType channel,const double radius,const double sigma,
5278 const double amount,const double threshold,ExceptionInfo *exception)
5279{
5280#define SharpenImageTag "Sharpen/Image"
5281
cristyc4c8d132010-01-07 01:58:38 +00005282 CacheView
5283 *image_view,
5284 *unsharp_view;
5285
cristy3ed852e2009-09-05 21:47:34 +00005286 Image
5287 *unsharp_image;
5288
5289 long
5290 progress,
5291 y;
5292
5293 MagickBooleanType
5294 status;
5295
5296 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00005297 bias;
cristy3ed852e2009-09-05 21:47:34 +00005298
5299 MagickRealType
5300 quantum_threshold;
5301
cristy3ed852e2009-09-05 21:47:34 +00005302 assert(image != (const Image *) NULL);
5303 assert(image->signature == MagickSignature);
5304 if (image->debug != MagickFalse)
5305 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5306 assert(exception != (ExceptionInfo *) NULL);
5307 unsharp_image=BlurImageChannel(image,channel,radius,sigma,exception);
5308 if (unsharp_image == (Image *) NULL)
5309 return((Image *) NULL);
5310 quantum_threshold=(MagickRealType) QuantumRange*threshold;
5311 /*
5312 Unsharp-mask image.
5313 */
5314 status=MagickTrue;
5315 progress=0;
cristyddd82202009-11-03 20:14:50 +00005316 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00005317 image_view=AcquireCacheView(image);
5318 unsharp_view=AcquireCacheView(unsharp_image);
cristyb5d5f722009-11-04 03:03:49 +00005319#if defined(MAGICKCORE_OPENMP_SUPPORT)
5320 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00005321#endif
5322 for (y=0; y < (long) image->rows; y++)
5323 {
5324 MagickPixelPacket
5325 pixel;
5326
5327 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00005328 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00005329
5330 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00005331 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00005332
5333 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00005334 *restrict unsharp_indexes;
cristy3ed852e2009-09-05 21:47:34 +00005335
5336 register long
5337 x;
5338
5339 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00005340 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00005341
5342 if (status == MagickFalse)
5343 continue;
5344 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
5345 q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
5346 exception);
5347 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
5348 {
5349 status=MagickFalse;
5350 continue;
5351 }
5352 indexes=GetCacheViewVirtualIndexQueue(image_view);
5353 unsharp_indexes=GetCacheViewAuthenticIndexQueue(unsharp_view);
cristyddd82202009-11-03 20:14:50 +00005354 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00005355 for (x=0; x < (long) image->columns; x++)
5356 {
5357 if ((channel & RedChannel) != 0)
5358 {
5359 pixel.red=p->red-(MagickRealType) q->red;
5360 if (fabs(2.0*pixel.red) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005361 pixel.red=(MagickRealType) GetRedPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005362 else
5363 pixel.red=(MagickRealType) p->red+(pixel.red*amount);
cristyce70c172010-01-07 17:15:30 +00005364 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00005365 }
5366 if ((channel & GreenChannel) != 0)
5367 {
5368 pixel.green=p->green-(MagickRealType) q->green;
5369 if (fabs(2.0*pixel.green) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005370 pixel.green=(MagickRealType) GetGreenPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005371 else
5372 pixel.green=(MagickRealType) p->green+(pixel.green*amount);
cristyce70c172010-01-07 17:15:30 +00005373 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00005374 }
5375 if ((channel & BlueChannel) != 0)
5376 {
5377 pixel.blue=p->blue-(MagickRealType) q->blue;
5378 if (fabs(2.0*pixel.blue) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005379 pixel.blue=(MagickRealType) GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005380 else
5381 pixel.blue=(MagickRealType) p->blue+(pixel.blue*amount);
cristyce70c172010-01-07 17:15:30 +00005382 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00005383 }
5384 if ((channel & OpacityChannel) != 0)
5385 {
5386 pixel.opacity=p->opacity-(MagickRealType) q->opacity;
5387 if (fabs(2.0*pixel.opacity) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005388 pixel.opacity=(MagickRealType) GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005389 else
5390 pixel.opacity=p->opacity+(pixel.opacity*amount);
cristyce70c172010-01-07 17:15:30 +00005391 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00005392 }
5393 if (((channel & IndexChannel) != 0) &&
5394 (image->colorspace == CMYKColorspace))
5395 {
5396 pixel.index=unsharp_indexes[x]-(MagickRealType) indexes[x];
5397 if (fabs(2.0*pixel.index) < quantum_threshold)
5398 pixel.index=(MagickRealType) unsharp_indexes[x];
5399 else
5400 pixel.index=(MagickRealType) unsharp_indexes[x]+(pixel.index*
5401 amount);
cristyce70c172010-01-07 17:15:30 +00005402 unsharp_indexes[x]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00005403 }
5404 p++;
5405 q++;
5406 }
5407 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
5408 status=MagickFalse;
5409 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5410 {
5411 MagickBooleanType
5412 proceed;
5413
cristyb5d5f722009-11-04 03:03:49 +00005414#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00005415 #pragma omp critical (MagickCore_UnsharpMaskImageChannel)
5416#endif
5417 proceed=SetImageProgress(image,SharpenImageTag,progress++,image->rows);
5418 if (proceed == MagickFalse)
5419 status=MagickFalse;
5420 }
5421 }
5422 unsharp_image->type=image->type;
5423 unsharp_view=DestroyCacheView(unsharp_view);
5424 image_view=DestroyCacheView(image_view);
5425 if (status == MagickFalse)
5426 unsharp_image=DestroyImage(unsharp_image);
5427 return(unsharp_image);
5428}