blob: 7956a3ca2a2b20e013023e39e1d6ab35e6d27c07 [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);
cristy8cd5b312010-01-07 01:10:24 +0000881 (void) FormatMagickString(format,MaxTextExtent,"%.15g ",*k++);
cristy3ed852e2009-09-05 21:47:34 +0000882 (void) ConcatenateString(&message,format);
883 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
884 }
885 message=DestroyString(message);
886 }
887 /*
888 Blur rows.
889 */
890 status=MagickTrue;
891 progress=0;
cristyddd82202009-11-03 20:14:50 +0000892 GetMagickPixelPacket(image,&bias);
893 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000894 image_view=AcquireCacheView(image);
895 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000896#if defined(MAGICKCORE_OPENMP_SUPPORT)
897 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000898#endif
899 for (y=0; y < (long) blur_image->rows; y++)
900 {
901 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000902 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000903
904 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000905 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000906
907 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000908 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000909
910 register long
911 x;
912
913 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000914 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000915
916 if (status == MagickFalse)
917 continue;
918 p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y,image->columns+
919 width,1,exception);
920 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
921 exception);
922 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
923 {
924 status=MagickFalse;
925 continue;
926 }
927 indexes=GetCacheViewVirtualIndexQueue(image_view);
928 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
929 for (x=0; x < (long) blur_image->columns; x++)
930 {
931 MagickPixelPacket
932 pixel;
933
934 register const double
cristyc47d1f82009-11-26 01:44:43 +0000935 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000936
937 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000938 *restrict kernel_pixels;
cristy3ed852e2009-09-05 21:47:34 +0000939
940 register long
941 i;
942
cristyddd82202009-11-03 20:14:50 +0000943 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +0000944 k=kernel;
945 kernel_pixels=p;
946 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
947 {
948 for (i=0; i < (long) width; i++)
949 {
950 pixel.red+=(*k)*kernel_pixels->red;
951 pixel.green+=(*k)*kernel_pixels->green;
952 pixel.blue+=(*k)*kernel_pixels->blue;
953 k++;
954 kernel_pixels++;
955 }
956 if ((channel & RedChannel) != 0)
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 {
cristy8cd5b312010-01-07 01:10:24 +00001381 (void) FormatMagickString(format,MaxTextExtent,"%.15g ",*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% %
cristy56a9e512010-01-06 18:18:55 +00002119% F i l t e r I m a g e %
2120% %
2121% %
2122% %
2123%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2124%
2125% FilterImage() applies a custom convolution kernel to the image.
2126%
2127% The format of the FilterImage method is:
2128%
2129% Image *FilterImage(const Image *image,const MagickKernel *kernel,
2130% ExceptionInfo *exception)
2131% Image *FilterImageChannel(const Image *image,const ChannelType channel,
2132% const MagickKernel *kernel,ExceptionInfo *exception)
2133%
2134% A description of each parameter follows:
2135%
2136% o image: the image.
2137%
2138% o channel: the channel type.
2139%
2140% o kernel: the filtering kernel.
2141%
2142% o exception: return any errors or warnings in this structure.
2143%
2144*/
2145
2146MagickExport Image *FilterImage(const Image *image,const MagickKernel *kernel,
2147 ExceptionInfo *exception)
2148{
2149 Image
2150 *filter_image;
2151
2152 filter_image=FilterImageChannel(image,DefaultChannels,kernel,exception);
2153 return(filter_image);
2154}
2155
2156MagickExport Image *FilterImageChannel(const Image *image,
2157 const ChannelType channel,const MagickKernel *kernel,ExceptionInfo *exception)
2158{
2159#define FilterImageTag "Filter/Image"
2160
2161 CacheView
2162 *filter_view,
2163 *image_view;
2164
2165 double
2166 *normal_kernel;
2167
2168 Image
2169 *filter_image;
2170
2171 long
2172 progress,
2173 y;
2174
2175 MagickBooleanType
2176 status;
2177
2178 MagickPixelPacket
2179 bias;
2180
2181 MagickRealType
2182 gamma;
2183
2184 register long
2185 i;
2186
2187 /*
2188 Initialize filter image attributes.
2189 */
2190 assert(image != (Image *) NULL);
2191 assert(image->signature == MagickSignature);
2192 if (image->debug != MagickFalse)
2193 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2194 assert(exception != (ExceptionInfo *) NULL);
2195 assert(exception->signature == MagickSignature);
2196 if ((kernel->width % 2) == 0)
2197 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
2198 filter_image=CloneImage(image,0,0,MagickTrue,exception);
2199 if (filter_image == (Image *) NULL)
2200 return((Image *) NULL);
2201 if (SetImageStorageClass(filter_image,DirectClass) == MagickFalse)
2202 {
2203 InheritException(exception,&filter_image->exception);
2204 filter_image=DestroyImage(filter_image);
2205 return((Image *) NULL);
2206 }
2207 if (image->debug != MagickFalse)
2208 {
2209 char
2210 format[MaxTextExtent],
2211 *message;
2212
2213 long
2214 u,
2215 v;
2216
2217 register const double
2218 *k;
2219
2220 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
2221 " FilterImage with %ldx%ld kernel:",kernel->width,kernel->height);
2222 message=AcquireString("");
2223 k=kernel->values;
2224 for (v=0; v < (long) kernel->height; v++)
2225 {
2226 *message='\0';
2227 (void) FormatMagickString(format,MaxTextExtent,"%ld: ",v);
2228 (void) ConcatenateString(&message,format);
2229 for (u=0; u < (long) kernel->width; u++)
2230 {
cristy8cd5b312010-01-07 01:10:24 +00002231 (void) FormatMagickString(format,MaxTextExtent,"%.15g ",*k++);
cristy56a9e512010-01-06 18:18:55 +00002232 (void) ConcatenateString(&message,format);
2233 }
2234 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
2235 }
2236 message=DestroyString(message);
2237 }
2238 /*
2239 Normalize kernel.
2240 */
2241 normal_kernel=(double *) AcquireQuantumMemory(kernel->width*kernel->height,
2242 sizeof(*normal_kernel));
2243 if (normal_kernel == (double *) NULL)
2244 {
2245 filter_image=DestroyImage(filter_image);
2246 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2247 }
2248 gamma=0.0;
2249 for (i=0; i < (long) (kernel->width*kernel->height); i++)
2250 gamma+=kernel->values[i];
2251 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
2252 for (i=0; i < (long) (kernel->width*kernel->height); i++)
2253 normal_kernel[i]=gamma*kernel->values[i];
2254 /*
2255 Filter image.
2256 */
2257 status=MagickTrue;
2258 progress=0;
2259 GetMagickPixelPacket(image,&bias);
2260 SetMagickPixelPacketBias(image,&bias);
2261 image_view=AcquireCacheView(image);
2262 filter_view=AcquireCacheView(filter_image);
2263#if defined(MAGICKCORE_OPENMP_SUPPORT)
2264 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2265#endif
2266 for (y=0; y < (long) image->rows; y++)
2267 {
2268 MagickBooleanType
2269 sync;
2270
2271 register const IndexPacket
2272 *restrict indexes;
2273
2274 register const PixelPacket
2275 *restrict p;
2276
2277 register IndexPacket
2278 *restrict filter_indexes;
2279
2280 register long
2281 x;
2282
2283 register PixelPacket
2284 *restrict q;
2285
2286 if (status == MagickFalse)
2287 continue;
2288 p=GetCacheViewVirtualPixels(image_view,-((long) kernel->width/2L),
2289 y-(long) (kernel->height/2L),image->columns+kernel->width,kernel->height,
2290 exception);
2291 q=GetCacheViewAuthenticPixels(filter_view,0,y,filter_image->columns,1,
2292 exception);
2293 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2294 {
2295 status=MagickFalse;
2296 continue;
2297 }
2298 indexes=GetCacheViewVirtualIndexQueue(image_view);
2299 filter_indexes=GetCacheViewAuthenticIndexQueue(filter_view);
2300 for (x=0; x < (long) image->columns; x++)
2301 {
2302 long
2303 v;
2304
2305 MagickPixelPacket
2306 pixel;
2307
2308 register const double
2309 *restrict k;
2310
2311 register const PixelPacket
2312 *restrict kernel_pixels;
2313
2314 register long
2315 u;
2316
2317 pixel=bias;
2318 k=normal_kernel;
2319 kernel_pixels=p;
2320 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
2321 {
2322 for (v=0; v < (long) kernel->width; v++)
2323 {
2324 for (u=0; u < (long) kernel->height; u++)
2325 {
2326 pixel.red+=(*k)*kernel_pixels[u].red;
2327 pixel.green+=(*k)*kernel_pixels[u].green;
2328 pixel.blue+=(*k)*kernel_pixels[u].blue;
2329 k++;
2330 }
2331 kernel_pixels+=image->columns+kernel->width;
2332 }
2333 if ((channel & RedChannel) != 0)
2334 q->red=RoundToQuantum(pixel.red);
2335 if ((channel & GreenChannel) != 0)
2336 q->green=RoundToQuantum(pixel.green);
2337 if ((channel & BlueChannel) != 0)
2338 q->blue=RoundToQuantum(pixel.blue);
2339 if ((channel & OpacityChannel) != 0)
2340 {
2341 k=normal_kernel;
2342 kernel_pixels=p;
2343 for (v=0; v < (long) kernel->width; v++)
2344 {
2345 for (u=0; u < (long) kernel->height; u++)
2346 {
2347 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
2348 k++;
2349 }
2350 kernel_pixels+=image->columns+kernel->width;
2351 }
2352 q->opacity=RoundToQuantum(pixel.opacity);
2353 }
2354 if (((channel & IndexChannel) != 0) &&
2355 (image->colorspace == CMYKColorspace))
2356 {
2357 register const IndexPacket
2358 *restrict kernel_indexes;
2359
2360 k=normal_kernel;
2361 kernel_indexes=indexes;
2362 for (v=0; v < (long) kernel->width; v++)
2363 {
2364 for (u=0; u < (long) kernel->height; u++)
2365 {
2366 pixel.index+=(*k)*kernel_indexes[u];
2367 k++;
2368 }
2369 kernel_indexes+=image->columns+kernel->width;
2370 }
2371 filter_indexes[x]=RoundToQuantum(pixel.index);
2372 }
2373 }
2374 else
2375 {
2376 MagickRealType
2377 alpha,
2378 gamma;
2379
2380 gamma=0.0;
2381 for (v=0; v < (long) kernel->width; v++)
2382 {
2383 for (u=0; u < (long) kernel->height; u++)
2384 {
2385 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
2386 kernel_pixels[u].opacity));
2387 pixel.red+=(*k)*alpha*kernel_pixels[u].red;
2388 pixel.green+=(*k)*alpha*kernel_pixels[u].green;
2389 pixel.blue+=(*k)*alpha*kernel_pixels[u].blue;
2390 gamma+=(*k)*alpha;
2391 k++;
2392 }
2393 kernel_pixels+=image->columns+kernel->width;
2394 }
2395 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
2396 if ((channel & RedChannel) != 0)
2397 q->red=RoundToQuantum(gamma*pixel.red);
2398 if ((channel & GreenChannel) != 0)
2399 q->green=RoundToQuantum(gamma*pixel.green);
2400 if ((channel & BlueChannel) != 0)
2401 q->blue=RoundToQuantum(gamma*pixel.blue);
2402 if ((channel & OpacityChannel) != 0)
2403 {
2404 k=normal_kernel;
2405 kernel_pixels=p;
2406 for (v=0; v < (long) kernel->width; v++)
2407 {
2408 for (u=0; u < (long) kernel->height; u++)
2409 {
2410 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
2411 k++;
2412 }
2413 kernel_pixels+=image->columns+kernel->width;
2414 }
2415 q->opacity=RoundToQuantum(pixel.opacity);
2416 }
2417 if (((channel & IndexChannel) != 0) &&
2418 (image->colorspace == CMYKColorspace))
2419 {
2420 register const IndexPacket
2421 *restrict kernel_indexes;
2422
2423 k=normal_kernel;
2424 kernel_pixels=p;
2425 kernel_indexes=indexes;
2426 for (v=0; v < (long) kernel->width; v++)
2427 {
2428 for (u=0; u < (long) kernel->height; u++)
2429 {
2430 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
2431 kernel_pixels[u].opacity));
2432 pixel.index+=(*k)*alpha*kernel_indexes[u];
2433 k++;
2434 }
2435 kernel_pixels+=image->columns+kernel->width;
2436 kernel_indexes+=image->columns+kernel->width;
2437 }
2438 filter_indexes[x]=RoundToQuantum(gamma*pixel.index);
2439 }
2440 }
2441 p++;
2442 q++;
2443 }
2444 sync=SyncCacheViewAuthenticPixels(filter_view,exception);
2445 if (sync == MagickFalse)
2446 status=MagickFalse;
2447 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2448 {
2449 MagickBooleanType
2450 proceed;
2451
2452#if defined(MAGICKCORE_OPENMP_SUPPORT)
2453 #pragma omp critical (MagickCore_FilterImageChannel)
2454#endif
2455 proceed=SetImageProgress(image,FilterImageTag,progress++,image->rows);
2456 if (proceed == MagickFalse)
2457 status=MagickFalse;
2458 }
2459 }
2460 filter_image->type=image->type;
2461 filter_view=DestroyCacheView(filter_view);
2462 image_view=DestroyCacheView(image_view);
2463 normal_kernel=(double *) RelinquishMagickMemory(normal_kernel);
2464 if (status == MagickFalse)
2465 filter_image=DestroyImage(filter_image);
2466 return(filter_image);
2467}
2468
2469/*
2470%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2471% %
2472% %
2473% %
cristy3ed852e2009-09-05 21:47:34 +00002474% G a u s s i a n B l u r I m a g e %
2475% %
2476% %
2477% %
2478%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2479%
2480% GaussianBlurImage() blurs an image. We convolve the image with a
2481% Gaussian operator of the given radius and standard deviation (sigma).
2482% For reasonable results, the radius should be larger than sigma. Use a
2483% radius of 0 and GaussianBlurImage() selects a suitable radius for you
2484%
2485% The format of the GaussianBlurImage method is:
2486%
2487% Image *GaussianBlurImage(const Image *image,onst double radius,
2488% const double sigma,ExceptionInfo *exception)
2489% Image *GaussianBlurImageChannel(const Image *image,
2490% const ChannelType channel,const double radius,const double sigma,
2491% ExceptionInfo *exception)
2492%
2493% A description of each parameter follows:
2494%
2495% o image: the image.
2496%
2497% o channel: the channel type.
2498%
2499% o radius: the radius of the Gaussian, in pixels, not counting the center
2500% pixel.
2501%
2502% o sigma: the standard deviation of the Gaussian, in pixels.
2503%
2504% o exception: return any errors or warnings in this structure.
2505%
2506*/
2507
2508MagickExport Image *GaussianBlurImage(const Image *image,const double radius,
2509 const double sigma,ExceptionInfo *exception)
2510{
2511 Image
2512 *blur_image;
2513
2514 blur_image=GaussianBlurImageChannel(image,DefaultChannels,radius,sigma,
2515 exception);
2516 return(blur_image);
2517}
2518
2519MagickExport Image *GaussianBlurImageChannel(const Image *image,
2520 const ChannelType channel,const double radius,const double sigma,
2521 ExceptionInfo *exception)
2522{
2523 double
2524 *kernel;
2525
2526 Image
2527 *blur_image;
2528
cristy47e00502009-12-17 19:19:57 +00002529 long
2530 j,
cristy3ed852e2009-09-05 21:47:34 +00002531 u,
2532 v;
2533
cristy47e00502009-12-17 19:19:57 +00002534 register long
2535 i;
2536
cristy3ed852e2009-09-05 21:47:34 +00002537 unsigned long
2538 width;
2539
2540 assert(image != (const Image *) NULL);
2541 assert(image->signature == MagickSignature);
2542 if (image->debug != MagickFalse)
2543 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2544 assert(exception != (ExceptionInfo *) NULL);
2545 assert(exception->signature == MagickSignature);
2546 width=GetOptimalKernelWidth2D(radius,sigma);
2547 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2548 if (kernel == (double *) NULL)
2549 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy47e00502009-12-17 19:19:57 +00002550 j=(long) width/2;
cristy3ed852e2009-09-05 21:47:34 +00002551 i=0;
cristy47e00502009-12-17 19:19:57 +00002552 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002553 {
cristy47e00502009-12-17 19:19:57 +00002554 for (u=(-j); u <= j; u++)
2555 kernel[i++]=exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
2556 (2.0*MagickPI*MagickSigma*MagickSigma);
cristy3ed852e2009-09-05 21:47:34 +00002557 }
2558 blur_image=ConvolveImageChannel(image,channel,width,kernel,exception);
2559 kernel=(double *) RelinquishMagickMemory(kernel);
2560 return(blur_image);
2561}
2562
2563/*
2564%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2565% %
2566% %
2567% %
2568% M e d i a n F i l t e r I m a g e %
2569% %
2570% %
2571% %
2572%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2573%
2574% MedianFilterImage() applies a digital filter that improves the quality
2575% of a noisy image. Each pixel is replaced by the median in a set of
2576% neighboring pixels as defined by radius.
2577%
2578% The algorithm was contributed by Mike Edmonds and implements an insertion
2579% sort for selecting median color-channel values. For more on this algorithm
2580% see "Skip Lists: A probabilistic Alternative to Balanced Trees" by William
2581% Pugh in the June 1990 of Communications of the ACM.
2582%
2583% The format of the MedianFilterImage method is:
2584%
2585% Image *MedianFilterImage(const Image *image,const double radius,
2586% ExceptionInfo *exception)
2587%
2588% A description of each parameter follows:
2589%
2590% o image: the image.
2591%
2592% o radius: the radius of the pixel neighborhood.
2593%
2594% o exception: return any errors or warnings in this structure.
2595%
2596*/
2597
2598#define MedianListChannels 5
2599
2600typedef struct _MedianListNode
2601{
2602 unsigned long
2603 next[9],
2604 count,
2605 signature;
2606} MedianListNode;
2607
2608typedef struct _MedianSkipList
2609{
2610 long
2611 level;
2612
2613 MedianListNode
2614 *nodes;
2615} MedianSkipList;
2616
2617typedef struct _MedianPixelList
2618{
2619 unsigned long
2620 center,
2621 seed,
2622 signature;
2623
2624 MedianSkipList
2625 lists[MedianListChannels];
2626} MedianPixelList;
2627
2628static MedianPixelList *DestroyMedianPixelList(MedianPixelList *pixel_list)
2629{
2630 register long
2631 i;
2632
2633 if (pixel_list == (MedianPixelList *) NULL)
2634 return((MedianPixelList *) NULL);
2635 for (i=0; i < MedianListChannels; i++)
2636 if (pixel_list->lists[i].nodes != (MedianListNode *) NULL)
2637 pixel_list->lists[i].nodes=(MedianListNode *) RelinquishMagickMemory(
2638 pixel_list->lists[i].nodes);
2639 pixel_list=(MedianPixelList *) RelinquishAlignedMemory(pixel_list);
2640 return(pixel_list);
2641}
2642
2643static MedianPixelList **DestroyMedianPixelListThreadSet(
2644 MedianPixelList **pixel_list)
2645{
2646 register long
2647 i;
2648
2649 assert(pixel_list != (MedianPixelList **) NULL);
2650 for (i=0; i < (long) GetOpenMPMaximumThreads(); i++)
2651 if (pixel_list[i] != (MedianPixelList *) NULL)
2652 pixel_list[i]=DestroyMedianPixelList(pixel_list[i]);
2653 pixel_list=(MedianPixelList **) RelinquishAlignedMemory(pixel_list);
2654 return(pixel_list);
2655}
2656
2657static MedianPixelList *AcquireMedianPixelList(const unsigned long width)
2658{
2659 MedianPixelList
2660 *pixel_list;
2661
2662 register long
2663 i;
2664
2665 pixel_list=(MedianPixelList *) AcquireAlignedMemory(1,sizeof(*pixel_list));
2666 if (pixel_list == (MedianPixelList *) NULL)
2667 return(pixel_list);
2668 (void) ResetMagickMemory((void *) pixel_list,0,sizeof(*pixel_list));
2669 pixel_list->center=width*width/2;
2670 for (i=0; i < MedianListChannels; i++)
2671 {
2672 pixel_list->lists[i].nodes=(MedianListNode *) AcquireQuantumMemory(65537UL,
2673 sizeof(*pixel_list->lists[i].nodes));
2674 if (pixel_list->lists[i].nodes == (MedianListNode *) NULL)
2675 return(DestroyMedianPixelList(pixel_list));
2676 (void) ResetMagickMemory(pixel_list->lists[i].nodes,0,65537UL*
2677 sizeof(*pixel_list->lists[i].nodes));
2678 }
2679 pixel_list->signature=MagickSignature;
2680 return(pixel_list);
2681}
2682
2683static MedianPixelList **AcquireMedianPixelListThreadSet(
2684 const unsigned long width)
2685{
2686 register long
2687 i;
2688
2689 MedianPixelList
2690 **pixel_list;
2691
2692 unsigned long
2693 number_threads;
2694
2695 number_threads=GetOpenMPMaximumThreads();
2696 pixel_list=(MedianPixelList **) AcquireAlignedMemory(number_threads,
2697 sizeof(*pixel_list));
2698 if (pixel_list == (MedianPixelList **) NULL)
2699 return((MedianPixelList **) NULL);
2700 (void) ResetMagickMemory(pixel_list,0,number_threads*sizeof(*pixel_list));
2701 for (i=0; i < (long) number_threads; i++)
2702 {
2703 pixel_list[i]=AcquireMedianPixelList(width);
2704 if (pixel_list[i] == (MedianPixelList *) NULL)
2705 return(DestroyMedianPixelListThreadSet(pixel_list));
2706 }
2707 return(pixel_list);
2708}
2709
2710static void AddNodeMedianPixelList(MedianPixelList *pixel_list,
2711 const long channel,const unsigned long color)
2712{
2713 register long
2714 level;
2715
2716 register MedianSkipList
2717 *list;
2718
2719 unsigned long
2720 search,
2721 update[9];
2722
2723 /*
2724 Initialize the node.
2725 */
2726 list=pixel_list->lists+channel;
2727 list->nodes[color].signature=pixel_list->signature;
2728 list->nodes[color].count=1;
2729 /*
2730 Determine where it belongs in the list.
2731 */
2732 search=65536UL;
2733 for (level=list->level; level >= 0; level--)
2734 {
2735 while (list->nodes[search].next[level] < color)
2736 search=list->nodes[search].next[level];
2737 update[level]=search;
2738 }
2739 /*
2740 Generate a pseudo-random level for this node.
2741 */
2742 for (level=0; ; level++)
2743 {
2744 pixel_list->seed=(pixel_list->seed*42893621L)+1L;
2745 if ((pixel_list->seed & 0x300) != 0x300)
2746 break;
2747 }
2748 if (level > 8)
2749 level=8;
2750 if (level > (list->level+2))
2751 level=list->level+2;
2752 /*
2753 If we're raising the list's level, link back to the root node.
2754 */
2755 while (level > list->level)
2756 {
2757 list->level++;
2758 update[list->level]=65536UL;
2759 }
2760 /*
2761 Link the node into the skip-list.
2762 */
2763 do
2764 {
2765 list->nodes[color].next[level]=list->nodes[update[level]].next[level];
2766 list->nodes[update[level]].next[level]=color;
2767 }
2768 while (level-- > 0);
2769}
2770
2771static MagickPixelPacket GetMedianPixelList(MedianPixelList *pixel_list)
2772{
2773 MagickPixelPacket
2774 pixel;
2775
2776 register long
2777 channel;
2778
2779 register MedianSkipList
2780 *list;
2781
2782 unsigned long
2783 center,
2784 color,
2785 count;
2786
2787 unsigned short
2788 channels[MedianListChannels];
2789
2790 /*
2791 Find the median value for each of the color.
2792 */
2793 center=pixel_list->center;
2794 for (channel=0; channel < 5; channel++)
2795 {
2796 list=pixel_list->lists+channel;
2797 color=65536UL;
2798 count=0;
2799 do
2800 {
2801 color=list->nodes[color].next[0];
2802 count+=list->nodes[color].count;
2803 }
2804 while (count <= center);
2805 channels[channel]=(unsigned short) color;
2806 }
2807 GetMagickPixelPacket((const Image *) NULL,&pixel);
2808 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
2809 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
2810 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
2811 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
2812 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
2813 return(pixel);
2814}
2815
2816static inline void InsertMedianPixelList(const Image *image,
2817 const PixelPacket *pixel,const IndexPacket *indexes,
2818 MedianPixelList *pixel_list)
2819{
2820 unsigned long
2821 signature;
2822
2823 unsigned short
2824 index;
2825
2826 index=ScaleQuantumToShort(pixel->red);
2827 signature=pixel_list->lists[0].nodes[index].signature;
2828 if (signature == pixel_list->signature)
2829 pixel_list->lists[0].nodes[index].count++;
2830 else
2831 AddNodeMedianPixelList(pixel_list,0,index);
2832 index=ScaleQuantumToShort(pixel->green);
2833 signature=pixel_list->lists[1].nodes[index].signature;
2834 if (signature == pixel_list->signature)
2835 pixel_list->lists[1].nodes[index].count++;
2836 else
2837 AddNodeMedianPixelList(pixel_list,1,index);
2838 index=ScaleQuantumToShort(pixel->blue);
2839 signature=pixel_list->lists[2].nodes[index].signature;
2840 if (signature == pixel_list->signature)
2841 pixel_list->lists[2].nodes[index].count++;
2842 else
2843 AddNodeMedianPixelList(pixel_list,2,index);
2844 index=ScaleQuantumToShort(pixel->opacity);
2845 signature=pixel_list->lists[3].nodes[index].signature;
2846 if (signature == pixel_list->signature)
2847 pixel_list->lists[3].nodes[index].count++;
2848 else
2849 AddNodeMedianPixelList(pixel_list,3,index);
2850 if (image->colorspace == CMYKColorspace)
2851 index=ScaleQuantumToShort(*indexes);
2852 signature=pixel_list->lists[4].nodes[index].signature;
2853 if (signature == pixel_list->signature)
2854 pixel_list->lists[4].nodes[index].count++;
2855 else
2856 AddNodeMedianPixelList(pixel_list,4,index);
2857}
2858
2859static void ResetMedianPixelList(MedianPixelList *pixel_list)
2860{
2861 int
2862 level;
2863
2864 register long
2865 channel;
2866
2867 register MedianListNode
2868 *root;
2869
2870 register MedianSkipList
2871 *list;
2872
2873 /*
2874 Reset the skip-list.
2875 */
2876 for (channel=0; channel < 5; channel++)
2877 {
2878 list=pixel_list->lists+channel;
2879 root=list->nodes+65536UL;
2880 list->level=0;
2881 for (level=0; level < 9; level++)
2882 root->next[level]=65536UL;
2883 }
2884 pixel_list->seed=pixel_list->signature++;
2885}
2886
2887MagickExport Image *MedianFilterImage(const Image *image,const double radius,
2888 ExceptionInfo *exception)
2889{
2890#define MedianFilterImageTag "MedianFilter/Image"
2891
2892 Image
2893 *median_image;
2894
2895 long
2896 progress,
2897 y;
2898
2899 MagickBooleanType
2900 status;
2901
2902 MedianPixelList
cristyfa112112010-01-04 17:48:07 +00002903 **restrict pixel_list;
cristy3ed852e2009-09-05 21:47:34 +00002904
2905 unsigned long
2906 width;
2907
2908 CacheView
2909 *image_view,
2910 *median_view;
2911
2912 /*
2913 Initialize median image attributes.
2914 */
2915 assert(image != (Image *) NULL);
2916 assert(image->signature == MagickSignature);
2917 if (image->debug != MagickFalse)
2918 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2919 assert(exception != (ExceptionInfo *) NULL);
2920 assert(exception->signature == MagickSignature);
2921 width=GetOptimalKernelWidth2D(radius,0.5);
2922 if ((image->columns < width) || (image->rows < width))
2923 ThrowImageException(OptionError,"ImageSmallerThanKernelRadius");
2924 median_image=CloneImage(image,image->columns,image->rows,MagickTrue,
2925 exception);
2926 if (median_image == (Image *) NULL)
2927 return((Image *) NULL);
2928 if (SetImageStorageClass(median_image,DirectClass) == MagickFalse)
2929 {
2930 InheritException(exception,&median_image->exception);
2931 median_image=DestroyImage(median_image);
2932 return((Image *) NULL);
2933 }
2934 pixel_list=AcquireMedianPixelListThreadSet(width);
2935 if (pixel_list == (MedianPixelList **) NULL)
2936 {
2937 median_image=DestroyImage(median_image);
2938 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2939 }
2940 /*
2941 Median filter each image row.
2942 */
2943 status=MagickTrue;
2944 progress=0;
2945 image_view=AcquireCacheView(image);
2946 median_view=AcquireCacheView(median_image);
cristyb5d5f722009-11-04 03:03:49 +00002947#if defined(MAGICKCORE_OPENMP_SUPPORT)
2948 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002949#endif
2950 for (y=0; y < (long) median_image->rows; y++)
2951 {
2952 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002953 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002954
2955 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002956 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002957
2958 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002959 *restrict median_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002960
2961 register long
2962 id,
2963 x;
2964
2965 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002966 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002967
2968 if (status == MagickFalse)
2969 continue;
2970 p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y-(long) (width/
2971 2L),image->columns+width,width,exception);
2972 q=QueueCacheViewAuthenticPixels(median_view,0,y,median_image->columns,1,
2973 exception);
2974 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2975 {
2976 status=MagickFalse;
2977 continue;
2978 }
2979 indexes=GetCacheViewVirtualIndexQueue(image_view);
2980 median_indexes=GetCacheViewAuthenticIndexQueue(median_view);
2981 id=GetOpenMPThreadId();
2982 for (x=0; x < (long) median_image->columns; x++)
2983 {
2984 MagickPixelPacket
2985 pixel;
2986
2987 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002988 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +00002989
2990 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002991 *restrict s;
cristy3ed852e2009-09-05 21:47:34 +00002992
2993 register long
2994 u,
2995 v;
2996
2997 r=p;
2998 s=indexes+x;
2999 ResetMedianPixelList(pixel_list[id]);
3000 for (v=0; v < (long) width; v++)
3001 {
3002 for (u=0; u < (long) width; u++)
3003 InsertMedianPixelList(image,r+u,s+u,pixel_list[id]);
3004 r+=image->columns+width;
3005 s+=image->columns+width;
3006 }
3007 pixel=GetMedianPixelList(pixel_list[id]);
3008 SetPixelPacket(median_image,&pixel,q,median_indexes+x);
3009 p++;
3010 q++;
3011 }
3012 if (SyncCacheViewAuthenticPixels(median_view,exception) == MagickFalse)
3013 status=MagickFalse;
3014 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3015 {
3016 MagickBooleanType
3017 proceed;
3018
cristyb5d5f722009-11-04 03:03:49 +00003019#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003020 #pragma omp critical (MagickCore_MedianFilterImage)
3021#endif
3022 proceed=SetImageProgress(image,MedianFilterImageTag,progress++,
3023 image->rows);
3024 if (proceed == MagickFalse)
3025 status=MagickFalse;
3026 }
3027 }
3028 median_view=DestroyCacheView(median_view);
3029 image_view=DestroyCacheView(image_view);
3030 pixel_list=DestroyMedianPixelListThreadSet(pixel_list);
3031 return(median_image);
3032}
3033
3034/*
3035%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3036% %
3037% %
3038% %
3039% M o t i o n B l u r I m a g e %
3040% %
3041% %
3042% %
3043%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3044%
3045% MotionBlurImage() simulates motion blur. We convolve the image with a
3046% Gaussian operator of the given radius and standard deviation (sigma).
3047% For reasonable results, radius should be larger than sigma. Use a
3048% radius of 0 and MotionBlurImage() selects a suitable radius for you.
3049% Angle gives the angle of the blurring motion.
3050%
3051% Andrew Protano contributed this effect.
3052%
3053% The format of the MotionBlurImage method is:
3054%
3055% Image *MotionBlurImage(const Image *image,const double radius,
3056% const double sigma,const double angle,ExceptionInfo *exception)
3057% Image *MotionBlurImageChannel(const Image *image,const ChannelType channel,
3058% const double radius,const double sigma,const double angle,
3059% ExceptionInfo *exception)
3060%
3061% A description of each parameter follows:
3062%
3063% o image: the image.
3064%
3065% o channel: the channel type.
3066%
3067% o radius: the radius of the Gaussian, in pixels, not counting the center
3068% o radius: the radius of the Gaussian, in pixels, not counting
3069% the center pixel.
3070%
3071% o sigma: the standard deviation of the Gaussian, in pixels.
3072%
3073% o angle: Apply the effect along this angle.
3074%
3075% o exception: return any errors or warnings in this structure.
3076%
3077*/
3078
cristy47e00502009-12-17 19:19:57 +00003079static double *GetMotionBlurKernel(const unsigned long width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +00003080{
cristy3ed852e2009-09-05 21:47:34 +00003081 double
cristy47e00502009-12-17 19:19:57 +00003082 *kernel,
cristy3ed852e2009-09-05 21:47:34 +00003083 normalize;
3084
3085 register long
3086 i;
3087
3088 /*
cristy47e00502009-12-17 19:19:57 +00003089 Generate a 1-D convolution kernel.
cristy3ed852e2009-09-05 21:47:34 +00003090 */
3091 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
3092 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
3093 if (kernel == (double *) NULL)
3094 return(kernel);
cristy3ed852e2009-09-05 21:47:34 +00003095 normalize=0.0;
3096 for (i=0; i < (long) width; i++)
cristy47e00502009-12-17 19:19:57 +00003097 {
3098 kernel[i]=exp((-((double) i*i)/(double) (2.0*MagickSigma*MagickSigma)))/
3099 (MagickSQ2PI*MagickSigma);
cristy3ed852e2009-09-05 21:47:34 +00003100 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +00003101 }
cristy3ed852e2009-09-05 21:47:34 +00003102 for (i=0; i < (long) width; i++)
3103 kernel[i]/=normalize;
3104 return(kernel);
3105}
3106
3107MagickExport Image *MotionBlurImage(const Image *image,const double radius,
3108 const double sigma,const double angle,ExceptionInfo *exception)
3109{
3110 Image
3111 *motion_blur;
3112
3113 motion_blur=MotionBlurImageChannel(image,DefaultChannels,radius,sigma,angle,
3114 exception);
3115 return(motion_blur);
3116}
3117
3118MagickExport Image *MotionBlurImageChannel(const Image *image,
3119 const ChannelType channel,const double radius,const double sigma,
3120 const double angle,ExceptionInfo *exception)
3121{
3122 typedef struct _OffsetInfo
3123 {
3124 long
3125 x,
3126 y;
3127 } OffsetInfo;
3128
3129 double
3130 *kernel;
3131
3132 Image
3133 *blur_image;
3134
3135 long
3136 progress,
3137 y;
3138
3139 MagickBooleanType
3140 status;
3141
3142 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00003143 bias;
cristy3ed852e2009-09-05 21:47:34 +00003144
3145 OffsetInfo
3146 *offset;
3147
3148 PointInfo
3149 point;
3150
3151 register long
3152 i;
3153
3154 unsigned long
3155 width;
3156
3157 CacheView
3158 *blur_view,
3159 *image_view;
3160
3161 assert(image != (Image *) NULL);
3162 assert(image->signature == MagickSignature);
3163 if (image->debug != MagickFalse)
3164 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3165 assert(exception != (ExceptionInfo *) NULL);
3166 width=GetOptimalKernelWidth1D(radius,sigma);
3167 kernel=GetMotionBlurKernel(width,sigma);
3168 if (kernel == (double *) NULL)
3169 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3170 offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset));
3171 if (offset == (OffsetInfo *) NULL)
3172 {
3173 kernel=(double *) RelinquishMagickMemory(kernel);
3174 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3175 }
3176 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3177 if (blur_image == (Image *) NULL)
3178 {
3179 kernel=(double *) RelinquishMagickMemory(kernel);
3180 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
3181 return((Image *) NULL);
3182 }
3183 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3184 {
3185 kernel=(double *) RelinquishMagickMemory(kernel);
3186 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
3187 InheritException(exception,&blur_image->exception);
3188 blur_image=DestroyImage(blur_image);
3189 return((Image *) NULL);
3190 }
3191 point.x=(double) width*sin(DegreesToRadians(angle));
3192 point.y=(double) width*cos(DegreesToRadians(angle));
3193 for (i=0; i < (long) width; i++)
3194 {
3195 offset[i].x=(long) ((i*point.y)/hypot(point.x,point.y)+0.5);
3196 offset[i].y=(long) ((i*point.x)/hypot(point.x,point.y)+0.5);
3197 }
3198 /*
3199 Motion blur image.
3200 */
3201 status=MagickTrue;
3202 progress=0;
cristyddd82202009-11-03 20:14:50 +00003203 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003204 image_view=AcquireCacheView(image);
3205 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003206#if defined(MAGICKCORE_OPENMP_SUPPORT)
3207 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003208#endif
3209 for (y=0; y < (long) image->rows; y++)
3210 {
3211 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003212 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00003213
3214 register long
3215 x;
3216
3217 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003218 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003219
3220 if (status == MagickFalse)
3221 continue;
3222 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3223 exception);
3224 if (q == (PixelPacket *) NULL)
3225 {
3226 status=MagickFalse;
3227 continue;
3228 }
3229 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
3230 for (x=0; x < (long) image->columns; x++)
3231 {
3232 MagickPixelPacket
3233 qixel;
3234
3235 PixelPacket
3236 pixel;
3237
3238 register double
cristyc47d1f82009-11-26 01:44:43 +00003239 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00003240
3241 register long
3242 i;
3243
3244 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003245 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003246
3247 k=kernel;
cristyddd82202009-11-03 20:14:50 +00003248 qixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00003249 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
3250 {
3251 for (i=0; i < (long) width; i++)
3252 {
3253 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
3254 offset[i].y,&pixel,exception);
3255 qixel.red+=(*k)*pixel.red;
3256 qixel.green+=(*k)*pixel.green;
3257 qixel.blue+=(*k)*pixel.blue;
3258 qixel.opacity+=(*k)*pixel.opacity;
3259 if (image->colorspace == CMYKColorspace)
3260 {
3261 indexes=GetCacheViewVirtualIndexQueue(image_view);
3262 qixel.index+=(*k)*(*indexes);
3263 }
3264 k++;
3265 }
3266 if ((channel & RedChannel) != 0)
3267 q->red=RoundToQuantum(qixel.red);
3268 if ((channel & GreenChannel) != 0)
3269 q->green=RoundToQuantum(qixel.green);
3270 if ((channel & BlueChannel) != 0)
3271 q->blue=RoundToQuantum(qixel.blue);
3272 if ((channel & OpacityChannel) != 0)
3273 q->opacity=RoundToQuantum(qixel.opacity);
3274 if (((channel & IndexChannel) != 0) &&
3275 (image->colorspace == CMYKColorspace))
3276 blur_indexes[x]=(IndexPacket) RoundToQuantum(qixel.index);
3277 }
3278 else
3279 {
3280 MagickRealType
3281 alpha,
3282 gamma;
3283
3284 alpha=0.0;
3285 gamma=0.0;
3286 for (i=0; i < (long) width; i++)
3287 {
3288 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
3289 offset[i].y,&pixel,exception);
3290 alpha=(MagickRealType) (QuantumScale*(QuantumRange-pixel.opacity));
3291 qixel.red+=(*k)*alpha*pixel.red;
3292 qixel.green+=(*k)*alpha*pixel.green;
3293 qixel.blue+=(*k)*alpha*pixel.blue;
3294 qixel.opacity+=(*k)*pixel.opacity;
3295 if (image->colorspace == CMYKColorspace)
3296 {
3297 indexes=GetCacheViewVirtualIndexQueue(image_view);
3298 qixel.index+=(*k)*alpha*(*indexes);
3299 }
3300 gamma+=(*k)*alpha;
3301 k++;
3302 }
3303 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3304 if ((channel & RedChannel) != 0)
3305 q->red=RoundToQuantum(gamma*qixel.red);
3306 if ((channel & GreenChannel) != 0)
3307 q->green=RoundToQuantum(gamma*qixel.green);
3308 if ((channel & BlueChannel) != 0)
3309 q->blue=RoundToQuantum(gamma*qixel.blue);
3310 if ((channel & OpacityChannel) != 0)
3311 q->opacity=RoundToQuantum(qixel.opacity);
3312 if (((channel & IndexChannel) != 0) &&
3313 (image->colorspace == CMYKColorspace))
3314 blur_indexes[x]=(IndexPacket) RoundToQuantum(gamma*qixel.index);
3315 }
3316 q++;
3317 }
3318 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
3319 status=MagickFalse;
3320 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3321 {
3322 MagickBooleanType
3323 proceed;
3324
cristyb5d5f722009-11-04 03:03:49 +00003325#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003326 #pragma omp critical (MagickCore_MotionBlurImageChannel)
3327#endif
3328 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
3329 if (proceed == MagickFalse)
3330 status=MagickFalse;
3331 }
3332 }
3333 blur_view=DestroyCacheView(blur_view);
3334 image_view=DestroyCacheView(image_view);
3335 kernel=(double *) RelinquishMagickMemory(kernel);
3336 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
3337 if (status == MagickFalse)
3338 blur_image=DestroyImage(blur_image);
3339 return(blur_image);
3340}
3341
3342/*
3343%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3344% %
3345% %
3346% %
3347% P r e v i e w I m a g e %
3348% %
3349% %
3350% %
3351%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3352%
3353% PreviewImage() tiles 9 thumbnails of the specified image with an image
3354% processing operation applied with varying parameters. This may be helpful
3355% pin-pointing an appropriate parameter for a particular image processing
3356% operation.
3357%
3358% The format of the PreviewImages method is:
3359%
3360% Image *PreviewImages(const Image *image,const PreviewType preview,
3361% ExceptionInfo *exception)
3362%
3363% A description of each parameter follows:
3364%
3365% o image: the image.
3366%
3367% o preview: the image processing operation.
3368%
3369% o exception: return any errors or warnings in this structure.
3370%
3371*/
3372MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
3373 ExceptionInfo *exception)
3374{
3375#define NumberTiles 9
3376#define PreviewImageTag "Preview/Image"
3377#define DefaultPreviewGeometry "204x204+10+10"
3378
3379 char
3380 factor[MaxTextExtent],
3381 label[MaxTextExtent];
3382
3383 double
3384 degrees,
3385 gamma,
3386 percentage,
3387 radius,
3388 sigma,
3389 threshold;
3390
3391 Image
3392 *images,
3393 *montage_image,
3394 *preview_image,
3395 *thumbnail;
3396
3397 ImageInfo
3398 *preview_info;
3399
3400 long
3401 y;
3402
3403 MagickBooleanType
3404 proceed;
3405
3406 MontageInfo
3407 *montage_info;
3408
3409 QuantizeInfo
3410 quantize_info;
3411
3412 RectangleInfo
3413 geometry;
3414
3415 register long
3416 i,
3417 x;
3418
3419 unsigned long
3420 colors;
3421
3422 /*
3423 Open output image file.
3424 */
3425 assert(image != (Image *) NULL);
3426 assert(image->signature == MagickSignature);
3427 if (image->debug != MagickFalse)
3428 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3429 colors=2;
3430 degrees=0.0;
3431 gamma=(-0.2f);
3432 preview_info=AcquireImageInfo();
3433 SetGeometry(image,&geometry);
3434 (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
3435 &geometry.width,&geometry.height);
3436 images=NewImageList();
3437 percentage=12.5;
3438 GetQuantizeInfo(&quantize_info);
3439 radius=0.0;
3440 sigma=1.0;
3441 threshold=0.0;
3442 x=0;
3443 y=0;
3444 for (i=0; i < NumberTiles; i++)
3445 {
3446 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
3447 if (thumbnail == (Image *) NULL)
3448 break;
3449 (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
3450 (void *) NULL);
3451 (void) SetImageProperty(thumbnail,"label",DefaultTileLabel);
3452 if (i == (NumberTiles/2))
3453 {
3454 (void) QueryColorDatabase("#dfdfdf",&thumbnail->matte_color,exception);
3455 AppendImageToList(&images,thumbnail);
3456 continue;
3457 }
3458 switch (preview)
3459 {
3460 case RotatePreview:
3461 {
3462 degrees+=45.0;
3463 preview_image=RotateImage(thumbnail,degrees,exception);
cristy8cd5b312010-01-07 01:10:24 +00003464 (void) FormatMagickString(label,MaxTextExtent,"rotate %.15g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003465 break;
3466 }
3467 case ShearPreview:
3468 {
3469 degrees+=5.0;
3470 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
cristy8cd5b312010-01-07 01:10:24 +00003471 (void) FormatMagickString(label,MaxTextExtent,"shear %.15gx%.15g",
cristy3ed852e2009-09-05 21:47:34 +00003472 degrees,2.0*degrees);
3473 break;
3474 }
3475 case RollPreview:
3476 {
3477 x=(long) ((i+1)*thumbnail->columns)/NumberTiles;
3478 y=(long) ((i+1)*thumbnail->rows)/NumberTiles;
3479 preview_image=RollImage(thumbnail,x,y,exception);
3480 (void) FormatMagickString(label,MaxTextExtent,"roll %ldx%ld",x,y);
3481 break;
3482 }
3483 case HuePreview:
3484 {
3485 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3486 if (preview_image == (Image *) NULL)
3487 break;
cristy8cd5b312010-01-07 01:10:24 +00003488 (void) FormatMagickString(factor,MaxTextExtent,"100,100,%.15g",
cristy3ed852e2009-09-05 21:47:34 +00003489 2.0*percentage);
3490 (void) ModulateImage(preview_image,factor);
3491 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
3492 break;
3493 }
3494 case SaturationPreview:
3495 {
3496 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3497 if (preview_image == (Image *) NULL)
3498 break;
cristy8cd5b312010-01-07 01:10:24 +00003499 (void) FormatMagickString(factor,MaxTextExtent,"100,%.15g",
3500 2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00003501 (void) ModulateImage(preview_image,factor);
3502 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
3503 break;
3504 }
3505 case BrightnessPreview:
3506 {
3507 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3508 if (preview_image == (Image *) NULL)
3509 break;
cristy8cd5b312010-01-07 01:10:24 +00003510 (void) FormatMagickString(factor,MaxTextExtent,"%.15g",2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00003511 (void) ModulateImage(preview_image,factor);
3512 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
3513 break;
3514 }
3515 case GammaPreview:
3516 default:
3517 {
3518 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3519 if (preview_image == (Image *) NULL)
3520 break;
3521 gamma+=0.4f;
3522 (void) GammaImageChannel(preview_image,DefaultChannels,gamma);
cristy8cd5b312010-01-07 01:10:24 +00003523 (void) FormatMagickString(label,MaxTextExtent,"gamma %.15g",gamma);
cristy3ed852e2009-09-05 21:47:34 +00003524 break;
3525 }
3526 case SpiffPreview:
3527 {
3528 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3529 if (preview_image != (Image *) NULL)
3530 for (x=0; x < i; x++)
3531 (void) ContrastImage(preview_image,MagickTrue);
3532 (void) FormatMagickString(label,MaxTextExtent,"contrast (%ld)",i+1);
3533 break;
3534 }
3535 case DullPreview:
3536 {
3537 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3538 if (preview_image == (Image *) NULL)
3539 break;
3540 for (x=0; x < i; x++)
3541 (void) ContrastImage(preview_image,MagickFalse);
3542 (void) FormatMagickString(label,MaxTextExtent,"+contrast (%ld)",i+1);
3543 break;
3544 }
3545 case GrayscalePreview:
3546 {
3547 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3548 if (preview_image == (Image *) NULL)
3549 break;
3550 colors<<=1;
3551 quantize_info.number_colors=colors;
3552 quantize_info.colorspace=GRAYColorspace;
3553 (void) QuantizeImage(&quantize_info,preview_image);
3554 (void) FormatMagickString(label,MaxTextExtent,
3555 "-colorspace gray -colors %ld",colors);
3556 break;
3557 }
3558 case QuantizePreview:
3559 {
3560 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3561 if (preview_image == (Image *) NULL)
3562 break;
3563 colors<<=1;
3564 quantize_info.number_colors=colors;
3565 (void) QuantizeImage(&quantize_info,preview_image);
3566 (void) FormatMagickString(label,MaxTextExtent,"colors %ld",colors);
3567 break;
3568 }
3569 case DespecklePreview:
3570 {
3571 for (x=0; x < (i-1); x++)
3572 {
3573 preview_image=DespeckleImage(thumbnail,exception);
3574 if (preview_image == (Image *) NULL)
3575 break;
3576 thumbnail=DestroyImage(thumbnail);
3577 thumbnail=preview_image;
3578 }
3579 preview_image=DespeckleImage(thumbnail,exception);
3580 if (preview_image == (Image *) NULL)
3581 break;
3582 (void) FormatMagickString(label,MaxTextExtent,"despeckle (%ld)",i+1);
3583 break;
3584 }
3585 case ReduceNoisePreview:
3586 {
3587 preview_image=ReduceNoiseImage(thumbnail,radius,exception);
cristy8cd5b312010-01-07 01:10:24 +00003588 (void) FormatMagickString(label,MaxTextExtent,"noise %.15g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003589 break;
3590 }
3591 case AddNoisePreview:
3592 {
3593 switch ((int) i)
3594 {
3595 case 0:
3596 {
3597 (void) CopyMagickString(factor,"uniform",MaxTextExtent);
3598 break;
3599 }
3600 case 1:
3601 {
3602 (void) CopyMagickString(factor,"gaussian",MaxTextExtent);
3603 break;
3604 }
3605 case 2:
3606 {
3607 (void) CopyMagickString(factor,"multiplicative",MaxTextExtent);
3608 break;
3609 }
3610 case 3:
3611 {
3612 (void) CopyMagickString(factor,"impulse",MaxTextExtent);
3613 break;
3614 }
3615 case 4:
3616 {
3617 (void) CopyMagickString(factor,"laplacian",MaxTextExtent);
3618 break;
3619 }
3620 case 5:
3621 {
3622 (void) CopyMagickString(factor,"Poisson",MaxTextExtent);
3623 break;
3624 }
3625 default:
3626 {
3627 (void) CopyMagickString(thumbnail->magick,"NULL",MaxTextExtent);
3628 break;
3629 }
3630 }
3631 preview_image=ReduceNoiseImage(thumbnail,(double) i,exception);
3632 (void) FormatMagickString(label,MaxTextExtent,"+noise %s",factor);
3633 break;
3634 }
3635 case SharpenPreview:
3636 {
3637 preview_image=SharpenImage(thumbnail,radius,sigma,exception);
cristy8cd5b312010-01-07 01:10:24 +00003638 (void) FormatMagickString(label,MaxTextExtent,"sharpen %.15gx%.15g",
3639 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00003640 break;
3641 }
3642 case BlurPreview:
3643 {
3644 preview_image=BlurImage(thumbnail,radius,sigma,exception);
cristy8cd5b312010-01-07 01:10:24 +00003645 (void) FormatMagickString(label,MaxTextExtent,"blur %.15gx%.15g",radius,
cristy3ed852e2009-09-05 21:47:34 +00003646 sigma);
3647 break;
3648 }
3649 case ThresholdPreview:
3650 {
3651 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3652 if (preview_image == (Image *) NULL)
3653 break;
3654 (void) BilevelImage(thumbnail,
3655 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
cristy8cd5b312010-01-07 01:10:24 +00003656 (void) FormatMagickString(label,MaxTextExtent,"threshold %.15g",
cristy3ed852e2009-09-05 21:47:34 +00003657 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
3658 break;
3659 }
3660 case EdgeDetectPreview:
3661 {
3662 preview_image=EdgeImage(thumbnail,radius,exception);
cristy8cd5b312010-01-07 01:10:24 +00003663 (void) FormatMagickString(label,MaxTextExtent,"edge %.15g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003664 break;
3665 }
3666 case SpreadPreview:
3667 {
3668 preview_image=SpreadImage(thumbnail,radius,exception);
cristy8cd5b312010-01-07 01:10:24 +00003669 (void) FormatMagickString(label,MaxTextExtent,"spread %.15g",
3670 radius+0.5);
cristy3ed852e2009-09-05 21:47:34 +00003671 break;
3672 }
3673 case SolarizePreview:
3674 {
3675 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3676 if (preview_image == (Image *) NULL)
3677 break;
3678 (void) SolarizeImage(preview_image,(double) QuantumRange*
3679 percentage/100.0);
cristy8cd5b312010-01-07 01:10:24 +00003680 (void) FormatMagickString(label,MaxTextExtent,"solarize %.15g",
cristy3ed852e2009-09-05 21:47:34 +00003681 (QuantumRange*percentage)/100.0);
3682 break;
3683 }
3684 case ShadePreview:
3685 {
3686 degrees+=10.0;
3687 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
3688 exception);
cristy8cd5b312010-01-07 01:10:24 +00003689 (void) FormatMagickString(label,MaxTextExtent,"shade %.15gx%.15g",
3690 degrees,degrees);
cristy3ed852e2009-09-05 21:47:34 +00003691 break;
3692 }
3693 case RaisePreview:
3694 {
3695 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3696 if (preview_image == (Image *) NULL)
3697 break;
3698 geometry.width=(unsigned long) (2*i+2);
3699 geometry.height=(unsigned long) (2*i+2);
3700 geometry.x=i/2;
3701 geometry.y=i/2;
3702 (void) RaiseImage(preview_image,&geometry,MagickTrue);
3703 (void) FormatMagickString(label,MaxTextExtent,"raise %lux%lu%+ld%+ld",
3704 geometry.width,geometry.height,geometry.x,geometry.y);
3705 break;
3706 }
3707 case SegmentPreview:
3708 {
3709 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3710 if (preview_image == (Image *) NULL)
3711 break;
3712 threshold+=0.4f;
3713 (void) SegmentImage(preview_image,RGBColorspace,MagickFalse,threshold,
3714 threshold);
cristy8cd5b312010-01-07 01:10:24 +00003715 (void) FormatMagickString(label,MaxTextExtent,"segment %.15gx%.15g",
cristy3ed852e2009-09-05 21:47:34 +00003716 threshold,threshold);
3717 break;
3718 }
3719 case SwirlPreview:
3720 {
3721 preview_image=SwirlImage(thumbnail,degrees,exception);
cristy8cd5b312010-01-07 01:10:24 +00003722 (void) FormatMagickString(label,MaxTextExtent,"swirl %.15g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003723 degrees+=45.0;
3724 break;
3725 }
3726 case ImplodePreview:
3727 {
3728 degrees+=0.1f;
3729 preview_image=ImplodeImage(thumbnail,degrees,exception);
cristy8cd5b312010-01-07 01:10:24 +00003730 (void) FormatMagickString(label,MaxTextExtent,"implode %.15g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003731 break;
3732 }
3733 case WavePreview:
3734 {
3735 degrees+=5.0f;
3736 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,exception);
cristy8cd5b312010-01-07 01:10:24 +00003737 (void) FormatMagickString(label,MaxTextExtent,"wave %.15gx%.15g",
3738 0.5*degrees,2.0*degrees);
cristy3ed852e2009-09-05 21:47:34 +00003739 break;
3740 }
3741 case OilPaintPreview:
3742 {
3743 preview_image=OilPaintImage(thumbnail,(double) radius,exception);
cristy8cd5b312010-01-07 01:10:24 +00003744 (void) FormatMagickString(label,MaxTextExtent,"paint %.15g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003745 break;
3746 }
3747 case CharcoalDrawingPreview:
3748 {
3749 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
3750 exception);
cristy8cd5b312010-01-07 01:10:24 +00003751 (void) FormatMagickString(label,MaxTextExtent,"charcoal %.15gx%.15g",
3752 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00003753 break;
3754 }
3755 case JPEGPreview:
3756 {
3757 char
3758 filename[MaxTextExtent];
3759
3760 int
3761 file;
3762
3763 MagickBooleanType
3764 status;
3765
3766 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3767 if (preview_image == (Image *) NULL)
3768 break;
3769 preview_info->quality=(unsigned long) percentage;
3770 (void) FormatMagickString(factor,MaxTextExtent,"%lu",
3771 preview_info->quality);
3772 file=AcquireUniqueFileResource(filename);
3773 if (file != -1)
3774 file=close(file)-1;
3775 (void) FormatMagickString(preview_image->filename,MaxTextExtent,
3776 "jpeg:%s",filename);
3777 status=WriteImage(preview_info,preview_image);
3778 if (status != MagickFalse)
3779 {
3780 Image
3781 *quality_image;
3782
3783 (void) CopyMagickString(preview_info->filename,
3784 preview_image->filename,MaxTextExtent);
3785 quality_image=ReadImage(preview_info,exception);
3786 if (quality_image != (Image *) NULL)
3787 {
3788 preview_image=DestroyImage(preview_image);
3789 preview_image=quality_image;
3790 }
3791 }
3792 (void) RelinquishUniqueFileResource(preview_image->filename);
3793 if ((GetBlobSize(preview_image)/1024) >= 1024)
cristy8cd5b312010-01-07 01:10:24 +00003794 (void) FormatMagickString(label,MaxTextExtent,"quality %s\n%.15gmb ",
cristy3ed852e2009-09-05 21:47:34 +00003795 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
3796 1024.0/1024.0);
3797 else
3798 if (GetBlobSize(preview_image) >= 1024)
cristy8cd5b312010-01-07 01:10:24 +00003799 (void) FormatMagickString(label,MaxTextExtent,
3800 "quality %s\n%.15gkb ",factor,(double) ((MagickOffsetType)
3801 GetBlobSize(preview_image))/1024.0);
cristy3ed852e2009-09-05 21:47:34 +00003802 else
3803 (void) FormatMagickString(label,MaxTextExtent,"quality %s\n%lub ",
3804 factor,(unsigned long) GetBlobSize(thumbnail));
3805 break;
3806 }
3807 }
3808 thumbnail=DestroyImage(thumbnail);
3809 percentage+=12.5;
3810 radius+=0.5;
3811 sigma+=0.25;
3812 if (preview_image == (Image *) NULL)
3813 break;
3814 (void) DeleteImageProperty(preview_image,"label");
3815 (void) SetImageProperty(preview_image,"label",label);
3816 AppendImageToList(&images,preview_image);
3817 proceed=SetImageProgress(image,PreviewImageTag,i,NumberTiles);
3818 if (proceed == MagickFalse)
3819 break;
3820 }
3821 if (images == (Image *) NULL)
3822 {
3823 preview_info=DestroyImageInfo(preview_info);
3824 return((Image *) NULL);
3825 }
3826 /*
3827 Create the montage.
3828 */
3829 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
3830 (void) CopyMagickString(montage_info->filename,image->filename,MaxTextExtent);
3831 montage_info->shadow=MagickTrue;
3832 (void) CloneString(&montage_info->tile,"3x3");
3833 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
3834 (void) CloneString(&montage_info->frame,DefaultTileFrame);
3835 montage_image=MontageImages(images,montage_info,exception);
3836 montage_info=DestroyMontageInfo(montage_info);
3837 images=DestroyImageList(images);
3838 if (montage_image == (Image *) NULL)
3839 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3840 if (montage_image->montage != (char *) NULL)
3841 {
3842 /*
3843 Free image directory.
3844 */
3845 montage_image->montage=(char *) RelinquishMagickMemory(
3846 montage_image->montage);
3847 if (image->directory != (char *) NULL)
3848 montage_image->directory=(char *) RelinquishMagickMemory(
3849 montage_image->directory);
3850 }
3851 preview_info=DestroyImageInfo(preview_info);
3852 return(montage_image);
3853}
3854
3855/*
3856%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3857% %
3858% %
3859% %
3860% R a d i a l B l u r I m a g e %
3861% %
3862% %
3863% %
3864%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3865%
3866% RadialBlurImage() applies a radial blur to the image.
3867%
3868% Andrew Protano contributed this effect.
3869%
3870% The format of the RadialBlurImage method is:
3871%
3872% Image *RadialBlurImage(const Image *image,const double angle,
3873% ExceptionInfo *exception)
3874% Image *RadialBlurImageChannel(const Image *image,const ChannelType channel,
3875% const double angle,ExceptionInfo *exception)
3876%
3877% A description of each parameter follows:
3878%
3879% o image: the image.
3880%
3881% o channel: the channel type.
3882%
3883% o angle: the angle of the radial blur.
3884%
3885% o exception: return any errors or warnings in this structure.
3886%
3887*/
3888
3889MagickExport Image *RadialBlurImage(const Image *image,const double angle,
3890 ExceptionInfo *exception)
3891{
3892 Image
3893 *blur_image;
3894
3895 blur_image=RadialBlurImageChannel(image,DefaultChannels,angle,exception);
3896 return(blur_image);
3897}
3898
3899MagickExport Image *RadialBlurImageChannel(const Image *image,
3900 const ChannelType channel,const double angle,ExceptionInfo *exception)
3901{
3902 Image
3903 *blur_image;
3904
3905 long
3906 progress,
3907 y;
3908
3909 MagickBooleanType
3910 status;
3911
3912 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00003913 bias;
cristy3ed852e2009-09-05 21:47:34 +00003914
3915 MagickRealType
3916 blur_radius,
3917 *cos_theta,
3918 offset,
3919 *sin_theta,
3920 theta;
3921
3922 PointInfo
3923 blur_center;
3924
3925 register long
3926 i;
3927
3928 unsigned long
3929 n;
3930
3931 CacheView
3932 *blur_view,
3933 *image_view;
3934
3935 /*
3936 Allocate blur image.
3937 */
3938 assert(image != (Image *) NULL);
3939 assert(image->signature == MagickSignature);
3940 if (image->debug != MagickFalse)
3941 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3942 assert(exception != (ExceptionInfo *) NULL);
3943 assert(exception->signature == MagickSignature);
3944 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3945 if (blur_image == (Image *) NULL)
3946 return((Image *) NULL);
3947 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3948 {
3949 InheritException(exception,&blur_image->exception);
3950 blur_image=DestroyImage(blur_image);
3951 return((Image *) NULL);
3952 }
3953 blur_center.x=(double) image->columns/2.0;
3954 blur_center.y=(double) image->rows/2.0;
3955 blur_radius=hypot(blur_center.x,blur_center.y);
3956 n=(unsigned long) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+
3957 2UL);
3958 theta=DegreesToRadians(angle)/(MagickRealType) (n-1);
3959 cos_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3960 sizeof(*cos_theta));
3961 sin_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3962 sizeof(*sin_theta));
3963 if ((cos_theta == (MagickRealType *) NULL) ||
3964 (sin_theta == (MagickRealType *) NULL))
3965 {
3966 blur_image=DestroyImage(blur_image);
3967 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3968 }
3969 offset=theta*(MagickRealType) (n-1)/2.0;
3970 for (i=0; i < (long) n; i++)
3971 {
3972 cos_theta[i]=cos((double) (theta*i-offset));
3973 sin_theta[i]=sin((double) (theta*i-offset));
3974 }
3975 /*
3976 Radial blur image.
3977 */
3978 status=MagickTrue;
3979 progress=0;
cristyddd82202009-11-03 20:14:50 +00003980 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003981 image_view=AcquireCacheView(image);
3982 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003983#if defined(MAGICKCORE_OPENMP_SUPPORT)
3984 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003985#endif
3986 for (y=0; y < (long) blur_image->rows; y++)
3987 {
3988 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003989 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003990
3991 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003992 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00003993
3994 register long
3995 x;
3996
3997 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003998 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003999
4000 if (status == MagickFalse)
4001 continue;
4002 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
4003 exception);
4004 if (q == (PixelPacket *) NULL)
4005 {
4006 status=MagickFalse;
4007 continue;
4008 }
4009 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
4010 for (x=0; x < (long) blur_image->columns; x++)
4011 {
4012 MagickPixelPacket
4013 qixel;
4014
4015 MagickRealType
4016 normalize,
4017 radius;
4018
4019 PixelPacket
4020 pixel;
4021
4022 PointInfo
4023 center;
4024
4025 register long
4026 i;
4027
4028 unsigned long
4029 step;
4030
4031 center.x=(double) x-blur_center.x;
4032 center.y=(double) y-blur_center.y;
4033 radius=hypot((double) center.x,center.y);
4034 if (radius == 0)
4035 step=1;
4036 else
4037 {
4038 step=(unsigned long) (blur_radius/radius);
4039 if (step == 0)
4040 step=1;
4041 else
4042 if (step >= n)
4043 step=n-1;
4044 }
4045 normalize=0.0;
cristyddd82202009-11-03 20:14:50 +00004046 qixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00004047 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
4048 {
4049 for (i=0; i < (long) n; i+=step)
4050 {
4051 (void) GetOneCacheViewVirtualPixel(image_view,(long) (blur_center.x+
4052 center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),(long) (
4053 blur_center.y+center.x*sin_theta[i]+center.y*cos_theta[i]+0.5),
4054 &pixel,exception);
4055 qixel.red+=pixel.red;
4056 qixel.green+=pixel.green;
4057 qixel.blue+=pixel.blue;
4058 qixel.opacity+=pixel.opacity;
4059 if (image->colorspace == CMYKColorspace)
4060 {
4061 indexes=GetCacheViewVirtualIndexQueue(image_view);
4062 qixel.index+=(*indexes);
4063 }
4064 normalize+=1.0;
4065 }
4066 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
4067 normalize);
4068 if ((channel & RedChannel) != 0)
4069 q->red=RoundToQuantum(normalize*qixel.red);
4070 if ((channel & GreenChannel) != 0)
4071 q->green=RoundToQuantum(normalize*qixel.green);
4072 if ((channel & BlueChannel) != 0)
4073 q->blue=RoundToQuantum(normalize*qixel.blue);
4074 if ((channel & OpacityChannel) != 0)
4075 q->opacity=RoundToQuantum(normalize*qixel.opacity);
4076 if (((channel & IndexChannel) != 0) &&
4077 (image->colorspace == CMYKColorspace))
4078 blur_indexes[x]=(IndexPacket) RoundToQuantum(normalize*qixel.index);
4079 }
4080 else
4081 {
4082 MagickRealType
4083 alpha,
4084 gamma;
4085
4086 alpha=1.0;
4087 gamma=0.0;
4088 for (i=0; i < (long) n; i+=step)
4089 {
4090 (void) GetOneCacheViewVirtualPixel(image_view,(long) (blur_center.x+
4091 center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),(long) (
4092 blur_center.y+center.x*sin_theta[i]+center.y*cos_theta[i]+0.5),
4093 &pixel,exception);
4094 alpha=(MagickRealType) (QuantumScale*(QuantumRange-pixel.opacity));
4095 qixel.red+=alpha*pixel.red;
4096 qixel.green+=alpha*pixel.green;
4097 qixel.blue+=alpha*pixel.blue;
4098 qixel.opacity+=pixel.opacity;
4099 if (image->colorspace == CMYKColorspace)
4100 {
4101 indexes=GetCacheViewVirtualIndexQueue(image_view);
4102 qixel.index+=alpha*(*indexes);
4103 }
4104 gamma+=alpha;
4105 normalize+=1.0;
4106 }
4107 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
4108 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
4109 normalize);
4110 if ((channel & RedChannel) != 0)
4111 q->red=RoundToQuantum(gamma*qixel.red);
4112 if ((channel & GreenChannel) != 0)
4113 q->green=RoundToQuantum(gamma*qixel.green);
4114 if ((channel & BlueChannel) != 0)
4115 q->blue=RoundToQuantum(gamma*qixel.blue);
4116 if ((channel & OpacityChannel) != 0)
4117 q->opacity=RoundToQuantum(normalize*qixel.opacity);
4118 if (((channel & IndexChannel) != 0) &&
4119 (image->colorspace == CMYKColorspace))
4120 blur_indexes[x]=(IndexPacket) RoundToQuantum(gamma*qixel.index);
4121 }
4122 q++;
4123 }
4124 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
4125 status=MagickFalse;
4126 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4127 {
4128 MagickBooleanType
4129 proceed;
4130
cristyb5d5f722009-11-04 03:03:49 +00004131#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004132 #pragma omp critical (MagickCore_RadialBlurImageChannel)
4133#endif
4134 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
4135 if (proceed == MagickFalse)
4136 status=MagickFalse;
4137 }
4138 }
4139 blur_view=DestroyCacheView(blur_view);
4140 image_view=DestroyCacheView(image_view);
4141 cos_theta=(MagickRealType *) RelinquishMagickMemory(cos_theta);
4142 sin_theta=(MagickRealType *) RelinquishMagickMemory(sin_theta);
4143 if (status == MagickFalse)
4144 blur_image=DestroyImage(blur_image);
4145 return(blur_image);
4146}
4147
4148/*
4149%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4150% %
4151% %
4152% %
4153% R e d u c e N o i s e I m a g e %
4154% %
4155% %
4156% %
4157%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4158%
4159% ReduceNoiseImage() smooths the contours of an image while still preserving
4160% edge information. The algorithm works by replacing each pixel with its
4161% neighbor closest in value. A neighbor is defined by radius. Use a radius
4162% of 0 and ReduceNoise() selects a suitable radius for you.
4163%
4164% The format of the ReduceNoiseImage method is:
4165%
4166% Image *ReduceNoiseImage(const Image *image,const double radius,
4167% ExceptionInfo *exception)
4168%
4169% A description of each parameter follows:
4170%
4171% o image: the image.
4172%
4173% o radius: the radius of the pixel neighborhood.
4174%
4175% o exception: return any errors or warnings in this structure.
4176%
4177*/
4178
4179static MagickPixelPacket GetNonpeakMedianPixelList(MedianPixelList *pixel_list)
4180{
4181 MagickPixelPacket
4182 pixel;
4183
4184 register long
4185 channel;
4186
4187 register MedianSkipList
4188 *list;
4189
4190 unsigned long
4191 center,
4192 color,
4193 count,
4194 previous,
4195 next;
4196
4197 unsigned short
4198 channels[5];
4199
4200 /*
4201 Finds the median value for each of the color.
4202 */
4203 center=pixel_list->center;
4204 for (channel=0; channel < 5; channel++)
4205 {
4206 list=pixel_list->lists+channel;
4207 color=65536UL;
4208 next=list->nodes[color].next[0];
4209 count=0;
4210 do
4211 {
4212 previous=color;
4213 color=next;
4214 next=list->nodes[color].next[0];
4215 count+=list->nodes[color].count;
4216 }
4217 while (count <= center);
4218 if ((previous == 65536UL) && (next != 65536UL))
4219 color=next;
4220 else
4221 if ((previous != 65536UL) && (next == 65536UL))
4222 color=previous;
4223 channels[channel]=(unsigned short) color;
4224 }
4225 GetMagickPixelPacket((const Image *) NULL,&pixel);
4226 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4227 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4228 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
4229 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
4230 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
4231 return(pixel);
4232}
4233
4234MagickExport Image *ReduceNoiseImage(const Image *image,const double radius,
4235 ExceptionInfo *exception)
4236{
4237#define ReduceNoiseImageTag "ReduceNoise/Image"
4238
cristyfa112112010-01-04 17:48:07 +00004239 CacheView
4240 *image_view,
4241 *noise_view;
4242
cristy3ed852e2009-09-05 21:47:34 +00004243 Image
4244 *noise_image;
4245
4246 long
4247 progress,
4248 y;
4249
4250 MagickBooleanType
4251 status;
4252
4253 MedianPixelList
cristyfa112112010-01-04 17:48:07 +00004254 **restrict pixel_list;
cristy3ed852e2009-09-05 21:47:34 +00004255
4256 unsigned long
4257 width;
4258
cristy3ed852e2009-09-05 21:47:34 +00004259 /*
4260 Initialize noise image attributes.
4261 */
4262 assert(image != (Image *) NULL);
4263 assert(image->signature == MagickSignature);
4264 if (image->debug != MagickFalse)
4265 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4266 assert(exception != (ExceptionInfo *) NULL);
4267 assert(exception->signature == MagickSignature);
4268 width=GetOptimalKernelWidth2D(radius,0.5);
4269 if ((image->columns < width) || (image->rows < width))
4270 ThrowImageException(OptionError,"ImageSmallerThanKernelRadius");
4271 noise_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4272 exception);
4273 if (noise_image == (Image *) NULL)
4274 return((Image *) NULL);
4275 if (SetImageStorageClass(noise_image,DirectClass) == MagickFalse)
4276 {
4277 InheritException(exception,&noise_image->exception);
4278 noise_image=DestroyImage(noise_image);
4279 return((Image *) NULL);
4280 }
4281 pixel_list=AcquireMedianPixelListThreadSet(width);
4282 if (pixel_list == (MedianPixelList **) NULL)
4283 {
4284 noise_image=DestroyImage(noise_image);
4285 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
4286 }
4287 /*
4288 Reduce noise image.
4289 */
4290 status=MagickTrue;
4291 progress=0;
4292 image_view=AcquireCacheView(image);
4293 noise_view=AcquireCacheView(noise_image);
cristyb5d5f722009-11-04 03:03:49 +00004294#if defined(MAGICKCORE_OPENMP_SUPPORT)
4295 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004296#endif
4297 for (y=0; y < (long) noise_image->rows; y++)
4298 {
4299 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004300 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00004301
4302 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004303 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00004304
4305 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004306 *restrict noise_indexes;
cristy3ed852e2009-09-05 21:47:34 +00004307
4308 register long
4309 id,
4310 x;
4311
4312 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004313 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004314
4315 if (status == MagickFalse)
4316 continue;
4317 p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y-(long) (width/
4318 2L),image->columns+width,width,exception);
4319 q=QueueCacheViewAuthenticPixels(noise_view,0,y,noise_image->columns,1,
4320 exception);
4321 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
4322 {
4323 status=MagickFalse;
4324 continue;
4325 }
4326 indexes=GetCacheViewVirtualIndexQueue(image_view);
4327 noise_indexes=GetCacheViewAuthenticIndexQueue(noise_view);
4328 id=GetOpenMPThreadId();
4329 for (x=0; x < (long) noise_image->columns; x++)
4330 {
4331 MagickPixelPacket
4332 pixel;
4333
4334 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004335 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +00004336
4337 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004338 *restrict s;
cristy3ed852e2009-09-05 21:47:34 +00004339
4340 register long
4341 u,
4342 v;
4343
4344 r=p;
4345 s=indexes+x;
4346 ResetMedianPixelList(pixel_list[id]);
4347 for (v=0; v < (long) width; v++)
4348 {
4349 for (u=0; u < (long) width; u++)
4350 InsertMedianPixelList(image,r+u,s+u,pixel_list[id]);
4351 r+=image->columns+width;
4352 s+=image->columns+width;
4353 }
4354 pixel=GetNonpeakMedianPixelList(pixel_list[id]);
4355 SetPixelPacket(noise_image,&pixel,q,noise_indexes+x);
4356 p++;
4357 q++;
4358 }
4359 if (SyncCacheViewAuthenticPixels(noise_view,exception) == MagickFalse)
4360 status=MagickFalse;
4361 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4362 {
4363 MagickBooleanType
4364 proceed;
4365
cristyb5d5f722009-11-04 03:03:49 +00004366#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004367 #pragma omp critical (MagickCore_ReduceNoiseImage)
4368#endif
4369 proceed=SetImageProgress(image,ReduceNoiseImageTag,progress++,
4370 image->rows);
4371 if (proceed == MagickFalse)
4372 status=MagickFalse;
4373 }
4374 }
4375 noise_view=DestroyCacheView(noise_view);
4376 image_view=DestroyCacheView(image_view);
4377 pixel_list=DestroyMedianPixelListThreadSet(pixel_list);
4378 return(noise_image);
4379}
4380
4381/*
4382%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4383% %
4384% %
4385% %
4386% S e l e c t i v e B l u r I m a g e %
4387% %
4388% %
4389% %
4390%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4391%
4392% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
4393% It is similar to the unsharpen mask that sharpens everything with contrast
4394% above a certain threshold.
4395%
4396% The format of the SelectiveBlurImage method is:
4397%
4398% Image *SelectiveBlurImage(const Image *image,const double radius,
4399% const double sigma,const double threshold,ExceptionInfo *exception)
4400% Image *SelectiveBlurImageChannel(const Image *image,
4401% const ChannelType channel,const double radius,const double sigma,
4402% const double threshold,ExceptionInfo *exception)
4403%
4404% A description of each parameter follows:
4405%
4406% o image: the image.
4407%
4408% o channel: the channel type.
4409%
4410% o radius: the radius of the Gaussian, in pixels, not counting the center
4411% pixel.
4412%
4413% o sigma: the standard deviation of the Gaussian, in pixels.
4414%
4415% o threshold: only pixels within this contrast threshold are included
4416% in the blur operation.
4417%
4418% o exception: return any errors or warnings in this structure.
4419%
4420*/
4421
4422static inline MagickBooleanType SelectiveContrast(const PixelPacket *p,
4423 const PixelPacket *q,const double threshold)
4424{
4425 if (fabs(PixelIntensity(p)-PixelIntensity(q)) < threshold)
4426 return(MagickTrue);
4427 return(MagickFalse);
4428}
4429
4430MagickExport Image *SelectiveBlurImage(const Image *image,const double radius,
4431 const double sigma,const double threshold,ExceptionInfo *exception)
4432{
4433 Image
4434 *blur_image;
4435
4436 blur_image=SelectiveBlurImageChannel(image,DefaultChannels,radius,sigma,
4437 threshold,exception);
4438 return(blur_image);
4439}
4440
4441MagickExport Image *SelectiveBlurImageChannel(const Image *image,
4442 const ChannelType channel,const double radius,const double sigma,
4443 const double threshold,ExceptionInfo *exception)
4444{
4445#define SelectiveBlurImageTag "SelectiveBlur/Image"
4446
cristy47e00502009-12-17 19:19:57 +00004447 CacheView
4448 *blur_view,
4449 *image_view;
4450
cristy3ed852e2009-09-05 21:47:34 +00004451 double
cristy3ed852e2009-09-05 21:47:34 +00004452 *kernel;
4453
4454 Image
4455 *blur_image;
4456
4457 long
cristy47e00502009-12-17 19:19:57 +00004458 j,
cristy3ed852e2009-09-05 21:47:34 +00004459 progress,
cristy47e00502009-12-17 19:19:57 +00004460 u,
cristy3ed852e2009-09-05 21:47:34 +00004461 v,
4462 y;
4463
4464 MagickBooleanType
4465 status;
4466
4467 MagickPixelPacket
cristy3ed852e2009-09-05 21:47:34 +00004468 bias;
4469
4470 register long
cristy47e00502009-12-17 19:19:57 +00004471 i;
cristy3ed852e2009-09-05 21:47:34 +00004472
4473 unsigned long
4474 width;
4475
cristy3ed852e2009-09-05 21:47:34 +00004476 /*
4477 Initialize blur image attributes.
4478 */
4479 assert(image != (Image *) NULL);
4480 assert(image->signature == MagickSignature);
4481 if (image->debug != MagickFalse)
4482 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4483 assert(exception != (ExceptionInfo *) NULL);
4484 assert(exception->signature == MagickSignature);
4485 width=GetOptimalKernelWidth1D(radius,sigma);
4486 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
4487 if (kernel == (double *) NULL)
4488 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy47e00502009-12-17 19:19:57 +00004489 j=(long) width/2;
cristy3ed852e2009-09-05 21:47:34 +00004490 i=0;
cristy47e00502009-12-17 19:19:57 +00004491 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00004492 {
cristy47e00502009-12-17 19:19:57 +00004493 for (u=(-j); u <= j; u++)
4494 kernel[i++]=exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
4495 (2.0*MagickPI*MagickSigma*MagickSigma);
cristy3ed852e2009-09-05 21:47:34 +00004496 }
4497 if (image->debug != MagickFalse)
4498 {
4499 char
4500 format[MaxTextExtent],
4501 *message;
4502
4503 long
4504 u,
4505 v;
4506
4507 register const double
4508 *k;
4509
4510 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
4511 " SelectiveBlurImage with %ldx%ld kernel:",width,width);
4512 message=AcquireString("");
4513 k=kernel;
4514 for (v=0; v < (long) width; v++)
4515 {
4516 *message='\0';
4517 (void) FormatMagickString(format,MaxTextExtent,"%ld: ",v);
4518 (void) ConcatenateString(&message,format);
4519 for (u=0; u < (long) width; u++)
4520 {
4521 (void) FormatMagickString(format,MaxTextExtent,"%+f ",*k++);
4522 (void) ConcatenateString(&message,format);
4523 }
4524 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
4525 }
4526 message=DestroyString(message);
4527 }
4528 blur_image=CloneImage(image,0,0,MagickTrue,exception);
4529 if (blur_image == (Image *) NULL)
4530 return((Image *) NULL);
4531 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
4532 {
4533 InheritException(exception,&blur_image->exception);
4534 blur_image=DestroyImage(blur_image);
4535 return((Image *) NULL);
4536 }
4537 /*
4538 Threshold blur image.
4539 */
4540 status=MagickTrue;
4541 progress=0;
cristyddd82202009-11-03 20:14:50 +00004542 GetMagickPixelPacket(image,&bias);
4543 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00004544 image_view=AcquireCacheView(image);
4545 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00004546#if defined(MAGICKCORE_OPENMP_SUPPORT)
4547 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004548#endif
4549 for (y=0; y < (long) image->rows; y++)
4550 {
4551 MagickBooleanType
4552 sync;
4553
4554 MagickRealType
4555 gamma;
4556
4557 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004558 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00004559
4560 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004561 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00004562
4563 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004564 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00004565
4566 register long
4567 x;
4568
4569 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004570 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004571
4572 if (status == MagickFalse)
4573 continue;
4574 p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y-(long) (width/
4575 2L),image->columns+width,width,exception);
4576 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
4577 exception);
4578 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
4579 {
4580 status=MagickFalse;
4581 continue;
4582 }
4583 indexes=GetCacheViewVirtualIndexQueue(image_view);
4584 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
4585 for (x=0; x < (long) image->columns; x++)
4586 {
4587 long
4588 j,
4589 v;
4590
4591 MagickPixelPacket
4592 pixel;
4593
4594 register const double
cristyc47d1f82009-11-26 01:44:43 +00004595 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00004596
4597 register long
4598 u;
4599
cristyddd82202009-11-03 20:14:50 +00004600 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00004601 k=kernel;
4602 gamma=0.0;
4603 j=0;
4604 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
4605 {
4606 for (v=0; v < (long) width; v++)
4607 {
4608 for (u=0; u < (long) width; u++)
4609 {
4610 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4611 {
4612 pixel.red+=(*k)*(p+u+j)->red;
4613 pixel.green+=(*k)*(p+u+j)->green;
4614 pixel.blue+=(*k)*(p+u+j)->blue;
4615 gamma+=(*k);
4616 k++;
4617 }
4618 }
4619 j+=image->columns+width;
4620 }
4621 if (gamma != 0.0)
4622 {
4623 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
4624 if ((channel & RedChannel) != 0)
cristyddd82202009-11-03 20:14:50 +00004625 q->red=RoundToQuantum(gamma*pixel.red);
cristy3ed852e2009-09-05 21:47:34 +00004626 if ((channel & GreenChannel) != 0)
cristyddd82202009-11-03 20:14:50 +00004627 q->green=RoundToQuantum(gamma*pixel.green);
cristy3ed852e2009-09-05 21:47:34 +00004628 if ((channel & BlueChannel) != 0)
cristyddd82202009-11-03 20:14:50 +00004629 q->blue=RoundToQuantum(gamma*pixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00004630 }
4631 if ((channel & OpacityChannel) != 0)
4632 {
4633 gamma=0.0;
4634 j=0;
4635 for (v=0; v < (long) width; v++)
4636 {
4637 for (u=0; u < (long) width; u++)
4638 {
4639 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4640 {
4641 pixel.opacity+=(*k)*(p+u+j)->opacity;
4642 gamma+=(*k);
4643 k++;
4644 }
4645 }
4646 j+=image->columns+width;
4647 }
4648 if (gamma != 0.0)
4649 {
4650 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4651 gamma);
cristyddd82202009-11-03 20:14:50 +00004652 q->opacity=RoundToQuantum(gamma*pixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00004653 }
4654 }
4655 if (((channel & IndexChannel) != 0) &&
4656 (image->colorspace == CMYKColorspace))
4657 {
4658 gamma=0.0;
4659 j=0;
4660 for (v=0; v < (long) width; v++)
4661 {
4662 for (u=0; u < (long) width; u++)
4663 {
4664 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4665 {
4666 pixel.index+=(*k)*indexes[x+u+j];
4667 gamma+=(*k);
4668 k++;
4669 }
4670 }
4671 j+=image->columns+width;
4672 }
4673 if (gamma != 0.0)
4674 {
4675 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4676 gamma);
cristyddd82202009-11-03 20:14:50 +00004677 blur_indexes[x]=RoundToQuantum(gamma*pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00004678 }
4679 }
4680 }
4681 else
4682 {
4683 MagickRealType
4684 alpha;
4685
4686 for (v=0; v < (long) width; v++)
4687 {
4688 for (u=0; u < (long) width; u++)
4689 {
4690 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4691 {
4692 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
4693 (p+u+j)->opacity));
4694 pixel.red+=(*k)*alpha*(p+u+j)->red;
4695 pixel.green+=(*k)*alpha*(p+u+j)->green;
4696 pixel.blue+=(*k)*alpha*(p+u+j)->blue;
4697 pixel.opacity+=(*k)*(p+u+j)->opacity;
4698 gamma+=(*k)*alpha;
4699 k++;
4700 }
4701 }
4702 j+=image->columns+width;
4703 }
4704 if (gamma != 0.0)
4705 {
4706 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
4707 if ((channel & RedChannel) != 0)
cristyddd82202009-11-03 20:14:50 +00004708 q->red=RoundToQuantum(gamma*pixel.red);
cristy3ed852e2009-09-05 21:47:34 +00004709 if ((channel & GreenChannel) != 0)
cristyddd82202009-11-03 20:14:50 +00004710 q->green=RoundToQuantum(gamma*pixel.green);
cristy3ed852e2009-09-05 21:47:34 +00004711 if ((channel & BlueChannel) != 0)
cristyddd82202009-11-03 20:14:50 +00004712 q->blue=RoundToQuantum(gamma*pixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00004713 }
4714 if ((channel & OpacityChannel) != 0)
4715 {
4716 gamma=0.0;
4717 j=0;
4718 for (v=0; v < (long) width; v++)
4719 {
4720 for (u=0; u < (long) width; u++)
4721 {
4722 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4723 {
4724 pixel.opacity+=(*k)*(p+u+j)->opacity;
4725 gamma+=(*k);
4726 k++;
4727 }
4728 }
4729 j+=image->columns+width;
4730 }
4731 if (gamma != 0.0)
4732 {
4733 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4734 gamma);
cristyddd82202009-11-03 20:14:50 +00004735 q->opacity=RoundToQuantum(pixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00004736 }
4737 }
4738 if (((channel & IndexChannel) != 0) &&
4739 (image->colorspace == CMYKColorspace))
4740 {
4741 gamma=0.0;
4742 j=0;
4743 for (v=0; v < (long) width; v++)
4744 {
4745 for (u=0; u < (long) width; u++)
4746 {
4747 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4748 {
4749 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
4750 (p+u+j)->opacity));
4751 pixel.index+=(*k)*alpha*indexes[x+u+j];
4752 gamma+=(*k);
4753 k++;
4754 }
4755 }
4756 j+=image->columns+width;
4757 }
4758 if (gamma != 0.0)
4759 {
4760 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4761 gamma);
cristyddd82202009-11-03 20:14:50 +00004762 blur_indexes[x]=RoundToQuantum(gamma*pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00004763 }
4764 }
4765 }
4766 p++;
4767 q++;
4768 }
4769 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
4770 if (sync == MagickFalse)
4771 status=MagickFalse;
4772 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4773 {
4774 MagickBooleanType
4775 proceed;
4776
cristyb5d5f722009-11-04 03:03:49 +00004777#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004778 #pragma omp critical (MagickCore_SelectiveBlurImageChannel)
4779#endif
4780 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress++,
4781 image->rows);
4782 if (proceed == MagickFalse)
4783 status=MagickFalse;
4784 }
4785 }
4786 blur_image->type=image->type;
4787 blur_view=DestroyCacheView(blur_view);
4788 image_view=DestroyCacheView(image_view);
4789 kernel=(double *) RelinquishMagickMemory(kernel);
4790 if (status == MagickFalse)
4791 blur_image=DestroyImage(blur_image);
4792 return(blur_image);
4793}
4794
4795/*
4796%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4797% %
4798% %
4799% %
4800% S h a d e I m a g e %
4801% %
4802% %
4803% %
4804%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4805%
4806% ShadeImage() shines a distant light on an image to create a
4807% three-dimensional effect. You control the positioning of the light with
4808% azimuth and elevation; azimuth is measured in degrees off the x axis
4809% and elevation is measured in pixels above the Z axis.
4810%
4811% The format of the ShadeImage method is:
4812%
4813% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
4814% const double azimuth,const double elevation,ExceptionInfo *exception)
4815%
4816% A description of each parameter follows:
4817%
4818% o image: the image.
4819%
4820% o gray: A value other than zero shades the intensity of each pixel.
4821%
4822% o azimuth, elevation: Define the light source direction.
4823%
4824% o exception: return any errors or warnings in this structure.
4825%
4826*/
4827MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
4828 const double azimuth,const double elevation,ExceptionInfo *exception)
4829{
4830#define ShadeImageTag "Shade/Image"
4831
4832 Image
4833 *shade_image;
4834
4835 long
4836 progress,
4837 y;
4838
4839 MagickBooleanType
4840 status;
4841
4842 PrimaryInfo
4843 light;
4844
4845 CacheView
4846 *image_view,
4847 *shade_view;
4848
4849 /*
4850 Initialize shaded image attributes.
4851 */
4852 assert(image != (const Image *) NULL);
4853 assert(image->signature == MagickSignature);
4854 if (image->debug != MagickFalse)
4855 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4856 assert(exception != (ExceptionInfo *) NULL);
4857 assert(exception->signature == MagickSignature);
4858 shade_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
4859 if (shade_image == (Image *) NULL)
4860 return((Image *) NULL);
4861 if (SetImageStorageClass(shade_image,DirectClass) == MagickFalse)
4862 {
4863 InheritException(exception,&shade_image->exception);
4864 shade_image=DestroyImage(shade_image);
4865 return((Image *) NULL);
4866 }
4867 /*
4868 Compute the light vector.
4869 */
4870 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
4871 cos(DegreesToRadians(elevation));
4872 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
4873 cos(DegreesToRadians(elevation));
4874 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
4875 /*
4876 Shade image.
4877 */
4878 status=MagickTrue;
4879 progress=0;
4880 image_view=AcquireCacheView(image);
4881 shade_view=AcquireCacheView(shade_image);
cristyb5d5f722009-11-04 03:03:49 +00004882#if defined(MAGICKCORE_OPENMP_SUPPORT)
4883 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004884#endif
4885 for (y=0; y < (long) image->rows; y++)
4886 {
4887 MagickRealType
4888 distance,
4889 normal_distance,
4890 shade;
4891
4892 PrimaryInfo
4893 normal;
4894
4895 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004896 *restrict p,
4897 *restrict s0,
4898 *restrict s1,
4899 *restrict s2;
cristy3ed852e2009-09-05 21:47:34 +00004900
4901 register long
4902 x;
4903
4904 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004905 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004906
4907 if (status == MagickFalse)
4908 continue;
4909 p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception);
4910 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
4911 exception);
4912 if ((p == (PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
4913 {
4914 status=MagickFalse;
4915 continue;
4916 }
4917 /*
4918 Shade this row of pixels.
4919 */
4920 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
4921 s0=p+1;
4922 s1=s0+image->columns+2;
4923 s2=s1+image->columns+2;
4924 for (x=0; x < (long) image->columns; x++)
4925 {
4926 /*
4927 Determine the surface normal and compute shading.
4928 */
4929 normal.x=(double) (PixelIntensity(s0-1)+PixelIntensity(s1-1)+
4930 PixelIntensity(s2-1)-PixelIntensity(s0+1)-PixelIntensity(s1+1)-
4931 PixelIntensity(s2+1));
4932 normal.y=(double) (PixelIntensity(s2-1)+PixelIntensity(s2)+
4933 PixelIntensity(s2+1)-PixelIntensity(s0-1)-PixelIntensity(s0)-
4934 PixelIntensity(s0+1));
4935 if ((normal.x == 0.0) && (normal.y == 0.0))
4936 shade=light.z;
4937 else
4938 {
4939 shade=0.0;
4940 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
4941 if (distance > MagickEpsilon)
4942 {
4943 normal_distance=
4944 normal.x*normal.x+normal.y*normal.y+normal.z*normal.z;
4945 if (normal_distance > (MagickEpsilon*MagickEpsilon))
4946 shade=distance/sqrt((double) normal_distance);
4947 }
4948 }
4949 if (gray != MagickFalse)
4950 {
4951 q->red=(Quantum) shade;
4952 q->green=(Quantum) shade;
4953 q->blue=(Quantum) shade;
4954 }
4955 else
4956 {
4957 q->red=RoundToQuantum(QuantumScale*shade*s1->red);
4958 q->green=RoundToQuantum(QuantumScale*shade*s1->green);
4959 q->blue=RoundToQuantum(QuantumScale*shade*s1->blue);
4960 }
4961 q->opacity=s1->opacity;
4962 s0++;
4963 s1++;
4964 s2++;
4965 q++;
4966 }
4967 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
4968 status=MagickFalse;
4969 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4970 {
4971 MagickBooleanType
4972 proceed;
4973
cristyb5d5f722009-11-04 03:03:49 +00004974#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004975 #pragma omp critical (MagickCore_ShadeImage)
4976#endif
4977 proceed=SetImageProgress(image,ShadeImageTag,progress++,image->rows);
4978 if (proceed == MagickFalse)
4979 status=MagickFalse;
4980 }
4981 }
4982 shade_view=DestroyCacheView(shade_view);
4983 image_view=DestroyCacheView(image_view);
4984 if (status == MagickFalse)
4985 shade_image=DestroyImage(shade_image);
4986 return(shade_image);
4987}
4988
4989/*
4990%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4991% %
4992% %
4993% %
4994% S h a r p e n I m a g e %
4995% %
4996% %
4997% %
4998%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4999%
5000% SharpenImage() sharpens the image. We convolve the image with a Gaussian
5001% operator of the given radius and standard deviation (sigma). For
5002% reasonable results, radius should be larger than sigma. Use a radius of 0
5003% and SharpenImage() selects a suitable radius for you.
5004%
5005% Using a separable kernel would be faster, but the negative weights cancel
5006% out on the corners of the kernel producing often undesirable ringing in the
5007% filtered result; this can be avoided by using a 2D gaussian shaped image
5008% sharpening kernel instead.
5009%
5010% The format of the SharpenImage method is:
5011%
5012% Image *SharpenImage(const Image *image,const double radius,
5013% const double sigma,ExceptionInfo *exception)
5014% Image *SharpenImageChannel(const Image *image,const ChannelType channel,
5015% const double radius,const double sigma,ExceptionInfo *exception)
5016%
5017% A description of each parameter follows:
5018%
5019% o image: the image.
5020%
5021% o channel: the channel type.
5022%
5023% o radius: the radius of the Gaussian, in pixels, not counting the center
5024% pixel.
5025%
5026% o sigma: the standard deviation of the Laplacian, in pixels.
5027%
5028% o exception: return any errors or warnings in this structure.
5029%
5030*/
5031
5032MagickExport Image *SharpenImage(const Image *image,const double radius,
5033 const double sigma,ExceptionInfo *exception)
5034{
5035 Image
5036 *sharp_image;
5037
5038 sharp_image=SharpenImageChannel(image,DefaultChannels,radius,sigma,exception);
5039 return(sharp_image);
5040}
5041
5042MagickExport Image *SharpenImageChannel(const Image *image,
5043 const ChannelType channel,const double radius,const double sigma,
5044 ExceptionInfo *exception)
5045{
5046 double
cristy47e00502009-12-17 19:19:57 +00005047 *kernel,
5048 normalize;
cristy3ed852e2009-09-05 21:47:34 +00005049
5050 Image
5051 *sharp_image;
5052
cristy47e00502009-12-17 19:19:57 +00005053 long
5054 j,
cristy3ed852e2009-09-05 21:47:34 +00005055 u,
5056 v;
5057
cristy47e00502009-12-17 19:19:57 +00005058 register long
5059 i;
5060
cristy3ed852e2009-09-05 21:47:34 +00005061 unsigned long
5062 width;
5063
5064 assert(image != (const Image *) NULL);
5065 assert(image->signature == MagickSignature);
5066 if (image->debug != MagickFalse)
5067 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5068 assert(exception != (ExceptionInfo *) NULL);
5069 assert(exception->signature == MagickSignature);
5070 width=GetOptimalKernelWidth2D(radius,sigma);
5071 kernel=(double *) AcquireQuantumMemory((size_t) width*width,sizeof(*kernel));
5072 if (kernel == (double *) NULL)
5073 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy3ed852e2009-09-05 21:47:34 +00005074 normalize=0.0;
cristy47e00502009-12-17 19:19:57 +00005075 j=(long) width/2;
5076 i=0;
5077 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00005078 {
cristy47e00502009-12-17 19:19:57 +00005079 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00005080 {
cristy47e00502009-12-17 19:19:57 +00005081 kernel[i]=(-exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
5082 (2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00005083 normalize+=kernel[i];
5084 i++;
5085 }
5086 }
5087 kernel[i/2]=(double) ((-2.0)*normalize);
5088 sharp_image=ConvolveImageChannel(image,channel,width,kernel,exception);
5089 kernel=(double *) RelinquishMagickMemory(kernel);
5090 return(sharp_image);
5091}
5092
5093/*
5094%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5095% %
5096% %
5097% %
5098% S p r e a d I m a g e %
5099% %
5100% %
5101% %
5102%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5103%
5104% SpreadImage() is a special effects method that randomly displaces each
5105% pixel in a block defined by the radius parameter.
5106%
5107% The format of the SpreadImage method is:
5108%
5109% Image *SpreadImage(const Image *image,const double radius,
5110% ExceptionInfo *exception)
5111%
5112% A description of each parameter follows:
5113%
5114% o image: the image.
5115%
5116% o radius: Choose a random pixel in a neighborhood of this extent.
5117%
5118% o exception: return any errors or warnings in this structure.
5119%
5120*/
5121MagickExport Image *SpreadImage(const Image *image,const double radius,
5122 ExceptionInfo *exception)
5123{
5124#define SpreadImageTag "Spread/Image"
5125
cristyfa112112010-01-04 17:48:07 +00005126 CacheView
5127 *image_view;
5128
cristy3ed852e2009-09-05 21:47:34 +00005129 Image
5130 *spread_image;
5131
5132 long
5133 progress,
5134 y;
5135
5136 MagickBooleanType
5137 status;
5138
5139 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00005140 bias;
cristy3ed852e2009-09-05 21:47:34 +00005141
5142 RandomInfo
cristyfa112112010-01-04 17:48:07 +00005143 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00005144
5145 ResampleFilter
cristyfa112112010-01-04 17:48:07 +00005146 **restrict resample_filter;
cristy3ed852e2009-09-05 21:47:34 +00005147
5148 unsigned long
5149 width;
5150
cristy3ed852e2009-09-05 21:47:34 +00005151 /*
5152 Initialize spread image attributes.
5153 */
5154 assert(image != (Image *) NULL);
5155 assert(image->signature == MagickSignature);
5156 if (image->debug != MagickFalse)
5157 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5158 assert(exception != (ExceptionInfo *) NULL);
5159 assert(exception->signature == MagickSignature);
5160 spread_image=CloneImage(image,image->columns,image->rows,MagickTrue,
5161 exception);
5162 if (spread_image == (Image *) NULL)
5163 return((Image *) NULL);
5164 if (SetImageStorageClass(spread_image,DirectClass) == MagickFalse)
5165 {
5166 InheritException(exception,&spread_image->exception);
5167 spread_image=DestroyImage(spread_image);
5168 return((Image *) NULL);
5169 }
5170 /*
5171 Spread image.
5172 */
5173 status=MagickTrue;
5174 progress=0;
cristyddd82202009-11-03 20:14:50 +00005175 GetMagickPixelPacket(spread_image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00005176 width=GetOptimalKernelWidth1D(radius,0.5);
5177 resample_filter=AcquireResampleFilterThreadSet(image,MagickTrue,exception);
5178 random_info=AcquireRandomInfoThreadSet();
5179 image_view=AcquireCacheView(spread_image);
cristyb5d5f722009-11-04 03:03:49 +00005180#if defined(MAGICKCORE_OPENMP_SUPPORT)
5181 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00005182#endif
5183 for (y=0; y < (long) spread_image->rows; y++)
5184 {
5185 MagickPixelPacket
5186 pixel;
5187
5188 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00005189 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00005190
5191 register long
5192 id,
5193 x;
5194
5195 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00005196 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00005197
5198 if (status == MagickFalse)
5199 continue;
5200 q=QueueCacheViewAuthenticPixels(image_view,0,y,spread_image->columns,1,
5201 exception);
5202 if (q == (PixelPacket *) NULL)
5203 {
5204 status=MagickFalse;
5205 continue;
5206 }
5207 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristyddd82202009-11-03 20:14:50 +00005208 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00005209 id=GetOpenMPThreadId();
5210 for (x=0; x < (long) spread_image->columns; x++)
5211 {
5212 (void) ResamplePixelColor(resample_filter[id],(double) x+width*
5213 (GetPseudoRandomValue(random_info[id])-0.5),(double) y+width*
5214 (GetPseudoRandomValue(random_info[id])-0.5),&pixel);
5215 SetPixelPacket(spread_image,&pixel,q,indexes+x);
5216 q++;
5217 }
5218 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
5219 status=MagickFalse;
5220 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5221 {
5222 MagickBooleanType
5223 proceed;
5224
cristyb5d5f722009-11-04 03:03:49 +00005225#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00005226 #pragma omp critical (MagickCore_SpreadImage)
5227#endif
5228 proceed=SetImageProgress(image,SpreadImageTag,progress++,image->rows);
5229 if (proceed == MagickFalse)
5230 status=MagickFalse;
5231 }
5232 }
5233 image_view=DestroyCacheView(image_view);
5234 random_info=DestroyRandomInfoThreadSet(random_info);
5235 resample_filter=DestroyResampleFilterThreadSet(resample_filter);
5236 return(spread_image);
5237}
5238
5239/*
5240%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5241% %
5242% %
5243% %
5244% U n s h a r p M a s k I m a g e %
5245% %
5246% %
5247% %
5248%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5249%
5250% UnsharpMaskImage() sharpens one or more image channels. We convolve the
5251% image with a Gaussian operator of the given radius and standard deviation
5252% (sigma). For reasonable results, radius should be larger than sigma. Use a
5253% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
5254%
5255% The format of the UnsharpMaskImage method is:
5256%
5257% Image *UnsharpMaskImage(const Image *image,const double radius,
5258% const double sigma,const double amount,const double threshold,
5259% ExceptionInfo *exception)
5260% Image *UnsharpMaskImageChannel(const Image *image,
5261% const ChannelType channel,const double radius,const double sigma,
5262% const double amount,const double threshold,ExceptionInfo *exception)
5263%
5264% A description of each parameter follows:
5265%
5266% o image: the image.
5267%
5268% o channel: the channel type.
5269%
5270% o radius: the radius of the Gaussian, in pixels, not counting the center
5271% pixel.
5272%
5273% o sigma: the standard deviation of the Gaussian, in pixels.
5274%
5275% o amount: the percentage of the difference between the original and the
5276% blur image that is added back into the original.
5277%
5278% o threshold: the threshold in pixels needed to apply the diffence amount.
5279%
5280% o exception: return any errors or warnings in this structure.
5281%
5282*/
5283
5284MagickExport Image *UnsharpMaskImage(const Image *image,const double radius,
5285 const double sigma,const double amount,const double threshold,
5286 ExceptionInfo *exception)
5287{
5288 Image
5289 *sharp_image;
5290
5291 sharp_image=UnsharpMaskImageChannel(image,DefaultChannels,radius,sigma,amount,
5292 threshold,exception);
5293 return(sharp_image);
5294}
5295
5296MagickExport Image *UnsharpMaskImageChannel(const Image *image,
5297 const ChannelType channel,const double radius,const double sigma,
5298 const double amount,const double threshold,ExceptionInfo *exception)
5299{
5300#define SharpenImageTag "Sharpen/Image"
5301
5302 Image
5303 *unsharp_image;
5304
5305 long
5306 progress,
5307 y;
5308
5309 MagickBooleanType
5310 status;
5311
5312 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00005313 bias;
cristy3ed852e2009-09-05 21:47:34 +00005314
5315 MagickRealType
5316 quantum_threshold;
5317
5318 CacheView
5319 *image_view,
5320 *unsharp_view;
5321
5322 assert(image != (const Image *) NULL);
5323 assert(image->signature == MagickSignature);
5324 if (image->debug != MagickFalse)
5325 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5326 assert(exception != (ExceptionInfo *) NULL);
5327 unsharp_image=BlurImageChannel(image,channel,radius,sigma,exception);
5328 if (unsharp_image == (Image *) NULL)
5329 return((Image *) NULL);
5330 quantum_threshold=(MagickRealType) QuantumRange*threshold;
5331 /*
5332 Unsharp-mask image.
5333 */
5334 status=MagickTrue;
5335 progress=0;
cristyddd82202009-11-03 20:14:50 +00005336 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00005337 image_view=AcquireCacheView(image);
5338 unsharp_view=AcquireCacheView(unsharp_image);
cristyb5d5f722009-11-04 03:03:49 +00005339#if defined(MAGICKCORE_OPENMP_SUPPORT)
5340 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00005341#endif
5342 for (y=0; y < (long) image->rows; y++)
5343 {
5344 MagickPixelPacket
5345 pixel;
5346
5347 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00005348 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00005349
5350 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00005351 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00005352
5353 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00005354 *restrict unsharp_indexes;
cristy3ed852e2009-09-05 21:47:34 +00005355
5356 register long
5357 x;
5358
5359 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00005360 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00005361
5362 if (status == MagickFalse)
5363 continue;
5364 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
5365 q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
5366 exception);
5367 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
5368 {
5369 status=MagickFalse;
5370 continue;
5371 }
5372 indexes=GetCacheViewVirtualIndexQueue(image_view);
5373 unsharp_indexes=GetCacheViewAuthenticIndexQueue(unsharp_view);
cristyddd82202009-11-03 20:14:50 +00005374 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00005375 for (x=0; x < (long) image->columns; x++)
5376 {
5377 if ((channel & RedChannel) != 0)
5378 {
5379 pixel.red=p->red-(MagickRealType) q->red;
5380 if (fabs(2.0*pixel.red) < quantum_threshold)
5381 pixel.red=(MagickRealType) p->red;
5382 else
5383 pixel.red=(MagickRealType) p->red+(pixel.red*amount);
5384 q->red=RoundToQuantum(pixel.red);
5385 }
5386 if ((channel & GreenChannel) != 0)
5387 {
5388 pixel.green=p->green-(MagickRealType) q->green;
5389 if (fabs(2.0*pixel.green) < quantum_threshold)
5390 pixel.green=(MagickRealType) p->green;
5391 else
5392 pixel.green=(MagickRealType) p->green+(pixel.green*amount);
5393 q->green=RoundToQuantum(pixel.green);
5394 }
5395 if ((channel & BlueChannel) != 0)
5396 {
5397 pixel.blue=p->blue-(MagickRealType) q->blue;
5398 if (fabs(2.0*pixel.blue) < quantum_threshold)
5399 pixel.blue=(MagickRealType) p->blue;
5400 else
5401 pixel.blue=(MagickRealType) p->blue+(pixel.blue*amount);
5402 q->blue=RoundToQuantum(pixel.blue);
5403 }
5404 if ((channel & OpacityChannel) != 0)
5405 {
5406 pixel.opacity=p->opacity-(MagickRealType) q->opacity;
5407 if (fabs(2.0*pixel.opacity) < quantum_threshold)
5408 pixel.opacity=(MagickRealType) p->opacity;
5409 else
5410 pixel.opacity=p->opacity+(pixel.opacity*amount);
5411 q->opacity=RoundToQuantum(pixel.opacity);
5412 }
5413 if (((channel & IndexChannel) != 0) &&
5414 (image->colorspace == CMYKColorspace))
5415 {
5416 pixel.index=unsharp_indexes[x]-(MagickRealType) indexes[x];
5417 if (fabs(2.0*pixel.index) < quantum_threshold)
5418 pixel.index=(MagickRealType) unsharp_indexes[x];
5419 else
5420 pixel.index=(MagickRealType) unsharp_indexes[x]+(pixel.index*
5421 amount);
5422 unsharp_indexes[x]=RoundToQuantum(pixel.index);
5423 }
5424 p++;
5425 q++;
5426 }
5427 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
5428 status=MagickFalse;
5429 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5430 {
5431 MagickBooleanType
5432 proceed;
5433
cristyb5d5f722009-11-04 03:03:49 +00005434#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00005435 #pragma omp critical (MagickCore_UnsharpMaskImageChannel)
5436#endif
5437 proceed=SetImageProgress(image,SharpenImageTag,progress++,image->rows);
5438 if (proceed == MagickFalse)
5439 status=MagickFalse;
5440 }
5441 }
5442 unsharp_image->type=image->type;
5443 unsharp_view=DestroyCacheView(unsharp_view);
5444 image_view=DestroyCacheView(image_view);
5445 if (status == MagickFalse)
5446 unsharp_image=DestroyImage(unsharp_image);
5447 return(unsharp_image);
5448}