blob: 7ddc998c1324a20f4b7ce9c3ba469a9cc80b418f [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"
44#include "magick/property.h"
45#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
144 double
cristy47e00502009-12-17 19:19:57 +0000145 **kernel,
146 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000147
148 Image
149 *blur_image,
150 *edge_image,
151 *gaussian_image;
152
153 long
154 j,
cristy47e00502009-12-17 19:19:57 +0000155 k,
cristy3ed852e2009-09-05 21:47:34 +0000156 progress,
cristy47e00502009-12-17 19:19:57 +0000157 u,
158 v,
cristy3ed852e2009-09-05 21:47:34 +0000159 y;
160
161 MagickBooleanType
162 status;
163
164 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +0000165 bias;
cristy3ed852e2009-09-05 21:47:34 +0000166
cristy3ed852e2009-09-05 21:47:34 +0000167 register long
cristy47e00502009-12-17 19:19:57 +0000168 i;
cristy3ed852e2009-09-05 21:47:34 +0000169
170 unsigned long
171 width;
172
173 CacheView
174 *blur_view,
175 *edge_view,
176 *image_view;
177
178 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))
340 alpha=(MagickRealType) (QuantumScale*(QuantumRange-p->opacity));
341 if ((channel & RedChannel) != 0)
342 pixel.red+=(*k)*alpha*p->red;
343 if ((channel & GreenChannel) != 0)
344 pixel.green+=(*k)*alpha*p->green;
345 if ((channel & BlueChannel) != 0)
346 pixel.blue+=(*k)*alpha*p->blue;
347 if ((channel & OpacityChannel) != 0)
348 pixel.opacity+=(*k)*p->opacity;
349 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)
cristyddd82202009-11-03 20:14:50 +0000359 q->red=RoundToQuantum(gamma*pixel.red);
cristy3ed852e2009-09-05 21:47:34 +0000360 if ((channel & GreenChannel) != 0)
cristyddd82202009-11-03 20:14:50 +0000361 q->green=RoundToQuantum(gamma*pixel.green);
cristy3ed852e2009-09-05 21:47:34 +0000362 if ((channel & BlueChannel) != 0)
cristyddd82202009-11-03 20:14:50 +0000363 q->blue=RoundToQuantum(gamma*pixel.blue);
cristy3ed852e2009-09-05 21:47:34 +0000364 if ((channel & OpacityChannel) != 0)
cristyddd82202009-11-03 20:14:50 +0000365 q->opacity=RoundToQuantum(pixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +0000366 if (((channel & IndexChannel) != 0) &&
367 (image->colorspace == CMYKColorspace))
cristyddd82202009-11-03 20:14:50 +0000368 blur_indexes[x]=RoundToQuantum(gamma*pixel.index);
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
459 double
cristy47e00502009-12-17 19:19:57 +0000460 **kernel,
461 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000462
463 Image
464 *sharp_image,
465 *edge_image,
466 *gaussian_image;
467
468 long
469 j,
cristy47e00502009-12-17 19:19:57 +0000470 k,
cristy3ed852e2009-09-05 21:47:34 +0000471 progress,
cristy47e00502009-12-17 19:19:57 +0000472 u,
473 v,
cristy3ed852e2009-09-05 21:47:34 +0000474 y;
475
476 MagickBooleanType
477 status;
478
479 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +0000480 bias;
cristy3ed852e2009-09-05 21:47:34 +0000481
cristy3ed852e2009-09-05 21:47:34 +0000482 register long
cristy47e00502009-12-17 19:19:57 +0000483 i;
cristy3ed852e2009-09-05 21:47:34 +0000484
485 unsigned long
486 width;
487
488 CacheView
489 *sharp_view,
490 *edge_view,
491 *image_view;
492
493 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))
655 alpha=(MagickRealType) (QuantumScale*(QuantumRange-p->opacity));
656 if ((channel & RedChannel) != 0)
657 pixel.red+=(*k)*alpha*p->red;
658 if ((channel & GreenChannel) != 0)
659 pixel.green+=(*k)*alpha*p->green;
660 if ((channel & BlueChannel) != 0)
661 pixel.blue+=(*k)*alpha*p->blue;
662 if ((channel & OpacityChannel) != 0)
663 pixel.opacity+=(*k)*p->opacity;
664 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)
cristyddd82202009-11-03 20:14:50 +0000674 q->red=RoundToQuantum(gamma*pixel.red);
cristy3ed852e2009-09-05 21:47:34 +0000675 if ((channel & GreenChannel) != 0)
cristyddd82202009-11-03 20:14:50 +0000676 q->green=RoundToQuantum(gamma*pixel.green);
cristy3ed852e2009-09-05 21:47:34 +0000677 if ((channel & BlueChannel) != 0)
cristyddd82202009-11-03 20:14:50 +0000678 q->blue=RoundToQuantum(gamma*pixel.blue);
cristy3ed852e2009-09-05 21:47:34 +0000679 if ((channel & OpacityChannel) != 0)
cristyddd82202009-11-03 20:14:50 +0000680 q->opacity=RoundToQuantum(pixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +0000681 if (((channel & IndexChannel) != 0) &&
682 (image->colorspace == CMYKColorspace))
cristyddd82202009-11-03 20:14:50 +0000683 sharp_indexes[x]=RoundToQuantum(gamma*pixel.index);
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
809 double
810 *kernel;
811
812 Image
813 *blur_image;
814
815 long
816 progress,
817 x,
818 y;
819
820 MagickBooleanType
821 status;
822
823 MagickPixelPacket
cristy3ed852e2009-09-05 21:47:34 +0000824 bias;
825
826 register long
827 i;
828
829 unsigned long
830 width;
831
832 CacheView
833 *blur_view,
834 *image_view;
835
836 /*
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);
881 (void) FormatMagickString(format,MaxTextExtent,"%g ",*k++);
882 (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)
cristyddd82202009-11-03 20:14:50 +0000957 q->red=RoundToQuantum(pixel.red);
cristy3ed852e2009-09-05 21:47:34 +0000958 if ((channel & GreenChannel) != 0)
cristyddd82202009-11-03 20:14:50 +0000959 q->green=RoundToQuantum(pixel.green);
cristy3ed852e2009-09-05 21:47:34 +0000960 if ((channel & BlueChannel) != 0)
cristyddd82202009-11-03 20:14:50 +0000961 q->blue=RoundToQuantum(pixel.blue);
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 }
cristyddd82202009-11-03 20:14:50 +0000972 q->opacity=RoundToQuantum(pixel.opacity);
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 }
cristyddd82202009-11-03 20:14:50 +0000988 blur_indexes[x]=RoundToQuantum(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 {
1000 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
1001 kernel_pixels->opacity));
1002 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)
cristyddd82202009-11-03 20:14:50 +00001011 q->red=RoundToQuantum(gamma*pixel.red);
cristy3ed852e2009-09-05 21:47:34 +00001012 if ((channel & GreenChannel) != 0)
cristyddd82202009-11-03 20:14:50 +00001013 q->green=RoundToQuantum(gamma*pixel.green);
cristy3ed852e2009-09-05 21:47:34 +00001014 if ((channel & BlueChannel) != 0)
cristyddd82202009-11-03 20:14:50 +00001015 q->blue=RoundToQuantum(gamma*pixel.blue);
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 }
cristyddd82202009-11-03 20:14:50 +00001026 q->opacity=RoundToQuantum(pixel.opacity);
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 {
1039 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
1040 kernel_pixels->opacity));
1041 pixel.index+=(*k)*alpha*(*kernel_indexes);
1042 k++;
1043 kernel_pixels++;
1044 kernel_indexes++;
1045 }
cristyddd82202009-11-03 20:14:50 +00001046 blur_indexes[x]=RoundToQuantum(gamma*pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00001047 }
1048 }
1049 p++;
1050 q++;
1051 }
1052 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1053 status=MagickFalse;
1054 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1055 {
1056 MagickBooleanType
1057 proceed;
1058
cristyb5d5f722009-11-04 03:03:49 +00001059#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001060 #pragma omp critical (MagickCore_BlurImageChannel)
1061#endif
1062 proceed=SetImageProgress(image,BlurImageTag,progress++,blur_image->rows+
1063 blur_image->columns);
1064 if (proceed == MagickFalse)
1065 status=MagickFalse;
1066 }
1067 }
1068 blur_view=DestroyCacheView(blur_view);
1069 image_view=DestroyCacheView(image_view);
1070 /*
1071 Blur columns.
1072 */
1073 image_view=AcquireCacheView(blur_image);
1074 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00001075#if defined(MAGICKCORE_OPENMP_SUPPORT)
1076 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001077#endif
1078 for (x=0; x < (long) blur_image->columns; x++)
1079 {
1080 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001081 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001082
1083 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001084 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001085
1086 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001087 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001088
1089 register long
1090 y;
1091
1092 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001093 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001094
1095 if (status == MagickFalse)
1096 continue;
1097 p=GetCacheViewVirtualPixels(image_view,x,-((long) width/2L),1,image->rows+
1098 width,exception);
1099 q=GetCacheViewAuthenticPixels(blur_view,x,0,1,blur_image->rows,exception);
1100 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1101 {
1102 status=MagickFalse;
1103 continue;
1104 }
1105 indexes=GetCacheViewVirtualIndexQueue(image_view);
1106 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
1107 for (y=0; y < (long) blur_image->rows; y++)
1108 {
1109 MagickPixelPacket
1110 pixel;
1111
1112 register const double
cristyc47d1f82009-11-26 01:44:43 +00001113 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00001114
1115 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001116 *restrict kernel_pixels;
cristy3ed852e2009-09-05 21:47:34 +00001117
1118 register long
1119 i;
1120
cristyddd82202009-11-03 20:14:50 +00001121 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00001122 k=kernel;
1123 kernel_pixels=p;
1124 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
1125 {
1126 for (i=0; i < (long) width; i++)
1127 {
1128 pixel.red+=(*k)*kernel_pixels->red;
1129 pixel.green+=(*k)*kernel_pixels->green;
1130 pixel.blue+=(*k)*kernel_pixels->blue;
1131 k++;
1132 kernel_pixels++;
1133 }
1134 if ((channel & RedChannel) != 0)
cristyddd82202009-11-03 20:14:50 +00001135 q->red=RoundToQuantum(pixel.red);
cristy3ed852e2009-09-05 21:47:34 +00001136 if ((channel & GreenChannel) != 0)
cristyddd82202009-11-03 20:14:50 +00001137 q->green=RoundToQuantum(pixel.green);
cristy3ed852e2009-09-05 21:47:34 +00001138 if ((channel & BlueChannel) != 0)
cristyddd82202009-11-03 20:14:50 +00001139 q->blue=RoundToQuantum(pixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00001140 if ((channel & OpacityChannel) != 0)
1141 {
1142 k=kernel;
1143 kernel_pixels=p;
1144 for (i=0; i < (long) width; i++)
1145 {
1146 pixel.opacity+=(*k)*kernel_pixels->opacity;
1147 k++;
1148 kernel_pixels++;
1149 }
cristyddd82202009-11-03 20:14:50 +00001150 q->opacity=RoundToQuantum(pixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00001151 }
1152 if (((channel & IndexChannel) != 0) &&
1153 (image->colorspace == CMYKColorspace))
1154 {
1155 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001156 *restrict kernel_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001157
1158 k=kernel;
1159 kernel_indexes=indexes;
1160 for (i=0; i < (long) width; i++)
1161 {
1162 pixel.index+=(*k)*(*kernel_indexes);
1163 k++;
1164 kernel_indexes++;
1165 }
cristyddd82202009-11-03 20:14:50 +00001166 blur_indexes[y]=RoundToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00001167 }
1168 }
1169 else
1170 {
1171 MagickRealType
1172 alpha,
1173 gamma;
1174
1175 gamma=0.0;
1176 for (i=0; i < (long) width; i++)
1177 {
1178 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
1179 kernel_pixels->opacity));
1180 pixel.red+=(*k)*alpha*kernel_pixels->red;
1181 pixel.green+=(*k)*alpha*kernel_pixels->green;
1182 pixel.blue+=(*k)*alpha*kernel_pixels->blue;
1183 gamma+=(*k)*alpha;
1184 k++;
1185 kernel_pixels++;
1186 }
1187 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1188 if ((channel & RedChannel) != 0)
cristyddd82202009-11-03 20:14:50 +00001189 q->red=RoundToQuantum(gamma*pixel.red);
cristy3ed852e2009-09-05 21:47:34 +00001190 if ((channel & GreenChannel) != 0)
cristyddd82202009-11-03 20:14:50 +00001191 q->green=RoundToQuantum(gamma*pixel.green);
cristy3ed852e2009-09-05 21:47:34 +00001192 if ((channel & BlueChannel) != 0)
cristyddd82202009-11-03 20:14:50 +00001193 q->blue=RoundToQuantum(gamma*pixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00001194 if ((channel & OpacityChannel) != 0)
1195 {
1196 k=kernel;
1197 kernel_pixels=p;
1198 for (i=0; i < (long) width; i++)
1199 {
1200 pixel.opacity+=(*k)*kernel_pixels->opacity;
1201 k++;
1202 kernel_pixels++;
1203 }
cristyddd82202009-11-03 20:14:50 +00001204 q->opacity=RoundToQuantum(pixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00001205 }
1206 if (((channel & IndexChannel) != 0) &&
1207 (image->colorspace == CMYKColorspace))
1208 {
1209 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001210 *restrict kernel_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001211
1212 k=kernel;
1213 kernel_pixels=p;
1214 kernel_indexes=indexes;
1215 for (i=0; i < (long) width; i++)
1216 {
1217 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
1218 kernel_pixels->opacity));
1219 pixel.index+=(*k)*alpha*(*kernel_indexes);
1220 k++;
1221 kernel_pixels++;
1222 kernel_indexes++;
1223 }
cristyddd82202009-11-03 20:14:50 +00001224 blur_indexes[y]=RoundToQuantum(gamma*pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00001225 }
1226 }
1227 p++;
1228 q++;
1229 }
1230 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1231 status=MagickFalse;
1232 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1233 {
1234 MagickBooleanType
1235 proceed;
1236
cristyb5d5f722009-11-04 03:03:49 +00001237#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001238 #pragma omp critical (MagickCore_BlurImageChannel)
1239#endif
1240 proceed=SetImageProgress(image,BlurImageTag,progress++,blur_image->rows+
1241 blur_image->columns);
1242 if (proceed == MagickFalse)
1243 status=MagickFalse;
1244 }
1245 }
1246 blur_view=DestroyCacheView(blur_view);
1247 image_view=DestroyCacheView(image_view);
1248 kernel=(double *) RelinquishMagickMemory(kernel);
1249 if (status == MagickFalse)
1250 blur_image=DestroyImage(blur_image);
1251 blur_image->type=image->type;
1252 return(blur_image);
1253}
1254
1255/*
1256%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1257% %
1258% %
1259% %
cristyfccdab92009-11-30 16:43:57 +00001260% C o n v o l v e I m a g e %
1261% %
1262% %
1263% %
1264%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1265%
1266% ConvolveImage() applies a custom convolution kernel to the image.
1267%
1268% The format of the ConvolveImage method is:
1269%
1270% Image *ConvolveImage(const Image *image,const unsigned long order,
1271% const double *kernel,ExceptionInfo *exception)
1272% Image *ConvolveImageChannel(const Image *image,const ChannelType channel,
1273% const unsigned long order,const double *kernel,
1274% ExceptionInfo *exception)
1275%
1276% A description of each parameter follows:
1277%
1278% o image: the image.
1279%
1280% o channel: the channel type.
1281%
1282% o order: the number of columns and rows in the filter kernel.
1283%
1284% o kernel: An array of double representing the convolution kernel.
1285%
1286% o exception: return any errors or warnings in this structure.
1287%
1288*/
1289
1290MagickExport Image *ConvolveImage(const Image *image,const unsigned long order,
1291 const double *kernel,ExceptionInfo *exception)
1292{
1293 Image
1294 *convolve_image;
1295
1296 convolve_image=ConvolveImageChannel(image,DefaultChannels,order,kernel,
1297 exception);
1298 return(convolve_image);
1299}
1300
1301MagickExport Image *ConvolveImageChannel(const Image *image,
1302 const ChannelType channel,const unsigned long order,const double *kernel,
1303 ExceptionInfo *exception)
1304{
1305#define ConvolveImageTag "Convolve/Image"
1306
1307 double
1308 *normal_kernel;
1309
1310 Image
1311 *convolve_image;
1312
1313 long
1314 progress,
1315 y;
1316
1317 MagickBooleanType
1318 status;
1319
1320 MagickPixelPacket
1321 bias;
1322
1323 MagickRealType
1324 gamma;
1325
1326 register long
1327 i;
1328
1329 unsigned long
1330 width;
1331
1332 CacheView
1333 *convolve_view,
1334 *image_view;
1335
1336 /*
1337 Initialize convolve image attributes.
1338 */
1339 assert(image != (Image *) NULL);
1340 assert(image->signature == MagickSignature);
1341 if (image->debug != MagickFalse)
1342 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1343 assert(exception != (ExceptionInfo *) NULL);
1344 assert(exception->signature == MagickSignature);
1345 width=order;
1346 if ((width % 2) == 0)
1347 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
1348 convolve_image=CloneImage(image,0,0,MagickTrue,exception);
1349 if (convolve_image == (Image *) NULL)
1350 return((Image *) NULL);
1351 if (SetImageStorageClass(convolve_image,DirectClass) == MagickFalse)
1352 {
1353 InheritException(exception,&convolve_image->exception);
1354 convolve_image=DestroyImage(convolve_image);
1355 return((Image *) NULL);
1356 }
1357 if (image->debug != MagickFalse)
1358 {
1359 char
1360 format[MaxTextExtent],
1361 *message;
1362
1363 long
1364 u,
1365 v;
1366
1367 register const double
1368 *k;
1369
1370 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
1371 " ConvolveImage with %ldx%ld kernel:",width,width);
1372 message=AcquireString("");
1373 k=kernel;
1374 for (v=0; v < (long) width; v++)
1375 {
1376 *message='\0';
1377 (void) FormatMagickString(format,MaxTextExtent,"%ld: ",v);
1378 (void) ConcatenateString(&message,format);
1379 for (u=0; u < (long) width; u++)
1380 {
cristy47e00502009-12-17 19:19:57 +00001381 (void) FormatMagickString(format,MaxTextExtent,"%g ",*k++);
cristyfccdab92009-11-30 16:43:57 +00001382 (void) ConcatenateString(&message,format);
1383 }
1384 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
1385 }
1386 message=DestroyString(message);
1387 }
1388 /*
1389 Normalize kernel.
1390 */
1391 normal_kernel=(double *) AcquireQuantumMemory(width*width,
1392 sizeof(*normal_kernel));
1393 if (normal_kernel == (double *) NULL)
1394 {
1395 convolve_image=DestroyImage(convolve_image);
1396 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1397 }
1398 gamma=0.0;
1399 for (i=0; i < (long) (width*width); i++)
1400 gamma+=kernel[i];
1401 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1402 for (i=0; i < (long) (width*width); i++)
1403 normal_kernel[i]=gamma*kernel[i];
1404 /*
1405 Convolve image.
1406 */
1407 status=MagickTrue;
1408 progress=0;
1409 GetMagickPixelPacket(image,&bias);
1410 SetMagickPixelPacketBias(image,&bias);
1411 image_view=AcquireCacheView(image);
1412 convolve_view=AcquireCacheView(convolve_image);
1413#if defined(MAGICKCORE_OPENMP_SUPPORT)
1414 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1415#endif
1416 for (y=0; y < (long) image->rows; y++)
1417 {
1418 MagickBooleanType
1419 sync;
1420
1421 register const IndexPacket
1422 *restrict indexes;
1423
1424 register const PixelPacket
1425 *restrict p;
1426
1427 register IndexPacket
1428 *restrict convolve_indexes;
1429
1430 register long
1431 x;
1432
1433 register PixelPacket
1434 *restrict q;
1435
1436 if (status == MagickFalse)
1437 continue;
1438 p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y-(long) (width/
1439 2L),image->columns+width,width,exception);
1440 q=GetCacheViewAuthenticPixels(convolve_view,0,y,convolve_image->columns,1,
1441 exception);
1442 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1443 {
1444 status=MagickFalse;
1445 continue;
1446 }
1447 indexes=GetCacheViewVirtualIndexQueue(image_view);
1448 convolve_indexes=GetCacheViewAuthenticIndexQueue(convolve_view);
1449 for (x=0; x < (long) image->columns; x++)
1450 {
1451 long
1452 v;
1453
1454 MagickPixelPacket
1455 pixel;
1456
1457 register const double
1458 *restrict k;
1459
1460 register const PixelPacket
1461 *restrict kernel_pixels;
1462
1463 register long
1464 u;
1465
1466 pixel=bias;
1467 k=normal_kernel;
1468 kernel_pixels=p;
1469 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
1470 {
1471 for (v=0; v < (long) width; v++)
1472 {
1473 for (u=0; u < (long) width; u++)
1474 {
1475 pixel.red+=(*k)*kernel_pixels[u].red;
1476 pixel.green+=(*k)*kernel_pixels[u].green;
1477 pixel.blue+=(*k)*kernel_pixels[u].blue;
1478 k++;
1479 }
1480 kernel_pixels+=image->columns+width;
1481 }
1482 if ((channel & RedChannel) != 0)
1483 q->red=RoundToQuantum(pixel.red);
1484 if ((channel & GreenChannel) != 0)
1485 q->green=RoundToQuantum(pixel.green);
1486 if ((channel & BlueChannel) != 0)
1487 q->blue=RoundToQuantum(pixel.blue);
1488 if ((channel & OpacityChannel) != 0)
1489 {
1490 k=normal_kernel;
1491 kernel_pixels=p;
1492 for (v=0; v < (long) width; v++)
1493 {
1494 for (u=0; u < (long) width; u++)
1495 {
1496 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
1497 k++;
1498 }
1499 kernel_pixels+=image->columns+width;
1500 }
1501 q->opacity=RoundToQuantum(pixel.opacity);
1502 }
1503 if (((channel & IndexChannel) != 0) &&
1504 (image->colorspace == CMYKColorspace))
1505 {
1506 register const IndexPacket
1507 *restrict kernel_indexes;
1508
1509 k=normal_kernel;
1510 kernel_indexes=indexes;
1511 for (v=0; v < (long) width; v++)
1512 {
1513 for (u=0; u < (long) width; u++)
1514 {
1515 pixel.index+=(*k)*kernel_indexes[u];
1516 k++;
1517 }
1518 kernel_indexes+=image->columns+width;
1519 }
1520 convolve_indexes[x]=RoundToQuantum(pixel.index);
1521 }
1522 }
1523 else
1524 {
1525 MagickRealType
1526 alpha,
1527 gamma;
1528
1529 gamma=0.0;
1530 for (v=0; v < (long) width; v++)
1531 {
1532 for (u=0; u < (long) width; u++)
1533 {
1534 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
1535 kernel_pixels[u].opacity));
1536 pixel.red+=(*k)*alpha*kernel_pixels[u].red;
1537 pixel.green+=(*k)*alpha*kernel_pixels[u].green;
1538 pixel.blue+=(*k)*alpha*kernel_pixels[u].blue;
cristyfccdab92009-11-30 16:43:57 +00001539 gamma+=(*k)*alpha;
1540 k++;
1541 }
1542 kernel_pixels+=image->columns+width;
1543 }
1544 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1545 if ((channel & RedChannel) != 0)
1546 q->red=RoundToQuantum(gamma*pixel.red);
1547 if ((channel & GreenChannel) != 0)
1548 q->green=RoundToQuantum(gamma*pixel.green);
1549 if ((channel & BlueChannel) != 0)
1550 q->blue=RoundToQuantum(gamma*pixel.blue);
1551 if ((channel & OpacityChannel) != 0)
1552 {
1553 k=normal_kernel;
1554 kernel_pixels=p;
1555 for (v=0; v < (long) width; v++)
1556 {
1557 for (u=0; u < (long) width; u++)
1558 {
1559 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
1560 k++;
1561 }
1562 kernel_pixels+=image->columns+width;
1563 }
1564 q->opacity=RoundToQuantum(pixel.opacity);
1565 }
1566 if (((channel & IndexChannel) != 0) &&
1567 (image->colorspace == CMYKColorspace))
1568 {
1569 register const IndexPacket
1570 *restrict kernel_indexes;
1571
1572 k=normal_kernel;
1573 kernel_pixels=p;
1574 kernel_indexes=indexes;
1575 for (v=0; v < (long) width; v++)
1576 {
1577 for (u=0; u < (long) width; u++)
1578 {
1579 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
1580 kernel_pixels[u].opacity));
1581 pixel.index+=(*k)*alpha*kernel_indexes[u];
1582 k++;
1583 }
1584 kernel_pixels+=image->columns+width;
1585 kernel_indexes+=image->columns+width;
1586 }
1587 convolve_indexes[x]=RoundToQuantum(gamma*pixel.index);
1588 }
1589 }
1590 p++;
1591 q++;
1592 }
1593 sync=SyncCacheViewAuthenticPixels(convolve_view,exception);
1594 if (sync == MagickFalse)
1595 status=MagickFalse;
1596 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1597 {
1598 MagickBooleanType
1599 proceed;
1600
1601#if defined(MAGICKCORE_OPENMP_SUPPORT)
1602 #pragma omp critical (MagickCore_ConvolveImageChannel)
1603#endif
1604 proceed=SetImageProgress(image,ConvolveImageTag,progress++,image->rows);
1605 if (proceed == MagickFalse)
1606 status=MagickFalse;
1607 }
1608 }
1609 convolve_image->type=image->type;
1610 convolve_view=DestroyCacheView(convolve_view);
1611 image_view=DestroyCacheView(image_view);
1612 normal_kernel=(double *) RelinquishMagickMemory(normal_kernel);
1613 if (status == MagickFalse)
1614 convolve_image=DestroyImage(convolve_image);
1615 return(convolve_image);
1616}
1617
1618/*
1619%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1620% %
1621% %
1622% %
cristy3ed852e2009-09-05 21:47:34 +00001623% D e s p e c k l e I m a g e %
1624% %
1625% %
1626% %
1627%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1628%
1629% DespeckleImage() reduces the speckle noise in an image while perserving the
1630% edges of the original image.
1631%
1632% The format of the DespeckleImage method is:
1633%
1634% Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1635%
1636% A description of each parameter follows:
1637%
1638% o image: the image.
1639%
1640% o exception: return any errors or warnings in this structure.
1641%
1642*/
1643
1644static Quantum **DestroyPixelThreadSet(Quantum **pixels)
1645{
1646 register long
1647 i;
1648
1649 assert(pixels != (Quantum **) NULL);
1650 for (i=0; i < (long) GetOpenMPMaximumThreads(); i++)
1651 if (pixels[i] != (Quantum *) NULL)
1652 pixels[i]=(Quantum *) RelinquishMagickMemory(pixels[i]);
1653 pixels=(Quantum **) RelinquishAlignedMemory(pixels);
1654 return(pixels);
1655}
1656
1657static Quantum **AcquirePixelThreadSet(const size_t count)
1658{
1659 register long
1660 i;
1661
1662 Quantum
1663 **pixels;
1664
1665 unsigned long
1666 number_threads;
1667
1668 number_threads=GetOpenMPMaximumThreads();
1669 pixels=(Quantum **) AcquireAlignedMemory(number_threads,sizeof(*pixels));
1670 if (pixels == (Quantum **) NULL)
1671 return((Quantum **) NULL);
1672 (void) ResetMagickMemory(pixels,0,number_threads*sizeof(*pixels));
1673 for (i=0; i < (long) number_threads; i++)
1674 {
1675 pixels[i]=(Quantum *) AcquireQuantumMemory(count,sizeof(**pixels));
1676 if (pixels[i] == (Quantum *) NULL)
1677 return(DestroyPixelThreadSet(pixels));
1678 }
1679 return(pixels);
1680}
1681
1682static void Hull(const long x_offset,const long y_offset,
1683 const unsigned long columns,const unsigned long rows,Quantum *f,Quantum *g,
1684 const int polarity)
1685{
1686 long
1687 y;
1688
1689 MagickRealType
1690 v;
1691
1692 register long
1693 x;
1694
1695 register Quantum
1696 *p,
1697 *q,
1698 *r,
1699 *s;
1700
1701 assert(f != (Quantum *) NULL);
1702 assert(g != (Quantum *) NULL);
1703 p=f+(columns+2);
1704 q=g+(columns+2);
1705 r=p+(y_offset*((long) columns+2)+x_offset);
1706 for (y=0; y < (long) rows; y++)
1707 {
1708 p++;
1709 q++;
1710 r++;
1711 if (polarity > 0)
1712 for (x=(long) columns; x != 0; x--)
1713 {
1714 v=(MagickRealType) (*p);
1715 if ((MagickRealType) *r >= (v+(MagickRealType) ScaleCharToQuantum(2)))
1716 v+=ScaleCharToQuantum(1);
1717 *q=(Quantum) v;
1718 p++;
1719 q++;
1720 r++;
1721 }
1722 else
1723 for (x=(long) columns; x != 0; x--)
1724 {
1725 v=(MagickRealType) (*p);
1726 if ((MagickRealType) *r <= (v-(MagickRealType) ScaleCharToQuantum(2)))
1727 v-=(long) ScaleCharToQuantum(1);
1728 *q=(Quantum) v;
1729 p++;
1730 q++;
1731 r++;
1732 }
1733 p++;
1734 q++;
1735 r++;
1736 }
1737 p=f+(columns+2);
1738 q=g+(columns+2);
1739 r=q+(y_offset*((long) columns+2)+x_offset);
1740 s=q-(y_offset*((long) columns+2)+x_offset);
1741 for (y=0; y < (long) rows; y++)
1742 {
1743 p++;
1744 q++;
1745 r++;
1746 s++;
1747 if (polarity > 0)
1748 for (x=(long) columns; x != 0; x--)
1749 {
1750 v=(MagickRealType) (*q);
1751 if (((MagickRealType) *s >=
1752 (v+(MagickRealType) ScaleCharToQuantum(2))) &&
1753 ((MagickRealType) *r > v))
1754 v+=ScaleCharToQuantum(1);
1755 *p=(Quantum) v;
1756 p++;
1757 q++;
1758 r++;
1759 s++;
1760 }
1761 else
1762 for (x=(long) columns; x != 0; x--)
1763 {
1764 v=(MagickRealType) (*q);
1765 if (((MagickRealType) *s <=
1766 (v-(MagickRealType) ScaleCharToQuantum(2))) &&
1767 ((MagickRealType) *r < v))
1768 v-=(MagickRealType) ScaleCharToQuantum(1);
1769 *p=(Quantum) v;
1770 p++;
1771 q++;
1772 r++;
1773 s++;
1774 }
1775 p++;
1776 q++;
1777 r++;
1778 s++;
1779 }
1780}
1781
1782MagickExport Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1783{
1784#define DespeckleImageTag "Despeckle/Image"
1785
cristy2407fc22009-09-11 00:55:25 +00001786 CacheView
1787 *despeckle_view,
1788 *image_view;
1789
cristy3ed852e2009-09-05 21:47:34 +00001790 Image
1791 *despeckle_image;
1792
1793 long
1794 channel;
1795
1796 MagickBooleanType
1797 status;
1798
1799 Quantum
cristyfa112112010-01-04 17:48:07 +00001800 **restrict buffers,
1801 **restrict pixels;
cristy3ed852e2009-09-05 21:47:34 +00001802
1803 size_t
1804 length;
1805
1806 static const int
cristy691a29e2009-09-11 00:44:10 +00001807 X[4] = {0, 1, 1,-1},
1808 Y[4] = {1, 0, 1, 1};
cristy3ed852e2009-09-05 21:47:34 +00001809
cristy3ed852e2009-09-05 21:47:34 +00001810 /*
1811 Allocate despeckled image.
1812 */
1813 assert(image != (const Image *) NULL);
1814 assert(image->signature == MagickSignature);
1815 if (image->debug != MagickFalse)
1816 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1817 assert(exception != (ExceptionInfo *) NULL);
1818 assert(exception->signature == MagickSignature);
1819 despeckle_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1820 exception);
1821 if (despeckle_image == (Image *) NULL)
1822 return((Image *) NULL);
1823 if (SetImageStorageClass(despeckle_image,DirectClass) == MagickFalse)
1824 {
1825 InheritException(exception,&despeckle_image->exception);
1826 despeckle_image=DestroyImage(despeckle_image);
1827 return((Image *) NULL);
1828 }
1829 /*
1830 Allocate image buffers.
1831 */
1832 length=(size_t) ((image->columns+2)*(image->rows+2));
1833 pixels=AcquirePixelThreadSet(length);
1834 buffers=AcquirePixelThreadSet(length);
1835 if ((pixels == (Quantum **) NULL) || (buffers == (Quantum **) NULL))
1836 {
1837 if (buffers != (Quantum **) NULL)
1838 buffers=DestroyPixelThreadSet(buffers);
1839 if (pixels != (Quantum **) NULL)
1840 pixels=DestroyPixelThreadSet(pixels);
1841 despeckle_image=DestroyImage(despeckle_image);
1842 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1843 }
1844 /*
1845 Reduce speckle in the image.
1846 */
1847 status=MagickTrue;
1848 image_view=AcquireCacheView(image);
1849 despeckle_view=AcquireCacheView(despeckle_image);
cristyb5d5f722009-11-04 03:03:49 +00001850#if defined(MAGICKCORE_OPENMP_SUPPORT)
1851 #pragma omp parallel for schedule(dynamic,4) shared(status)
cristy3ed852e2009-09-05 21:47:34 +00001852#endif
1853 for (channel=0; channel <= 3; channel++)
1854 {
1855 long
1856 j,
1857 y;
1858
1859 register long
1860 i,
cristy691a29e2009-09-11 00:44:10 +00001861 id,
cristy3ed852e2009-09-05 21:47:34 +00001862 x;
1863
1864 register Quantum
1865 *buffer,
1866 *pixel;
1867
1868 if (status == MagickFalse)
1869 continue;
cristy691a29e2009-09-11 00:44:10 +00001870 id=GetOpenMPThreadId();
1871 pixel=pixels[id];
cristy3ed852e2009-09-05 21:47:34 +00001872 (void) ResetMagickMemory(pixel,0,length*sizeof(*pixel));
cristy691a29e2009-09-11 00:44:10 +00001873 buffer=buffers[id];
cristy3ed852e2009-09-05 21:47:34 +00001874 j=(long) image->columns+2;
1875 for (y=0; y < (long) image->rows; y++)
1876 {
1877 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001878 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001879
1880 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1881 if (p == (const PixelPacket *) NULL)
1882 break;
1883 j++;
1884 for (x=0; x < (long) image->columns; x++)
1885 {
1886 switch (channel)
1887 {
1888 case 0: pixel[j]=p->red; break;
1889 case 1: pixel[j]=p->green; break;
1890 case 2: pixel[j]=p->blue; break;
1891 case 3: pixel[j]=p->opacity; break;
1892 default: break;
1893 }
1894 p++;
1895 j++;
1896 }
1897 j++;
1898 }
cristy3ed852e2009-09-05 21:47:34 +00001899 (void) ResetMagickMemory(buffer,0,length*sizeof(*buffer));
1900 for (i=0; i < 4; i++)
1901 {
1902 Hull(X[i],Y[i],image->columns,image->rows,pixel,buffer,1);
1903 Hull(-X[i],-Y[i],image->columns,image->rows,pixel,buffer,1);
1904 Hull(-X[i],-Y[i],image->columns,image->rows,pixel,buffer,-1);
1905 Hull(X[i],Y[i],image->columns,image->rows,pixel,buffer,-1);
1906 }
1907 j=(long) image->columns+2;
1908 for (y=0; y < (long) image->rows; y++)
1909 {
1910 MagickBooleanType
1911 sync;
1912
1913 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001914 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001915
1916 q=GetCacheViewAuthenticPixels(despeckle_view,0,y,despeckle_image->columns,
1917 1,exception);
1918 if (q == (PixelPacket *) NULL)
1919 break;
1920 j++;
1921 for (x=0; x < (long) image->columns; x++)
1922 {
1923 switch (channel)
1924 {
1925 case 0: q->red=pixel[j]; break;
1926 case 1: q->green=pixel[j]; break;
1927 case 2: q->blue=pixel[j]; break;
1928 case 3: q->opacity=pixel[j]; break;
1929 default: break;
1930 }
1931 q++;
1932 j++;
1933 }
1934 sync=SyncCacheViewAuthenticPixels(despeckle_view,exception);
1935 if (sync == MagickFalse)
1936 {
1937 status=MagickFalse;
1938 break;
1939 }
1940 j++;
1941 }
1942 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1943 {
1944 MagickBooleanType
1945 proceed;
1946
cristyb5d5f722009-11-04 03:03:49 +00001947#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001948 #pragma omp critical (MagickCore_DespeckleImage)
1949#endif
1950 proceed=SetImageProgress(image,DespeckleImageTag,channel,3);
1951 if (proceed == MagickFalse)
1952 status=MagickFalse;
1953 }
1954 }
1955 despeckle_view=DestroyCacheView(despeckle_view);
1956 image_view=DestroyCacheView(image_view);
1957 buffers=DestroyPixelThreadSet(buffers);
1958 pixels=DestroyPixelThreadSet(pixels);
1959 despeckle_image->type=image->type;
1960 if (status == MagickFalse)
1961 despeckle_image=DestroyImage(despeckle_image);
1962 return(despeckle_image);
1963}
1964
1965/*
1966%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1967% %
1968% %
1969% %
1970% E d g e I m a g e %
1971% %
1972% %
1973% %
1974%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1975%
1976% EdgeImage() finds edges in an image. Radius defines the radius of the
1977% convolution filter. Use a radius of 0 and EdgeImage() selects a suitable
1978% radius for you.
1979%
1980% The format of the EdgeImage method is:
1981%
1982% Image *EdgeImage(const Image *image,const double radius,
1983% ExceptionInfo *exception)
1984%
1985% A description of each parameter follows:
1986%
1987% o image: the image.
1988%
1989% o radius: the radius of the pixel neighborhood.
1990%
1991% o exception: return any errors or warnings in this structure.
1992%
1993*/
1994MagickExport Image *EdgeImage(const Image *image,const double radius,
1995 ExceptionInfo *exception)
1996{
1997 Image
1998 *edge_image;
1999
2000 double
2001 *kernel;
2002
2003 register long
2004 i;
2005
2006 unsigned long
2007 width;
2008
2009 assert(image != (const Image *) NULL);
2010 assert(image->signature == MagickSignature);
2011 if (image->debug != MagickFalse)
2012 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2013 assert(exception != (ExceptionInfo *) NULL);
2014 assert(exception->signature == MagickSignature);
2015 width=GetOptimalKernelWidth1D(radius,0.5);
2016 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2017 if (kernel == (double *) NULL)
2018 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2019 for (i=0; i < (long) (width*width); i++)
2020 kernel[i]=(-1.0);
2021 kernel[i/2]=(double) (width*width-1.0);
2022 edge_image=ConvolveImage(image,width,kernel,exception);
2023 kernel=(double *) RelinquishMagickMemory(kernel);
2024 return(edge_image);
2025}
2026
2027/*
2028%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2029% %
2030% %
2031% %
2032% E m b o s s I m a g e %
2033% %
2034% %
2035% %
2036%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2037%
2038% EmbossImage() returns a grayscale image with a three-dimensional effect.
2039% We convolve the image with a Gaussian operator of the given radius and
2040% standard deviation (sigma). For reasonable results, radius should be
2041% larger than sigma. Use a radius of 0 and Emboss() selects a suitable
2042% radius for you.
2043%
2044% The format of the EmbossImage method is:
2045%
2046% Image *EmbossImage(const Image *image,const double radius,
2047% const double sigma,ExceptionInfo *exception)
2048%
2049% A description of each parameter follows:
2050%
2051% o image: the image.
2052%
2053% o radius: the radius of the pixel neighborhood.
2054%
2055% o sigma: the standard deviation of the Gaussian, in pixels.
2056%
2057% o exception: return any errors or warnings in this structure.
2058%
2059*/
2060MagickExport Image *EmbossImage(const Image *image,const double radius,
2061 const double sigma,ExceptionInfo *exception)
2062{
2063 double
2064 *kernel;
2065
2066 Image
2067 *emboss_image;
2068
2069 long
cristy47e00502009-12-17 19:19:57 +00002070 j,
2071 k,
cristy3ed852e2009-09-05 21:47:34 +00002072 u,
2073 v;
2074
cristy47e00502009-12-17 19:19:57 +00002075 register long
2076 i;
2077
cristy3ed852e2009-09-05 21:47:34 +00002078 unsigned long
2079 width;
2080
2081 assert(image != (Image *) NULL);
2082 assert(image->signature == MagickSignature);
2083 if (image->debug != MagickFalse)
2084 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2085 assert(exception != (ExceptionInfo *) NULL);
2086 assert(exception->signature == MagickSignature);
2087 width=GetOptimalKernelWidth2D(radius,sigma);
2088 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2089 if (kernel == (double *) NULL)
2090 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy3ed852e2009-09-05 21:47:34 +00002091 j=(long) width/2;
cristy47e00502009-12-17 19:19:57 +00002092 k=j;
2093 i=0;
2094 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002095 {
cristy47e00502009-12-17 19:19:57 +00002096 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00002097 {
cristy47e00502009-12-17 19:19:57 +00002098 kernel[i]=((u < 0) || (v < 0) ? -8.0 : 8.0)*
2099 exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
2100 (2.0*MagickPI*MagickSigma*MagickSigma);
2101 if (u != k)
cristy3ed852e2009-09-05 21:47:34 +00002102 kernel[i]=0.0;
2103 i++;
2104 }
cristy47e00502009-12-17 19:19:57 +00002105 k--;
cristy3ed852e2009-09-05 21:47:34 +00002106 }
2107 emboss_image=ConvolveImage(image,width,kernel,exception);
2108 if (emboss_image != (Image *) NULL)
2109 (void) EqualizeImage(emboss_image);
2110 kernel=(double *) RelinquishMagickMemory(kernel);
2111 return(emboss_image);
2112}
2113
2114/*
2115%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2116% %
2117% %
2118% %
2119% G a u s s i a n B l u r I m a g e %
2120% %
2121% %
2122% %
2123%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2124%
2125% GaussianBlurImage() blurs an image. We convolve the image with a
2126% Gaussian operator of the given radius and standard deviation (sigma).
2127% For reasonable results, the radius should be larger than sigma. Use a
2128% radius of 0 and GaussianBlurImage() selects a suitable radius for you
2129%
2130% The format of the GaussianBlurImage method is:
2131%
2132% Image *GaussianBlurImage(const Image *image,onst double radius,
2133% const double sigma,ExceptionInfo *exception)
2134% Image *GaussianBlurImageChannel(const Image *image,
2135% const ChannelType channel,const double radius,const double sigma,
2136% ExceptionInfo *exception)
2137%
2138% A description of each parameter follows:
2139%
2140% o image: the image.
2141%
2142% o channel: the channel type.
2143%
2144% o radius: the radius of the Gaussian, in pixels, not counting the center
2145% pixel.
2146%
2147% o sigma: the standard deviation of the Gaussian, in pixels.
2148%
2149% o exception: return any errors or warnings in this structure.
2150%
2151*/
2152
2153MagickExport Image *GaussianBlurImage(const Image *image,const double radius,
2154 const double sigma,ExceptionInfo *exception)
2155{
2156 Image
2157 *blur_image;
2158
2159 blur_image=GaussianBlurImageChannel(image,DefaultChannels,radius,sigma,
2160 exception);
2161 return(blur_image);
2162}
2163
2164MagickExport Image *GaussianBlurImageChannel(const Image *image,
2165 const ChannelType channel,const double radius,const double sigma,
2166 ExceptionInfo *exception)
2167{
2168 double
2169 *kernel;
2170
2171 Image
2172 *blur_image;
2173
cristy47e00502009-12-17 19:19:57 +00002174 long
2175 j,
cristy3ed852e2009-09-05 21:47:34 +00002176 u,
2177 v;
2178
cristy47e00502009-12-17 19:19:57 +00002179 register long
2180 i;
2181
cristy3ed852e2009-09-05 21:47:34 +00002182 unsigned long
2183 width;
2184
2185 assert(image != (const Image *) NULL);
2186 assert(image->signature == MagickSignature);
2187 if (image->debug != MagickFalse)
2188 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2189 assert(exception != (ExceptionInfo *) NULL);
2190 assert(exception->signature == MagickSignature);
2191 width=GetOptimalKernelWidth2D(radius,sigma);
2192 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2193 if (kernel == (double *) NULL)
2194 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy47e00502009-12-17 19:19:57 +00002195 j=(long) width/2;
cristy3ed852e2009-09-05 21:47:34 +00002196 i=0;
cristy47e00502009-12-17 19:19:57 +00002197 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002198 {
cristy47e00502009-12-17 19:19:57 +00002199 for (u=(-j); u <= j; u++)
2200 kernel[i++]=exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
2201 (2.0*MagickPI*MagickSigma*MagickSigma);
cristy3ed852e2009-09-05 21:47:34 +00002202 }
2203 blur_image=ConvolveImageChannel(image,channel,width,kernel,exception);
2204 kernel=(double *) RelinquishMagickMemory(kernel);
2205 return(blur_image);
2206}
2207
2208/*
2209%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2210% %
2211% %
2212% %
2213% M e d i a n F i l t e r I m a g e %
2214% %
2215% %
2216% %
2217%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2218%
2219% MedianFilterImage() applies a digital filter that improves the quality
2220% of a noisy image. Each pixel is replaced by the median in a set of
2221% neighboring pixels as defined by radius.
2222%
2223% The algorithm was contributed by Mike Edmonds and implements an insertion
2224% sort for selecting median color-channel values. For more on this algorithm
2225% see "Skip Lists: A probabilistic Alternative to Balanced Trees" by William
2226% Pugh in the June 1990 of Communications of the ACM.
2227%
2228% The format of the MedianFilterImage method is:
2229%
2230% Image *MedianFilterImage(const Image *image,const double radius,
2231% ExceptionInfo *exception)
2232%
2233% A description of each parameter follows:
2234%
2235% o image: the image.
2236%
2237% o radius: the radius of the pixel neighborhood.
2238%
2239% o exception: return any errors or warnings in this structure.
2240%
2241*/
2242
2243#define MedianListChannels 5
2244
2245typedef struct _MedianListNode
2246{
2247 unsigned long
2248 next[9],
2249 count,
2250 signature;
2251} MedianListNode;
2252
2253typedef struct _MedianSkipList
2254{
2255 long
2256 level;
2257
2258 MedianListNode
2259 *nodes;
2260} MedianSkipList;
2261
2262typedef struct _MedianPixelList
2263{
2264 unsigned long
2265 center,
2266 seed,
2267 signature;
2268
2269 MedianSkipList
2270 lists[MedianListChannels];
2271} MedianPixelList;
2272
2273static MedianPixelList *DestroyMedianPixelList(MedianPixelList *pixel_list)
2274{
2275 register long
2276 i;
2277
2278 if (pixel_list == (MedianPixelList *) NULL)
2279 return((MedianPixelList *) NULL);
2280 for (i=0; i < MedianListChannels; i++)
2281 if (pixel_list->lists[i].nodes != (MedianListNode *) NULL)
2282 pixel_list->lists[i].nodes=(MedianListNode *) RelinquishMagickMemory(
2283 pixel_list->lists[i].nodes);
2284 pixel_list=(MedianPixelList *) RelinquishAlignedMemory(pixel_list);
2285 return(pixel_list);
2286}
2287
2288static MedianPixelList **DestroyMedianPixelListThreadSet(
2289 MedianPixelList **pixel_list)
2290{
2291 register long
2292 i;
2293
2294 assert(pixel_list != (MedianPixelList **) NULL);
2295 for (i=0; i < (long) GetOpenMPMaximumThreads(); i++)
2296 if (pixel_list[i] != (MedianPixelList *) NULL)
2297 pixel_list[i]=DestroyMedianPixelList(pixel_list[i]);
2298 pixel_list=(MedianPixelList **) RelinquishAlignedMemory(pixel_list);
2299 return(pixel_list);
2300}
2301
2302static MedianPixelList *AcquireMedianPixelList(const unsigned long width)
2303{
2304 MedianPixelList
2305 *pixel_list;
2306
2307 register long
2308 i;
2309
2310 pixel_list=(MedianPixelList *) AcquireAlignedMemory(1,sizeof(*pixel_list));
2311 if (pixel_list == (MedianPixelList *) NULL)
2312 return(pixel_list);
2313 (void) ResetMagickMemory((void *) pixel_list,0,sizeof(*pixel_list));
2314 pixel_list->center=width*width/2;
2315 for (i=0; i < MedianListChannels; i++)
2316 {
2317 pixel_list->lists[i].nodes=(MedianListNode *) AcquireQuantumMemory(65537UL,
2318 sizeof(*pixel_list->lists[i].nodes));
2319 if (pixel_list->lists[i].nodes == (MedianListNode *) NULL)
2320 return(DestroyMedianPixelList(pixel_list));
2321 (void) ResetMagickMemory(pixel_list->lists[i].nodes,0,65537UL*
2322 sizeof(*pixel_list->lists[i].nodes));
2323 }
2324 pixel_list->signature=MagickSignature;
2325 return(pixel_list);
2326}
2327
2328static MedianPixelList **AcquireMedianPixelListThreadSet(
2329 const unsigned long width)
2330{
2331 register long
2332 i;
2333
2334 MedianPixelList
2335 **pixel_list;
2336
2337 unsigned long
2338 number_threads;
2339
2340 number_threads=GetOpenMPMaximumThreads();
2341 pixel_list=(MedianPixelList **) AcquireAlignedMemory(number_threads,
2342 sizeof(*pixel_list));
2343 if (pixel_list == (MedianPixelList **) NULL)
2344 return((MedianPixelList **) NULL);
2345 (void) ResetMagickMemory(pixel_list,0,number_threads*sizeof(*pixel_list));
2346 for (i=0; i < (long) number_threads; i++)
2347 {
2348 pixel_list[i]=AcquireMedianPixelList(width);
2349 if (pixel_list[i] == (MedianPixelList *) NULL)
2350 return(DestroyMedianPixelListThreadSet(pixel_list));
2351 }
2352 return(pixel_list);
2353}
2354
2355static void AddNodeMedianPixelList(MedianPixelList *pixel_list,
2356 const long channel,const unsigned long color)
2357{
2358 register long
2359 level;
2360
2361 register MedianSkipList
2362 *list;
2363
2364 unsigned long
2365 search,
2366 update[9];
2367
2368 /*
2369 Initialize the node.
2370 */
2371 list=pixel_list->lists+channel;
2372 list->nodes[color].signature=pixel_list->signature;
2373 list->nodes[color].count=1;
2374 /*
2375 Determine where it belongs in the list.
2376 */
2377 search=65536UL;
2378 for (level=list->level; level >= 0; level--)
2379 {
2380 while (list->nodes[search].next[level] < color)
2381 search=list->nodes[search].next[level];
2382 update[level]=search;
2383 }
2384 /*
2385 Generate a pseudo-random level for this node.
2386 */
2387 for (level=0; ; level++)
2388 {
2389 pixel_list->seed=(pixel_list->seed*42893621L)+1L;
2390 if ((pixel_list->seed & 0x300) != 0x300)
2391 break;
2392 }
2393 if (level > 8)
2394 level=8;
2395 if (level > (list->level+2))
2396 level=list->level+2;
2397 /*
2398 If we're raising the list's level, link back to the root node.
2399 */
2400 while (level > list->level)
2401 {
2402 list->level++;
2403 update[list->level]=65536UL;
2404 }
2405 /*
2406 Link the node into the skip-list.
2407 */
2408 do
2409 {
2410 list->nodes[color].next[level]=list->nodes[update[level]].next[level];
2411 list->nodes[update[level]].next[level]=color;
2412 }
2413 while (level-- > 0);
2414}
2415
2416static MagickPixelPacket GetMedianPixelList(MedianPixelList *pixel_list)
2417{
2418 MagickPixelPacket
2419 pixel;
2420
2421 register long
2422 channel;
2423
2424 register MedianSkipList
2425 *list;
2426
2427 unsigned long
2428 center,
2429 color,
2430 count;
2431
2432 unsigned short
2433 channels[MedianListChannels];
2434
2435 /*
2436 Find the median value for each of the color.
2437 */
2438 center=pixel_list->center;
2439 for (channel=0; channel < 5; channel++)
2440 {
2441 list=pixel_list->lists+channel;
2442 color=65536UL;
2443 count=0;
2444 do
2445 {
2446 color=list->nodes[color].next[0];
2447 count+=list->nodes[color].count;
2448 }
2449 while (count <= center);
2450 channels[channel]=(unsigned short) color;
2451 }
2452 GetMagickPixelPacket((const Image *) NULL,&pixel);
2453 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
2454 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
2455 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
2456 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
2457 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
2458 return(pixel);
2459}
2460
2461static inline void InsertMedianPixelList(const Image *image,
2462 const PixelPacket *pixel,const IndexPacket *indexes,
2463 MedianPixelList *pixel_list)
2464{
2465 unsigned long
2466 signature;
2467
2468 unsigned short
2469 index;
2470
2471 index=ScaleQuantumToShort(pixel->red);
2472 signature=pixel_list->lists[0].nodes[index].signature;
2473 if (signature == pixel_list->signature)
2474 pixel_list->lists[0].nodes[index].count++;
2475 else
2476 AddNodeMedianPixelList(pixel_list,0,index);
2477 index=ScaleQuantumToShort(pixel->green);
2478 signature=pixel_list->lists[1].nodes[index].signature;
2479 if (signature == pixel_list->signature)
2480 pixel_list->lists[1].nodes[index].count++;
2481 else
2482 AddNodeMedianPixelList(pixel_list,1,index);
2483 index=ScaleQuantumToShort(pixel->blue);
2484 signature=pixel_list->lists[2].nodes[index].signature;
2485 if (signature == pixel_list->signature)
2486 pixel_list->lists[2].nodes[index].count++;
2487 else
2488 AddNodeMedianPixelList(pixel_list,2,index);
2489 index=ScaleQuantumToShort(pixel->opacity);
2490 signature=pixel_list->lists[3].nodes[index].signature;
2491 if (signature == pixel_list->signature)
2492 pixel_list->lists[3].nodes[index].count++;
2493 else
2494 AddNodeMedianPixelList(pixel_list,3,index);
2495 if (image->colorspace == CMYKColorspace)
2496 index=ScaleQuantumToShort(*indexes);
2497 signature=pixel_list->lists[4].nodes[index].signature;
2498 if (signature == pixel_list->signature)
2499 pixel_list->lists[4].nodes[index].count++;
2500 else
2501 AddNodeMedianPixelList(pixel_list,4,index);
2502}
2503
2504static void ResetMedianPixelList(MedianPixelList *pixel_list)
2505{
2506 int
2507 level;
2508
2509 register long
2510 channel;
2511
2512 register MedianListNode
2513 *root;
2514
2515 register MedianSkipList
2516 *list;
2517
2518 /*
2519 Reset the skip-list.
2520 */
2521 for (channel=0; channel < 5; channel++)
2522 {
2523 list=pixel_list->lists+channel;
2524 root=list->nodes+65536UL;
2525 list->level=0;
2526 for (level=0; level < 9; level++)
2527 root->next[level]=65536UL;
2528 }
2529 pixel_list->seed=pixel_list->signature++;
2530}
2531
2532MagickExport Image *MedianFilterImage(const Image *image,const double radius,
2533 ExceptionInfo *exception)
2534{
2535#define MedianFilterImageTag "MedianFilter/Image"
2536
2537 Image
2538 *median_image;
2539
2540 long
2541 progress,
2542 y;
2543
2544 MagickBooleanType
2545 status;
2546
2547 MedianPixelList
cristyfa112112010-01-04 17:48:07 +00002548 **restrict pixel_list;
cristy3ed852e2009-09-05 21:47:34 +00002549
2550 unsigned long
2551 width;
2552
2553 CacheView
2554 *image_view,
2555 *median_view;
2556
2557 /*
2558 Initialize median image attributes.
2559 */
2560 assert(image != (Image *) NULL);
2561 assert(image->signature == MagickSignature);
2562 if (image->debug != MagickFalse)
2563 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2564 assert(exception != (ExceptionInfo *) NULL);
2565 assert(exception->signature == MagickSignature);
2566 width=GetOptimalKernelWidth2D(radius,0.5);
2567 if ((image->columns < width) || (image->rows < width))
2568 ThrowImageException(OptionError,"ImageSmallerThanKernelRadius");
2569 median_image=CloneImage(image,image->columns,image->rows,MagickTrue,
2570 exception);
2571 if (median_image == (Image *) NULL)
2572 return((Image *) NULL);
2573 if (SetImageStorageClass(median_image,DirectClass) == MagickFalse)
2574 {
2575 InheritException(exception,&median_image->exception);
2576 median_image=DestroyImage(median_image);
2577 return((Image *) NULL);
2578 }
2579 pixel_list=AcquireMedianPixelListThreadSet(width);
2580 if (pixel_list == (MedianPixelList **) NULL)
2581 {
2582 median_image=DestroyImage(median_image);
2583 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2584 }
2585 /*
2586 Median filter each image row.
2587 */
2588 status=MagickTrue;
2589 progress=0;
2590 image_view=AcquireCacheView(image);
2591 median_view=AcquireCacheView(median_image);
cristyb5d5f722009-11-04 03:03:49 +00002592#if defined(MAGICKCORE_OPENMP_SUPPORT)
2593 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002594#endif
2595 for (y=0; y < (long) median_image->rows; y++)
2596 {
2597 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002598 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002599
2600 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002601 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002602
2603 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002604 *restrict median_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002605
2606 register long
2607 id,
2608 x;
2609
2610 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002611 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002612
2613 if (status == MagickFalse)
2614 continue;
2615 p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y-(long) (width/
2616 2L),image->columns+width,width,exception);
2617 q=QueueCacheViewAuthenticPixels(median_view,0,y,median_image->columns,1,
2618 exception);
2619 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2620 {
2621 status=MagickFalse;
2622 continue;
2623 }
2624 indexes=GetCacheViewVirtualIndexQueue(image_view);
2625 median_indexes=GetCacheViewAuthenticIndexQueue(median_view);
2626 id=GetOpenMPThreadId();
2627 for (x=0; x < (long) median_image->columns; x++)
2628 {
2629 MagickPixelPacket
2630 pixel;
2631
2632 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002633 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +00002634
2635 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002636 *restrict s;
cristy3ed852e2009-09-05 21:47:34 +00002637
2638 register long
2639 u,
2640 v;
2641
2642 r=p;
2643 s=indexes+x;
2644 ResetMedianPixelList(pixel_list[id]);
2645 for (v=0; v < (long) width; v++)
2646 {
2647 for (u=0; u < (long) width; u++)
2648 InsertMedianPixelList(image,r+u,s+u,pixel_list[id]);
2649 r+=image->columns+width;
2650 s+=image->columns+width;
2651 }
2652 pixel=GetMedianPixelList(pixel_list[id]);
2653 SetPixelPacket(median_image,&pixel,q,median_indexes+x);
2654 p++;
2655 q++;
2656 }
2657 if (SyncCacheViewAuthenticPixels(median_view,exception) == MagickFalse)
2658 status=MagickFalse;
2659 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2660 {
2661 MagickBooleanType
2662 proceed;
2663
cristyb5d5f722009-11-04 03:03:49 +00002664#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002665 #pragma omp critical (MagickCore_MedianFilterImage)
2666#endif
2667 proceed=SetImageProgress(image,MedianFilterImageTag,progress++,
2668 image->rows);
2669 if (proceed == MagickFalse)
2670 status=MagickFalse;
2671 }
2672 }
2673 median_view=DestroyCacheView(median_view);
2674 image_view=DestroyCacheView(image_view);
2675 pixel_list=DestroyMedianPixelListThreadSet(pixel_list);
2676 return(median_image);
2677}
2678
2679/*
2680%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2681% %
2682% %
2683% %
2684% M o t i o n B l u r I m a g e %
2685% %
2686% %
2687% %
2688%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2689%
2690% MotionBlurImage() simulates motion blur. We convolve the image with a
2691% Gaussian operator of the given radius and standard deviation (sigma).
2692% For reasonable results, radius should be larger than sigma. Use a
2693% radius of 0 and MotionBlurImage() selects a suitable radius for you.
2694% Angle gives the angle of the blurring motion.
2695%
2696% Andrew Protano contributed this effect.
2697%
2698% The format of the MotionBlurImage method is:
2699%
2700% Image *MotionBlurImage(const Image *image,const double radius,
2701% const double sigma,const double angle,ExceptionInfo *exception)
2702% Image *MotionBlurImageChannel(const Image *image,const ChannelType channel,
2703% const double radius,const double sigma,const double angle,
2704% ExceptionInfo *exception)
2705%
2706% A description of each parameter follows:
2707%
2708% o image: the image.
2709%
2710% o channel: the channel type.
2711%
2712% o radius: the radius of the Gaussian, in pixels, not counting the center
2713% o radius: the radius of the Gaussian, in pixels, not counting
2714% the center pixel.
2715%
2716% o sigma: the standard deviation of the Gaussian, in pixels.
2717%
2718% o angle: Apply the effect along this angle.
2719%
2720% o exception: return any errors or warnings in this structure.
2721%
2722*/
2723
cristy47e00502009-12-17 19:19:57 +00002724static double *GetMotionBlurKernel(const unsigned long width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +00002725{
cristy3ed852e2009-09-05 21:47:34 +00002726 double
cristy47e00502009-12-17 19:19:57 +00002727 *kernel,
cristy3ed852e2009-09-05 21:47:34 +00002728 normalize;
2729
2730 register long
2731 i;
2732
2733 /*
cristy47e00502009-12-17 19:19:57 +00002734 Generate a 1-D convolution kernel.
cristy3ed852e2009-09-05 21:47:34 +00002735 */
2736 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
2737 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
2738 if (kernel == (double *) NULL)
2739 return(kernel);
cristy3ed852e2009-09-05 21:47:34 +00002740 normalize=0.0;
2741 for (i=0; i < (long) width; i++)
cristy47e00502009-12-17 19:19:57 +00002742 {
2743 kernel[i]=exp((-((double) i*i)/(double) (2.0*MagickSigma*MagickSigma)))/
2744 (MagickSQ2PI*MagickSigma);
cristy3ed852e2009-09-05 21:47:34 +00002745 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +00002746 }
cristy3ed852e2009-09-05 21:47:34 +00002747 for (i=0; i < (long) width; i++)
2748 kernel[i]/=normalize;
2749 return(kernel);
2750}
2751
2752MagickExport Image *MotionBlurImage(const Image *image,const double radius,
2753 const double sigma,const double angle,ExceptionInfo *exception)
2754{
2755 Image
2756 *motion_blur;
2757
2758 motion_blur=MotionBlurImageChannel(image,DefaultChannels,radius,sigma,angle,
2759 exception);
2760 return(motion_blur);
2761}
2762
2763MagickExport Image *MotionBlurImageChannel(const Image *image,
2764 const ChannelType channel,const double radius,const double sigma,
2765 const double angle,ExceptionInfo *exception)
2766{
2767 typedef struct _OffsetInfo
2768 {
2769 long
2770 x,
2771 y;
2772 } OffsetInfo;
2773
2774 double
2775 *kernel;
2776
2777 Image
2778 *blur_image;
2779
2780 long
2781 progress,
2782 y;
2783
2784 MagickBooleanType
2785 status;
2786
2787 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00002788 bias;
cristy3ed852e2009-09-05 21:47:34 +00002789
2790 OffsetInfo
2791 *offset;
2792
2793 PointInfo
2794 point;
2795
2796 register long
2797 i;
2798
2799 unsigned long
2800 width;
2801
2802 CacheView
2803 *blur_view,
2804 *image_view;
2805
2806 assert(image != (Image *) NULL);
2807 assert(image->signature == MagickSignature);
2808 if (image->debug != MagickFalse)
2809 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2810 assert(exception != (ExceptionInfo *) NULL);
2811 width=GetOptimalKernelWidth1D(radius,sigma);
2812 kernel=GetMotionBlurKernel(width,sigma);
2813 if (kernel == (double *) NULL)
2814 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2815 offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset));
2816 if (offset == (OffsetInfo *) NULL)
2817 {
2818 kernel=(double *) RelinquishMagickMemory(kernel);
2819 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2820 }
2821 blur_image=CloneImage(image,0,0,MagickTrue,exception);
2822 if (blur_image == (Image *) NULL)
2823 {
2824 kernel=(double *) RelinquishMagickMemory(kernel);
2825 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2826 return((Image *) NULL);
2827 }
2828 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
2829 {
2830 kernel=(double *) RelinquishMagickMemory(kernel);
2831 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2832 InheritException(exception,&blur_image->exception);
2833 blur_image=DestroyImage(blur_image);
2834 return((Image *) NULL);
2835 }
2836 point.x=(double) width*sin(DegreesToRadians(angle));
2837 point.y=(double) width*cos(DegreesToRadians(angle));
2838 for (i=0; i < (long) width; i++)
2839 {
2840 offset[i].x=(long) ((i*point.y)/hypot(point.x,point.y)+0.5);
2841 offset[i].y=(long) ((i*point.x)/hypot(point.x,point.y)+0.5);
2842 }
2843 /*
2844 Motion blur image.
2845 */
2846 status=MagickTrue;
2847 progress=0;
cristyddd82202009-11-03 20:14:50 +00002848 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00002849 image_view=AcquireCacheView(image);
2850 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00002851#if defined(MAGICKCORE_OPENMP_SUPPORT)
2852 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002853#endif
2854 for (y=0; y < (long) image->rows; y++)
2855 {
2856 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002857 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002858
2859 register long
2860 x;
2861
2862 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002863 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002864
2865 if (status == MagickFalse)
2866 continue;
2867 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
2868 exception);
2869 if (q == (PixelPacket *) NULL)
2870 {
2871 status=MagickFalse;
2872 continue;
2873 }
2874 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
2875 for (x=0; x < (long) image->columns; x++)
2876 {
2877 MagickPixelPacket
2878 qixel;
2879
2880 PixelPacket
2881 pixel;
2882
2883 register double
cristyc47d1f82009-11-26 01:44:43 +00002884 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00002885
2886 register long
2887 i;
2888
2889 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002890 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002891
2892 k=kernel;
cristyddd82202009-11-03 20:14:50 +00002893 qixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00002894 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
2895 {
2896 for (i=0; i < (long) width; i++)
2897 {
2898 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
2899 offset[i].y,&pixel,exception);
2900 qixel.red+=(*k)*pixel.red;
2901 qixel.green+=(*k)*pixel.green;
2902 qixel.blue+=(*k)*pixel.blue;
2903 qixel.opacity+=(*k)*pixel.opacity;
2904 if (image->colorspace == CMYKColorspace)
2905 {
2906 indexes=GetCacheViewVirtualIndexQueue(image_view);
2907 qixel.index+=(*k)*(*indexes);
2908 }
2909 k++;
2910 }
2911 if ((channel & RedChannel) != 0)
2912 q->red=RoundToQuantum(qixel.red);
2913 if ((channel & GreenChannel) != 0)
2914 q->green=RoundToQuantum(qixel.green);
2915 if ((channel & BlueChannel) != 0)
2916 q->blue=RoundToQuantum(qixel.blue);
2917 if ((channel & OpacityChannel) != 0)
2918 q->opacity=RoundToQuantum(qixel.opacity);
2919 if (((channel & IndexChannel) != 0) &&
2920 (image->colorspace == CMYKColorspace))
2921 blur_indexes[x]=(IndexPacket) RoundToQuantum(qixel.index);
2922 }
2923 else
2924 {
2925 MagickRealType
2926 alpha,
2927 gamma;
2928
2929 alpha=0.0;
2930 gamma=0.0;
2931 for (i=0; i < (long) width; i++)
2932 {
2933 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
2934 offset[i].y,&pixel,exception);
2935 alpha=(MagickRealType) (QuantumScale*(QuantumRange-pixel.opacity));
2936 qixel.red+=(*k)*alpha*pixel.red;
2937 qixel.green+=(*k)*alpha*pixel.green;
2938 qixel.blue+=(*k)*alpha*pixel.blue;
2939 qixel.opacity+=(*k)*pixel.opacity;
2940 if (image->colorspace == CMYKColorspace)
2941 {
2942 indexes=GetCacheViewVirtualIndexQueue(image_view);
2943 qixel.index+=(*k)*alpha*(*indexes);
2944 }
2945 gamma+=(*k)*alpha;
2946 k++;
2947 }
2948 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
2949 if ((channel & RedChannel) != 0)
2950 q->red=RoundToQuantum(gamma*qixel.red);
2951 if ((channel & GreenChannel) != 0)
2952 q->green=RoundToQuantum(gamma*qixel.green);
2953 if ((channel & BlueChannel) != 0)
2954 q->blue=RoundToQuantum(gamma*qixel.blue);
2955 if ((channel & OpacityChannel) != 0)
2956 q->opacity=RoundToQuantum(qixel.opacity);
2957 if (((channel & IndexChannel) != 0) &&
2958 (image->colorspace == CMYKColorspace))
2959 blur_indexes[x]=(IndexPacket) RoundToQuantum(gamma*qixel.index);
2960 }
2961 q++;
2962 }
2963 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
2964 status=MagickFalse;
2965 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2966 {
2967 MagickBooleanType
2968 proceed;
2969
cristyb5d5f722009-11-04 03:03:49 +00002970#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00002971 #pragma omp critical (MagickCore_MotionBlurImageChannel)
2972#endif
2973 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
2974 if (proceed == MagickFalse)
2975 status=MagickFalse;
2976 }
2977 }
2978 blur_view=DestroyCacheView(blur_view);
2979 image_view=DestroyCacheView(image_view);
2980 kernel=(double *) RelinquishMagickMemory(kernel);
2981 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2982 if (status == MagickFalse)
2983 blur_image=DestroyImage(blur_image);
2984 return(blur_image);
2985}
2986
2987/*
2988%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2989% %
2990% %
2991% %
2992% P r e v i e w I m a g e %
2993% %
2994% %
2995% %
2996%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2997%
2998% PreviewImage() tiles 9 thumbnails of the specified image with an image
2999% processing operation applied with varying parameters. This may be helpful
3000% pin-pointing an appropriate parameter for a particular image processing
3001% operation.
3002%
3003% The format of the PreviewImages method is:
3004%
3005% Image *PreviewImages(const Image *image,const PreviewType preview,
3006% ExceptionInfo *exception)
3007%
3008% A description of each parameter follows:
3009%
3010% o image: the image.
3011%
3012% o preview: the image processing operation.
3013%
3014% o exception: return any errors or warnings in this structure.
3015%
3016*/
3017MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
3018 ExceptionInfo *exception)
3019{
3020#define NumberTiles 9
3021#define PreviewImageTag "Preview/Image"
3022#define DefaultPreviewGeometry "204x204+10+10"
3023
3024 char
3025 factor[MaxTextExtent],
3026 label[MaxTextExtent];
3027
3028 double
3029 degrees,
3030 gamma,
3031 percentage,
3032 radius,
3033 sigma,
3034 threshold;
3035
3036 Image
3037 *images,
3038 *montage_image,
3039 *preview_image,
3040 *thumbnail;
3041
3042 ImageInfo
3043 *preview_info;
3044
3045 long
3046 y;
3047
3048 MagickBooleanType
3049 proceed;
3050
3051 MontageInfo
3052 *montage_info;
3053
3054 QuantizeInfo
3055 quantize_info;
3056
3057 RectangleInfo
3058 geometry;
3059
3060 register long
3061 i,
3062 x;
3063
3064 unsigned long
3065 colors;
3066
3067 /*
3068 Open output image file.
3069 */
3070 assert(image != (Image *) NULL);
3071 assert(image->signature == MagickSignature);
3072 if (image->debug != MagickFalse)
3073 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3074 colors=2;
3075 degrees=0.0;
3076 gamma=(-0.2f);
3077 preview_info=AcquireImageInfo();
3078 SetGeometry(image,&geometry);
3079 (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
3080 &geometry.width,&geometry.height);
3081 images=NewImageList();
3082 percentage=12.5;
3083 GetQuantizeInfo(&quantize_info);
3084 radius=0.0;
3085 sigma=1.0;
3086 threshold=0.0;
3087 x=0;
3088 y=0;
3089 for (i=0; i < NumberTiles; i++)
3090 {
3091 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
3092 if (thumbnail == (Image *) NULL)
3093 break;
3094 (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
3095 (void *) NULL);
3096 (void) SetImageProperty(thumbnail,"label",DefaultTileLabel);
3097 if (i == (NumberTiles/2))
3098 {
3099 (void) QueryColorDatabase("#dfdfdf",&thumbnail->matte_color,exception);
3100 AppendImageToList(&images,thumbnail);
3101 continue;
3102 }
3103 switch (preview)
3104 {
3105 case RotatePreview:
3106 {
3107 degrees+=45.0;
3108 preview_image=RotateImage(thumbnail,degrees,exception);
3109 (void) FormatMagickString(label,MaxTextExtent,"rotate %g",degrees);
3110 break;
3111 }
3112 case ShearPreview:
3113 {
3114 degrees+=5.0;
3115 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
3116 (void) FormatMagickString(label,MaxTextExtent,"shear %gx%g",
3117 degrees,2.0*degrees);
3118 break;
3119 }
3120 case RollPreview:
3121 {
3122 x=(long) ((i+1)*thumbnail->columns)/NumberTiles;
3123 y=(long) ((i+1)*thumbnail->rows)/NumberTiles;
3124 preview_image=RollImage(thumbnail,x,y,exception);
3125 (void) FormatMagickString(label,MaxTextExtent,"roll %ldx%ld",x,y);
3126 break;
3127 }
3128 case HuePreview:
3129 {
3130 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3131 if (preview_image == (Image *) NULL)
3132 break;
3133 (void) FormatMagickString(factor,MaxTextExtent,"100,100,%g",
3134 2.0*percentage);
3135 (void) ModulateImage(preview_image,factor);
3136 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
3137 break;
3138 }
3139 case SaturationPreview:
3140 {
3141 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3142 if (preview_image == (Image *) NULL)
3143 break;
3144 (void) FormatMagickString(factor,MaxTextExtent,"100,%g",2.0*percentage);
3145 (void) ModulateImage(preview_image,factor);
3146 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
3147 break;
3148 }
3149 case BrightnessPreview:
3150 {
3151 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3152 if (preview_image == (Image *) NULL)
3153 break;
3154 (void) FormatMagickString(factor,MaxTextExtent,"%g",2.0*percentage);
3155 (void) ModulateImage(preview_image,factor);
3156 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
3157 break;
3158 }
3159 case GammaPreview:
3160 default:
3161 {
3162 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3163 if (preview_image == (Image *) NULL)
3164 break;
3165 gamma+=0.4f;
3166 (void) GammaImageChannel(preview_image,DefaultChannels,gamma);
3167 (void) FormatMagickString(label,MaxTextExtent,"gamma %g",gamma);
3168 break;
3169 }
3170 case SpiffPreview:
3171 {
3172 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3173 if (preview_image != (Image *) NULL)
3174 for (x=0; x < i; x++)
3175 (void) ContrastImage(preview_image,MagickTrue);
3176 (void) FormatMagickString(label,MaxTextExtent,"contrast (%ld)",i+1);
3177 break;
3178 }
3179 case DullPreview:
3180 {
3181 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3182 if (preview_image == (Image *) NULL)
3183 break;
3184 for (x=0; x < i; x++)
3185 (void) ContrastImage(preview_image,MagickFalse);
3186 (void) FormatMagickString(label,MaxTextExtent,"+contrast (%ld)",i+1);
3187 break;
3188 }
3189 case GrayscalePreview:
3190 {
3191 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3192 if (preview_image == (Image *) NULL)
3193 break;
3194 colors<<=1;
3195 quantize_info.number_colors=colors;
3196 quantize_info.colorspace=GRAYColorspace;
3197 (void) QuantizeImage(&quantize_info,preview_image);
3198 (void) FormatMagickString(label,MaxTextExtent,
3199 "-colorspace gray -colors %ld",colors);
3200 break;
3201 }
3202 case QuantizePreview:
3203 {
3204 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3205 if (preview_image == (Image *) NULL)
3206 break;
3207 colors<<=1;
3208 quantize_info.number_colors=colors;
3209 (void) QuantizeImage(&quantize_info,preview_image);
3210 (void) FormatMagickString(label,MaxTextExtent,"colors %ld",colors);
3211 break;
3212 }
3213 case DespecklePreview:
3214 {
3215 for (x=0; x < (i-1); x++)
3216 {
3217 preview_image=DespeckleImage(thumbnail,exception);
3218 if (preview_image == (Image *) NULL)
3219 break;
3220 thumbnail=DestroyImage(thumbnail);
3221 thumbnail=preview_image;
3222 }
3223 preview_image=DespeckleImage(thumbnail,exception);
3224 if (preview_image == (Image *) NULL)
3225 break;
3226 (void) FormatMagickString(label,MaxTextExtent,"despeckle (%ld)",i+1);
3227 break;
3228 }
3229 case ReduceNoisePreview:
3230 {
3231 preview_image=ReduceNoiseImage(thumbnail,radius,exception);
3232 (void) FormatMagickString(label,MaxTextExtent,"noise %g",radius);
3233 break;
3234 }
3235 case AddNoisePreview:
3236 {
3237 switch ((int) i)
3238 {
3239 case 0:
3240 {
3241 (void) CopyMagickString(factor,"uniform",MaxTextExtent);
3242 break;
3243 }
3244 case 1:
3245 {
3246 (void) CopyMagickString(factor,"gaussian",MaxTextExtent);
3247 break;
3248 }
3249 case 2:
3250 {
3251 (void) CopyMagickString(factor,"multiplicative",MaxTextExtent);
3252 break;
3253 }
3254 case 3:
3255 {
3256 (void) CopyMagickString(factor,"impulse",MaxTextExtent);
3257 break;
3258 }
3259 case 4:
3260 {
3261 (void) CopyMagickString(factor,"laplacian",MaxTextExtent);
3262 break;
3263 }
3264 case 5:
3265 {
3266 (void) CopyMagickString(factor,"Poisson",MaxTextExtent);
3267 break;
3268 }
3269 default:
3270 {
3271 (void) CopyMagickString(thumbnail->magick,"NULL",MaxTextExtent);
3272 break;
3273 }
3274 }
3275 preview_image=ReduceNoiseImage(thumbnail,(double) i,exception);
3276 (void) FormatMagickString(label,MaxTextExtent,"+noise %s",factor);
3277 break;
3278 }
3279 case SharpenPreview:
3280 {
3281 preview_image=SharpenImage(thumbnail,radius,sigma,exception);
3282 (void) FormatMagickString(label,MaxTextExtent,"sharpen %gx%g",radius,
3283 sigma);
3284 break;
3285 }
3286 case BlurPreview:
3287 {
3288 preview_image=BlurImage(thumbnail,radius,sigma,exception);
3289 (void) FormatMagickString(label,MaxTextExtent,"blur %gx%g",radius,
3290 sigma);
3291 break;
3292 }
3293 case ThresholdPreview:
3294 {
3295 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3296 if (preview_image == (Image *) NULL)
3297 break;
3298 (void) BilevelImage(thumbnail,
3299 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
3300 (void) FormatMagickString(label,MaxTextExtent,"threshold %g",
3301 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
3302 break;
3303 }
3304 case EdgeDetectPreview:
3305 {
3306 preview_image=EdgeImage(thumbnail,radius,exception);
3307 (void) FormatMagickString(label,MaxTextExtent,"edge %g",radius);
3308 break;
3309 }
3310 case SpreadPreview:
3311 {
3312 preview_image=SpreadImage(thumbnail,radius,exception);
3313 (void) FormatMagickString(label,MaxTextExtent,"spread %g",radius+0.5);
3314 break;
3315 }
3316 case SolarizePreview:
3317 {
3318 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3319 if (preview_image == (Image *) NULL)
3320 break;
3321 (void) SolarizeImage(preview_image,(double) QuantumRange*
3322 percentage/100.0);
3323 (void) FormatMagickString(label,MaxTextExtent,"solarize %g",
3324 (QuantumRange*percentage)/100.0);
3325 break;
3326 }
3327 case ShadePreview:
3328 {
3329 degrees+=10.0;
3330 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
3331 exception);
3332 (void) FormatMagickString(label,MaxTextExtent,"shade %gx%g",degrees,
3333 degrees);
3334 break;
3335 }
3336 case RaisePreview:
3337 {
3338 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3339 if (preview_image == (Image *) NULL)
3340 break;
3341 geometry.width=(unsigned long) (2*i+2);
3342 geometry.height=(unsigned long) (2*i+2);
3343 geometry.x=i/2;
3344 geometry.y=i/2;
3345 (void) RaiseImage(preview_image,&geometry,MagickTrue);
3346 (void) FormatMagickString(label,MaxTextExtent,"raise %lux%lu%+ld%+ld",
3347 geometry.width,geometry.height,geometry.x,geometry.y);
3348 break;
3349 }
3350 case SegmentPreview:
3351 {
3352 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3353 if (preview_image == (Image *) NULL)
3354 break;
3355 threshold+=0.4f;
3356 (void) SegmentImage(preview_image,RGBColorspace,MagickFalse,threshold,
3357 threshold);
3358 (void) FormatMagickString(label,MaxTextExtent,"segment %gx%g",
3359 threshold,threshold);
3360 break;
3361 }
3362 case SwirlPreview:
3363 {
3364 preview_image=SwirlImage(thumbnail,degrees,exception);
3365 (void) FormatMagickString(label,MaxTextExtent,"swirl %g",degrees);
3366 degrees+=45.0;
3367 break;
3368 }
3369 case ImplodePreview:
3370 {
3371 degrees+=0.1f;
3372 preview_image=ImplodeImage(thumbnail,degrees,exception);
3373 (void) FormatMagickString(label,MaxTextExtent,"implode %g",degrees);
3374 break;
3375 }
3376 case WavePreview:
3377 {
3378 degrees+=5.0f;
3379 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,exception);
3380 (void) FormatMagickString(label,MaxTextExtent,"wave %gx%g",0.5*degrees,
3381 2.0*degrees);
3382 break;
3383 }
3384 case OilPaintPreview:
3385 {
3386 preview_image=OilPaintImage(thumbnail,(double) radius,exception);
3387 (void) FormatMagickString(label,MaxTextExtent,"paint %g",radius);
3388 break;
3389 }
3390 case CharcoalDrawingPreview:
3391 {
3392 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
3393 exception);
3394 (void) FormatMagickString(label,MaxTextExtent,"charcoal %gx%g",radius,
3395 sigma);
3396 break;
3397 }
3398 case JPEGPreview:
3399 {
3400 char
3401 filename[MaxTextExtent];
3402
3403 int
3404 file;
3405
3406 MagickBooleanType
3407 status;
3408
3409 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3410 if (preview_image == (Image *) NULL)
3411 break;
3412 preview_info->quality=(unsigned long) percentage;
3413 (void) FormatMagickString(factor,MaxTextExtent,"%lu",
3414 preview_info->quality);
3415 file=AcquireUniqueFileResource(filename);
3416 if (file != -1)
3417 file=close(file)-1;
3418 (void) FormatMagickString(preview_image->filename,MaxTextExtent,
3419 "jpeg:%s",filename);
3420 status=WriteImage(preview_info,preview_image);
3421 if (status != MagickFalse)
3422 {
3423 Image
3424 *quality_image;
3425
3426 (void) CopyMagickString(preview_info->filename,
3427 preview_image->filename,MaxTextExtent);
3428 quality_image=ReadImage(preview_info,exception);
3429 if (quality_image != (Image *) NULL)
3430 {
3431 preview_image=DestroyImage(preview_image);
3432 preview_image=quality_image;
3433 }
3434 }
3435 (void) RelinquishUniqueFileResource(preview_image->filename);
3436 if ((GetBlobSize(preview_image)/1024) >= 1024)
3437 (void) FormatMagickString(label,MaxTextExtent,"quality %s\n%gmb ",
3438 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
3439 1024.0/1024.0);
3440 else
3441 if (GetBlobSize(preview_image) >= 1024)
3442 (void) FormatMagickString(label,MaxTextExtent,"quality %s\n%gkb ",
3443 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
3444 1024.0);
3445 else
3446 (void) FormatMagickString(label,MaxTextExtent,"quality %s\n%lub ",
3447 factor,(unsigned long) GetBlobSize(thumbnail));
3448 break;
3449 }
3450 }
3451 thumbnail=DestroyImage(thumbnail);
3452 percentage+=12.5;
3453 radius+=0.5;
3454 sigma+=0.25;
3455 if (preview_image == (Image *) NULL)
3456 break;
3457 (void) DeleteImageProperty(preview_image,"label");
3458 (void) SetImageProperty(preview_image,"label",label);
3459 AppendImageToList(&images,preview_image);
3460 proceed=SetImageProgress(image,PreviewImageTag,i,NumberTiles);
3461 if (proceed == MagickFalse)
3462 break;
3463 }
3464 if (images == (Image *) NULL)
3465 {
3466 preview_info=DestroyImageInfo(preview_info);
3467 return((Image *) NULL);
3468 }
3469 /*
3470 Create the montage.
3471 */
3472 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
3473 (void) CopyMagickString(montage_info->filename,image->filename,MaxTextExtent);
3474 montage_info->shadow=MagickTrue;
3475 (void) CloneString(&montage_info->tile,"3x3");
3476 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
3477 (void) CloneString(&montage_info->frame,DefaultTileFrame);
3478 montage_image=MontageImages(images,montage_info,exception);
3479 montage_info=DestroyMontageInfo(montage_info);
3480 images=DestroyImageList(images);
3481 if (montage_image == (Image *) NULL)
3482 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3483 if (montage_image->montage != (char *) NULL)
3484 {
3485 /*
3486 Free image directory.
3487 */
3488 montage_image->montage=(char *) RelinquishMagickMemory(
3489 montage_image->montage);
3490 if (image->directory != (char *) NULL)
3491 montage_image->directory=(char *) RelinquishMagickMemory(
3492 montage_image->directory);
3493 }
3494 preview_info=DestroyImageInfo(preview_info);
3495 return(montage_image);
3496}
3497
3498/*
3499%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3500% %
3501% %
3502% %
3503% R a d i a l B l u r I m a g e %
3504% %
3505% %
3506% %
3507%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3508%
3509% RadialBlurImage() applies a radial blur to the image.
3510%
3511% Andrew Protano contributed this effect.
3512%
3513% The format of the RadialBlurImage method is:
3514%
3515% Image *RadialBlurImage(const Image *image,const double angle,
3516% ExceptionInfo *exception)
3517% Image *RadialBlurImageChannel(const Image *image,const ChannelType channel,
3518% const double angle,ExceptionInfo *exception)
3519%
3520% A description of each parameter follows:
3521%
3522% o image: the image.
3523%
3524% o channel: the channel type.
3525%
3526% o angle: the angle of the radial blur.
3527%
3528% o exception: return any errors or warnings in this structure.
3529%
3530*/
3531
3532MagickExport Image *RadialBlurImage(const Image *image,const double angle,
3533 ExceptionInfo *exception)
3534{
3535 Image
3536 *blur_image;
3537
3538 blur_image=RadialBlurImageChannel(image,DefaultChannels,angle,exception);
3539 return(blur_image);
3540}
3541
3542MagickExport Image *RadialBlurImageChannel(const Image *image,
3543 const ChannelType channel,const double angle,ExceptionInfo *exception)
3544{
3545 Image
3546 *blur_image;
3547
3548 long
3549 progress,
3550 y;
3551
3552 MagickBooleanType
3553 status;
3554
3555 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00003556 bias;
cristy3ed852e2009-09-05 21:47:34 +00003557
3558 MagickRealType
3559 blur_radius,
3560 *cos_theta,
3561 offset,
3562 *sin_theta,
3563 theta;
3564
3565 PointInfo
3566 blur_center;
3567
3568 register long
3569 i;
3570
3571 unsigned long
3572 n;
3573
3574 CacheView
3575 *blur_view,
3576 *image_view;
3577
3578 /*
3579 Allocate blur image.
3580 */
3581 assert(image != (Image *) NULL);
3582 assert(image->signature == MagickSignature);
3583 if (image->debug != MagickFalse)
3584 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3585 assert(exception != (ExceptionInfo *) NULL);
3586 assert(exception->signature == MagickSignature);
3587 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3588 if (blur_image == (Image *) NULL)
3589 return((Image *) NULL);
3590 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3591 {
3592 InheritException(exception,&blur_image->exception);
3593 blur_image=DestroyImage(blur_image);
3594 return((Image *) NULL);
3595 }
3596 blur_center.x=(double) image->columns/2.0;
3597 blur_center.y=(double) image->rows/2.0;
3598 blur_radius=hypot(blur_center.x,blur_center.y);
3599 n=(unsigned long) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+
3600 2UL);
3601 theta=DegreesToRadians(angle)/(MagickRealType) (n-1);
3602 cos_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3603 sizeof(*cos_theta));
3604 sin_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3605 sizeof(*sin_theta));
3606 if ((cos_theta == (MagickRealType *) NULL) ||
3607 (sin_theta == (MagickRealType *) NULL))
3608 {
3609 blur_image=DestroyImage(blur_image);
3610 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3611 }
3612 offset=theta*(MagickRealType) (n-1)/2.0;
3613 for (i=0; i < (long) n; i++)
3614 {
3615 cos_theta[i]=cos((double) (theta*i-offset));
3616 sin_theta[i]=sin((double) (theta*i-offset));
3617 }
3618 /*
3619 Radial blur image.
3620 */
3621 status=MagickTrue;
3622 progress=0;
cristyddd82202009-11-03 20:14:50 +00003623 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003624 image_view=AcquireCacheView(image);
3625 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003626#if defined(MAGICKCORE_OPENMP_SUPPORT)
3627 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003628#endif
3629 for (y=0; y < (long) blur_image->rows; y++)
3630 {
3631 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003632 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003633
3634 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003635 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00003636
3637 register long
3638 x;
3639
3640 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003641 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003642
3643 if (status == MagickFalse)
3644 continue;
3645 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3646 exception);
3647 if (q == (PixelPacket *) NULL)
3648 {
3649 status=MagickFalse;
3650 continue;
3651 }
3652 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
3653 for (x=0; x < (long) blur_image->columns; x++)
3654 {
3655 MagickPixelPacket
3656 qixel;
3657
3658 MagickRealType
3659 normalize,
3660 radius;
3661
3662 PixelPacket
3663 pixel;
3664
3665 PointInfo
3666 center;
3667
3668 register long
3669 i;
3670
3671 unsigned long
3672 step;
3673
3674 center.x=(double) x-blur_center.x;
3675 center.y=(double) y-blur_center.y;
3676 radius=hypot((double) center.x,center.y);
3677 if (radius == 0)
3678 step=1;
3679 else
3680 {
3681 step=(unsigned long) (blur_radius/radius);
3682 if (step == 0)
3683 step=1;
3684 else
3685 if (step >= n)
3686 step=n-1;
3687 }
3688 normalize=0.0;
cristyddd82202009-11-03 20:14:50 +00003689 qixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00003690 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
3691 {
3692 for (i=0; i < (long) n; i+=step)
3693 {
3694 (void) GetOneCacheViewVirtualPixel(image_view,(long) (blur_center.x+
3695 center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),(long) (
3696 blur_center.y+center.x*sin_theta[i]+center.y*cos_theta[i]+0.5),
3697 &pixel,exception);
3698 qixel.red+=pixel.red;
3699 qixel.green+=pixel.green;
3700 qixel.blue+=pixel.blue;
3701 qixel.opacity+=pixel.opacity;
3702 if (image->colorspace == CMYKColorspace)
3703 {
3704 indexes=GetCacheViewVirtualIndexQueue(image_view);
3705 qixel.index+=(*indexes);
3706 }
3707 normalize+=1.0;
3708 }
3709 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
3710 normalize);
3711 if ((channel & RedChannel) != 0)
3712 q->red=RoundToQuantum(normalize*qixel.red);
3713 if ((channel & GreenChannel) != 0)
3714 q->green=RoundToQuantum(normalize*qixel.green);
3715 if ((channel & BlueChannel) != 0)
3716 q->blue=RoundToQuantum(normalize*qixel.blue);
3717 if ((channel & OpacityChannel) != 0)
3718 q->opacity=RoundToQuantum(normalize*qixel.opacity);
3719 if (((channel & IndexChannel) != 0) &&
3720 (image->colorspace == CMYKColorspace))
3721 blur_indexes[x]=(IndexPacket) RoundToQuantum(normalize*qixel.index);
3722 }
3723 else
3724 {
3725 MagickRealType
3726 alpha,
3727 gamma;
3728
3729 alpha=1.0;
3730 gamma=0.0;
3731 for (i=0; i < (long) n; i+=step)
3732 {
3733 (void) GetOneCacheViewVirtualPixel(image_view,(long) (blur_center.x+
3734 center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),(long) (
3735 blur_center.y+center.x*sin_theta[i]+center.y*cos_theta[i]+0.5),
3736 &pixel,exception);
3737 alpha=(MagickRealType) (QuantumScale*(QuantumRange-pixel.opacity));
3738 qixel.red+=alpha*pixel.red;
3739 qixel.green+=alpha*pixel.green;
3740 qixel.blue+=alpha*pixel.blue;
3741 qixel.opacity+=pixel.opacity;
3742 if (image->colorspace == CMYKColorspace)
3743 {
3744 indexes=GetCacheViewVirtualIndexQueue(image_view);
3745 qixel.index+=alpha*(*indexes);
3746 }
3747 gamma+=alpha;
3748 normalize+=1.0;
3749 }
3750 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3751 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
3752 normalize);
3753 if ((channel & RedChannel) != 0)
3754 q->red=RoundToQuantum(gamma*qixel.red);
3755 if ((channel & GreenChannel) != 0)
3756 q->green=RoundToQuantum(gamma*qixel.green);
3757 if ((channel & BlueChannel) != 0)
3758 q->blue=RoundToQuantum(gamma*qixel.blue);
3759 if ((channel & OpacityChannel) != 0)
3760 q->opacity=RoundToQuantum(normalize*qixel.opacity);
3761 if (((channel & IndexChannel) != 0) &&
3762 (image->colorspace == CMYKColorspace))
3763 blur_indexes[x]=(IndexPacket) RoundToQuantum(gamma*qixel.index);
3764 }
3765 q++;
3766 }
3767 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
3768 status=MagickFalse;
3769 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3770 {
3771 MagickBooleanType
3772 proceed;
3773
cristyb5d5f722009-11-04 03:03:49 +00003774#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003775 #pragma omp critical (MagickCore_RadialBlurImageChannel)
3776#endif
3777 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
3778 if (proceed == MagickFalse)
3779 status=MagickFalse;
3780 }
3781 }
3782 blur_view=DestroyCacheView(blur_view);
3783 image_view=DestroyCacheView(image_view);
3784 cos_theta=(MagickRealType *) RelinquishMagickMemory(cos_theta);
3785 sin_theta=(MagickRealType *) RelinquishMagickMemory(sin_theta);
3786 if (status == MagickFalse)
3787 blur_image=DestroyImage(blur_image);
3788 return(blur_image);
3789}
3790
3791/*
3792%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3793% %
3794% %
3795% %
3796% R e d u c e N o i s e I m a g e %
3797% %
3798% %
3799% %
3800%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3801%
3802% ReduceNoiseImage() smooths the contours of an image while still preserving
3803% edge information. The algorithm works by replacing each pixel with its
3804% neighbor closest in value. A neighbor is defined by radius. Use a radius
3805% of 0 and ReduceNoise() selects a suitable radius for you.
3806%
3807% The format of the ReduceNoiseImage method is:
3808%
3809% Image *ReduceNoiseImage(const Image *image,const double radius,
3810% ExceptionInfo *exception)
3811%
3812% A description of each parameter follows:
3813%
3814% o image: the image.
3815%
3816% o radius: the radius of the pixel neighborhood.
3817%
3818% o exception: return any errors or warnings in this structure.
3819%
3820*/
3821
3822static MagickPixelPacket GetNonpeakMedianPixelList(MedianPixelList *pixel_list)
3823{
3824 MagickPixelPacket
3825 pixel;
3826
3827 register long
3828 channel;
3829
3830 register MedianSkipList
3831 *list;
3832
3833 unsigned long
3834 center,
3835 color,
3836 count,
3837 previous,
3838 next;
3839
3840 unsigned short
3841 channels[5];
3842
3843 /*
3844 Finds the median value for each of the color.
3845 */
3846 center=pixel_list->center;
3847 for (channel=0; channel < 5; channel++)
3848 {
3849 list=pixel_list->lists+channel;
3850 color=65536UL;
3851 next=list->nodes[color].next[0];
3852 count=0;
3853 do
3854 {
3855 previous=color;
3856 color=next;
3857 next=list->nodes[color].next[0];
3858 count+=list->nodes[color].count;
3859 }
3860 while (count <= center);
3861 if ((previous == 65536UL) && (next != 65536UL))
3862 color=next;
3863 else
3864 if ((previous != 65536UL) && (next == 65536UL))
3865 color=previous;
3866 channels[channel]=(unsigned short) color;
3867 }
3868 GetMagickPixelPacket((const Image *) NULL,&pixel);
3869 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
3870 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
3871 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
3872 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
3873 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
3874 return(pixel);
3875}
3876
3877MagickExport Image *ReduceNoiseImage(const Image *image,const double radius,
3878 ExceptionInfo *exception)
3879{
3880#define ReduceNoiseImageTag "ReduceNoise/Image"
3881
cristyfa112112010-01-04 17:48:07 +00003882 CacheView
3883 *image_view,
3884 *noise_view;
3885
cristy3ed852e2009-09-05 21:47:34 +00003886 Image
3887 *noise_image;
3888
3889 long
3890 progress,
3891 y;
3892
3893 MagickBooleanType
3894 status;
3895
3896 MedianPixelList
cristyfa112112010-01-04 17:48:07 +00003897 **restrict pixel_list;
cristy3ed852e2009-09-05 21:47:34 +00003898
3899 unsigned long
3900 width;
3901
cristy3ed852e2009-09-05 21:47:34 +00003902 /*
3903 Initialize noise image attributes.
3904 */
3905 assert(image != (Image *) NULL);
3906 assert(image->signature == MagickSignature);
3907 if (image->debug != MagickFalse)
3908 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3909 assert(exception != (ExceptionInfo *) NULL);
3910 assert(exception->signature == MagickSignature);
3911 width=GetOptimalKernelWidth2D(radius,0.5);
3912 if ((image->columns < width) || (image->rows < width))
3913 ThrowImageException(OptionError,"ImageSmallerThanKernelRadius");
3914 noise_image=CloneImage(image,image->columns,image->rows,MagickTrue,
3915 exception);
3916 if (noise_image == (Image *) NULL)
3917 return((Image *) NULL);
3918 if (SetImageStorageClass(noise_image,DirectClass) == MagickFalse)
3919 {
3920 InheritException(exception,&noise_image->exception);
3921 noise_image=DestroyImage(noise_image);
3922 return((Image *) NULL);
3923 }
3924 pixel_list=AcquireMedianPixelListThreadSet(width);
3925 if (pixel_list == (MedianPixelList **) NULL)
3926 {
3927 noise_image=DestroyImage(noise_image);
3928 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3929 }
3930 /*
3931 Reduce noise image.
3932 */
3933 status=MagickTrue;
3934 progress=0;
3935 image_view=AcquireCacheView(image);
3936 noise_view=AcquireCacheView(noise_image);
cristyb5d5f722009-11-04 03:03:49 +00003937#if defined(MAGICKCORE_OPENMP_SUPPORT)
3938 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003939#endif
3940 for (y=0; y < (long) noise_image->rows; y++)
3941 {
3942 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003943 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003944
3945 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003946 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003947
3948 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003949 *restrict noise_indexes;
cristy3ed852e2009-09-05 21:47:34 +00003950
3951 register long
3952 id,
3953 x;
3954
3955 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003956 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003957
3958 if (status == MagickFalse)
3959 continue;
3960 p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y-(long) (width/
3961 2L),image->columns+width,width,exception);
3962 q=QueueCacheViewAuthenticPixels(noise_view,0,y,noise_image->columns,1,
3963 exception);
3964 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
3965 {
3966 status=MagickFalse;
3967 continue;
3968 }
3969 indexes=GetCacheViewVirtualIndexQueue(image_view);
3970 noise_indexes=GetCacheViewAuthenticIndexQueue(noise_view);
3971 id=GetOpenMPThreadId();
3972 for (x=0; x < (long) noise_image->columns; x++)
3973 {
3974 MagickPixelPacket
3975 pixel;
3976
3977 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003978 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +00003979
3980 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003981 *restrict s;
cristy3ed852e2009-09-05 21:47:34 +00003982
3983 register long
3984 u,
3985 v;
3986
3987 r=p;
3988 s=indexes+x;
3989 ResetMedianPixelList(pixel_list[id]);
3990 for (v=0; v < (long) width; v++)
3991 {
3992 for (u=0; u < (long) width; u++)
3993 InsertMedianPixelList(image,r+u,s+u,pixel_list[id]);
3994 r+=image->columns+width;
3995 s+=image->columns+width;
3996 }
3997 pixel=GetNonpeakMedianPixelList(pixel_list[id]);
3998 SetPixelPacket(noise_image,&pixel,q,noise_indexes+x);
3999 p++;
4000 q++;
4001 }
4002 if (SyncCacheViewAuthenticPixels(noise_view,exception) == MagickFalse)
4003 status=MagickFalse;
4004 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4005 {
4006 MagickBooleanType
4007 proceed;
4008
cristyb5d5f722009-11-04 03:03:49 +00004009#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004010 #pragma omp critical (MagickCore_ReduceNoiseImage)
4011#endif
4012 proceed=SetImageProgress(image,ReduceNoiseImageTag,progress++,
4013 image->rows);
4014 if (proceed == MagickFalse)
4015 status=MagickFalse;
4016 }
4017 }
4018 noise_view=DestroyCacheView(noise_view);
4019 image_view=DestroyCacheView(image_view);
4020 pixel_list=DestroyMedianPixelListThreadSet(pixel_list);
4021 return(noise_image);
4022}
4023
4024/*
4025%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4026% %
4027% %
4028% %
4029% S e l e c t i v e B l u r I m a g e %
4030% %
4031% %
4032% %
4033%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4034%
4035% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
4036% It is similar to the unsharpen mask that sharpens everything with contrast
4037% above a certain threshold.
4038%
4039% The format of the SelectiveBlurImage method is:
4040%
4041% Image *SelectiveBlurImage(const Image *image,const double radius,
4042% const double sigma,const double threshold,ExceptionInfo *exception)
4043% Image *SelectiveBlurImageChannel(const Image *image,
4044% const ChannelType channel,const double radius,const double sigma,
4045% const double threshold,ExceptionInfo *exception)
4046%
4047% A description of each parameter follows:
4048%
4049% o image: the image.
4050%
4051% o channel: the channel type.
4052%
4053% o radius: the radius of the Gaussian, in pixels, not counting the center
4054% pixel.
4055%
4056% o sigma: the standard deviation of the Gaussian, in pixels.
4057%
4058% o threshold: only pixels within this contrast threshold are included
4059% in the blur operation.
4060%
4061% o exception: return any errors or warnings in this structure.
4062%
4063*/
4064
4065static inline MagickBooleanType SelectiveContrast(const PixelPacket *p,
4066 const PixelPacket *q,const double threshold)
4067{
4068 if (fabs(PixelIntensity(p)-PixelIntensity(q)) < threshold)
4069 return(MagickTrue);
4070 return(MagickFalse);
4071}
4072
4073MagickExport Image *SelectiveBlurImage(const Image *image,const double radius,
4074 const double sigma,const double threshold,ExceptionInfo *exception)
4075{
4076 Image
4077 *blur_image;
4078
4079 blur_image=SelectiveBlurImageChannel(image,DefaultChannels,radius,sigma,
4080 threshold,exception);
4081 return(blur_image);
4082}
4083
4084MagickExport Image *SelectiveBlurImageChannel(const Image *image,
4085 const ChannelType channel,const double radius,const double sigma,
4086 const double threshold,ExceptionInfo *exception)
4087{
4088#define SelectiveBlurImageTag "SelectiveBlur/Image"
4089
cristy47e00502009-12-17 19:19:57 +00004090 CacheView
4091 *blur_view,
4092 *image_view;
4093
cristy3ed852e2009-09-05 21:47:34 +00004094 double
cristy3ed852e2009-09-05 21:47:34 +00004095 *kernel;
4096
4097 Image
4098 *blur_image;
4099
4100 long
cristy47e00502009-12-17 19:19:57 +00004101 j,
cristy3ed852e2009-09-05 21:47:34 +00004102 progress,
cristy47e00502009-12-17 19:19:57 +00004103 u,
cristy3ed852e2009-09-05 21:47:34 +00004104 v,
4105 y;
4106
4107 MagickBooleanType
4108 status;
4109
4110 MagickPixelPacket
cristy3ed852e2009-09-05 21:47:34 +00004111 bias;
4112
4113 register long
cristy47e00502009-12-17 19:19:57 +00004114 i;
cristy3ed852e2009-09-05 21:47:34 +00004115
4116 unsigned long
4117 width;
4118
cristy3ed852e2009-09-05 21:47:34 +00004119 /*
4120 Initialize blur image attributes.
4121 */
4122 assert(image != (Image *) NULL);
4123 assert(image->signature == MagickSignature);
4124 if (image->debug != MagickFalse)
4125 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4126 assert(exception != (ExceptionInfo *) NULL);
4127 assert(exception->signature == MagickSignature);
4128 width=GetOptimalKernelWidth1D(radius,sigma);
4129 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
4130 if (kernel == (double *) NULL)
4131 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy47e00502009-12-17 19:19:57 +00004132 j=(long) width/2;
cristy3ed852e2009-09-05 21:47:34 +00004133 i=0;
cristy47e00502009-12-17 19:19:57 +00004134 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00004135 {
cristy47e00502009-12-17 19:19:57 +00004136 for (u=(-j); u <= j; u++)
4137 kernel[i++]=exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
4138 (2.0*MagickPI*MagickSigma*MagickSigma);
cristy3ed852e2009-09-05 21:47:34 +00004139 }
4140 if (image->debug != MagickFalse)
4141 {
4142 char
4143 format[MaxTextExtent],
4144 *message;
4145
4146 long
4147 u,
4148 v;
4149
4150 register const double
4151 *k;
4152
4153 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
4154 " SelectiveBlurImage with %ldx%ld kernel:",width,width);
4155 message=AcquireString("");
4156 k=kernel;
4157 for (v=0; v < (long) width; v++)
4158 {
4159 *message='\0';
4160 (void) FormatMagickString(format,MaxTextExtent,"%ld: ",v);
4161 (void) ConcatenateString(&message,format);
4162 for (u=0; u < (long) width; u++)
4163 {
4164 (void) FormatMagickString(format,MaxTextExtent,"%+f ",*k++);
4165 (void) ConcatenateString(&message,format);
4166 }
4167 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
4168 }
4169 message=DestroyString(message);
4170 }
4171 blur_image=CloneImage(image,0,0,MagickTrue,exception);
4172 if (blur_image == (Image *) NULL)
4173 return((Image *) NULL);
4174 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
4175 {
4176 InheritException(exception,&blur_image->exception);
4177 blur_image=DestroyImage(blur_image);
4178 return((Image *) NULL);
4179 }
4180 /*
4181 Threshold blur image.
4182 */
4183 status=MagickTrue;
4184 progress=0;
cristyddd82202009-11-03 20:14:50 +00004185 GetMagickPixelPacket(image,&bias);
4186 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00004187 image_view=AcquireCacheView(image);
4188 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00004189#if defined(MAGICKCORE_OPENMP_SUPPORT)
4190 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004191#endif
4192 for (y=0; y < (long) image->rows; y++)
4193 {
4194 MagickBooleanType
4195 sync;
4196
4197 MagickRealType
4198 gamma;
4199
4200 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004201 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00004202
4203 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004204 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00004205
4206 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004207 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00004208
4209 register long
4210 x;
4211
4212 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004213 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004214
4215 if (status == MagickFalse)
4216 continue;
4217 p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y-(long) (width/
4218 2L),image->columns+width,width,exception);
4219 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
4220 exception);
4221 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
4222 {
4223 status=MagickFalse;
4224 continue;
4225 }
4226 indexes=GetCacheViewVirtualIndexQueue(image_view);
4227 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
4228 for (x=0; x < (long) image->columns; x++)
4229 {
4230 long
4231 j,
4232 v;
4233
4234 MagickPixelPacket
4235 pixel;
4236
4237 register const double
cristyc47d1f82009-11-26 01:44:43 +00004238 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00004239
4240 register long
4241 u;
4242
cristyddd82202009-11-03 20:14:50 +00004243 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00004244 k=kernel;
4245 gamma=0.0;
4246 j=0;
4247 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
4248 {
4249 for (v=0; v < (long) width; v++)
4250 {
4251 for (u=0; u < (long) width; u++)
4252 {
4253 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4254 {
4255 pixel.red+=(*k)*(p+u+j)->red;
4256 pixel.green+=(*k)*(p+u+j)->green;
4257 pixel.blue+=(*k)*(p+u+j)->blue;
4258 gamma+=(*k);
4259 k++;
4260 }
4261 }
4262 j+=image->columns+width;
4263 }
4264 if (gamma != 0.0)
4265 {
4266 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
4267 if ((channel & RedChannel) != 0)
cristyddd82202009-11-03 20:14:50 +00004268 q->red=RoundToQuantum(gamma*pixel.red);
cristy3ed852e2009-09-05 21:47:34 +00004269 if ((channel & GreenChannel) != 0)
cristyddd82202009-11-03 20:14:50 +00004270 q->green=RoundToQuantum(gamma*pixel.green);
cristy3ed852e2009-09-05 21:47:34 +00004271 if ((channel & BlueChannel) != 0)
cristyddd82202009-11-03 20:14:50 +00004272 q->blue=RoundToQuantum(gamma*pixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00004273 }
4274 if ((channel & OpacityChannel) != 0)
4275 {
4276 gamma=0.0;
4277 j=0;
4278 for (v=0; v < (long) width; v++)
4279 {
4280 for (u=0; u < (long) width; u++)
4281 {
4282 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4283 {
4284 pixel.opacity+=(*k)*(p+u+j)->opacity;
4285 gamma+=(*k);
4286 k++;
4287 }
4288 }
4289 j+=image->columns+width;
4290 }
4291 if (gamma != 0.0)
4292 {
4293 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4294 gamma);
cristyddd82202009-11-03 20:14:50 +00004295 q->opacity=RoundToQuantum(gamma*pixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00004296 }
4297 }
4298 if (((channel & IndexChannel) != 0) &&
4299 (image->colorspace == CMYKColorspace))
4300 {
4301 gamma=0.0;
4302 j=0;
4303 for (v=0; v < (long) width; v++)
4304 {
4305 for (u=0; u < (long) width; u++)
4306 {
4307 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4308 {
4309 pixel.index+=(*k)*indexes[x+u+j];
4310 gamma+=(*k);
4311 k++;
4312 }
4313 }
4314 j+=image->columns+width;
4315 }
4316 if (gamma != 0.0)
4317 {
4318 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4319 gamma);
cristyddd82202009-11-03 20:14:50 +00004320 blur_indexes[x]=RoundToQuantum(gamma*pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00004321 }
4322 }
4323 }
4324 else
4325 {
4326 MagickRealType
4327 alpha;
4328
4329 for (v=0; v < (long) width; v++)
4330 {
4331 for (u=0; u < (long) width; u++)
4332 {
4333 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4334 {
4335 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
4336 (p+u+j)->opacity));
4337 pixel.red+=(*k)*alpha*(p+u+j)->red;
4338 pixel.green+=(*k)*alpha*(p+u+j)->green;
4339 pixel.blue+=(*k)*alpha*(p+u+j)->blue;
4340 pixel.opacity+=(*k)*(p+u+j)->opacity;
4341 gamma+=(*k)*alpha;
4342 k++;
4343 }
4344 }
4345 j+=image->columns+width;
4346 }
4347 if (gamma != 0.0)
4348 {
4349 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
4350 if ((channel & RedChannel) != 0)
cristyddd82202009-11-03 20:14:50 +00004351 q->red=RoundToQuantum(gamma*pixel.red);
cristy3ed852e2009-09-05 21:47:34 +00004352 if ((channel & GreenChannel) != 0)
cristyddd82202009-11-03 20:14:50 +00004353 q->green=RoundToQuantum(gamma*pixel.green);
cristy3ed852e2009-09-05 21:47:34 +00004354 if ((channel & BlueChannel) != 0)
cristyddd82202009-11-03 20:14:50 +00004355 q->blue=RoundToQuantum(gamma*pixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00004356 }
4357 if ((channel & OpacityChannel) != 0)
4358 {
4359 gamma=0.0;
4360 j=0;
4361 for (v=0; v < (long) width; v++)
4362 {
4363 for (u=0; u < (long) width; u++)
4364 {
4365 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4366 {
4367 pixel.opacity+=(*k)*(p+u+j)->opacity;
4368 gamma+=(*k);
4369 k++;
4370 }
4371 }
4372 j+=image->columns+width;
4373 }
4374 if (gamma != 0.0)
4375 {
4376 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4377 gamma);
cristyddd82202009-11-03 20:14:50 +00004378 q->opacity=RoundToQuantum(pixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00004379 }
4380 }
4381 if (((channel & IndexChannel) != 0) &&
4382 (image->colorspace == CMYKColorspace))
4383 {
4384 gamma=0.0;
4385 j=0;
4386 for (v=0; v < (long) width; v++)
4387 {
4388 for (u=0; u < (long) width; u++)
4389 {
4390 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4391 {
4392 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
4393 (p+u+j)->opacity));
4394 pixel.index+=(*k)*alpha*indexes[x+u+j];
4395 gamma+=(*k);
4396 k++;
4397 }
4398 }
4399 j+=image->columns+width;
4400 }
4401 if (gamma != 0.0)
4402 {
4403 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4404 gamma);
cristyddd82202009-11-03 20:14:50 +00004405 blur_indexes[x]=RoundToQuantum(gamma*pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00004406 }
4407 }
4408 }
4409 p++;
4410 q++;
4411 }
4412 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
4413 if (sync == MagickFalse)
4414 status=MagickFalse;
4415 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4416 {
4417 MagickBooleanType
4418 proceed;
4419
cristyb5d5f722009-11-04 03:03:49 +00004420#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004421 #pragma omp critical (MagickCore_SelectiveBlurImageChannel)
4422#endif
4423 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress++,
4424 image->rows);
4425 if (proceed == MagickFalse)
4426 status=MagickFalse;
4427 }
4428 }
4429 blur_image->type=image->type;
4430 blur_view=DestroyCacheView(blur_view);
4431 image_view=DestroyCacheView(image_view);
4432 kernel=(double *) RelinquishMagickMemory(kernel);
4433 if (status == MagickFalse)
4434 blur_image=DestroyImage(blur_image);
4435 return(blur_image);
4436}
4437
4438/*
4439%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4440% %
4441% %
4442% %
4443% S h a d e I m a g e %
4444% %
4445% %
4446% %
4447%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4448%
4449% ShadeImage() shines a distant light on an image to create a
4450% three-dimensional effect. You control the positioning of the light with
4451% azimuth and elevation; azimuth is measured in degrees off the x axis
4452% and elevation is measured in pixels above the Z axis.
4453%
4454% The format of the ShadeImage method is:
4455%
4456% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
4457% const double azimuth,const double elevation,ExceptionInfo *exception)
4458%
4459% A description of each parameter follows:
4460%
4461% o image: the image.
4462%
4463% o gray: A value other than zero shades the intensity of each pixel.
4464%
4465% o azimuth, elevation: Define the light source direction.
4466%
4467% o exception: return any errors or warnings in this structure.
4468%
4469*/
4470MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
4471 const double azimuth,const double elevation,ExceptionInfo *exception)
4472{
4473#define ShadeImageTag "Shade/Image"
4474
4475 Image
4476 *shade_image;
4477
4478 long
4479 progress,
4480 y;
4481
4482 MagickBooleanType
4483 status;
4484
4485 PrimaryInfo
4486 light;
4487
4488 CacheView
4489 *image_view,
4490 *shade_view;
4491
4492 /*
4493 Initialize shaded image attributes.
4494 */
4495 assert(image != (const Image *) NULL);
4496 assert(image->signature == MagickSignature);
4497 if (image->debug != MagickFalse)
4498 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4499 assert(exception != (ExceptionInfo *) NULL);
4500 assert(exception->signature == MagickSignature);
4501 shade_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
4502 if (shade_image == (Image *) NULL)
4503 return((Image *) NULL);
4504 if (SetImageStorageClass(shade_image,DirectClass) == MagickFalse)
4505 {
4506 InheritException(exception,&shade_image->exception);
4507 shade_image=DestroyImage(shade_image);
4508 return((Image *) NULL);
4509 }
4510 /*
4511 Compute the light vector.
4512 */
4513 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
4514 cos(DegreesToRadians(elevation));
4515 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
4516 cos(DegreesToRadians(elevation));
4517 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
4518 /*
4519 Shade image.
4520 */
4521 status=MagickTrue;
4522 progress=0;
4523 image_view=AcquireCacheView(image);
4524 shade_view=AcquireCacheView(shade_image);
cristyb5d5f722009-11-04 03:03:49 +00004525#if defined(MAGICKCORE_OPENMP_SUPPORT)
4526 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004527#endif
4528 for (y=0; y < (long) image->rows; y++)
4529 {
4530 MagickRealType
4531 distance,
4532 normal_distance,
4533 shade;
4534
4535 PrimaryInfo
4536 normal;
4537
4538 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004539 *restrict p,
4540 *restrict s0,
4541 *restrict s1,
4542 *restrict s2;
cristy3ed852e2009-09-05 21:47:34 +00004543
4544 register long
4545 x;
4546
4547 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004548 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004549
4550 if (status == MagickFalse)
4551 continue;
4552 p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception);
4553 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
4554 exception);
4555 if ((p == (PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
4556 {
4557 status=MagickFalse;
4558 continue;
4559 }
4560 /*
4561 Shade this row of pixels.
4562 */
4563 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
4564 s0=p+1;
4565 s1=s0+image->columns+2;
4566 s2=s1+image->columns+2;
4567 for (x=0; x < (long) image->columns; x++)
4568 {
4569 /*
4570 Determine the surface normal and compute shading.
4571 */
4572 normal.x=(double) (PixelIntensity(s0-1)+PixelIntensity(s1-1)+
4573 PixelIntensity(s2-1)-PixelIntensity(s0+1)-PixelIntensity(s1+1)-
4574 PixelIntensity(s2+1));
4575 normal.y=(double) (PixelIntensity(s2-1)+PixelIntensity(s2)+
4576 PixelIntensity(s2+1)-PixelIntensity(s0-1)-PixelIntensity(s0)-
4577 PixelIntensity(s0+1));
4578 if ((normal.x == 0.0) && (normal.y == 0.0))
4579 shade=light.z;
4580 else
4581 {
4582 shade=0.0;
4583 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
4584 if (distance > MagickEpsilon)
4585 {
4586 normal_distance=
4587 normal.x*normal.x+normal.y*normal.y+normal.z*normal.z;
4588 if (normal_distance > (MagickEpsilon*MagickEpsilon))
4589 shade=distance/sqrt((double) normal_distance);
4590 }
4591 }
4592 if (gray != MagickFalse)
4593 {
4594 q->red=(Quantum) shade;
4595 q->green=(Quantum) shade;
4596 q->blue=(Quantum) shade;
4597 }
4598 else
4599 {
4600 q->red=RoundToQuantum(QuantumScale*shade*s1->red);
4601 q->green=RoundToQuantum(QuantumScale*shade*s1->green);
4602 q->blue=RoundToQuantum(QuantumScale*shade*s1->blue);
4603 }
4604 q->opacity=s1->opacity;
4605 s0++;
4606 s1++;
4607 s2++;
4608 q++;
4609 }
4610 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
4611 status=MagickFalse;
4612 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4613 {
4614 MagickBooleanType
4615 proceed;
4616
cristyb5d5f722009-11-04 03:03:49 +00004617#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004618 #pragma omp critical (MagickCore_ShadeImage)
4619#endif
4620 proceed=SetImageProgress(image,ShadeImageTag,progress++,image->rows);
4621 if (proceed == MagickFalse)
4622 status=MagickFalse;
4623 }
4624 }
4625 shade_view=DestroyCacheView(shade_view);
4626 image_view=DestroyCacheView(image_view);
4627 if (status == MagickFalse)
4628 shade_image=DestroyImage(shade_image);
4629 return(shade_image);
4630}
4631
4632/*
4633%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4634% %
4635% %
4636% %
4637% S h a r p e n I m a g e %
4638% %
4639% %
4640% %
4641%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4642%
4643% SharpenImage() sharpens the image. We convolve the image with a Gaussian
4644% operator of the given radius and standard deviation (sigma). For
4645% reasonable results, radius should be larger than sigma. Use a radius of 0
4646% and SharpenImage() selects a suitable radius for you.
4647%
4648% Using a separable kernel would be faster, but the negative weights cancel
4649% out on the corners of the kernel producing often undesirable ringing in the
4650% filtered result; this can be avoided by using a 2D gaussian shaped image
4651% sharpening kernel instead.
4652%
4653% The format of the SharpenImage method is:
4654%
4655% Image *SharpenImage(const Image *image,const double radius,
4656% const double sigma,ExceptionInfo *exception)
4657% Image *SharpenImageChannel(const Image *image,const ChannelType channel,
4658% const double radius,const double sigma,ExceptionInfo *exception)
4659%
4660% A description of each parameter follows:
4661%
4662% o image: the image.
4663%
4664% o channel: the channel type.
4665%
4666% o radius: the radius of the Gaussian, in pixels, not counting the center
4667% pixel.
4668%
4669% o sigma: the standard deviation of the Laplacian, in pixels.
4670%
4671% o exception: return any errors or warnings in this structure.
4672%
4673*/
4674
4675MagickExport Image *SharpenImage(const Image *image,const double radius,
4676 const double sigma,ExceptionInfo *exception)
4677{
4678 Image
4679 *sharp_image;
4680
4681 sharp_image=SharpenImageChannel(image,DefaultChannels,radius,sigma,exception);
4682 return(sharp_image);
4683}
4684
4685MagickExport Image *SharpenImageChannel(const Image *image,
4686 const ChannelType channel,const double radius,const double sigma,
4687 ExceptionInfo *exception)
4688{
4689 double
cristy47e00502009-12-17 19:19:57 +00004690 *kernel,
4691 normalize;
cristy3ed852e2009-09-05 21:47:34 +00004692
4693 Image
4694 *sharp_image;
4695
cristy47e00502009-12-17 19:19:57 +00004696 long
4697 j,
cristy3ed852e2009-09-05 21:47:34 +00004698 u,
4699 v;
4700
cristy47e00502009-12-17 19:19:57 +00004701 register long
4702 i;
4703
cristy3ed852e2009-09-05 21:47:34 +00004704 unsigned long
4705 width;
4706
4707 assert(image != (const Image *) NULL);
4708 assert(image->signature == MagickSignature);
4709 if (image->debug != MagickFalse)
4710 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4711 assert(exception != (ExceptionInfo *) NULL);
4712 assert(exception->signature == MagickSignature);
4713 width=GetOptimalKernelWidth2D(radius,sigma);
4714 kernel=(double *) AcquireQuantumMemory((size_t) width*width,sizeof(*kernel));
4715 if (kernel == (double *) NULL)
4716 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy3ed852e2009-09-05 21:47:34 +00004717 normalize=0.0;
cristy47e00502009-12-17 19:19:57 +00004718 j=(long) width/2;
4719 i=0;
4720 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00004721 {
cristy47e00502009-12-17 19:19:57 +00004722 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00004723 {
cristy47e00502009-12-17 19:19:57 +00004724 kernel[i]=(-exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
4725 (2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00004726 normalize+=kernel[i];
4727 i++;
4728 }
4729 }
4730 kernel[i/2]=(double) ((-2.0)*normalize);
4731 sharp_image=ConvolveImageChannel(image,channel,width,kernel,exception);
4732 kernel=(double *) RelinquishMagickMemory(kernel);
4733 return(sharp_image);
4734}
4735
4736/*
4737%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4738% %
4739% %
4740% %
4741% S p r e a d I m a g e %
4742% %
4743% %
4744% %
4745%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4746%
4747% SpreadImage() is a special effects method that randomly displaces each
4748% pixel in a block defined by the radius parameter.
4749%
4750% The format of the SpreadImage method is:
4751%
4752% Image *SpreadImage(const Image *image,const double radius,
4753% ExceptionInfo *exception)
4754%
4755% A description of each parameter follows:
4756%
4757% o image: the image.
4758%
4759% o radius: Choose a random pixel in a neighborhood of this extent.
4760%
4761% o exception: return any errors or warnings in this structure.
4762%
4763*/
4764MagickExport Image *SpreadImage(const Image *image,const double radius,
4765 ExceptionInfo *exception)
4766{
4767#define SpreadImageTag "Spread/Image"
4768
cristyfa112112010-01-04 17:48:07 +00004769 CacheView
4770 *image_view;
4771
cristy3ed852e2009-09-05 21:47:34 +00004772 Image
4773 *spread_image;
4774
4775 long
4776 progress,
4777 y;
4778
4779 MagickBooleanType
4780 status;
4781
4782 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00004783 bias;
cristy3ed852e2009-09-05 21:47:34 +00004784
4785 RandomInfo
cristyfa112112010-01-04 17:48:07 +00004786 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00004787
4788 ResampleFilter
cristyfa112112010-01-04 17:48:07 +00004789 **restrict resample_filter;
cristy3ed852e2009-09-05 21:47:34 +00004790
4791 unsigned long
4792 width;
4793
cristy3ed852e2009-09-05 21:47:34 +00004794 /*
4795 Initialize spread image attributes.
4796 */
4797 assert(image != (Image *) NULL);
4798 assert(image->signature == MagickSignature);
4799 if (image->debug != MagickFalse)
4800 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4801 assert(exception != (ExceptionInfo *) NULL);
4802 assert(exception->signature == MagickSignature);
4803 spread_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4804 exception);
4805 if (spread_image == (Image *) NULL)
4806 return((Image *) NULL);
4807 if (SetImageStorageClass(spread_image,DirectClass) == MagickFalse)
4808 {
4809 InheritException(exception,&spread_image->exception);
4810 spread_image=DestroyImage(spread_image);
4811 return((Image *) NULL);
4812 }
4813 /*
4814 Spread image.
4815 */
4816 status=MagickTrue;
4817 progress=0;
cristyddd82202009-11-03 20:14:50 +00004818 GetMagickPixelPacket(spread_image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00004819 width=GetOptimalKernelWidth1D(radius,0.5);
4820 resample_filter=AcquireResampleFilterThreadSet(image,MagickTrue,exception);
4821 random_info=AcquireRandomInfoThreadSet();
4822 image_view=AcquireCacheView(spread_image);
cristyb5d5f722009-11-04 03:03:49 +00004823#if defined(MAGICKCORE_OPENMP_SUPPORT)
4824 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004825#endif
4826 for (y=0; y < (long) spread_image->rows; y++)
4827 {
4828 MagickPixelPacket
4829 pixel;
4830
4831 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004832 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00004833
4834 register long
4835 id,
4836 x;
4837
4838 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004839 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004840
4841 if (status == MagickFalse)
4842 continue;
4843 q=QueueCacheViewAuthenticPixels(image_view,0,y,spread_image->columns,1,
4844 exception);
4845 if (q == (PixelPacket *) NULL)
4846 {
4847 status=MagickFalse;
4848 continue;
4849 }
4850 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristyddd82202009-11-03 20:14:50 +00004851 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00004852 id=GetOpenMPThreadId();
4853 for (x=0; x < (long) spread_image->columns; x++)
4854 {
4855 (void) ResamplePixelColor(resample_filter[id],(double) x+width*
4856 (GetPseudoRandomValue(random_info[id])-0.5),(double) y+width*
4857 (GetPseudoRandomValue(random_info[id])-0.5),&pixel);
4858 SetPixelPacket(spread_image,&pixel,q,indexes+x);
4859 q++;
4860 }
4861 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
4862 status=MagickFalse;
4863 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4864 {
4865 MagickBooleanType
4866 proceed;
4867
cristyb5d5f722009-11-04 03:03:49 +00004868#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004869 #pragma omp critical (MagickCore_SpreadImage)
4870#endif
4871 proceed=SetImageProgress(image,SpreadImageTag,progress++,image->rows);
4872 if (proceed == MagickFalse)
4873 status=MagickFalse;
4874 }
4875 }
4876 image_view=DestroyCacheView(image_view);
4877 random_info=DestroyRandomInfoThreadSet(random_info);
4878 resample_filter=DestroyResampleFilterThreadSet(resample_filter);
4879 return(spread_image);
4880}
4881
4882/*
4883%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4884% %
4885% %
4886% %
4887% U n s h a r p M a s k I m a g e %
4888% %
4889% %
4890% %
4891%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4892%
4893% UnsharpMaskImage() sharpens one or more image channels. We convolve the
4894% image with a Gaussian operator of the given radius and standard deviation
4895% (sigma). For reasonable results, radius should be larger than sigma. Use a
4896% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
4897%
4898% The format of the UnsharpMaskImage method is:
4899%
4900% Image *UnsharpMaskImage(const Image *image,const double radius,
4901% const double sigma,const double amount,const double threshold,
4902% ExceptionInfo *exception)
4903% Image *UnsharpMaskImageChannel(const Image *image,
4904% const ChannelType channel,const double radius,const double sigma,
4905% const double amount,const double threshold,ExceptionInfo *exception)
4906%
4907% A description of each parameter follows:
4908%
4909% o image: the image.
4910%
4911% o channel: the channel type.
4912%
4913% o radius: the radius of the Gaussian, in pixels, not counting the center
4914% pixel.
4915%
4916% o sigma: the standard deviation of the Gaussian, in pixels.
4917%
4918% o amount: the percentage of the difference between the original and the
4919% blur image that is added back into the original.
4920%
4921% o threshold: the threshold in pixels needed to apply the diffence amount.
4922%
4923% o exception: return any errors or warnings in this structure.
4924%
4925*/
4926
4927MagickExport Image *UnsharpMaskImage(const Image *image,const double radius,
4928 const double sigma,const double amount,const double threshold,
4929 ExceptionInfo *exception)
4930{
4931 Image
4932 *sharp_image;
4933
4934 sharp_image=UnsharpMaskImageChannel(image,DefaultChannels,radius,sigma,amount,
4935 threshold,exception);
4936 return(sharp_image);
4937}
4938
4939MagickExport Image *UnsharpMaskImageChannel(const Image *image,
4940 const ChannelType channel,const double radius,const double sigma,
4941 const double amount,const double threshold,ExceptionInfo *exception)
4942{
4943#define SharpenImageTag "Sharpen/Image"
4944
4945 Image
4946 *unsharp_image;
4947
4948 long
4949 progress,
4950 y;
4951
4952 MagickBooleanType
4953 status;
4954
4955 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00004956 bias;
cristy3ed852e2009-09-05 21:47:34 +00004957
4958 MagickRealType
4959 quantum_threshold;
4960
4961 CacheView
4962 *image_view,
4963 *unsharp_view;
4964
4965 assert(image != (const Image *) NULL);
4966 assert(image->signature == MagickSignature);
4967 if (image->debug != MagickFalse)
4968 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4969 assert(exception != (ExceptionInfo *) NULL);
4970 unsharp_image=BlurImageChannel(image,channel,radius,sigma,exception);
4971 if (unsharp_image == (Image *) NULL)
4972 return((Image *) NULL);
4973 quantum_threshold=(MagickRealType) QuantumRange*threshold;
4974 /*
4975 Unsharp-mask image.
4976 */
4977 status=MagickTrue;
4978 progress=0;
cristyddd82202009-11-03 20:14:50 +00004979 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00004980 image_view=AcquireCacheView(image);
4981 unsharp_view=AcquireCacheView(unsharp_image);
cristyb5d5f722009-11-04 03:03:49 +00004982#if defined(MAGICKCORE_OPENMP_SUPPORT)
4983 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004984#endif
4985 for (y=0; y < (long) image->rows; y++)
4986 {
4987 MagickPixelPacket
4988 pixel;
4989
4990 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004991 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00004992
4993 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004994 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00004995
4996 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004997 *restrict unsharp_indexes;
cristy3ed852e2009-09-05 21:47:34 +00004998
4999 register long
5000 x;
5001
5002 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00005003 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00005004
5005 if (status == MagickFalse)
5006 continue;
5007 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
5008 q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
5009 exception);
5010 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
5011 {
5012 status=MagickFalse;
5013 continue;
5014 }
5015 indexes=GetCacheViewVirtualIndexQueue(image_view);
5016 unsharp_indexes=GetCacheViewAuthenticIndexQueue(unsharp_view);
cristyddd82202009-11-03 20:14:50 +00005017 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00005018 for (x=0; x < (long) image->columns; x++)
5019 {
5020 if ((channel & RedChannel) != 0)
5021 {
5022 pixel.red=p->red-(MagickRealType) q->red;
5023 if (fabs(2.0*pixel.red) < quantum_threshold)
5024 pixel.red=(MagickRealType) p->red;
5025 else
5026 pixel.red=(MagickRealType) p->red+(pixel.red*amount);
5027 q->red=RoundToQuantum(pixel.red);
5028 }
5029 if ((channel & GreenChannel) != 0)
5030 {
5031 pixel.green=p->green-(MagickRealType) q->green;
5032 if (fabs(2.0*pixel.green) < quantum_threshold)
5033 pixel.green=(MagickRealType) p->green;
5034 else
5035 pixel.green=(MagickRealType) p->green+(pixel.green*amount);
5036 q->green=RoundToQuantum(pixel.green);
5037 }
5038 if ((channel & BlueChannel) != 0)
5039 {
5040 pixel.blue=p->blue-(MagickRealType) q->blue;
5041 if (fabs(2.0*pixel.blue) < quantum_threshold)
5042 pixel.blue=(MagickRealType) p->blue;
5043 else
5044 pixel.blue=(MagickRealType) p->blue+(pixel.blue*amount);
5045 q->blue=RoundToQuantum(pixel.blue);
5046 }
5047 if ((channel & OpacityChannel) != 0)
5048 {
5049 pixel.opacity=p->opacity-(MagickRealType) q->opacity;
5050 if (fabs(2.0*pixel.opacity) < quantum_threshold)
5051 pixel.opacity=(MagickRealType) p->opacity;
5052 else
5053 pixel.opacity=p->opacity+(pixel.opacity*amount);
5054 q->opacity=RoundToQuantum(pixel.opacity);
5055 }
5056 if (((channel & IndexChannel) != 0) &&
5057 (image->colorspace == CMYKColorspace))
5058 {
5059 pixel.index=unsharp_indexes[x]-(MagickRealType) indexes[x];
5060 if (fabs(2.0*pixel.index) < quantum_threshold)
5061 pixel.index=(MagickRealType) unsharp_indexes[x];
5062 else
5063 pixel.index=(MagickRealType) unsharp_indexes[x]+(pixel.index*
5064 amount);
5065 unsharp_indexes[x]=RoundToQuantum(pixel.index);
5066 }
5067 p++;
5068 q++;
5069 }
5070 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
5071 status=MagickFalse;
5072 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5073 {
5074 MagickBooleanType
5075 proceed;
5076
cristyb5d5f722009-11-04 03:03:49 +00005077#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00005078 #pragma omp critical (MagickCore_UnsharpMaskImageChannel)
5079#endif
5080 proceed=SetImageProgress(image,SharpenImageTag,progress++,image->rows);
5081 if (proceed == MagickFalse)
5082 status=MagickFalse;
5083 }
5084 }
5085 unsharp_image->type=image->type;
5086 unsharp_view=DestroyCacheView(unsharp_view);
5087 image_view=DestroyCacheView(image_view);
5088 if (status == MagickFalse)
5089 unsharp_image=DestroyImage(unsharp_image);
5090 return(unsharp_image);
5091}