blob: c6fa13742f69c8247b6ff636297068e8fa57b88d [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
cristyc4c8d132010-01-07 01:58:38 +0000144 CacheView
145 *blur_view,
146 *edge_view,
147 *image_view;
148
cristy3ed852e2009-09-05 21:47:34 +0000149 double
cristy47e00502009-12-17 19:19:57 +0000150 **kernel,
151 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000152
153 Image
154 *blur_image,
155 *edge_image,
156 *gaussian_image;
157
158 long
159 j,
cristy47e00502009-12-17 19:19:57 +0000160 k,
cristy3ed852e2009-09-05 21:47:34 +0000161 progress,
cristy47e00502009-12-17 19:19:57 +0000162 u,
163 v,
cristy3ed852e2009-09-05 21:47:34 +0000164 y;
165
166 MagickBooleanType
167 status;
168
169 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +0000170 bias;
cristy3ed852e2009-09-05 21:47:34 +0000171
cristy3ed852e2009-09-05 21:47:34 +0000172 register long
cristy47e00502009-12-17 19:19:57 +0000173 i;
cristy3ed852e2009-09-05 21:47:34 +0000174
175 unsigned long
176 width;
177
cristy3ed852e2009-09-05 21:47:34 +0000178 assert(image != (const Image *) NULL);
179 assert(image->signature == MagickSignature);
180 if (image->debug != MagickFalse)
181 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
182 assert(exception != (ExceptionInfo *) NULL);
183 assert(exception->signature == MagickSignature);
184 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
185 if (blur_image == (Image *) NULL)
186 return((Image *) NULL);
187 if (fabs(sigma) <= MagickEpsilon)
188 return(blur_image);
189 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
190 {
191 InheritException(exception,&blur_image->exception);
192 blur_image=DestroyImage(blur_image);
193 return((Image *) NULL);
194 }
195 /*
196 Edge detect the image brighness channel, level, blur, and level again.
197 */
198 edge_image=EdgeImage(image,radius,exception);
199 if (edge_image == (Image *) NULL)
200 {
201 blur_image=DestroyImage(blur_image);
202 return((Image *) NULL);
203 }
204 (void) LevelImage(edge_image,"20%,95%");
205 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,exception);
206 if (gaussian_image != (Image *) NULL)
207 {
208 edge_image=DestroyImage(edge_image);
209 edge_image=gaussian_image;
210 }
211 (void) LevelImage(edge_image,"10%,95%");
212 /*
213 Create a set of kernels from maximum (radius,sigma) to minimum.
214 */
215 width=GetOptimalKernelWidth2D(radius,sigma);
216 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
217 if (kernel == (double **) NULL)
218 {
219 edge_image=DestroyImage(edge_image);
220 blur_image=DestroyImage(blur_image);
221 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
222 }
223 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
224 for (i=0; i < (long) width; i+=2)
225 {
226 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
227 sizeof(**kernel));
228 if (kernel[i] == (double *) NULL)
229 break;
cristy47e00502009-12-17 19:19:57 +0000230 normalize=0.0;
231 j=(long) (width-i)/2;
232 k=0;
233 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000234 {
cristy47e00502009-12-17 19:19:57 +0000235 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000236 {
cristy47e00502009-12-17 19:19:57 +0000237 kernel[i][k]=exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
238 (2.0*MagickPI*MagickSigma*MagickSigma);
239 normalize+=kernel[i][k];
240 k++;
cristy3ed852e2009-09-05 21:47:34 +0000241 }
242 }
cristy3ed852e2009-09-05 21:47:34 +0000243 if (fabs(normalize) <= MagickEpsilon)
244 normalize=1.0;
245 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000246 for (k=0; k < (j*j); k++)
247 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000248 }
249 if (i < (long) width)
250 {
251 for (i-=2; i >= 0; i-=2)
252 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
253 kernel=(double **) RelinquishMagickMemory(kernel);
254 edge_image=DestroyImage(edge_image);
255 blur_image=DestroyImage(blur_image);
256 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
257 }
258 /*
259 Adaptively blur image.
260 */
261 status=MagickTrue;
262 progress=0;
cristyddd82202009-11-03 20:14:50 +0000263 GetMagickPixelPacket(image,&bias);
264 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000265 image_view=AcquireCacheView(image);
266 edge_view=AcquireCacheView(edge_image);
267 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000268#if defined(MAGICKCORE_OPENMP_SUPPORT)
269 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000270#endif
271 for (y=0; y < (long) blur_image->rows; y++)
272 {
273 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000274 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000275
276 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000277 *restrict p,
278 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000279
280 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000281 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000282
283 register long
284 x;
285
286 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000287 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000288
289 if (status == MagickFalse)
290 continue;
291 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
292 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
293 exception);
294 if ((r == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
295 {
296 status=MagickFalse;
297 continue;
298 }
299 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
300 for (x=0; x < (long) blur_image->columns; x++)
301 {
302 MagickPixelPacket
303 pixel;
304
305 MagickRealType
306 alpha,
307 gamma;
308
309 register const double
cristyc47d1f82009-11-26 01:44:43 +0000310 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000311
312 register long
313 i,
314 u,
315 v;
316
317 gamma=0.0;
318 i=(long) (width*QuantumScale*PixelIntensity(r)+0.5);
319 if (i < 0)
320 i=0;
321 else
322 if (i > (long) width)
323 i=(long) width;
324 if ((i & 0x01) != 0)
325 i--;
326 p=GetCacheViewVirtualPixels(image_view,x-((long) (width-i)/2L),y-(long)
327 ((width-i)/2L),width-i,width-i,exception);
328 if (p == (const PixelPacket *) NULL)
329 break;
330 indexes=GetCacheViewVirtualIndexQueue(image_view);
cristyddd82202009-11-03 20:14:50 +0000331 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +0000332 k=kernel[i];
333 for (v=0; v < (long) (width-i); v++)
334 {
335 for (u=0; u < (long) (width-i); u++)
336 {
337 alpha=1.0;
338 if (((channel & OpacityChannel) != 0) &&
339 (image->matte != MagickFalse))
cristyc4c8d132010-01-07 01:58:38 +0000340 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
cristyce70c172010-01-07 17:15:30 +0000341 GetOpacityPixelComponent(p)));
cristy3ed852e2009-09-05 21:47:34 +0000342 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000343 pixel.red+=(*k)*alpha*GetRedPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000344 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000345 pixel.green+=(*k)*alpha*GetGreenPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000346 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000347 pixel.blue+=(*k)*alpha*GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000348 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000349 pixel.opacity+=(*k)*GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000350 if (((channel & IndexChannel) != 0) &&
351 (image->colorspace == CMYKColorspace))
352 pixel.index+=(*k)*alpha*indexes[x+(width-i)*v+u];
353 gamma+=(*k)*alpha;
354 k++;
355 p++;
356 }
357 }
358 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
359 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000360 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000361 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000362 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000363 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000364 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000365 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000366 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000367 if (((channel & IndexChannel) != 0) &&
368 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +0000369 blur_indexes[x]=ClampToQuantum(gamma*GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000370 q++;
371 r++;
372 }
373 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
374 status=MagickFalse;
375 if (image->progress_monitor != (MagickProgressMonitor) NULL)
376 {
377 MagickBooleanType
378 proceed;
379
cristyb5d5f722009-11-04 03:03:49 +0000380#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000381 #pragma omp critical (MagickCore_AdaptiveBlurImageChannel)
382#endif
383 proceed=SetImageProgress(image,AdaptiveBlurImageTag,progress++,
384 image->rows);
385 if (proceed == MagickFalse)
386 status=MagickFalse;
387 }
388 }
389 blur_image->type=image->type;
390 blur_view=DestroyCacheView(blur_view);
391 edge_view=DestroyCacheView(edge_view);
392 image_view=DestroyCacheView(image_view);
393 edge_image=DestroyImage(edge_image);
394 for (i=0; i < (long) width; i+=2)
395 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
396 kernel=(double **) RelinquishMagickMemory(kernel);
397 if (status == MagickFalse)
398 blur_image=DestroyImage(blur_image);
399 return(blur_image);
400}
401
402/*
403%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
404% %
405% %
406% %
407% A d a p t i v e S h a r p e n I m a g e %
408% %
409% %
410% %
411%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
412%
413% AdaptiveSharpenImage() adaptively sharpens the image by sharpening more
414% intensely near image edges and less intensely far from edges. We sharpen the
415% image with a Gaussian operator of the given radius and standard deviation
416% (sigma). For reasonable results, radius should be larger than sigma. Use a
417% radius of 0 and AdaptiveSharpenImage() selects a suitable radius for you.
418%
419% The format of the AdaptiveSharpenImage method is:
420%
421% Image *AdaptiveSharpenImage(const Image *image,const double radius,
422% const double sigma,ExceptionInfo *exception)
423% Image *AdaptiveSharpenImageChannel(const Image *image,
424% const ChannelType channel,double radius,const double sigma,
425% ExceptionInfo *exception)
426%
427% A description of each parameter follows:
428%
429% o image: the image.
430%
431% o channel: the channel type.
432%
433% o radius: the radius of the Gaussian, in pixels, not counting the center
434% pixel.
435%
436% o sigma: the standard deviation of the Laplacian, in pixels.
437%
438% o exception: return any errors or warnings in this structure.
439%
440*/
441
442MagickExport Image *AdaptiveSharpenImage(const Image *image,const double radius,
443 const double sigma,ExceptionInfo *exception)
444{
445 Image
446 *sharp_image;
447
448 sharp_image=AdaptiveSharpenImageChannel(image,DefaultChannels,radius,sigma,
449 exception);
450 return(sharp_image);
451}
452
453MagickExport Image *AdaptiveSharpenImageChannel(const Image *image,
454 const ChannelType channel,const double radius,const double sigma,
455 ExceptionInfo *exception)
456{
457#define AdaptiveSharpenImageTag "Convolve/Image"
458#define MagickSigma (fabs(sigma) <= MagickEpsilon ? 1.0 : sigma)
459
cristyc4c8d132010-01-07 01:58:38 +0000460 CacheView
461 *sharp_view,
462 *edge_view,
463 *image_view;
464
cristy3ed852e2009-09-05 21:47:34 +0000465 double
cristy47e00502009-12-17 19:19:57 +0000466 **kernel,
467 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000468
469 Image
470 *sharp_image,
471 *edge_image,
472 *gaussian_image;
473
474 long
475 j,
cristy47e00502009-12-17 19:19:57 +0000476 k,
cristy3ed852e2009-09-05 21:47:34 +0000477 progress,
cristy47e00502009-12-17 19:19:57 +0000478 u,
479 v,
cristy3ed852e2009-09-05 21:47:34 +0000480 y;
481
482 MagickBooleanType
483 status;
484
485 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +0000486 bias;
cristy3ed852e2009-09-05 21:47:34 +0000487
cristy3ed852e2009-09-05 21:47:34 +0000488 register long
cristy47e00502009-12-17 19:19:57 +0000489 i;
cristy3ed852e2009-09-05 21:47:34 +0000490
491 unsigned long
492 width;
493
cristy3ed852e2009-09-05 21:47:34 +0000494 assert(image != (const Image *) NULL);
495 assert(image->signature == MagickSignature);
496 if (image->debug != MagickFalse)
497 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
498 assert(exception != (ExceptionInfo *) NULL);
499 assert(exception->signature == MagickSignature);
500 sharp_image=CloneImage(image,0,0,MagickTrue,exception);
501 if (sharp_image == (Image *) NULL)
502 return((Image *) NULL);
503 if (fabs(sigma) <= MagickEpsilon)
504 return(sharp_image);
505 if (SetImageStorageClass(sharp_image,DirectClass) == MagickFalse)
506 {
507 InheritException(exception,&sharp_image->exception);
508 sharp_image=DestroyImage(sharp_image);
509 return((Image *) NULL);
510 }
511 /*
512 Edge detect the image brighness channel, level, sharp, and level again.
513 */
514 edge_image=EdgeImage(image,radius,exception);
515 if (edge_image == (Image *) NULL)
516 {
517 sharp_image=DestroyImage(sharp_image);
518 return((Image *) NULL);
519 }
520 (void) LevelImage(edge_image,"20%,95%");
521 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,exception);
522 if (gaussian_image != (Image *) NULL)
523 {
524 edge_image=DestroyImage(edge_image);
525 edge_image=gaussian_image;
526 }
527 (void) LevelImage(edge_image,"10%,95%");
528 /*
529 Create a set of kernels from maximum (radius,sigma) to minimum.
530 */
531 width=GetOptimalKernelWidth2D(radius,sigma);
532 kernel=(double **) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
533 if (kernel == (double **) NULL)
534 {
535 edge_image=DestroyImage(edge_image);
536 sharp_image=DestroyImage(sharp_image);
537 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
538 }
539 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
540 for (i=0; i < (long) width; i+=2)
541 {
542 kernel[i]=(double *) AcquireQuantumMemory((size_t) (width-i),(width-i)*
543 sizeof(**kernel));
544 if (kernel[i] == (double *) NULL)
545 break;
cristy47e00502009-12-17 19:19:57 +0000546 normalize=0.0;
547 j=(long) (width-i)/2;
548 k=0;
549 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000550 {
cristy47e00502009-12-17 19:19:57 +0000551 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000552 {
cristy47e00502009-12-17 19:19:57 +0000553 kernel[i][k]=(-exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
554 (2.0*MagickPI*MagickSigma*MagickSigma));
555 normalize+=kernel[i][k];
556 k++;
cristy3ed852e2009-09-05 21:47:34 +0000557 }
558 }
cristy3ed852e2009-09-05 21:47:34 +0000559 if (fabs(normalize) <= MagickEpsilon)
560 normalize=1.0;
561 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000562 for (k=0; k < (j*j); k++)
563 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000564 }
565 if (i < (long) width)
566 {
567 for (i-=2; i >= 0; i-=2)
568 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
569 kernel=(double **) RelinquishMagickMemory(kernel);
570 edge_image=DestroyImage(edge_image);
571 sharp_image=DestroyImage(sharp_image);
572 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
573 }
574 /*
575 Adaptively sharpen image.
576 */
577 status=MagickTrue;
578 progress=0;
cristyddd82202009-11-03 20:14:50 +0000579 GetMagickPixelPacket(image,&bias);
580 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000581 image_view=AcquireCacheView(image);
582 edge_view=AcquireCacheView(edge_image);
583 sharp_view=AcquireCacheView(sharp_image);
cristyb5d5f722009-11-04 03:03:49 +0000584#if defined(MAGICKCORE_OPENMP_SUPPORT)
585 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000586#endif
587 for (y=0; y < (long) sharp_image->rows; y++)
588 {
589 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000590 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000591
592 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000593 *restrict p,
594 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000595
596 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000597 *restrict sharp_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000598
599 register long
600 x;
601
602 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000603 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000604
605 if (status == MagickFalse)
606 continue;
607 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
608 q=QueueCacheViewAuthenticPixels(sharp_view,0,y,sharp_image->columns,1,
609 exception);
610 if ((r == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
611 {
612 status=MagickFalse;
613 continue;
614 }
615 sharp_indexes=GetCacheViewAuthenticIndexQueue(sharp_view);
616 for (x=0; x < (long) sharp_image->columns; x++)
617 {
618 MagickPixelPacket
619 pixel;
620
621 MagickRealType
622 alpha,
623 gamma;
624
625 register const double
cristyc47d1f82009-11-26 01:44:43 +0000626 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000627
628 register long
629 i,
630 u,
631 v;
632
633 gamma=0.0;
634 i=(long) (width*(QuantumRange-QuantumScale*PixelIntensity(r))+0.5);
635 if (i < 0)
636 i=0;
637 else
638 if (i > (long) width)
639 i=(long) width;
640 if ((i & 0x01) != 0)
641 i--;
642 p=GetCacheViewVirtualPixels(image_view,x-((long) (width-i)/2L),y-(long)
643 ((width-i)/2L),width-i,width-i,exception);
644 if (p == (const PixelPacket *) NULL)
645 break;
646 indexes=GetCacheViewVirtualIndexQueue(image_view);
647 k=kernel[i];
cristyddd82202009-11-03 20:14:50 +0000648 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +0000649 for (v=0; v < (long) (width-i); v++)
650 {
651 for (u=0; u < (long) (width-i); u++)
652 {
653 alpha=1.0;
654 if (((channel & OpacityChannel) != 0) &&
655 (image->matte != MagickFalse))
cristyce70c172010-01-07 17:15:30 +0000656 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
657 GetOpacityPixelComponent(p)));
cristy3ed852e2009-09-05 21:47:34 +0000658 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000659 pixel.red+=(*k)*alpha*GetRedPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000660 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000661 pixel.green+=(*k)*alpha*GetGreenPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000662 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000663 pixel.blue+=(*k)*alpha*GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000664 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000665 pixel.opacity+=(*k)*GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +0000666 if (((channel & IndexChannel) != 0) &&
667 (image->colorspace == CMYKColorspace))
668 pixel.index+=(*k)*alpha*indexes[x+(width-i)*v+u];
669 gamma+=(*k)*alpha;
670 k++;
671 p++;
672 }
673 }
674 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
675 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000676 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000677 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000678 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000679 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000680 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000681 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000682 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000683 if (((channel & IndexChannel) != 0) &&
684 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +0000685 sharp_indexes[x]=ClampToQuantum(gamma*GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000686 q++;
687 r++;
688 }
689 if (SyncCacheViewAuthenticPixels(sharp_view,exception) == MagickFalse)
690 status=MagickFalse;
691 if (image->progress_monitor != (MagickProgressMonitor) NULL)
692 {
693 MagickBooleanType
694 proceed;
695
cristyb5d5f722009-11-04 03:03:49 +0000696#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +0000697 #pragma omp critical (MagickCore_AdaptiveSharpenImageChannel)
698#endif
699 proceed=SetImageProgress(image,AdaptiveSharpenImageTag,progress++,
700 image->rows);
701 if (proceed == MagickFalse)
702 status=MagickFalse;
703 }
704 }
705 sharp_image->type=image->type;
706 sharp_view=DestroyCacheView(sharp_view);
707 edge_view=DestroyCacheView(edge_view);
708 image_view=DestroyCacheView(image_view);
709 edge_image=DestroyImage(edge_image);
710 for (i=0; i < (long) width; i+=2)
711 kernel[i]=(double *) RelinquishMagickMemory(kernel[i]);
712 kernel=(double **) RelinquishMagickMemory(kernel);
713 if (status == MagickFalse)
714 sharp_image=DestroyImage(sharp_image);
715 return(sharp_image);
716}
717
718/*
719%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
720% %
721% %
722% %
723% B l u r I m a g e %
724% %
725% %
726% %
727%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
728%
729% BlurImage() blurs an image. We convolve the image with a Gaussian operator
730% of the given radius and standard deviation (sigma). For reasonable results,
731% the radius should be larger than sigma. Use a radius of 0 and BlurImage()
732% selects a suitable radius for you.
733%
734% BlurImage() differs from GaussianBlurImage() in that it uses a separable
735% kernel which is faster but mathematically equivalent to the non-separable
736% kernel.
737%
738% The format of the BlurImage method is:
739%
740% Image *BlurImage(const Image *image,const double radius,
741% const double sigma,ExceptionInfo *exception)
742% Image *BlurImageChannel(const Image *image,const ChannelType channel,
743% const double radius,const double sigma,ExceptionInfo *exception)
744%
745% A description of each parameter follows:
746%
747% o image: the image.
748%
749% o channel: the channel type.
750%
751% o radius: the radius of the Gaussian, in pixels, not counting the center
752% pixel.
753%
754% o sigma: the standard deviation of the Gaussian, in pixels.
755%
756% o exception: return any errors or warnings in this structure.
757%
758*/
759
760MagickExport Image *BlurImage(const Image *image,const double radius,
761 const double sigma,ExceptionInfo *exception)
762{
763 Image
764 *blur_image;
765
766 blur_image=BlurImageChannel(image,DefaultChannels,radius,sigma,exception);
767 return(blur_image);
768}
769
cristy47e00502009-12-17 19:19:57 +0000770static double *GetBlurKernel(const unsigned long width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +0000771{
cristy3ed852e2009-09-05 21:47:34 +0000772 double
cristy47e00502009-12-17 19:19:57 +0000773 *kernel,
774 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000775
776 long
cristy47e00502009-12-17 19:19:57 +0000777 j,
778 k;
cristy3ed852e2009-09-05 21:47:34 +0000779
780 register long
781 i;
782
783 /*
784 Generate a 1-D convolution kernel.
785 */
786 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
787 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
788 if (kernel == (double *) NULL)
789 return(0);
cristy3ed852e2009-09-05 21:47:34 +0000790 normalize=0.0;
cristy47e00502009-12-17 19:19:57 +0000791 j=(long) width/2;
792 i=0;
793 for (k=(-j); k <= j; k++)
794 {
cristyf267c722009-12-18 00:07:22 +0000795 kernel[i]=exp(-((double) k*k)/(2.0*MagickSigma*MagickSigma))/
cristy47e00502009-12-17 19:19:57 +0000796 (MagickSQ2PI*MagickSigma);
cristy3ed852e2009-09-05 21:47:34 +0000797 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +0000798 i++;
799 }
cristy3ed852e2009-09-05 21:47:34 +0000800 for (i=0; i < (long) width; i++)
801 kernel[i]/=normalize;
802 return(kernel);
803}
804
805MagickExport Image *BlurImageChannel(const Image *image,
806 const ChannelType channel,const double radius,const double sigma,
807 ExceptionInfo *exception)
808{
809#define BlurImageTag "Blur/Image"
810
cristyc4c8d132010-01-07 01:58:38 +0000811 CacheView
812 *blur_view,
813 *image_view;
814
cristy3ed852e2009-09-05 21:47:34 +0000815 double
816 *kernel;
817
818 Image
819 *blur_image;
820
821 long
822 progress,
823 x,
824 y;
825
826 MagickBooleanType
827 status;
828
829 MagickPixelPacket
cristy3ed852e2009-09-05 21:47:34 +0000830 bias;
831
832 register long
833 i;
834
835 unsigned long
836 width;
837
cristy3ed852e2009-09-05 21:47:34 +0000838 /*
839 Initialize blur image attributes.
840 */
841 assert(image != (Image *) NULL);
842 assert(image->signature == MagickSignature);
843 if (image->debug != MagickFalse)
844 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
845 assert(exception != (ExceptionInfo *) NULL);
846 assert(exception->signature == MagickSignature);
847 blur_image=CloneImage(image,0,0,MagickTrue,exception);
848 if (blur_image == (Image *) NULL)
849 return((Image *) NULL);
850 if (fabs(sigma) <= MagickEpsilon)
851 return(blur_image);
852 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
853 {
854 InheritException(exception,&blur_image->exception);
855 blur_image=DestroyImage(blur_image);
856 return((Image *) NULL);
857 }
858 width=GetOptimalKernelWidth1D(radius,sigma);
859 kernel=GetBlurKernel(width,sigma);
860 if (kernel == (double *) NULL)
861 {
862 blur_image=DestroyImage(blur_image);
863 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
864 }
865 if (image->debug != MagickFalse)
866 {
867 char
868 format[MaxTextExtent],
869 *message;
870
871 register const double
872 *k;
873
874 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
875 " BlurImage with %ld kernel:",width);
876 message=AcquireString("");
877 k=kernel;
878 for (i=0; i < (long) width; i++)
879 {
880 *message='\0';
881 (void) FormatMagickString(format,MaxTextExtent,"%ld: ",i);
882 (void) ConcatenateString(&message,format);
cristy8cd5b312010-01-07 01:10:24 +0000883 (void) FormatMagickString(format,MaxTextExtent,"%.15g ",*k++);
cristy3ed852e2009-09-05 21:47:34 +0000884 (void) ConcatenateString(&message,format);
885 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
886 }
887 message=DestroyString(message);
888 }
889 /*
890 Blur rows.
891 */
892 status=MagickTrue;
893 progress=0;
cristyddd82202009-11-03 20:14:50 +0000894 GetMagickPixelPacket(image,&bias);
895 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +0000896 image_view=AcquireCacheView(image);
897 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +0000898#if defined(MAGICKCORE_OPENMP_SUPPORT)
899 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +0000900#endif
901 for (y=0; y < (long) blur_image->rows; y++)
902 {
903 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000904 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +0000905
906 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000907 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000908
909 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000910 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000911
912 register long
913 x;
914
915 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000916 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000917
918 if (status == MagickFalse)
919 continue;
920 p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y,image->columns+
921 width,1,exception);
922 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
923 exception);
924 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
925 {
926 status=MagickFalse;
927 continue;
928 }
929 indexes=GetCacheViewVirtualIndexQueue(image_view);
930 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
931 for (x=0; x < (long) blur_image->columns; x++)
932 {
933 MagickPixelPacket
934 pixel;
935
936 register const double
cristyc47d1f82009-11-26 01:44:43 +0000937 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +0000938
939 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +0000940 *restrict kernel_pixels;
cristy3ed852e2009-09-05 21:47:34 +0000941
942 register long
943 i;
944
cristyddd82202009-11-03 20:14:50 +0000945 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +0000946 k=kernel;
947 kernel_pixels=p;
948 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
949 {
950 for (i=0; i < (long) width; i++)
951 {
952 pixel.red+=(*k)*kernel_pixels->red;
953 pixel.green+=(*k)*kernel_pixels->green;
954 pixel.blue+=(*k)*kernel_pixels->blue;
955 k++;
956 kernel_pixels++;
957 }
958 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000959 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000960 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000961 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000962 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +0000963 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000964 if ((channel & OpacityChannel) != 0)
965 {
966 k=kernel;
967 kernel_pixels=p;
968 for (i=0; i < (long) width; i++)
969 {
970 pixel.opacity+=(*k)*kernel_pixels->opacity;
971 k++;
972 kernel_pixels++;
973 }
cristyce70c172010-01-07 17:15:30 +0000974 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +0000975 }
976 if (((channel & IndexChannel) != 0) &&
977 (image->colorspace == CMYKColorspace))
978 {
979 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +0000980 *restrict kernel_indexes;
cristy3ed852e2009-09-05 21:47:34 +0000981
982 k=kernel;
983 kernel_indexes=indexes;
984 for (i=0; i < (long) width; i++)
985 {
986 pixel.index+=(*k)*(*kernel_indexes);
987 k++;
988 kernel_indexes++;
989 }
cristyce70c172010-01-07 17:15:30 +0000990 blur_indexes[x]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +0000991 }
992 }
993 else
994 {
995 MagickRealType
996 alpha,
997 gamma;
998
999 gamma=0.0;
1000 for (i=0; i < (long) width; i++)
1001 {
1002 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
1003 kernel_pixels->opacity));
1004 pixel.red+=(*k)*alpha*kernel_pixels->red;
1005 pixel.green+=(*k)*alpha*kernel_pixels->green;
1006 pixel.blue+=(*k)*alpha*kernel_pixels->blue;
1007 gamma+=(*k)*alpha;
1008 k++;
1009 kernel_pixels++;
1010 }
1011 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1012 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001013 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001014 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001015 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001016 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001017 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001018 if ((channel & OpacityChannel) != 0)
1019 {
1020 k=kernel;
1021 kernel_pixels=p;
1022 for (i=0; i < (long) width; i++)
1023 {
1024 pixel.opacity+=(*k)*kernel_pixels->opacity;
1025 k++;
1026 kernel_pixels++;
1027 }
cristyce70c172010-01-07 17:15:30 +00001028 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001029 }
1030 if (((channel & IndexChannel) != 0) &&
1031 (image->colorspace == CMYKColorspace))
1032 {
1033 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001034 *restrict kernel_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001035
1036 k=kernel;
1037 kernel_pixels=p;
1038 kernel_indexes=indexes;
1039 for (i=0; i < (long) width; i++)
1040 {
1041 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
1042 kernel_pixels->opacity));
1043 pixel.index+=(*k)*alpha*(*kernel_indexes);
1044 k++;
1045 kernel_pixels++;
1046 kernel_indexes++;
1047 }
cristyce70c172010-01-07 17:15:30 +00001048 blur_indexes[x]=ClampToQuantum(gamma*GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001049 }
1050 }
1051 p++;
1052 q++;
1053 }
1054 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1055 status=MagickFalse;
1056 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1057 {
1058 MagickBooleanType
1059 proceed;
1060
cristyb5d5f722009-11-04 03:03:49 +00001061#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001062 #pragma omp critical (MagickCore_BlurImageChannel)
1063#endif
1064 proceed=SetImageProgress(image,BlurImageTag,progress++,blur_image->rows+
1065 blur_image->columns);
1066 if (proceed == MagickFalse)
1067 status=MagickFalse;
1068 }
1069 }
1070 blur_view=DestroyCacheView(blur_view);
1071 image_view=DestroyCacheView(image_view);
1072 /*
1073 Blur columns.
1074 */
1075 image_view=AcquireCacheView(blur_image);
1076 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00001077#if defined(MAGICKCORE_OPENMP_SUPPORT)
1078 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00001079#endif
1080 for (x=0; x < (long) blur_image->columns; x++)
1081 {
1082 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001083 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00001084
1085 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001086 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001087
1088 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001089 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001090
1091 register long
1092 y;
1093
1094 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001095 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001096
1097 if (status == MagickFalse)
1098 continue;
1099 p=GetCacheViewVirtualPixels(image_view,x,-((long) width/2L),1,image->rows+
1100 width,exception);
1101 q=GetCacheViewAuthenticPixels(blur_view,x,0,1,blur_image->rows,exception);
1102 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1103 {
1104 status=MagickFalse;
1105 continue;
1106 }
1107 indexes=GetCacheViewVirtualIndexQueue(image_view);
1108 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
1109 for (y=0; y < (long) blur_image->rows; y++)
1110 {
1111 MagickPixelPacket
1112 pixel;
1113
1114 register const double
cristyc47d1f82009-11-26 01:44:43 +00001115 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00001116
1117 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001118 *restrict kernel_pixels;
cristy3ed852e2009-09-05 21:47:34 +00001119
1120 register long
1121 i;
1122
cristyddd82202009-11-03 20:14:50 +00001123 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00001124 k=kernel;
1125 kernel_pixels=p;
1126 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
1127 {
1128 for (i=0; i < (long) width; i++)
1129 {
1130 pixel.red+=(*k)*kernel_pixels->red;
1131 pixel.green+=(*k)*kernel_pixels->green;
1132 pixel.blue+=(*k)*kernel_pixels->blue;
1133 k++;
1134 kernel_pixels++;
1135 }
1136 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001137 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001138 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001139 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001140 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001141 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001142 if ((channel & OpacityChannel) != 0)
1143 {
1144 k=kernel;
1145 kernel_pixels=p;
1146 for (i=0; i < (long) width; i++)
1147 {
1148 pixel.opacity+=(*k)*kernel_pixels->opacity;
1149 k++;
1150 kernel_pixels++;
1151 }
cristyce70c172010-01-07 17:15:30 +00001152 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001153 }
1154 if (((channel & IndexChannel) != 0) &&
1155 (image->colorspace == CMYKColorspace))
1156 {
1157 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001158 *restrict kernel_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001159
1160 k=kernel;
1161 kernel_indexes=indexes;
1162 for (i=0; i < (long) width; i++)
1163 {
1164 pixel.index+=(*k)*(*kernel_indexes);
1165 k++;
1166 kernel_indexes++;
1167 }
cristyce70c172010-01-07 17:15:30 +00001168 blur_indexes[y]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00001169 }
1170 }
1171 else
1172 {
1173 MagickRealType
1174 alpha,
1175 gamma;
1176
1177 gamma=0.0;
1178 for (i=0; i < (long) width; i++)
1179 {
1180 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
1181 kernel_pixels->opacity));
1182 pixel.red+=(*k)*alpha*kernel_pixels->red;
1183 pixel.green+=(*k)*alpha*kernel_pixels->green;
1184 pixel.blue+=(*k)*alpha*kernel_pixels->blue;
1185 gamma+=(*k)*alpha;
1186 k++;
1187 kernel_pixels++;
1188 }
1189 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1190 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001191 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001192 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001193 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001194 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001195 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001196 if ((channel & OpacityChannel) != 0)
1197 {
1198 k=kernel;
1199 kernel_pixels=p;
1200 for (i=0; i < (long) width; i++)
1201 {
1202 pixel.opacity+=(*k)*kernel_pixels->opacity;
1203 k++;
1204 kernel_pixels++;
1205 }
cristyce70c172010-01-07 17:15:30 +00001206 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001207 }
1208 if (((channel & IndexChannel) != 0) &&
1209 (image->colorspace == CMYKColorspace))
1210 {
1211 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00001212 *restrict kernel_indexes;
cristy3ed852e2009-09-05 21:47:34 +00001213
1214 k=kernel;
1215 kernel_pixels=p;
1216 kernel_indexes=indexes;
1217 for (i=0; i < (long) width; i++)
1218 {
1219 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
1220 kernel_pixels->opacity));
1221 pixel.index+=(*k)*alpha*(*kernel_indexes);
1222 k++;
1223 kernel_pixels++;
1224 kernel_indexes++;
1225 }
cristyce70c172010-01-07 17:15:30 +00001226 blur_indexes[y]=ClampToQuantum(gamma*GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00001227 }
1228 }
1229 p++;
1230 q++;
1231 }
1232 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1233 status=MagickFalse;
1234 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1235 {
1236 MagickBooleanType
1237 proceed;
1238
cristyb5d5f722009-11-04 03:03:49 +00001239#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001240 #pragma omp critical (MagickCore_BlurImageChannel)
1241#endif
1242 proceed=SetImageProgress(image,BlurImageTag,progress++,blur_image->rows+
1243 blur_image->columns);
1244 if (proceed == MagickFalse)
1245 status=MagickFalse;
1246 }
1247 }
1248 blur_view=DestroyCacheView(blur_view);
1249 image_view=DestroyCacheView(image_view);
1250 kernel=(double *) RelinquishMagickMemory(kernel);
1251 if (status == MagickFalse)
1252 blur_image=DestroyImage(blur_image);
1253 blur_image->type=image->type;
1254 return(blur_image);
1255}
1256
1257/*
1258%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1259% %
1260% %
1261% %
cristyfccdab92009-11-30 16:43:57 +00001262% C o n v o l v e I m a g e %
1263% %
1264% %
1265% %
1266%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1267%
1268% ConvolveImage() applies a custom convolution kernel to the image.
1269%
1270% The format of the ConvolveImage method is:
1271%
1272% Image *ConvolveImage(const Image *image,const unsigned long order,
1273% const double *kernel,ExceptionInfo *exception)
1274% Image *ConvolveImageChannel(const Image *image,const ChannelType channel,
1275% const unsigned long order,const double *kernel,
1276% ExceptionInfo *exception)
1277%
1278% A description of each parameter follows:
1279%
1280% o image: the image.
1281%
1282% o channel: the channel type.
1283%
1284% o order: the number of columns and rows in the filter kernel.
1285%
1286% o kernel: An array of double representing the convolution kernel.
1287%
1288% o exception: return any errors or warnings in this structure.
1289%
1290*/
1291
1292MagickExport Image *ConvolveImage(const Image *image,const unsigned long order,
1293 const double *kernel,ExceptionInfo *exception)
1294{
1295 Image
1296 *convolve_image;
1297
1298 convolve_image=ConvolveImageChannel(image,DefaultChannels,order,kernel,
1299 exception);
1300 return(convolve_image);
1301}
1302
1303MagickExport Image *ConvolveImageChannel(const Image *image,
1304 const ChannelType channel,const unsigned long order,const double *kernel,
1305 ExceptionInfo *exception)
1306{
1307#define ConvolveImageTag "Convolve/Image"
1308
cristyc4c8d132010-01-07 01:58:38 +00001309 CacheView
1310 *convolve_view,
1311 *image_view;
1312
cristyfccdab92009-11-30 16:43:57 +00001313 double
1314 *normal_kernel;
1315
1316 Image
1317 *convolve_image;
1318
1319 long
1320 progress,
1321 y;
1322
1323 MagickBooleanType
1324 status;
1325
1326 MagickPixelPacket
1327 bias;
1328
1329 MagickRealType
1330 gamma;
1331
1332 register long
1333 i;
1334
1335 unsigned long
1336 width;
1337
cristyfccdab92009-11-30 16:43:57 +00001338 /*
1339 Initialize convolve image attributes.
1340 */
1341 assert(image != (Image *) NULL);
1342 assert(image->signature == MagickSignature);
1343 if (image->debug != MagickFalse)
1344 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1345 assert(exception != (ExceptionInfo *) NULL);
1346 assert(exception->signature == MagickSignature);
1347 width=order;
1348 if ((width % 2) == 0)
1349 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
1350 convolve_image=CloneImage(image,0,0,MagickTrue,exception);
1351 if (convolve_image == (Image *) NULL)
1352 return((Image *) NULL);
1353 if (SetImageStorageClass(convolve_image,DirectClass) == MagickFalse)
1354 {
1355 InheritException(exception,&convolve_image->exception);
1356 convolve_image=DestroyImage(convolve_image);
1357 return((Image *) NULL);
1358 }
1359 if (image->debug != MagickFalse)
1360 {
1361 char
1362 format[MaxTextExtent],
1363 *message;
1364
1365 long
1366 u,
1367 v;
1368
1369 register const double
1370 *k;
1371
1372 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
1373 " ConvolveImage with %ldx%ld kernel:",width,width);
1374 message=AcquireString("");
1375 k=kernel;
1376 for (v=0; v < (long) width; v++)
1377 {
1378 *message='\0';
1379 (void) FormatMagickString(format,MaxTextExtent,"%ld: ",v);
1380 (void) ConcatenateString(&message,format);
1381 for (u=0; u < (long) width; u++)
1382 {
cristy8cd5b312010-01-07 01:10:24 +00001383 (void) FormatMagickString(format,MaxTextExtent,"%.15g ",*k++);
cristyfccdab92009-11-30 16:43:57 +00001384 (void) ConcatenateString(&message,format);
1385 }
1386 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
1387 }
1388 message=DestroyString(message);
1389 }
1390 /*
1391 Normalize kernel.
1392 */
1393 normal_kernel=(double *) AcquireQuantumMemory(width*width,
1394 sizeof(*normal_kernel));
1395 if (normal_kernel == (double *) NULL)
1396 {
1397 convolve_image=DestroyImage(convolve_image);
1398 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1399 }
1400 gamma=0.0;
1401 for (i=0; i < (long) (width*width); i++)
1402 gamma+=kernel[i];
1403 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1404 for (i=0; i < (long) (width*width); i++)
1405 normal_kernel[i]=gamma*kernel[i];
1406 /*
1407 Convolve image.
1408 */
1409 status=MagickTrue;
1410 progress=0;
1411 GetMagickPixelPacket(image,&bias);
1412 SetMagickPixelPacketBias(image,&bias);
1413 image_view=AcquireCacheView(image);
1414 convolve_view=AcquireCacheView(convolve_image);
1415#if defined(MAGICKCORE_OPENMP_SUPPORT)
1416 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
1417#endif
1418 for (y=0; y < (long) image->rows; y++)
1419 {
1420 MagickBooleanType
1421 sync;
1422
1423 register const IndexPacket
1424 *restrict indexes;
1425
1426 register const PixelPacket
1427 *restrict p;
1428
1429 register IndexPacket
1430 *restrict convolve_indexes;
1431
1432 register long
1433 x;
1434
1435 register PixelPacket
1436 *restrict q;
1437
1438 if (status == MagickFalse)
1439 continue;
1440 p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y-(long) (width/
1441 2L),image->columns+width,width,exception);
1442 q=GetCacheViewAuthenticPixels(convolve_view,0,y,convolve_image->columns,1,
1443 exception);
1444 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1445 {
1446 status=MagickFalse;
1447 continue;
1448 }
1449 indexes=GetCacheViewVirtualIndexQueue(image_view);
1450 convolve_indexes=GetCacheViewAuthenticIndexQueue(convolve_view);
1451 for (x=0; x < (long) image->columns; x++)
1452 {
1453 long
1454 v;
1455
1456 MagickPixelPacket
1457 pixel;
1458
1459 register const double
1460 *restrict k;
1461
1462 register const PixelPacket
1463 *restrict kernel_pixels;
1464
1465 register long
1466 u;
1467
1468 pixel=bias;
1469 k=normal_kernel;
1470 kernel_pixels=p;
1471 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
1472 {
1473 for (v=0; v < (long) width; v++)
1474 {
1475 for (u=0; u < (long) width; u++)
1476 {
1477 pixel.red+=(*k)*kernel_pixels[u].red;
1478 pixel.green+=(*k)*kernel_pixels[u].green;
1479 pixel.blue+=(*k)*kernel_pixels[u].blue;
1480 k++;
1481 }
1482 kernel_pixels+=image->columns+width;
1483 }
1484 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001485 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001486 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001487 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001488 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001489 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001490 if ((channel & OpacityChannel) != 0)
1491 {
1492 k=normal_kernel;
1493 kernel_pixels=p;
1494 for (v=0; v < (long) width; v++)
1495 {
1496 for (u=0; u < (long) width; u++)
1497 {
1498 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
1499 k++;
1500 }
1501 kernel_pixels+=image->columns+width;
1502 }
cristyce70c172010-01-07 17:15:30 +00001503 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001504 }
1505 if (((channel & IndexChannel) != 0) &&
1506 (image->colorspace == CMYKColorspace))
1507 {
1508 register const IndexPacket
1509 *restrict kernel_indexes;
1510
1511 k=normal_kernel;
1512 kernel_indexes=indexes;
1513 for (v=0; v < (long) width; v++)
1514 {
1515 for (u=0; u < (long) width; u++)
1516 {
1517 pixel.index+=(*k)*kernel_indexes[u];
1518 k++;
1519 }
1520 kernel_indexes+=image->columns+width;
1521 }
cristyce70c172010-01-07 17:15:30 +00001522 convolve_indexes[x]=ClampToQuantum(pixel.index);
cristyfccdab92009-11-30 16:43:57 +00001523 }
1524 }
1525 else
1526 {
1527 MagickRealType
1528 alpha,
1529 gamma;
1530
1531 gamma=0.0;
1532 for (v=0; v < (long) width; v++)
1533 {
1534 for (u=0; u < (long) width; u++)
1535 {
1536 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
1537 kernel_pixels[u].opacity));
1538 pixel.red+=(*k)*alpha*kernel_pixels[u].red;
1539 pixel.green+=(*k)*alpha*kernel_pixels[u].green;
1540 pixel.blue+=(*k)*alpha*kernel_pixels[u].blue;
cristyfccdab92009-11-30 16:43:57 +00001541 gamma+=(*k)*alpha;
1542 k++;
1543 }
1544 kernel_pixels+=image->columns+width;
1545 }
1546 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
1547 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001548 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001549 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001550 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001551 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00001552 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001553 if ((channel & OpacityChannel) != 0)
1554 {
1555 k=normal_kernel;
1556 kernel_pixels=p;
1557 for (v=0; v < (long) width; v++)
1558 {
1559 for (u=0; u < (long) width; u++)
1560 {
1561 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
1562 k++;
1563 }
1564 kernel_pixels+=image->columns+width;
1565 }
cristyce70c172010-01-07 17:15:30 +00001566 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001567 }
1568 if (((channel & IndexChannel) != 0) &&
1569 (image->colorspace == CMYKColorspace))
1570 {
1571 register const IndexPacket
1572 *restrict kernel_indexes;
1573
1574 k=normal_kernel;
1575 kernel_pixels=p;
1576 kernel_indexes=indexes;
1577 for (v=0; v < (long) width; v++)
1578 {
1579 for (u=0; u < (long) width; u++)
1580 {
1581 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
1582 kernel_pixels[u].opacity));
1583 pixel.index+=(*k)*alpha*kernel_indexes[u];
1584 k++;
1585 }
1586 kernel_pixels+=image->columns+width;
1587 kernel_indexes+=image->columns+width;
1588 }
cristy24b06da2010-01-09 23:05:56 +00001589 convolve_indexes[x]=ClampToQuantum(gamma*
1590 GetIndexPixelComponent(&pixel));
cristyfccdab92009-11-30 16:43:57 +00001591 }
1592 }
1593 p++;
1594 q++;
1595 }
1596 sync=SyncCacheViewAuthenticPixels(convolve_view,exception);
1597 if (sync == MagickFalse)
1598 status=MagickFalse;
1599 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1600 {
1601 MagickBooleanType
1602 proceed;
1603
1604#if defined(MAGICKCORE_OPENMP_SUPPORT)
1605 #pragma omp critical (MagickCore_ConvolveImageChannel)
1606#endif
1607 proceed=SetImageProgress(image,ConvolveImageTag,progress++,image->rows);
1608 if (proceed == MagickFalse)
1609 status=MagickFalse;
1610 }
1611 }
1612 convolve_image->type=image->type;
1613 convolve_view=DestroyCacheView(convolve_view);
1614 image_view=DestroyCacheView(image_view);
1615 normal_kernel=(double *) RelinquishMagickMemory(normal_kernel);
1616 if (status == MagickFalse)
1617 convolve_image=DestroyImage(convolve_image);
1618 return(convolve_image);
1619}
1620
1621/*
1622%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1623% %
1624% %
1625% %
cristy3ed852e2009-09-05 21:47:34 +00001626% D e s p e c k l e I m a g e %
1627% %
1628% %
1629% %
1630%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1631%
1632% DespeckleImage() reduces the speckle noise in an image while perserving the
1633% edges of the original image.
1634%
1635% The format of the DespeckleImage method is:
1636%
1637% Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1638%
1639% A description of each parameter follows:
1640%
1641% o image: the image.
1642%
1643% o exception: return any errors or warnings in this structure.
1644%
1645*/
1646
1647static Quantum **DestroyPixelThreadSet(Quantum **pixels)
1648{
1649 register long
1650 i;
1651
1652 assert(pixels != (Quantum **) NULL);
1653 for (i=0; i < (long) GetOpenMPMaximumThreads(); i++)
1654 if (pixels[i] != (Quantum *) NULL)
1655 pixels[i]=(Quantum *) RelinquishMagickMemory(pixels[i]);
1656 pixels=(Quantum **) RelinquishAlignedMemory(pixels);
1657 return(pixels);
1658}
1659
1660static Quantum **AcquirePixelThreadSet(const size_t count)
1661{
1662 register long
1663 i;
1664
1665 Quantum
1666 **pixels;
1667
1668 unsigned long
1669 number_threads;
1670
1671 number_threads=GetOpenMPMaximumThreads();
1672 pixels=(Quantum **) AcquireAlignedMemory(number_threads,sizeof(*pixels));
1673 if (pixels == (Quantum **) NULL)
1674 return((Quantum **) NULL);
1675 (void) ResetMagickMemory(pixels,0,number_threads*sizeof(*pixels));
1676 for (i=0; i < (long) number_threads; i++)
1677 {
1678 pixels[i]=(Quantum *) AcquireQuantumMemory(count,sizeof(**pixels));
1679 if (pixels[i] == (Quantum *) NULL)
1680 return(DestroyPixelThreadSet(pixels));
1681 }
1682 return(pixels);
1683}
1684
1685static void Hull(const long x_offset,const long y_offset,
1686 const unsigned long columns,const unsigned long rows,Quantum *f,Quantum *g,
1687 const int polarity)
1688{
1689 long
1690 y;
1691
1692 MagickRealType
1693 v;
1694
1695 register long
1696 x;
1697
1698 register Quantum
1699 *p,
1700 *q,
1701 *r,
1702 *s;
1703
1704 assert(f != (Quantum *) NULL);
1705 assert(g != (Quantum *) NULL);
1706 p=f+(columns+2);
1707 q=g+(columns+2);
1708 r=p+(y_offset*((long) columns+2)+x_offset);
1709 for (y=0; y < (long) rows; y++)
1710 {
1711 p++;
1712 q++;
1713 r++;
1714 if (polarity > 0)
1715 for (x=(long) columns; x != 0; x--)
1716 {
1717 v=(MagickRealType) (*p);
1718 if ((MagickRealType) *r >= (v+(MagickRealType) ScaleCharToQuantum(2)))
1719 v+=ScaleCharToQuantum(1);
1720 *q=(Quantum) v;
1721 p++;
1722 q++;
1723 r++;
1724 }
1725 else
1726 for (x=(long) columns; x != 0; x--)
1727 {
1728 v=(MagickRealType) (*p);
1729 if ((MagickRealType) *r <= (v-(MagickRealType) ScaleCharToQuantum(2)))
1730 v-=(long) ScaleCharToQuantum(1);
1731 *q=(Quantum) v;
1732 p++;
1733 q++;
1734 r++;
1735 }
1736 p++;
1737 q++;
1738 r++;
1739 }
1740 p=f+(columns+2);
1741 q=g+(columns+2);
1742 r=q+(y_offset*((long) columns+2)+x_offset);
1743 s=q-(y_offset*((long) columns+2)+x_offset);
1744 for (y=0; y < (long) rows; y++)
1745 {
1746 p++;
1747 q++;
1748 r++;
1749 s++;
1750 if (polarity > 0)
1751 for (x=(long) columns; x != 0; x--)
1752 {
1753 v=(MagickRealType) (*q);
1754 if (((MagickRealType) *s >=
1755 (v+(MagickRealType) ScaleCharToQuantum(2))) &&
1756 ((MagickRealType) *r > v))
1757 v+=ScaleCharToQuantum(1);
1758 *p=(Quantum) v;
1759 p++;
1760 q++;
1761 r++;
1762 s++;
1763 }
1764 else
1765 for (x=(long) columns; x != 0; x--)
1766 {
1767 v=(MagickRealType) (*q);
1768 if (((MagickRealType) *s <=
1769 (v-(MagickRealType) ScaleCharToQuantum(2))) &&
1770 ((MagickRealType) *r < v))
1771 v-=(MagickRealType) ScaleCharToQuantum(1);
1772 *p=(Quantum) v;
1773 p++;
1774 q++;
1775 r++;
1776 s++;
1777 }
1778 p++;
1779 q++;
1780 r++;
1781 s++;
1782 }
1783}
1784
1785MagickExport Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1786{
1787#define DespeckleImageTag "Despeckle/Image"
1788
cristy2407fc22009-09-11 00:55:25 +00001789 CacheView
1790 *despeckle_view,
1791 *image_view;
1792
cristy3ed852e2009-09-05 21:47:34 +00001793 Image
1794 *despeckle_image;
1795
1796 long
1797 channel;
1798
1799 MagickBooleanType
1800 status;
1801
1802 Quantum
cristyfa112112010-01-04 17:48:07 +00001803 **restrict buffers,
1804 **restrict pixels;
cristy3ed852e2009-09-05 21:47:34 +00001805
1806 size_t
1807 length;
1808
1809 static const int
cristy691a29e2009-09-11 00:44:10 +00001810 X[4] = {0, 1, 1,-1},
1811 Y[4] = {1, 0, 1, 1};
cristy3ed852e2009-09-05 21:47:34 +00001812
cristy3ed852e2009-09-05 21:47:34 +00001813 /*
1814 Allocate despeckled image.
1815 */
1816 assert(image != (const Image *) NULL);
1817 assert(image->signature == MagickSignature);
1818 if (image->debug != MagickFalse)
1819 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1820 assert(exception != (ExceptionInfo *) NULL);
1821 assert(exception->signature == MagickSignature);
1822 despeckle_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1823 exception);
1824 if (despeckle_image == (Image *) NULL)
1825 return((Image *) NULL);
1826 if (SetImageStorageClass(despeckle_image,DirectClass) == MagickFalse)
1827 {
1828 InheritException(exception,&despeckle_image->exception);
1829 despeckle_image=DestroyImage(despeckle_image);
1830 return((Image *) NULL);
1831 }
1832 /*
1833 Allocate image buffers.
1834 */
1835 length=(size_t) ((image->columns+2)*(image->rows+2));
1836 pixels=AcquirePixelThreadSet(length);
1837 buffers=AcquirePixelThreadSet(length);
1838 if ((pixels == (Quantum **) NULL) || (buffers == (Quantum **) NULL))
1839 {
1840 if (buffers != (Quantum **) NULL)
1841 buffers=DestroyPixelThreadSet(buffers);
1842 if (pixels != (Quantum **) NULL)
1843 pixels=DestroyPixelThreadSet(pixels);
1844 despeckle_image=DestroyImage(despeckle_image);
1845 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1846 }
1847 /*
1848 Reduce speckle in the image.
1849 */
1850 status=MagickTrue;
1851 image_view=AcquireCacheView(image);
1852 despeckle_view=AcquireCacheView(despeckle_image);
cristyb5d5f722009-11-04 03:03:49 +00001853#if defined(MAGICKCORE_OPENMP_SUPPORT)
1854 #pragma omp parallel for schedule(dynamic,4) shared(status)
cristy3ed852e2009-09-05 21:47:34 +00001855#endif
1856 for (channel=0; channel <= 3; channel++)
1857 {
1858 long
1859 j,
1860 y;
1861
1862 register long
1863 i,
cristy691a29e2009-09-11 00:44:10 +00001864 id,
cristy3ed852e2009-09-05 21:47:34 +00001865 x;
1866
1867 register Quantum
1868 *buffer,
1869 *pixel;
1870
1871 if (status == MagickFalse)
1872 continue;
cristy691a29e2009-09-11 00:44:10 +00001873 id=GetOpenMPThreadId();
1874 pixel=pixels[id];
cristy3ed852e2009-09-05 21:47:34 +00001875 (void) ResetMagickMemory(pixel,0,length*sizeof(*pixel));
cristy691a29e2009-09-11 00:44:10 +00001876 buffer=buffers[id];
cristy3ed852e2009-09-05 21:47:34 +00001877 j=(long) image->columns+2;
1878 for (y=0; y < (long) image->rows; y++)
1879 {
1880 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001881 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001882
1883 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1884 if (p == (const PixelPacket *) NULL)
1885 break;
1886 j++;
1887 for (x=0; x < (long) image->columns; x++)
1888 {
1889 switch (channel)
1890 {
cristyce70c172010-01-07 17:15:30 +00001891 case 0: pixel[j]=GetRedPixelComponent(p); break;
1892 case 1: pixel[j]=GetGreenPixelComponent(p); break;
1893 case 2: pixel[j]=GetBluePixelComponent(p); break;
1894 case 3: pixel[j]=GetOpacityPixelComponent(p); break;
cristy3ed852e2009-09-05 21:47:34 +00001895 default: break;
1896 }
1897 p++;
1898 j++;
1899 }
1900 j++;
1901 }
cristy3ed852e2009-09-05 21:47:34 +00001902 (void) ResetMagickMemory(buffer,0,length*sizeof(*buffer));
1903 for (i=0; i < 4; i++)
1904 {
1905 Hull(X[i],Y[i],image->columns,image->rows,pixel,buffer,1);
1906 Hull(-X[i],-Y[i],image->columns,image->rows,pixel,buffer,1);
1907 Hull(-X[i],-Y[i],image->columns,image->rows,pixel,buffer,-1);
1908 Hull(X[i],Y[i],image->columns,image->rows,pixel,buffer,-1);
1909 }
1910 j=(long) image->columns+2;
1911 for (y=0; y < (long) image->rows; y++)
1912 {
1913 MagickBooleanType
1914 sync;
1915
1916 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00001917 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001918
1919 q=GetCacheViewAuthenticPixels(despeckle_view,0,y,despeckle_image->columns,
1920 1,exception);
1921 if (q == (PixelPacket *) NULL)
1922 break;
1923 j++;
1924 for (x=0; x < (long) image->columns; x++)
1925 {
1926 switch (channel)
1927 {
1928 case 0: q->red=pixel[j]; break;
1929 case 1: q->green=pixel[j]; break;
1930 case 2: q->blue=pixel[j]; break;
1931 case 3: q->opacity=pixel[j]; break;
1932 default: break;
1933 }
1934 q++;
1935 j++;
1936 }
1937 sync=SyncCacheViewAuthenticPixels(despeckle_view,exception);
1938 if (sync == MagickFalse)
1939 {
1940 status=MagickFalse;
1941 break;
1942 }
1943 j++;
1944 }
1945 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1946 {
1947 MagickBooleanType
1948 proceed;
1949
cristyb5d5f722009-11-04 03:03:49 +00001950#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00001951 #pragma omp critical (MagickCore_DespeckleImage)
1952#endif
1953 proceed=SetImageProgress(image,DespeckleImageTag,channel,3);
1954 if (proceed == MagickFalse)
1955 status=MagickFalse;
1956 }
1957 }
1958 despeckle_view=DestroyCacheView(despeckle_view);
1959 image_view=DestroyCacheView(image_view);
1960 buffers=DestroyPixelThreadSet(buffers);
1961 pixels=DestroyPixelThreadSet(pixels);
1962 despeckle_image->type=image->type;
1963 if (status == MagickFalse)
1964 despeckle_image=DestroyImage(despeckle_image);
1965 return(despeckle_image);
1966}
1967
1968/*
1969%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1970% %
1971% %
1972% %
1973% E d g e I m a g e %
1974% %
1975% %
1976% %
1977%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1978%
1979% EdgeImage() finds edges in an image. Radius defines the radius of the
1980% convolution filter. Use a radius of 0 and EdgeImage() selects a suitable
1981% radius for you.
1982%
1983% The format of the EdgeImage method is:
1984%
1985% Image *EdgeImage(const Image *image,const double radius,
1986% ExceptionInfo *exception)
1987%
1988% A description of each parameter follows:
1989%
1990% o image: the image.
1991%
1992% o radius: the radius of the pixel neighborhood.
1993%
1994% o exception: return any errors or warnings in this structure.
1995%
1996*/
1997MagickExport Image *EdgeImage(const Image *image,const double radius,
1998 ExceptionInfo *exception)
1999{
2000 Image
2001 *edge_image;
2002
2003 double
2004 *kernel;
2005
2006 register long
2007 i;
2008
2009 unsigned long
2010 width;
2011
2012 assert(image != (const Image *) NULL);
2013 assert(image->signature == MagickSignature);
2014 if (image->debug != MagickFalse)
2015 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2016 assert(exception != (ExceptionInfo *) NULL);
2017 assert(exception->signature == MagickSignature);
2018 width=GetOptimalKernelWidth1D(radius,0.5);
2019 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2020 if (kernel == (double *) NULL)
2021 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2022 for (i=0; i < (long) (width*width); i++)
2023 kernel[i]=(-1.0);
2024 kernel[i/2]=(double) (width*width-1.0);
2025 edge_image=ConvolveImage(image,width,kernel,exception);
2026 kernel=(double *) RelinquishMagickMemory(kernel);
2027 return(edge_image);
2028}
2029
2030/*
2031%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2032% %
2033% %
2034% %
2035% E m b o s s I m a g e %
2036% %
2037% %
2038% %
2039%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2040%
2041% EmbossImage() returns a grayscale image with a three-dimensional effect.
2042% We convolve the image with a Gaussian operator of the given radius and
2043% standard deviation (sigma). For reasonable results, radius should be
2044% larger than sigma. Use a radius of 0 and Emboss() selects a suitable
2045% radius for you.
2046%
2047% The format of the EmbossImage method is:
2048%
2049% Image *EmbossImage(const Image *image,const double radius,
2050% const double sigma,ExceptionInfo *exception)
2051%
2052% A description of each parameter follows:
2053%
2054% o image: the image.
2055%
2056% o radius: the radius of the pixel neighborhood.
2057%
2058% o sigma: the standard deviation of the Gaussian, in pixels.
2059%
2060% o exception: return any errors or warnings in this structure.
2061%
2062*/
2063MagickExport Image *EmbossImage(const Image *image,const double radius,
2064 const double sigma,ExceptionInfo *exception)
2065{
2066 double
2067 *kernel;
2068
2069 Image
2070 *emboss_image;
2071
2072 long
cristy47e00502009-12-17 19:19:57 +00002073 j,
2074 k,
cristy3ed852e2009-09-05 21:47:34 +00002075 u,
2076 v;
2077
cristy47e00502009-12-17 19:19:57 +00002078 register long
2079 i;
2080
cristy3ed852e2009-09-05 21:47:34 +00002081 unsigned long
2082 width;
2083
2084 assert(image != (Image *) NULL);
2085 assert(image->signature == MagickSignature);
2086 if (image->debug != MagickFalse)
2087 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2088 assert(exception != (ExceptionInfo *) NULL);
2089 assert(exception->signature == MagickSignature);
2090 width=GetOptimalKernelWidth2D(radius,sigma);
2091 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2092 if (kernel == (double *) NULL)
2093 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy3ed852e2009-09-05 21:47:34 +00002094 j=(long) width/2;
cristy47e00502009-12-17 19:19:57 +00002095 k=j;
2096 i=0;
2097 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002098 {
cristy47e00502009-12-17 19:19:57 +00002099 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00002100 {
cristy47e00502009-12-17 19:19:57 +00002101 kernel[i]=((u < 0) || (v < 0) ? -8.0 : 8.0)*
2102 exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
2103 (2.0*MagickPI*MagickSigma*MagickSigma);
2104 if (u != k)
cristy3ed852e2009-09-05 21:47:34 +00002105 kernel[i]=0.0;
2106 i++;
2107 }
cristy47e00502009-12-17 19:19:57 +00002108 k--;
cristy3ed852e2009-09-05 21:47:34 +00002109 }
2110 emboss_image=ConvolveImage(image,width,kernel,exception);
2111 if (emboss_image != (Image *) NULL)
2112 (void) EqualizeImage(emboss_image);
2113 kernel=(double *) RelinquishMagickMemory(kernel);
2114 return(emboss_image);
2115}
2116
2117/*
2118%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2119% %
2120% %
2121% %
cristy56a9e512010-01-06 18:18:55 +00002122% F i l t e r I m a g e %
2123% %
2124% %
2125% %
2126%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2127%
2128% FilterImage() applies a custom convolution kernel to the image.
2129%
2130% The format of the FilterImage method is:
2131%
2132% Image *FilterImage(const Image *image,const MagickKernel *kernel,
2133% ExceptionInfo *exception)
2134% Image *FilterImageChannel(const Image *image,const ChannelType channel,
2135% const MagickKernel *kernel,ExceptionInfo *exception)
2136%
2137% A description of each parameter follows:
2138%
2139% o image: the image.
2140%
2141% o channel: the channel type.
2142%
2143% o kernel: the filtering kernel.
2144%
2145% o exception: return any errors or warnings in this structure.
2146%
2147*/
2148
2149MagickExport Image *FilterImage(const Image *image,const MagickKernel *kernel,
2150 ExceptionInfo *exception)
2151{
2152 Image
2153 *filter_image;
2154
2155 filter_image=FilterImageChannel(image,DefaultChannels,kernel,exception);
2156 return(filter_image);
2157}
2158
2159MagickExport Image *FilterImageChannel(const Image *image,
2160 const ChannelType channel,const MagickKernel *kernel,ExceptionInfo *exception)
2161{
2162#define FilterImageTag "Filter/Image"
2163
2164 CacheView
2165 *filter_view,
2166 *image_view;
2167
2168 double
2169 *normal_kernel;
2170
2171 Image
2172 *filter_image;
2173
2174 long
2175 progress,
2176 y;
2177
2178 MagickBooleanType
2179 status;
2180
2181 MagickPixelPacket
2182 bias;
2183
2184 MagickRealType
2185 gamma;
2186
2187 register long
2188 i;
2189
2190 /*
2191 Initialize filter image attributes.
2192 */
2193 assert(image != (Image *) NULL);
2194 assert(image->signature == MagickSignature);
2195 if (image->debug != MagickFalse)
2196 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2197 assert(exception != (ExceptionInfo *) NULL);
2198 assert(exception->signature == MagickSignature);
2199 if ((kernel->width % 2) == 0)
2200 ThrowImageException(OptionError,"KernelWidthMustBeAnOddNumber");
2201 filter_image=CloneImage(image,0,0,MagickTrue,exception);
2202 if (filter_image == (Image *) NULL)
2203 return((Image *) NULL);
2204 if (SetImageStorageClass(filter_image,DirectClass) == MagickFalse)
2205 {
2206 InheritException(exception,&filter_image->exception);
2207 filter_image=DestroyImage(filter_image);
2208 return((Image *) NULL);
2209 }
2210 if (image->debug != MagickFalse)
2211 {
2212 char
2213 format[MaxTextExtent],
2214 *message;
2215
2216 long
2217 u,
2218 v;
2219
2220 register const double
2221 *k;
2222
2223 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
2224 " FilterImage with %ldx%ld kernel:",kernel->width,kernel->height);
2225 message=AcquireString("");
2226 k=kernel->values;
2227 for (v=0; v < (long) kernel->height; v++)
2228 {
2229 *message='\0';
2230 (void) FormatMagickString(format,MaxTextExtent,"%ld: ",v);
2231 (void) ConcatenateString(&message,format);
2232 for (u=0; u < (long) kernel->width; u++)
2233 {
cristy8cd5b312010-01-07 01:10:24 +00002234 (void) FormatMagickString(format,MaxTextExtent,"%.15g ",*k++);
cristy56a9e512010-01-06 18:18:55 +00002235 (void) ConcatenateString(&message,format);
2236 }
2237 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
2238 }
2239 message=DestroyString(message);
2240 }
2241 /*
2242 Normalize kernel.
2243 */
2244 normal_kernel=(double *) AcquireQuantumMemory(kernel->width*kernel->height,
2245 sizeof(*normal_kernel));
2246 if (normal_kernel == (double *) NULL)
2247 {
2248 filter_image=DestroyImage(filter_image);
2249 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2250 }
2251 gamma=0.0;
2252 for (i=0; i < (long) (kernel->width*kernel->height); i++)
2253 gamma+=kernel->values[i];
2254 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
2255 for (i=0; i < (long) (kernel->width*kernel->height); i++)
2256 normal_kernel[i]=gamma*kernel->values[i];
2257 /*
2258 Filter image.
2259 */
2260 status=MagickTrue;
2261 progress=0;
2262 GetMagickPixelPacket(image,&bias);
2263 SetMagickPixelPacketBias(image,&bias);
2264 image_view=AcquireCacheView(image);
2265 filter_view=AcquireCacheView(filter_image);
2266#if defined(MAGICKCORE_OPENMP_SUPPORT)
2267 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
2268#endif
2269 for (y=0; y < (long) image->rows; y++)
2270 {
2271 MagickBooleanType
2272 sync;
2273
2274 register const IndexPacket
2275 *restrict indexes;
2276
2277 register const PixelPacket
2278 *restrict p;
2279
2280 register IndexPacket
2281 *restrict filter_indexes;
2282
2283 register long
2284 x;
2285
2286 register PixelPacket
2287 *restrict q;
2288
2289 if (status == MagickFalse)
2290 continue;
2291 p=GetCacheViewVirtualPixels(image_view,-((long) kernel->width/2L),
2292 y-(long) (kernel->height/2L),image->columns+kernel->width,kernel->height,
2293 exception);
2294 q=GetCacheViewAuthenticPixels(filter_view,0,y,filter_image->columns,1,
2295 exception);
2296 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2297 {
2298 status=MagickFalse;
2299 continue;
2300 }
2301 indexes=GetCacheViewVirtualIndexQueue(image_view);
2302 filter_indexes=GetCacheViewAuthenticIndexQueue(filter_view);
2303 for (x=0; x < (long) image->columns; x++)
2304 {
2305 long
2306 v;
2307
2308 MagickPixelPacket
2309 pixel;
2310
2311 register const double
2312 *restrict k;
2313
2314 register const PixelPacket
2315 *restrict kernel_pixels;
2316
2317 register long
2318 u;
2319
2320 pixel=bias;
2321 k=normal_kernel;
2322 kernel_pixels=p;
2323 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
2324 {
2325 for (v=0; v < (long) kernel->width; v++)
2326 {
2327 for (u=0; u < (long) kernel->height; u++)
2328 {
2329 pixel.red+=(*k)*kernel_pixels[u].red;
2330 pixel.green+=(*k)*kernel_pixels[u].green;
2331 pixel.blue+=(*k)*kernel_pixels[u].blue;
2332 k++;
2333 }
2334 kernel_pixels+=image->columns+kernel->width;
2335 }
2336 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002337 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002338 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002339 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002340 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002341 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002342 if ((channel & OpacityChannel) != 0)
2343 {
2344 k=normal_kernel;
2345 kernel_pixels=p;
2346 for (v=0; v < (long) kernel->width; v++)
2347 {
2348 for (u=0; u < (long) kernel->height; u++)
2349 {
2350 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
2351 k++;
2352 }
2353 kernel_pixels+=image->columns+kernel->width;
2354 }
cristyce70c172010-01-07 17:15:30 +00002355 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002356 }
2357 if (((channel & IndexChannel) != 0) &&
2358 (image->colorspace == CMYKColorspace))
2359 {
2360 register const IndexPacket
2361 *restrict kernel_indexes;
2362
2363 k=normal_kernel;
2364 kernel_indexes=indexes;
2365 for (v=0; v < (long) kernel->width; v++)
2366 {
2367 for (u=0; u < (long) kernel->height; u++)
2368 {
2369 pixel.index+=(*k)*kernel_indexes[u];
2370 k++;
2371 }
2372 kernel_indexes+=image->columns+kernel->width;
2373 }
cristyce70c172010-01-07 17:15:30 +00002374 filter_indexes[x]=ClampToQuantum(pixel.index);
cristy56a9e512010-01-06 18:18:55 +00002375 }
2376 }
2377 else
2378 {
2379 MagickRealType
2380 alpha,
2381 gamma;
2382
2383 gamma=0.0;
2384 for (v=0; v < (long) kernel->width; v++)
2385 {
2386 for (u=0; u < (long) kernel->height; u++)
2387 {
2388 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
2389 kernel_pixels[u].opacity));
2390 pixel.red+=(*k)*alpha*kernel_pixels[u].red;
2391 pixel.green+=(*k)*alpha*kernel_pixels[u].green;
2392 pixel.blue+=(*k)*alpha*kernel_pixels[u].blue;
2393 gamma+=(*k)*alpha;
2394 k++;
2395 }
2396 kernel_pixels+=image->columns+kernel->width;
2397 }
2398 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
2399 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002400 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002401 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002402 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002403 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00002404 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002405 if ((channel & OpacityChannel) != 0)
2406 {
2407 k=normal_kernel;
2408 kernel_pixels=p;
2409 for (v=0; v < (long) kernel->width; v++)
2410 {
2411 for (u=0; u < (long) kernel->height; u++)
2412 {
2413 pixel.opacity+=(*k)*kernel_pixels[u].opacity;
2414 k++;
2415 }
2416 kernel_pixels+=image->columns+kernel->width;
2417 }
cristyce70c172010-01-07 17:15:30 +00002418 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002419 }
2420 if (((channel & IndexChannel) != 0) &&
2421 (image->colorspace == CMYKColorspace))
2422 {
2423 register const IndexPacket
2424 *restrict kernel_indexes;
2425
2426 k=normal_kernel;
2427 kernel_pixels=p;
2428 kernel_indexes=indexes;
2429 for (v=0; v < (long) kernel->width; v++)
2430 {
2431 for (u=0; u < (long) kernel->height; u++)
2432 {
2433 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
2434 kernel_pixels[u].opacity));
2435 pixel.index+=(*k)*alpha*kernel_indexes[u];
2436 k++;
2437 }
2438 kernel_pixels+=image->columns+kernel->width;
2439 kernel_indexes+=image->columns+kernel->width;
2440 }
cristyce70c172010-01-07 17:15:30 +00002441 filter_indexes[x]=ClampToQuantum(gamma*GetIndexPixelComponent(&pixel));
cristy56a9e512010-01-06 18:18:55 +00002442 }
2443 }
2444 p++;
2445 q++;
2446 }
2447 sync=SyncCacheViewAuthenticPixels(filter_view,exception);
2448 if (sync == MagickFalse)
2449 status=MagickFalse;
2450 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2451 {
2452 MagickBooleanType
2453 proceed;
2454
2455#if defined(MAGICKCORE_OPENMP_SUPPORT)
2456 #pragma omp critical (MagickCore_FilterImageChannel)
2457#endif
2458 proceed=SetImageProgress(image,FilterImageTag,progress++,image->rows);
2459 if (proceed == MagickFalse)
2460 status=MagickFalse;
2461 }
2462 }
2463 filter_image->type=image->type;
2464 filter_view=DestroyCacheView(filter_view);
2465 image_view=DestroyCacheView(image_view);
2466 normal_kernel=(double *) RelinquishMagickMemory(normal_kernel);
2467 if (status == MagickFalse)
2468 filter_image=DestroyImage(filter_image);
2469 return(filter_image);
2470}
2471
2472/*
2473%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2474% %
2475% %
2476% %
cristy3ed852e2009-09-05 21:47:34 +00002477% G a u s s i a n B l u r I m a g e %
2478% %
2479% %
2480% %
2481%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2482%
2483% GaussianBlurImage() blurs an image. We convolve the image with a
2484% Gaussian operator of the given radius and standard deviation (sigma).
2485% For reasonable results, the radius should be larger than sigma. Use a
2486% radius of 0 and GaussianBlurImage() selects a suitable radius for you
2487%
2488% The format of the GaussianBlurImage method is:
2489%
2490% Image *GaussianBlurImage(const Image *image,onst double radius,
2491% const double sigma,ExceptionInfo *exception)
2492% Image *GaussianBlurImageChannel(const Image *image,
2493% const ChannelType channel,const double radius,const double sigma,
2494% ExceptionInfo *exception)
2495%
2496% A description of each parameter follows:
2497%
2498% o image: the image.
2499%
2500% o channel: the channel type.
2501%
2502% o radius: the radius of the Gaussian, in pixels, not counting the center
2503% pixel.
2504%
2505% o sigma: the standard deviation of the Gaussian, in pixels.
2506%
2507% o exception: return any errors or warnings in this structure.
2508%
2509*/
2510
2511MagickExport Image *GaussianBlurImage(const Image *image,const double radius,
2512 const double sigma,ExceptionInfo *exception)
2513{
2514 Image
2515 *blur_image;
2516
2517 blur_image=GaussianBlurImageChannel(image,DefaultChannels,radius,sigma,
2518 exception);
2519 return(blur_image);
2520}
2521
2522MagickExport Image *GaussianBlurImageChannel(const Image *image,
2523 const ChannelType channel,const double radius,const double sigma,
2524 ExceptionInfo *exception)
2525{
2526 double
2527 *kernel;
2528
2529 Image
2530 *blur_image;
2531
cristy47e00502009-12-17 19:19:57 +00002532 long
2533 j,
cristy3ed852e2009-09-05 21:47:34 +00002534 u,
2535 v;
2536
cristy47e00502009-12-17 19:19:57 +00002537 register long
2538 i;
2539
cristy3ed852e2009-09-05 21:47:34 +00002540 unsigned long
2541 width;
2542
2543 assert(image != (const Image *) NULL);
2544 assert(image->signature == MagickSignature);
2545 if (image->debug != MagickFalse)
2546 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2547 assert(exception != (ExceptionInfo *) NULL);
2548 assert(exception->signature == MagickSignature);
2549 width=GetOptimalKernelWidth2D(radius,sigma);
2550 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
2551 if (kernel == (double *) NULL)
2552 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy47e00502009-12-17 19:19:57 +00002553 j=(long) width/2;
cristy3ed852e2009-09-05 21:47:34 +00002554 i=0;
cristy47e00502009-12-17 19:19:57 +00002555 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002556 {
cristy47e00502009-12-17 19:19:57 +00002557 for (u=(-j); u <= j; u++)
2558 kernel[i++]=exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
2559 (2.0*MagickPI*MagickSigma*MagickSigma);
cristy3ed852e2009-09-05 21:47:34 +00002560 }
2561 blur_image=ConvolveImageChannel(image,channel,width,kernel,exception);
2562 kernel=(double *) RelinquishMagickMemory(kernel);
2563 return(blur_image);
2564}
2565
2566/*
2567%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2568% %
2569% %
2570% %
2571% M e d i a n F i l t e r I m a g e %
2572% %
2573% %
2574% %
2575%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2576%
2577% MedianFilterImage() applies a digital filter that improves the quality
2578% of a noisy image. Each pixel is replaced by the median in a set of
2579% neighboring pixels as defined by radius.
2580%
2581% The algorithm was contributed by Mike Edmonds and implements an insertion
2582% sort for selecting median color-channel values. For more on this algorithm
2583% see "Skip Lists: A probabilistic Alternative to Balanced Trees" by William
2584% Pugh in the June 1990 of Communications of the ACM.
2585%
2586% The format of the MedianFilterImage method is:
2587%
2588% Image *MedianFilterImage(const Image *image,const double radius,
2589% ExceptionInfo *exception)
2590%
2591% A description of each parameter follows:
2592%
2593% o image: the image.
2594%
2595% o radius: the radius of the pixel neighborhood.
2596%
2597% o exception: return any errors or warnings in this structure.
2598%
2599*/
2600
2601#define MedianListChannels 5
2602
2603typedef struct _MedianListNode
2604{
2605 unsigned long
2606 next[9],
2607 count,
2608 signature;
2609} MedianListNode;
2610
2611typedef struct _MedianSkipList
2612{
2613 long
2614 level;
2615
2616 MedianListNode
2617 *nodes;
2618} MedianSkipList;
2619
2620typedef struct _MedianPixelList
2621{
2622 unsigned long
2623 center,
2624 seed,
2625 signature;
2626
2627 MedianSkipList
2628 lists[MedianListChannels];
2629} MedianPixelList;
2630
2631static MedianPixelList *DestroyMedianPixelList(MedianPixelList *pixel_list)
2632{
2633 register long
2634 i;
2635
2636 if (pixel_list == (MedianPixelList *) NULL)
2637 return((MedianPixelList *) NULL);
2638 for (i=0; i < MedianListChannels; i++)
2639 if (pixel_list->lists[i].nodes != (MedianListNode *) NULL)
2640 pixel_list->lists[i].nodes=(MedianListNode *) RelinquishMagickMemory(
2641 pixel_list->lists[i].nodes);
2642 pixel_list=(MedianPixelList *) RelinquishAlignedMemory(pixel_list);
2643 return(pixel_list);
2644}
2645
2646static MedianPixelList **DestroyMedianPixelListThreadSet(
2647 MedianPixelList **pixel_list)
2648{
2649 register long
2650 i;
2651
2652 assert(pixel_list != (MedianPixelList **) NULL);
2653 for (i=0; i < (long) GetOpenMPMaximumThreads(); i++)
2654 if (pixel_list[i] != (MedianPixelList *) NULL)
2655 pixel_list[i]=DestroyMedianPixelList(pixel_list[i]);
2656 pixel_list=(MedianPixelList **) RelinquishAlignedMemory(pixel_list);
2657 return(pixel_list);
2658}
2659
2660static MedianPixelList *AcquireMedianPixelList(const unsigned long width)
2661{
2662 MedianPixelList
2663 *pixel_list;
2664
2665 register long
2666 i;
2667
2668 pixel_list=(MedianPixelList *) AcquireAlignedMemory(1,sizeof(*pixel_list));
2669 if (pixel_list == (MedianPixelList *) NULL)
2670 return(pixel_list);
2671 (void) ResetMagickMemory((void *) pixel_list,0,sizeof(*pixel_list));
2672 pixel_list->center=width*width/2;
2673 for (i=0; i < MedianListChannels; i++)
2674 {
2675 pixel_list->lists[i].nodes=(MedianListNode *) AcquireQuantumMemory(65537UL,
2676 sizeof(*pixel_list->lists[i].nodes));
2677 if (pixel_list->lists[i].nodes == (MedianListNode *) NULL)
2678 return(DestroyMedianPixelList(pixel_list));
2679 (void) ResetMagickMemory(pixel_list->lists[i].nodes,0,65537UL*
2680 sizeof(*pixel_list->lists[i].nodes));
2681 }
2682 pixel_list->signature=MagickSignature;
2683 return(pixel_list);
2684}
2685
2686static MedianPixelList **AcquireMedianPixelListThreadSet(
2687 const unsigned long width)
2688{
2689 register long
2690 i;
2691
2692 MedianPixelList
2693 **pixel_list;
2694
2695 unsigned long
2696 number_threads;
2697
2698 number_threads=GetOpenMPMaximumThreads();
2699 pixel_list=(MedianPixelList **) AcquireAlignedMemory(number_threads,
2700 sizeof(*pixel_list));
2701 if (pixel_list == (MedianPixelList **) NULL)
2702 return((MedianPixelList **) NULL);
2703 (void) ResetMagickMemory(pixel_list,0,number_threads*sizeof(*pixel_list));
2704 for (i=0; i < (long) number_threads; i++)
2705 {
2706 pixel_list[i]=AcquireMedianPixelList(width);
2707 if (pixel_list[i] == (MedianPixelList *) NULL)
2708 return(DestroyMedianPixelListThreadSet(pixel_list));
2709 }
2710 return(pixel_list);
2711}
2712
2713static void AddNodeMedianPixelList(MedianPixelList *pixel_list,
2714 const long channel,const unsigned long color)
2715{
2716 register long
2717 level;
2718
2719 register MedianSkipList
2720 *list;
2721
2722 unsigned long
2723 search,
2724 update[9];
2725
2726 /*
2727 Initialize the node.
2728 */
2729 list=pixel_list->lists+channel;
2730 list->nodes[color].signature=pixel_list->signature;
2731 list->nodes[color].count=1;
2732 /*
2733 Determine where it belongs in the list.
2734 */
2735 search=65536UL;
2736 for (level=list->level; level >= 0; level--)
2737 {
2738 while (list->nodes[search].next[level] < color)
2739 search=list->nodes[search].next[level];
2740 update[level]=search;
2741 }
2742 /*
2743 Generate a pseudo-random level for this node.
2744 */
2745 for (level=0; ; level++)
2746 {
2747 pixel_list->seed=(pixel_list->seed*42893621L)+1L;
2748 if ((pixel_list->seed & 0x300) != 0x300)
2749 break;
2750 }
2751 if (level > 8)
2752 level=8;
2753 if (level > (list->level+2))
2754 level=list->level+2;
2755 /*
2756 If we're raising the list's level, link back to the root node.
2757 */
2758 while (level > list->level)
2759 {
2760 list->level++;
2761 update[list->level]=65536UL;
2762 }
2763 /*
2764 Link the node into the skip-list.
2765 */
2766 do
2767 {
2768 list->nodes[color].next[level]=list->nodes[update[level]].next[level];
2769 list->nodes[update[level]].next[level]=color;
2770 }
2771 while (level-- > 0);
2772}
2773
2774static MagickPixelPacket GetMedianPixelList(MedianPixelList *pixel_list)
2775{
2776 MagickPixelPacket
2777 pixel;
2778
2779 register long
2780 channel;
2781
2782 register MedianSkipList
2783 *list;
2784
2785 unsigned long
2786 center,
2787 color,
2788 count;
2789
2790 unsigned short
2791 channels[MedianListChannels];
2792
2793 /*
2794 Find the median value for each of the color.
2795 */
2796 center=pixel_list->center;
2797 for (channel=0; channel < 5; channel++)
2798 {
2799 list=pixel_list->lists+channel;
2800 color=65536UL;
2801 count=0;
2802 do
2803 {
2804 color=list->nodes[color].next[0];
2805 count+=list->nodes[color].count;
2806 }
2807 while (count <= center);
2808 channels[channel]=(unsigned short) color;
2809 }
2810 GetMagickPixelPacket((const Image *) NULL,&pixel);
2811 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
2812 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
2813 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
2814 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
2815 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
2816 return(pixel);
2817}
2818
2819static inline void InsertMedianPixelList(const Image *image,
2820 const PixelPacket *pixel,const IndexPacket *indexes,
2821 MedianPixelList *pixel_list)
2822{
2823 unsigned long
2824 signature;
2825
2826 unsigned short
2827 index;
2828
2829 index=ScaleQuantumToShort(pixel->red);
2830 signature=pixel_list->lists[0].nodes[index].signature;
2831 if (signature == pixel_list->signature)
2832 pixel_list->lists[0].nodes[index].count++;
2833 else
2834 AddNodeMedianPixelList(pixel_list,0,index);
2835 index=ScaleQuantumToShort(pixel->green);
2836 signature=pixel_list->lists[1].nodes[index].signature;
2837 if (signature == pixel_list->signature)
2838 pixel_list->lists[1].nodes[index].count++;
2839 else
2840 AddNodeMedianPixelList(pixel_list,1,index);
2841 index=ScaleQuantumToShort(pixel->blue);
2842 signature=pixel_list->lists[2].nodes[index].signature;
2843 if (signature == pixel_list->signature)
2844 pixel_list->lists[2].nodes[index].count++;
2845 else
2846 AddNodeMedianPixelList(pixel_list,2,index);
2847 index=ScaleQuantumToShort(pixel->opacity);
2848 signature=pixel_list->lists[3].nodes[index].signature;
2849 if (signature == pixel_list->signature)
2850 pixel_list->lists[3].nodes[index].count++;
2851 else
2852 AddNodeMedianPixelList(pixel_list,3,index);
2853 if (image->colorspace == CMYKColorspace)
2854 index=ScaleQuantumToShort(*indexes);
2855 signature=pixel_list->lists[4].nodes[index].signature;
2856 if (signature == pixel_list->signature)
2857 pixel_list->lists[4].nodes[index].count++;
2858 else
2859 AddNodeMedianPixelList(pixel_list,4,index);
2860}
2861
2862static void ResetMedianPixelList(MedianPixelList *pixel_list)
2863{
2864 int
2865 level;
2866
2867 register long
2868 channel;
2869
2870 register MedianListNode
2871 *root;
2872
2873 register MedianSkipList
2874 *list;
2875
2876 /*
2877 Reset the skip-list.
2878 */
2879 for (channel=0; channel < 5; channel++)
2880 {
2881 list=pixel_list->lists+channel;
2882 root=list->nodes+65536UL;
2883 list->level=0;
2884 for (level=0; level < 9; level++)
2885 root->next[level]=65536UL;
2886 }
2887 pixel_list->seed=pixel_list->signature++;
2888}
2889
2890MagickExport Image *MedianFilterImage(const Image *image,const double radius,
2891 ExceptionInfo *exception)
2892{
2893#define MedianFilterImageTag "MedianFilter/Image"
2894
cristyc4c8d132010-01-07 01:58:38 +00002895 CacheView
2896 *image_view,
2897 *median_view;
2898
cristy3ed852e2009-09-05 21:47:34 +00002899 Image
2900 *median_image;
2901
2902 long
2903 progress,
2904 y;
2905
2906 MagickBooleanType
2907 status;
2908
2909 MedianPixelList
cristyfa112112010-01-04 17:48:07 +00002910 **restrict pixel_list;
cristy3ed852e2009-09-05 21:47:34 +00002911
2912 unsigned long
2913 width;
2914
cristy3ed852e2009-09-05 21:47:34 +00002915 /*
2916 Initialize median image attributes.
2917 */
2918 assert(image != (Image *) NULL);
2919 assert(image->signature == MagickSignature);
2920 if (image->debug != MagickFalse)
2921 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2922 assert(exception != (ExceptionInfo *) NULL);
2923 assert(exception->signature == MagickSignature);
2924 width=GetOptimalKernelWidth2D(radius,0.5);
2925 if ((image->columns < width) || (image->rows < width))
2926 ThrowImageException(OptionError,"ImageSmallerThanKernelRadius");
2927 median_image=CloneImage(image,image->columns,image->rows,MagickTrue,
2928 exception);
2929 if (median_image == (Image *) NULL)
2930 return((Image *) NULL);
2931 if (SetImageStorageClass(median_image,DirectClass) == MagickFalse)
2932 {
2933 InheritException(exception,&median_image->exception);
2934 median_image=DestroyImage(median_image);
2935 return((Image *) NULL);
2936 }
2937 pixel_list=AcquireMedianPixelListThreadSet(width);
2938 if (pixel_list == (MedianPixelList **) NULL)
2939 {
2940 median_image=DestroyImage(median_image);
2941 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2942 }
2943 /*
2944 Median filter each image row.
2945 */
2946 status=MagickTrue;
2947 progress=0;
2948 image_view=AcquireCacheView(image);
2949 median_view=AcquireCacheView(median_image);
cristyb5d5f722009-11-04 03:03:49 +00002950#if defined(MAGICKCORE_OPENMP_SUPPORT)
2951 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00002952#endif
2953 for (y=0; y < (long) median_image->rows; y++)
2954 {
2955 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002956 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00002957
2958 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002959 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002960
2961 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002962 *restrict median_indexes;
cristy3ed852e2009-09-05 21:47:34 +00002963
2964 register long
2965 id,
2966 x;
2967
2968 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002969 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002970
2971 if (status == MagickFalse)
2972 continue;
2973 p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y-(long) (width/
2974 2L),image->columns+width,width,exception);
2975 q=QueueCacheViewAuthenticPixels(median_view,0,y,median_image->columns,1,
2976 exception);
2977 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
2978 {
2979 status=MagickFalse;
2980 continue;
2981 }
2982 indexes=GetCacheViewVirtualIndexQueue(image_view);
2983 median_indexes=GetCacheViewAuthenticIndexQueue(median_view);
2984 id=GetOpenMPThreadId();
2985 for (x=0; x < (long) median_image->columns; x++)
2986 {
2987 MagickPixelPacket
2988 pixel;
2989
2990 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00002991 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +00002992
2993 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00002994 *restrict s;
cristy3ed852e2009-09-05 21:47:34 +00002995
2996 register long
2997 u,
2998 v;
2999
3000 r=p;
3001 s=indexes+x;
3002 ResetMedianPixelList(pixel_list[id]);
3003 for (v=0; v < (long) width; v++)
3004 {
3005 for (u=0; u < (long) width; u++)
3006 InsertMedianPixelList(image,r+u,s+u,pixel_list[id]);
3007 r+=image->columns+width;
3008 s+=image->columns+width;
3009 }
3010 pixel=GetMedianPixelList(pixel_list[id]);
3011 SetPixelPacket(median_image,&pixel,q,median_indexes+x);
3012 p++;
3013 q++;
3014 }
3015 if (SyncCacheViewAuthenticPixels(median_view,exception) == MagickFalse)
3016 status=MagickFalse;
3017 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3018 {
3019 MagickBooleanType
3020 proceed;
3021
cristyb5d5f722009-11-04 03:03:49 +00003022#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003023 #pragma omp critical (MagickCore_MedianFilterImage)
3024#endif
3025 proceed=SetImageProgress(image,MedianFilterImageTag,progress++,
3026 image->rows);
3027 if (proceed == MagickFalse)
3028 status=MagickFalse;
3029 }
3030 }
3031 median_view=DestroyCacheView(median_view);
3032 image_view=DestroyCacheView(image_view);
3033 pixel_list=DestroyMedianPixelListThreadSet(pixel_list);
3034 return(median_image);
3035}
3036
3037/*
3038%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3039% %
3040% %
3041% %
3042% M o t i o n B l u r I m a g e %
3043% %
3044% %
3045% %
3046%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3047%
3048% MotionBlurImage() simulates motion blur. We convolve the image with a
3049% Gaussian operator of the given radius and standard deviation (sigma).
3050% For reasonable results, radius should be larger than sigma. Use a
3051% radius of 0 and MotionBlurImage() selects a suitable radius for you.
3052% Angle gives the angle of the blurring motion.
3053%
3054% Andrew Protano contributed this effect.
3055%
3056% The format of the MotionBlurImage method is:
3057%
3058% Image *MotionBlurImage(const Image *image,const double radius,
3059% const double sigma,const double angle,ExceptionInfo *exception)
3060% Image *MotionBlurImageChannel(const Image *image,const ChannelType channel,
3061% const double radius,const double sigma,const double angle,
3062% ExceptionInfo *exception)
3063%
3064% A description of each parameter follows:
3065%
3066% o image: the image.
3067%
3068% o channel: the channel type.
3069%
3070% o radius: the radius of the Gaussian, in pixels, not counting the center
3071% o radius: the radius of the Gaussian, in pixels, not counting
3072% the center pixel.
3073%
3074% o sigma: the standard deviation of the Gaussian, in pixels.
3075%
3076% o angle: Apply the effect along this angle.
3077%
3078% o exception: return any errors or warnings in this structure.
3079%
3080*/
3081
cristy47e00502009-12-17 19:19:57 +00003082static double *GetMotionBlurKernel(const unsigned long width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +00003083{
cristy3ed852e2009-09-05 21:47:34 +00003084 double
cristy47e00502009-12-17 19:19:57 +00003085 *kernel,
cristy3ed852e2009-09-05 21:47:34 +00003086 normalize;
3087
3088 register long
3089 i;
3090
3091 /*
cristy47e00502009-12-17 19:19:57 +00003092 Generate a 1-D convolution kernel.
cristy3ed852e2009-09-05 21:47:34 +00003093 */
3094 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
3095 kernel=(double *) AcquireQuantumMemory((size_t) width,sizeof(*kernel));
3096 if (kernel == (double *) NULL)
3097 return(kernel);
cristy3ed852e2009-09-05 21:47:34 +00003098 normalize=0.0;
3099 for (i=0; i < (long) width; i++)
cristy47e00502009-12-17 19:19:57 +00003100 {
3101 kernel[i]=exp((-((double) i*i)/(double) (2.0*MagickSigma*MagickSigma)))/
3102 (MagickSQ2PI*MagickSigma);
cristy3ed852e2009-09-05 21:47:34 +00003103 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +00003104 }
cristy3ed852e2009-09-05 21:47:34 +00003105 for (i=0; i < (long) width; i++)
3106 kernel[i]/=normalize;
3107 return(kernel);
3108}
3109
3110MagickExport Image *MotionBlurImage(const Image *image,const double radius,
3111 const double sigma,const double angle,ExceptionInfo *exception)
3112{
3113 Image
3114 *motion_blur;
3115
3116 motion_blur=MotionBlurImageChannel(image,DefaultChannels,radius,sigma,angle,
3117 exception);
3118 return(motion_blur);
3119}
3120
3121MagickExport Image *MotionBlurImageChannel(const Image *image,
3122 const ChannelType channel,const double radius,const double sigma,
3123 const double angle,ExceptionInfo *exception)
3124{
3125 typedef struct _OffsetInfo
3126 {
3127 long
3128 x,
3129 y;
3130 } OffsetInfo;
3131
cristyc4c8d132010-01-07 01:58:38 +00003132 CacheView
3133 *blur_view,
3134 *image_view;
3135
cristy3ed852e2009-09-05 21:47:34 +00003136 double
3137 *kernel;
3138
3139 Image
3140 *blur_image;
3141
3142 long
3143 progress,
3144 y;
3145
3146 MagickBooleanType
3147 status;
3148
3149 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00003150 bias;
cristy3ed852e2009-09-05 21:47:34 +00003151
3152 OffsetInfo
3153 *offset;
3154
3155 PointInfo
3156 point;
3157
3158 register long
3159 i;
3160
3161 unsigned long
3162 width;
3163
cristy3ed852e2009-09-05 21:47:34 +00003164 assert(image != (Image *) NULL);
3165 assert(image->signature == MagickSignature);
3166 if (image->debug != MagickFalse)
3167 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3168 assert(exception != (ExceptionInfo *) NULL);
3169 width=GetOptimalKernelWidth1D(radius,sigma);
3170 kernel=GetMotionBlurKernel(width,sigma);
3171 if (kernel == (double *) NULL)
3172 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3173 offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset));
3174 if (offset == (OffsetInfo *) NULL)
3175 {
3176 kernel=(double *) RelinquishMagickMemory(kernel);
3177 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3178 }
3179 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3180 if (blur_image == (Image *) NULL)
3181 {
3182 kernel=(double *) RelinquishMagickMemory(kernel);
3183 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
3184 return((Image *) NULL);
3185 }
3186 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3187 {
3188 kernel=(double *) RelinquishMagickMemory(kernel);
3189 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
3190 InheritException(exception,&blur_image->exception);
3191 blur_image=DestroyImage(blur_image);
3192 return((Image *) NULL);
3193 }
3194 point.x=(double) width*sin(DegreesToRadians(angle));
3195 point.y=(double) width*cos(DegreesToRadians(angle));
3196 for (i=0; i < (long) width; i++)
3197 {
3198 offset[i].x=(long) ((i*point.y)/hypot(point.x,point.y)+0.5);
3199 offset[i].y=(long) ((i*point.x)/hypot(point.x,point.y)+0.5);
3200 }
3201 /*
3202 Motion blur image.
3203 */
3204 status=MagickTrue;
3205 progress=0;
cristyddd82202009-11-03 20:14:50 +00003206 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003207 image_view=AcquireCacheView(image);
3208 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003209#if defined(MAGICKCORE_OPENMP_SUPPORT)
3210 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003211#endif
3212 for (y=0; y < (long) image->rows; y++)
3213 {
3214 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003215 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00003216
3217 register long
3218 x;
3219
3220 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00003221 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003222
3223 if (status == MagickFalse)
3224 continue;
3225 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3226 exception);
3227 if (q == (PixelPacket *) NULL)
3228 {
3229 status=MagickFalse;
3230 continue;
3231 }
3232 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
3233 for (x=0; x < (long) image->columns; x++)
3234 {
3235 MagickPixelPacket
3236 qixel;
3237
3238 PixelPacket
3239 pixel;
3240
3241 register double
cristyc47d1f82009-11-26 01:44:43 +00003242 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00003243
3244 register long
3245 i;
3246
3247 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003248 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003249
3250 k=kernel;
cristyddd82202009-11-03 20:14:50 +00003251 qixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00003252 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
3253 {
3254 for (i=0; i < (long) width; i++)
3255 {
3256 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
3257 offset[i].y,&pixel,exception);
3258 qixel.red+=(*k)*pixel.red;
3259 qixel.green+=(*k)*pixel.green;
3260 qixel.blue+=(*k)*pixel.blue;
3261 qixel.opacity+=(*k)*pixel.opacity;
3262 if (image->colorspace == CMYKColorspace)
3263 {
3264 indexes=GetCacheViewVirtualIndexQueue(image_view);
3265 qixel.index+=(*k)*(*indexes);
3266 }
3267 k++;
3268 }
3269 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003270 q->red=ClampToQuantum(qixel.red);
cristy3ed852e2009-09-05 21:47:34 +00003271 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003272 q->green=ClampToQuantum(qixel.green);
cristy3ed852e2009-09-05 21:47:34 +00003273 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003274 q->blue=ClampToQuantum(qixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00003275 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003276 q->opacity=ClampToQuantum(qixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00003277 if (((channel & IndexChannel) != 0) &&
3278 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00003279 blur_indexes[x]=(IndexPacket) ClampToQuantum(qixel.index);
cristy3ed852e2009-09-05 21:47:34 +00003280 }
3281 else
3282 {
3283 MagickRealType
3284 alpha,
3285 gamma;
3286
3287 alpha=0.0;
3288 gamma=0.0;
3289 for (i=0; i < (long) width; i++)
3290 {
3291 (void) GetOneCacheViewVirtualPixel(image_view,x+offset[i].x,y+
3292 offset[i].y,&pixel,exception);
3293 alpha=(MagickRealType) (QuantumScale*(QuantumRange-pixel.opacity));
3294 qixel.red+=(*k)*alpha*pixel.red;
3295 qixel.green+=(*k)*alpha*pixel.green;
3296 qixel.blue+=(*k)*alpha*pixel.blue;
3297 qixel.opacity+=(*k)*pixel.opacity;
3298 if (image->colorspace == CMYKColorspace)
3299 {
3300 indexes=GetCacheViewVirtualIndexQueue(image_view);
3301 qixel.index+=(*k)*alpha*(*indexes);
3302 }
3303 gamma+=(*k)*alpha;
3304 k++;
3305 }
3306 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
3307 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003308 q->red=ClampToQuantum(gamma*qixel.red);
cristy3ed852e2009-09-05 21:47:34 +00003309 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003310 q->green=ClampToQuantum(gamma*qixel.green);
cristy3ed852e2009-09-05 21:47:34 +00003311 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003312 q->blue=ClampToQuantum(gamma*qixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00003313 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00003314 q->opacity=ClampToQuantum(qixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00003315 if (((channel & IndexChannel) != 0) &&
3316 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00003317 blur_indexes[x]=(IndexPacket) ClampToQuantum(gamma*qixel.index);
cristy3ed852e2009-09-05 21:47:34 +00003318 }
3319 q++;
3320 }
3321 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
3322 status=MagickFalse;
3323 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3324 {
3325 MagickBooleanType
3326 proceed;
3327
cristyb5d5f722009-11-04 03:03:49 +00003328#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00003329 #pragma omp critical (MagickCore_MotionBlurImageChannel)
3330#endif
3331 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
3332 if (proceed == MagickFalse)
3333 status=MagickFalse;
3334 }
3335 }
3336 blur_view=DestroyCacheView(blur_view);
3337 image_view=DestroyCacheView(image_view);
3338 kernel=(double *) RelinquishMagickMemory(kernel);
3339 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
3340 if (status == MagickFalse)
3341 blur_image=DestroyImage(blur_image);
3342 return(blur_image);
3343}
3344
3345/*
3346%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3347% %
3348% %
3349% %
3350% P r e v i e w I m a g e %
3351% %
3352% %
3353% %
3354%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3355%
3356% PreviewImage() tiles 9 thumbnails of the specified image with an image
3357% processing operation applied with varying parameters. This may be helpful
3358% pin-pointing an appropriate parameter for a particular image processing
3359% operation.
3360%
3361% The format of the PreviewImages method is:
3362%
3363% Image *PreviewImages(const Image *image,const PreviewType preview,
3364% ExceptionInfo *exception)
3365%
3366% A description of each parameter follows:
3367%
3368% o image: the image.
3369%
3370% o preview: the image processing operation.
3371%
3372% o exception: return any errors or warnings in this structure.
3373%
3374*/
3375MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
3376 ExceptionInfo *exception)
3377{
3378#define NumberTiles 9
3379#define PreviewImageTag "Preview/Image"
3380#define DefaultPreviewGeometry "204x204+10+10"
3381
3382 char
3383 factor[MaxTextExtent],
3384 label[MaxTextExtent];
3385
3386 double
3387 degrees,
3388 gamma,
3389 percentage,
3390 radius,
3391 sigma,
3392 threshold;
3393
3394 Image
3395 *images,
3396 *montage_image,
3397 *preview_image,
3398 *thumbnail;
3399
3400 ImageInfo
3401 *preview_info;
3402
3403 long
3404 y;
3405
3406 MagickBooleanType
3407 proceed;
3408
3409 MontageInfo
3410 *montage_info;
3411
3412 QuantizeInfo
3413 quantize_info;
3414
3415 RectangleInfo
3416 geometry;
3417
3418 register long
3419 i,
3420 x;
3421
3422 unsigned long
3423 colors;
3424
3425 /*
3426 Open output image file.
3427 */
3428 assert(image != (Image *) NULL);
3429 assert(image->signature == MagickSignature);
3430 if (image->debug != MagickFalse)
3431 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3432 colors=2;
3433 degrees=0.0;
3434 gamma=(-0.2f);
3435 preview_info=AcquireImageInfo();
3436 SetGeometry(image,&geometry);
3437 (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
3438 &geometry.width,&geometry.height);
3439 images=NewImageList();
3440 percentage=12.5;
3441 GetQuantizeInfo(&quantize_info);
3442 radius=0.0;
3443 sigma=1.0;
3444 threshold=0.0;
3445 x=0;
3446 y=0;
3447 for (i=0; i < NumberTiles; i++)
3448 {
3449 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
3450 if (thumbnail == (Image *) NULL)
3451 break;
3452 (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
3453 (void *) NULL);
3454 (void) SetImageProperty(thumbnail,"label",DefaultTileLabel);
3455 if (i == (NumberTiles/2))
3456 {
3457 (void) QueryColorDatabase("#dfdfdf",&thumbnail->matte_color,exception);
3458 AppendImageToList(&images,thumbnail);
3459 continue;
3460 }
3461 switch (preview)
3462 {
3463 case RotatePreview:
3464 {
3465 degrees+=45.0;
3466 preview_image=RotateImage(thumbnail,degrees,exception);
cristy8cd5b312010-01-07 01:10:24 +00003467 (void) FormatMagickString(label,MaxTextExtent,"rotate %.15g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003468 break;
3469 }
3470 case ShearPreview:
3471 {
3472 degrees+=5.0;
3473 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
cristy8cd5b312010-01-07 01:10:24 +00003474 (void) FormatMagickString(label,MaxTextExtent,"shear %.15gx%.15g",
cristy3ed852e2009-09-05 21:47:34 +00003475 degrees,2.0*degrees);
3476 break;
3477 }
3478 case RollPreview:
3479 {
3480 x=(long) ((i+1)*thumbnail->columns)/NumberTiles;
3481 y=(long) ((i+1)*thumbnail->rows)/NumberTiles;
3482 preview_image=RollImage(thumbnail,x,y,exception);
3483 (void) FormatMagickString(label,MaxTextExtent,"roll %ldx%ld",x,y);
3484 break;
3485 }
3486 case HuePreview:
3487 {
3488 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3489 if (preview_image == (Image *) NULL)
3490 break;
cristy8cd5b312010-01-07 01:10:24 +00003491 (void) FormatMagickString(factor,MaxTextExtent,"100,100,%.15g",
cristy3ed852e2009-09-05 21:47:34 +00003492 2.0*percentage);
3493 (void) ModulateImage(preview_image,factor);
3494 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
3495 break;
3496 }
3497 case SaturationPreview:
3498 {
3499 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3500 if (preview_image == (Image *) NULL)
3501 break;
cristy8cd5b312010-01-07 01:10:24 +00003502 (void) FormatMagickString(factor,MaxTextExtent,"100,%.15g",
3503 2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00003504 (void) ModulateImage(preview_image,factor);
3505 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
3506 break;
3507 }
3508 case BrightnessPreview:
3509 {
3510 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3511 if (preview_image == (Image *) NULL)
3512 break;
cristy8cd5b312010-01-07 01:10:24 +00003513 (void) FormatMagickString(factor,MaxTextExtent,"%.15g",2.0*percentage);
cristy3ed852e2009-09-05 21:47:34 +00003514 (void) ModulateImage(preview_image,factor);
3515 (void) FormatMagickString(label,MaxTextExtent,"modulate %s",factor);
3516 break;
3517 }
3518 case GammaPreview:
3519 default:
3520 {
3521 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3522 if (preview_image == (Image *) NULL)
3523 break;
3524 gamma+=0.4f;
3525 (void) GammaImageChannel(preview_image,DefaultChannels,gamma);
cristy8cd5b312010-01-07 01:10:24 +00003526 (void) FormatMagickString(label,MaxTextExtent,"gamma %.15g",gamma);
cristy3ed852e2009-09-05 21:47:34 +00003527 break;
3528 }
3529 case SpiffPreview:
3530 {
3531 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3532 if (preview_image != (Image *) NULL)
3533 for (x=0; x < i; x++)
3534 (void) ContrastImage(preview_image,MagickTrue);
3535 (void) FormatMagickString(label,MaxTextExtent,"contrast (%ld)",i+1);
3536 break;
3537 }
3538 case DullPreview:
3539 {
3540 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3541 if (preview_image == (Image *) NULL)
3542 break;
3543 for (x=0; x < i; x++)
3544 (void) ContrastImage(preview_image,MagickFalse);
3545 (void) FormatMagickString(label,MaxTextExtent,"+contrast (%ld)",i+1);
3546 break;
3547 }
3548 case GrayscalePreview:
3549 {
3550 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3551 if (preview_image == (Image *) NULL)
3552 break;
3553 colors<<=1;
3554 quantize_info.number_colors=colors;
3555 quantize_info.colorspace=GRAYColorspace;
3556 (void) QuantizeImage(&quantize_info,preview_image);
3557 (void) FormatMagickString(label,MaxTextExtent,
3558 "-colorspace gray -colors %ld",colors);
3559 break;
3560 }
3561 case QuantizePreview:
3562 {
3563 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3564 if (preview_image == (Image *) NULL)
3565 break;
3566 colors<<=1;
3567 quantize_info.number_colors=colors;
3568 (void) QuantizeImage(&quantize_info,preview_image);
3569 (void) FormatMagickString(label,MaxTextExtent,"colors %ld",colors);
3570 break;
3571 }
3572 case DespecklePreview:
3573 {
3574 for (x=0; x < (i-1); x++)
3575 {
3576 preview_image=DespeckleImage(thumbnail,exception);
3577 if (preview_image == (Image *) NULL)
3578 break;
3579 thumbnail=DestroyImage(thumbnail);
3580 thumbnail=preview_image;
3581 }
3582 preview_image=DespeckleImage(thumbnail,exception);
3583 if (preview_image == (Image *) NULL)
3584 break;
3585 (void) FormatMagickString(label,MaxTextExtent,"despeckle (%ld)",i+1);
3586 break;
3587 }
3588 case ReduceNoisePreview:
3589 {
3590 preview_image=ReduceNoiseImage(thumbnail,radius,exception);
cristy8cd5b312010-01-07 01:10:24 +00003591 (void) FormatMagickString(label,MaxTextExtent,"noise %.15g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003592 break;
3593 }
3594 case AddNoisePreview:
3595 {
3596 switch ((int) i)
3597 {
3598 case 0:
3599 {
3600 (void) CopyMagickString(factor,"uniform",MaxTextExtent);
3601 break;
3602 }
3603 case 1:
3604 {
3605 (void) CopyMagickString(factor,"gaussian",MaxTextExtent);
3606 break;
3607 }
3608 case 2:
3609 {
3610 (void) CopyMagickString(factor,"multiplicative",MaxTextExtent);
3611 break;
3612 }
3613 case 3:
3614 {
3615 (void) CopyMagickString(factor,"impulse",MaxTextExtent);
3616 break;
3617 }
3618 case 4:
3619 {
3620 (void) CopyMagickString(factor,"laplacian",MaxTextExtent);
3621 break;
3622 }
3623 case 5:
3624 {
3625 (void) CopyMagickString(factor,"Poisson",MaxTextExtent);
3626 break;
3627 }
3628 default:
3629 {
3630 (void) CopyMagickString(thumbnail->magick,"NULL",MaxTextExtent);
3631 break;
3632 }
3633 }
3634 preview_image=ReduceNoiseImage(thumbnail,(double) i,exception);
3635 (void) FormatMagickString(label,MaxTextExtent,"+noise %s",factor);
3636 break;
3637 }
3638 case SharpenPreview:
3639 {
3640 preview_image=SharpenImage(thumbnail,radius,sigma,exception);
cristy8cd5b312010-01-07 01:10:24 +00003641 (void) FormatMagickString(label,MaxTextExtent,"sharpen %.15gx%.15g",
3642 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00003643 break;
3644 }
3645 case BlurPreview:
3646 {
3647 preview_image=BlurImage(thumbnail,radius,sigma,exception);
cristy8cd5b312010-01-07 01:10:24 +00003648 (void) FormatMagickString(label,MaxTextExtent,"blur %.15gx%.15g",radius,
cristy3ed852e2009-09-05 21:47:34 +00003649 sigma);
3650 break;
3651 }
3652 case ThresholdPreview:
3653 {
3654 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3655 if (preview_image == (Image *) NULL)
3656 break;
3657 (void) BilevelImage(thumbnail,
3658 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
cristy8cd5b312010-01-07 01:10:24 +00003659 (void) FormatMagickString(label,MaxTextExtent,"threshold %.15g",
cristy3ed852e2009-09-05 21:47:34 +00003660 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
3661 break;
3662 }
3663 case EdgeDetectPreview:
3664 {
3665 preview_image=EdgeImage(thumbnail,radius,exception);
cristy8cd5b312010-01-07 01:10:24 +00003666 (void) FormatMagickString(label,MaxTextExtent,"edge %.15g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003667 break;
3668 }
3669 case SpreadPreview:
3670 {
3671 preview_image=SpreadImage(thumbnail,radius,exception);
cristy8cd5b312010-01-07 01:10:24 +00003672 (void) FormatMagickString(label,MaxTextExtent,"spread %.15g",
3673 radius+0.5);
cristy3ed852e2009-09-05 21:47:34 +00003674 break;
3675 }
3676 case SolarizePreview:
3677 {
3678 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3679 if (preview_image == (Image *) NULL)
3680 break;
3681 (void) SolarizeImage(preview_image,(double) QuantumRange*
3682 percentage/100.0);
cristy8cd5b312010-01-07 01:10:24 +00003683 (void) FormatMagickString(label,MaxTextExtent,"solarize %.15g",
cristy3ed852e2009-09-05 21:47:34 +00003684 (QuantumRange*percentage)/100.0);
3685 break;
3686 }
3687 case ShadePreview:
3688 {
3689 degrees+=10.0;
3690 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
3691 exception);
cristy8cd5b312010-01-07 01:10:24 +00003692 (void) FormatMagickString(label,MaxTextExtent,"shade %.15gx%.15g",
3693 degrees,degrees);
cristy3ed852e2009-09-05 21:47:34 +00003694 break;
3695 }
3696 case RaisePreview:
3697 {
3698 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3699 if (preview_image == (Image *) NULL)
3700 break;
3701 geometry.width=(unsigned long) (2*i+2);
3702 geometry.height=(unsigned long) (2*i+2);
3703 geometry.x=i/2;
3704 geometry.y=i/2;
3705 (void) RaiseImage(preview_image,&geometry,MagickTrue);
3706 (void) FormatMagickString(label,MaxTextExtent,"raise %lux%lu%+ld%+ld",
3707 geometry.width,geometry.height,geometry.x,geometry.y);
3708 break;
3709 }
3710 case SegmentPreview:
3711 {
3712 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3713 if (preview_image == (Image *) NULL)
3714 break;
3715 threshold+=0.4f;
3716 (void) SegmentImage(preview_image,RGBColorspace,MagickFalse,threshold,
3717 threshold);
cristy8cd5b312010-01-07 01:10:24 +00003718 (void) FormatMagickString(label,MaxTextExtent,"segment %.15gx%.15g",
cristy3ed852e2009-09-05 21:47:34 +00003719 threshold,threshold);
3720 break;
3721 }
3722 case SwirlPreview:
3723 {
3724 preview_image=SwirlImage(thumbnail,degrees,exception);
cristy8cd5b312010-01-07 01:10:24 +00003725 (void) FormatMagickString(label,MaxTextExtent,"swirl %.15g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003726 degrees+=45.0;
3727 break;
3728 }
3729 case ImplodePreview:
3730 {
3731 degrees+=0.1f;
3732 preview_image=ImplodeImage(thumbnail,degrees,exception);
cristy8cd5b312010-01-07 01:10:24 +00003733 (void) FormatMagickString(label,MaxTextExtent,"implode %.15g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00003734 break;
3735 }
3736 case WavePreview:
3737 {
3738 degrees+=5.0f;
3739 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,exception);
cristy8cd5b312010-01-07 01:10:24 +00003740 (void) FormatMagickString(label,MaxTextExtent,"wave %.15gx%.15g",
3741 0.5*degrees,2.0*degrees);
cristy3ed852e2009-09-05 21:47:34 +00003742 break;
3743 }
3744 case OilPaintPreview:
3745 {
3746 preview_image=OilPaintImage(thumbnail,(double) radius,exception);
cristy8cd5b312010-01-07 01:10:24 +00003747 (void) FormatMagickString(label,MaxTextExtent,"paint %.15g",radius);
cristy3ed852e2009-09-05 21:47:34 +00003748 break;
3749 }
3750 case CharcoalDrawingPreview:
3751 {
3752 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
3753 exception);
cristy8cd5b312010-01-07 01:10:24 +00003754 (void) FormatMagickString(label,MaxTextExtent,"charcoal %.15gx%.15g",
3755 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00003756 break;
3757 }
3758 case JPEGPreview:
3759 {
3760 char
3761 filename[MaxTextExtent];
3762
3763 int
3764 file;
3765
3766 MagickBooleanType
3767 status;
3768
3769 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3770 if (preview_image == (Image *) NULL)
3771 break;
3772 preview_info->quality=(unsigned long) percentage;
3773 (void) FormatMagickString(factor,MaxTextExtent,"%lu",
3774 preview_info->quality);
3775 file=AcquireUniqueFileResource(filename);
3776 if (file != -1)
3777 file=close(file)-1;
3778 (void) FormatMagickString(preview_image->filename,MaxTextExtent,
3779 "jpeg:%s",filename);
3780 status=WriteImage(preview_info,preview_image);
3781 if (status != MagickFalse)
3782 {
3783 Image
3784 *quality_image;
3785
3786 (void) CopyMagickString(preview_info->filename,
3787 preview_image->filename,MaxTextExtent);
3788 quality_image=ReadImage(preview_info,exception);
3789 if (quality_image != (Image *) NULL)
3790 {
3791 preview_image=DestroyImage(preview_image);
3792 preview_image=quality_image;
3793 }
3794 }
3795 (void) RelinquishUniqueFileResource(preview_image->filename);
3796 if ((GetBlobSize(preview_image)/1024) >= 1024)
cristy8cd5b312010-01-07 01:10:24 +00003797 (void) FormatMagickString(label,MaxTextExtent,"quality %s\n%.15gmb ",
cristy3ed852e2009-09-05 21:47:34 +00003798 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
3799 1024.0/1024.0);
3800 else
3801 if (GetBlobSize(preview_image) >= 1024)
cristy8cd5b312010-01-07 01:10:24 +00003802 (void) FormatMagickString(label,MaxTextExtent,
3803 "quality %s\n%.15gkb ",factor,(double) ((MagickOffsetType)
3804 GetBlobSize(preview_image))/1024.0);
cristy3ed852e2009-09-05 21:47:34 +00003805 else
3806 (void) FormatMagickString(label,MaxTextExtent,"quality %s\n%lub ",
3807 factor,(unsigned long) GetBlobSize(thumbnail));
3808 break;
3809 }
3810 }
3811 thumbnail=DestroyImage(thumbnail);
3812 percentage+=12.5;
3813 radius+=0.5;
3814 sigma+=0.25;
3815 if (preview_image == (Image *) NULL)
3816 break;
3817 (void) DeleteImageProperty(preview_image,"label");
3818 (void) SetImageProperty(preview_image,"label",label);
3819 AppendImageToList(&images,preview_image);
3820 proceed=SetImageProgress(image,PreviewImageTag,i,NumberTiles);
3821 if (proceed == MagickFalse)
3822 break;
3823 }
3824 if (images == (Image *) NULL)
3825 {
3826 preview_info=DestroyImageInfo(preview_info);
3827 return((Image *) NULL);
3828 }
3829 /*
3830 Create the montage.
3831 */
3832 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
3833 (void) CopyMagickString(montage_info->filename,image->filename,MaxTextExtent);
3834 montage_info->shadow=MagickTrue;
3835 (void) CloneString(&montage_info->tile,"3x3");
3836 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
3837 (void) CloneString(&montage_info->frame,DefaultTileFrame);
3838 montage_image=MontageImages(images,montage_info,exception);
3839 montage_info=DestroyMontageInfo(montage_info);
3840 images=DestroyImageList(images);
3841 if (montage_image == (Image *) NULL)
3842 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3843 if (montage_image->montage != (char *) NULL)
3844 {
3845 /*
3846 Free image directory.
3847 */
3848 montage_image->montage=(char *) RelinquishMagickMemory(
3849 montage_image->montage);
3850 if (image->directory != (char *) NULL)
3851 montage_image->directory=(char *) RelinquishMagickMemory(
3852 montage_image->directory);
3853 }
3854 preview_info=DestroyImageInfo(preview_info);
3855 return(montage_image);
3856}
3857
3858/*
3859%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3860% %
3861% %
3862% %
3863% R a d i a l B l u r I m a g e %
3864% %
3865% %
3866% %
3867%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3868%
3869% RadialBlurImage() applies a radial blur to the image.
3870%
3871% Andrew Protano contributed this effect.
3872%
3873% The format of the RadialBlurImage method is:
3874%
3875% Image *RadialBlurImage(const Image *image,const double angle,
3876% ExceptionInfo *exception)
3877% Image *RadialBlurImageChannel(const Image *image,const ChannelType channel,
3878% const double angle,ExceptionInfo *exception)
3879%
3880% A description of each parameter follows:
3881%
3882% o image: the image.
3883%
3884% o channel: the channel type.
3885%
3886% o angle: the angle of the radial blur.
3887%
3888% o exception: return any errors or warnings in this structure.
3889%
3890*/
3891
3892MagickExport Image *RadialBlurImage(const Image *image,const double angle,
3893 ExceptionInfo *exception)
3894{
3895 Image
3896 *blur_image;
3897
3898 blur_image=RadialBlurImageChannel(image,DefaultChannels,angle,exception);
3899 return(blur_image);
3900}
3901
3902MagickExport Image *RadialBlurImageChannel(const Image *image,
3903 const ChannelType channel,const double angle,ExceptionInfo *exception)
3904{
cristyc4c8d132010-01-07 01:58:38 +00003905 CacheView
3906 *blur_view,
3907 *image_view;
3908
cristy3ed852e2009-09-05 21:47:34 +00003909 Image
3910 *blur_image;
3911
3912 long
3913 progress,
3914 y;
3915
3916 MagickBooleanType
3917 status;
3918
3919 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00003920 bias;
cristy3ed852e2009-09-05 21:47:34 +00003921
3922 MagickRealType
3923 blur_radius,
3924 *cos_theta,
3925 offset,
3926 *sin_theta,
3927 theta;
3928
3929 PointInfo
3930 blur_center;
3931
3932 register long
3933 i;
3934
3935 unsigned long
3936 n;
3937
cristy3ed852e2009-09-05 21:47:34 +00003938 /*
3939 Allocate blur image.
3940 */
3941 assert(image != (Image *) NULL);
3942 assert(image->signature == MagickSignature);
3943 if (image->debug != MagickFalse)
3944 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3945 assert(exception != (ExceptionInfo *) NULL);
3946 assert(exception->signature == MagickSignature);
3947 blur_image=CloneImage(image,0,0,MagickTrue,exception);
3948 if (blur_image == (Image *) NULL)
3949 return((Image *) NULL);
3950 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
3951 {
3952 InheritException(exception,&blur_image->exception);
3953 blur_image=DestroyImage(blur_image);
3954 return((Image *) NULL);
3955 }
3956 blur_center.x=(double) image->columns/2.0;
3957 blur_center.y=(double) image->rows/2.0;
3958 blur_radius=hypot(blur_center.x,blur_center.y);
3959 n=(unsigned long) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+
3960 2UL);
3961 theta=DegreesToRadians(angle)/(MagickRealType) (n-1);
3962 cos_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3963 sizeof(*cos_theta));
3964 sin_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
3965 sizeof(*sin_theta));
3966 if ((cos_theta == (MagickRealType *) NULL) ||
3967 (sin_theta == (MagickRealType *) NULL))
3968 {
3969 blur_image=DestroyImage(blur_image);
3970 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3971 }
3972 offset=theta*(MagickRealType) (n-1)/2.0;
3973 for (i=0; i < (long) n; i++)
3974 {
3975 cos_theta[i]=cos((double) (theta*i-offset));
3976 sin_theta[i]=sin((double) (theta*i-offset));
3977 }
3978 /*
3979 Radial blur image.
3980 */
3981 status=MagickTrue;
3982 progress=0;
cristyddd82202009-11-03 20:14:50 +00003983 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00003984 image_view=AcquireCacheView(image);
3985 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00003986#if defined(MAGICKCORE_OPENMP_SUPPORT)
3987 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00003988#endif
3989 for (y=0; y < (long) blur_image->rows; y++)
3990 {
3991 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003992 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00003993
3994 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00003995 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00003996
3997 register long
3998 x;
3999
4000 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004001 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004002
4003 if (status == MagickFalse)
4004 continue;
4005 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
4006 exception);
4007 if (q == (PixelPacket *) NULL)
4008 {
4009 status=MagickFalse;
4010 continue;
4011 }
4012 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
4013 for (x=0; x < (long) blur_image->columns; x++)
4014 {
4015 MagickPixelPacket
4016 qixel;
4017
4018 MagickRealType
4019 normalize,
4020 radius;
4021
4022 PixelPacket
4023 pixel;
4024
4025 PointInfo
4026 center;
4027
4028 register long
4029 i;
4030
4031 unsigned long
4032 step;
4033
4034 center.x=(double) x-blur_center.x;
4035 center.y=(double) y-blur_center.y;
4036 radius=hypot((double) center.x,center.y);
4037 if (radius == 0)
4038 step=1;
4039 else
4040 {
4041 step=(unsigned long) (blur_radius/radius);
4042 if (step == 0)
4043 step=1;
4044 else
4045 if (step >= n)
4046 step=n-1;
4047 }
4048 normalize=0.0;
cristyddd82202009-11-03 20:14:50 +00004049 qixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00004050 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
4051 {
4052 for (i=0; i < (long) n; i+=step)
4053 {
4054 (void) GetOneCacheViewVirtualPixel(image_view,(long) (blur_center.x+
4055 center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),(long) (
4056 blur_center.y+center.x*sin_theta[i]+center.y*cos_theta[i]+0.5),
4057 &pixel,exception);
4058 qixel.red+=pixel.red;
4059 qixel.green+=pixel.green;
4060 qixel.blue+=pixel.blue;
4061 qixel.opacity+=pixel.opacity;
4062 if (image->colorspace == CMYKColorspace)
4063 {
4064 indexes=GetCacheViewVirtualIndexQueue(image_view);
4065 qixel.index+=(*indexes);
4066 }
4067 normalize+=1.0;
4068 }
4069 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
4070 normalize);
4071 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004072 q->red=ClampToQuantum(normalize*qixel.red);
cristy3ed852e2009-09-05 21:47:34 +00004073 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004074 q->green=ClampToQuantum(normalize*qixel.green);
cristy3ed852e2009-09-05 21:47:34 +00004075 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004076 q->blue=ClampToQuantum(normalize*qixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00004077 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004078 q->opacity=ClampToQuantum(normalize*qixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00004079 if (((channel & IndexChannel) != 0) &&
4080 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00004081 blur_indexes[x]=(IndexPacket) ClampToQuantum(normalize*qixel.index);
cristy3ed852e2009-09-05 21:47:34 +00004082 }
4083 else
4084 {
4085 MagickRealType
4086 alpha,
4087 gamma;
4088
4089 alpha=1.0;
4090 gamma=0.0;
4091 for (i=0; i < (long) n; i+=step)
4092 {
4093 (void) GetOneCacheViewVirtualPixel(image_view,(long) (blur_center.x+
4094 center.x*cos_theta[i]-center.y*sin_theta[i]+0.5),(long) (
4095 blur_center.y+center.x*sin_theta[i]+center.y*cos_theta[i]+0.5),
4096 &pixel,exception);
4097 alpha=(MagickRealType) (QuantumScale*(QuantumRange-pixel.opacity));
4098 qixel.red+=alpha*pixel.red;
4099 qixel.green+=alpha*pixel.green;
4100 qixel.blue+=alpha*pixel.blue;
4101 qixel.opacity+=pixel.opacity;
4102 if (image->colorspace == CMYKColorspace)
4103 {
4104 indexes=GetCacheViewVirtualIndexQueue(image_view);
4105 qixel.index+=alpha*(*indexes);
4106 }
4107 gamma+=alpha;
4108 normalize+=1.0;
4109 }
4110 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
4111 normalize=1.0/(fabs((double) normalize) <= MagickEpsilon ? 1.0 :
4112 normalize);
4113 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004114 q->red=ClampToQuantum(gamma*qixel.red);
cristy3ed852e2009-09-05 21:47:34 +00004115 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004116 q->green=ClampToQuantum(gamma*qixel.green);
cristy3ed852e2009-09-05 21:47:34 +00004117 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004118 q->blue=ClampToQuantum(gamma*qixel.blue);
cristy3ed852e2009-09-05 21:47:34 +00004119 if ((channel & OpacityChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004120 q->opacity=ClampToQuantum(normalize*qixel.opacity);
cristy3ed852e2009-09-05 21:47:34 +00004121 if (((channel & IndexChannel) != 0) &&
4122 (image->colorspace == CMYKColorspace))
cristyce70c172010-01-07 17:15:30 +00004123 blur_indexes[x]=(IndexPacket) ClampToQuantum(gamma*qixel.index);
cristy3ed852e2009-09-05 21:47:34 +00004124 }
4125 q++;
4126 }
4127 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
4128 status=MagickFalse;
4129 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4130 {
4131 MagickBooleanType
4132 proceed;
4133
cristyb5d5f722009-11-04 03:03:49 +00004134#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004135 #pragma omp critical (MagickCore_RadialBlurImageChannel)
4136#endif
4137 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
4138 if (proceed == MagickFalse)
4139 status=MagickFalse;
4140 }
4141 }
4142 blur_view=DestroyCacheView(blur_view);
4143 image_view=DestroyCacheView(image_view);
4144 cos_theta=(MagickRealType *) RelinquishMagickMemory(cos_theta);
4145 sin_theta=(MagickRealType *) RelinquishMagickMemory(sin_theta);
4146 if (status == MagickFalse)
4147 blur_image=DestroyImage(blur_image);
4148 return(blur_image);
4149}
4150
4151/*
4152%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4153% %
4154% %
4155% %
4156% R e d u c e N o i s e I m a g e %
4157% %
4158% %
4159% %
4160%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4161%
4162% ReduceNoiseImage() smooths the contours of an image while still preserving
4163% edge information. The algorithm works by replacing each pixel with its
4164% neighbor closest in value. A neighbor is defined by radius. Use a radius
4165% of 0 and ReduceNoise() selects a suitable radius for you.
4166%
4167% The format of the ReduceNoiseImage method is:
4168%
4169% Image *ReduceNoiseImage(const Image *image,const double radius,
4170% ExceptionInfo *exception)
4171%
4172% A description of each parameter follows:
4173%
4174% o image: the image.
4175%
4176% o radius: the radius of the pixel neighborhood.
4177%
4178% o exception: return any errors or warnings in this structure.
4179%
4180*/
4181
4182static MagickPixelPacket GetNonpeakMedianPixelList(MedianPixelList *pixel_list)
4183{
4184 MagickPixelPacket
4185 pixel;
4186
4187 register long
4188 channel;
4189
4190 register MedianSkipList
4191 *list;
4192
4193 unsigned long
4194 center,
4195 color,
4196 count,
4197 previous,
4198 next;
4199
4200 unsigned short
4201 channels[5];
4202
4203 /*
4204 Finds the median value for each of the color.
4205 */
4206 center=pixel_list->center;
4207 for (channel=0; channel < 5; channel++)
4208 {
4209 list=pixel_list->lists+channel;
4210 color=65536UL;
4211 next=list->nodes[color].next[0];
4212 count=0;
4213 do
4214 {
4215 previous=color;
4216 color=next;
4217 next=list->nodes[color].next[0];
4218 count+=list->nodes[color].count;
4219 }
4220 while (count <= center);
4221 if ((previous == 65536UL) && (next != 65536UL))
4222 color=next;
4223 else
4224 if ((previous != 65536UL) && (next == 65536UL))
4225 color=previous;
4226 channels[channel]=(unsigned short) color;
4227 }
4228 GetMagickPixelPacket((const Image *) NULL,&pixel);
4229 pixel.red=(MagickRealType) ScaleShortToQuantum(channels[0]);
4230 pixel.green=(MagickRealType) ScaleShortToQuantum(channels[1]);
4231 pixel.blue=(MagickRealType) ScaleShortToQuantum(channels[2]);
4232 pixel.opacity=(MagickRealType) ScaleShortToQuantum(channels[3]);
4233 pixel.index=(MagickRealType) ScaleShortToQuantum(channels[4]);
4234 return(pixel);
4235}
4236
4237MagickExport Image *ReduceNoiseImage(const Image *image,const double radius,
4238 ExceptionInfo *exception)
4239{
4240#define ReduceNoiseImageTag "ReduceNoise/Image"
4241
cristyfa112112010-01-04 17:48:07 +00004242 CacheView
4243 *image_view,
4244 *noise_view;
4245
cristy3ed852e2009-09-05 21:47:34 +00004246 Image
4247 *noise_image;
4248
4249 long
4250 progress,
4251 y;
4252
4253 MagickBooleanType
4254 status;
4255
4256 MedianPixelList
cristyfa112112010-01-04 17:48:07 +00004257 **restrict pixel_list;
cristy3ed852e2009-09-05 21:47:34 +00004258
4259 unsigned long
4260 width;
4261
cristy3ed852e2009-09-05 21:47:34 +00004262 /*
4263 Initialize noise image attributes.
4264 */
4265 assert(image != (Image *) NULL);
4266 assert(image->signature == MagickSignature);
4267 if (image->debug != MagickFalse)
4268 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4269 assert(exception != (ExceptionInfo *) NULL);
4270 assert(exception->signature == MagickSignature);
4271 width=GetOptimalKernelWidth2D(radius,0.5);
4272 if ((image->columns < width) || (image->rows < width))
4273 ThrowImageException(OptionError,"ImageSmallerThanKernelRadius");
4274 noise_image=CloneImage(image,image->columns,image->rows,MagickTrue,
4275 exception);
4276 if (noise_image == (Image *) NULL)
4277 return((Image *) NULL);
4278 if (SetImageStorageClass(noise_image,DirectClass) == MagickFalse)
4279 {
4280 InheritException(exception,&noise_image->exception);
4281 noise_image=DestroyImage(noise_image);
4282 return((Image *) NULL);
4283 }
4284 pixel_list=AcquireMedianPixelListThreadSet(width);
4285 if (pixel_list == (MedianPixelList **) NULL)
4286 {
4287 noise_image=DestroyImage(noise_image);
4288 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
4289 }
4290 /*
4291 Reduce noise image.
4292 */
4293 status=MagickTrue;
4294 progress=0;
4295 image_view=AcquireCacheView(image);
4296 noise_view=AcquireCacheView(noise_image);
cristyb5d5f722009-11-04 03:03:49 +00004297#if defined(MAGICKCORE_OPENMP_SUPPORT)
4298 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004299#endif
4300 for (y=0; y < (long) noise_image->rows; y++)
4301 {
4302 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004303 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00004304
4305 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004306 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00004307
4308 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004309 *restrict noise_indexes;
cristy3ed852e2009-09-05 21:47:34 +00004310
4311 register long
4312 id,
4313 x;
4314
4315 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004316 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004317
4318 if (status == MagickFalse)
4319 continue;
4320 p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y-(long) (width/
4321 2L),image->columns+width,width,exception);
4322 q=QueueCacheViewAuthenticPixels(noise_view,0,y,noise_image->columns,1,
4323 exception);
4324 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
4325 {
4326 status=MagickFalse;
4327 continue;
4328 }
4329 indexes=GetCacheViewVirtualIndexQueue(image_view);
4330 noise_indexes=GetCacheViewAuthenticIndexQueue(noise_view);
4331 id=GetOpenMPThreadId();
4332 for (x=0; x < (long) noise_image->columns; x++)
4333 {
4334 MagickPixelPacket
4335 pixel;
4336
4337 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004338 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +00004339
4340 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004341 *restrict s;
cristy3ed852e2009-09-05 21:47:34 +00004342
4343 register long
4344 u,
4345 v;
4346
4347 r=p;
4348 s=indexes+x;
4349 ResetMedianPixelList(pixel_list[id]);
4350 for (v=0; v < (long) width; v++)
4351 {
4352 for (u=0; u < (long) width; u++)
4353 InsertMedianPixelList(image,r+u,s+u,pixel_list[id]);
4354 r+=image->columns+width;
4355 s+=image->columns+width;
4356 }
4357 pixel=GetNonpeakMedianPixelList(pixel_list[id]);
4358 SetPixelPacket(noise_image,&pixel,q,noise_indexes+x);
4359 p++;
4360 q++;
4361 }
4362 if (SyncCacheViewAuthenticPixels(noise_view,exception) == MagickFalse)
4363 status=MagickFalse;
4364 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4365 {
4366 MagickBooleanType
4367 proceed;
4368
cristyb5d5f722009-11-04 03:03:49 +00004369#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004370 #pragma omp critical (MagickCore_ReduceNoiseImage)
4371#endif
4372 proceed=SetImageProgress(image,ReduceNoiseImageTag,progress++,
4373 image->rows);
4374 if (proceed == MagickFalse)
4375 status=MagickFalse;
4376 }
4377 }
4378 noise_view=DestroyCacheView(noise_view);
4379 image_view=DestroyCacheView(image_view);
4380 pixel_list=DestroyMedianPixelListThreadSet(pixel_list);
4381 return(noise_image);
4382}
4383
4384/*
4385%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4386% %
4387% %
4388% %
4389% S e l e c t i v e B l u r I m a g e %
4390% %
4391% %
4392% %
4393%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4394%
4395% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
4396% It is similar to the unsharpen mask that sharpens everything with contrast
4397% above a certain threshold.
4398%
4399% The format of the SelectiveBlurImage method is:
4400%
4401% Image *SelectiveBlurImage(const Image *image,const double radius,
4402% const double sigma,const double threshold,ExceptionInfo *exception)
4403% Image *SelectiveBlurImageChannel(const Image *image,
4404% const ChannelType channel,const double radius,const double sigma,
4405% const double threshold,ExceptionInfo *exception)
4406%
4407% A description of each parameter follows:
4408%
4409% o image: the image.
4410%
4411% o channel: the channel type.
4412%
4413% o radius: the radius of the Gaussian, in pixels, not counting the center
4414% pixel.
4415%
4416% o sigma: the standard deviation of the Gaussian, in pixels.
4417%
4418% o threshold: only pixels within this contrast threshold are included
4419% in the blur operation.
4420%
4421% o exception: return any errors or warnings in this structure.
4422%
4423*/
4424
4425static inline MagickBooleanType SelectiveContrast(const PixelPacket *p,
4426 const PixelPacket *q,const double threshold)
4427{
4428 if (fabs(PixelIntensity(p)-PixelIntensity(q)) < threshold)
4429 return(MagickTrue);
4430 return(MagickFalse);
4431}
4432
4433MagickExport Image *SelectiveBlurImage(const Image *image,const double radius,
4434 const double sigma,const double threshold,ExceptionInfo *exception)
4435{
4436 Image
4437 *blur_image;
4438
4439 blur_image=SelectiveBlurImageChannel(image,DefaultChannels,radius,sigma,
4440 threshold,exception);
4441 return(blur_image);
4442}
4443
4444MagickExport Image *SelectiveBlurImageChannel(const Image *image,
4445 const ChannelType channel,const double radius,const double sigma,
4446 const double threshold,ExceptionInfo *exception)
4447{
4448#define SelectiveBlurImageTag "SelectiveBlur/Image"
4449
cristy47e00502009-12-17 19:19:57 +00004450 CacheView
4451 *blur_view,
4452 *image_view;
4453
cristy3ed852e2009-09-05 21:47:34 +00004454 double
cristy3ed852e2009-09-05 21:47:34 +00004455 *kernel;
4456
4457 Image
4458 *blur_image;
4459
4460 long
cristy47e00502009-12-17 19:19:57 +00004461 j,
cristy3ed852e2009-09-05 21:47:34 +00004462 progress,
cristy47e00502009-12-17 19:19:57 +00004463 u,
cristy3ed852e2009-09-05 21:47:34 +00004464 v,
4465 y;
4466
4467 MagickBooleanType
4468 status;
4469
4470 MagickPixelPacket
cristy3ed852e2009-09-05 21:47:34 +00004471 bias;
4472
4473 register long
cristy47e00502009-12-17 19:19:57 +00004474 i;
cristy3ed852e2009-09-05 21:47:34 +00004475
4476 unsigned long
4477 width;
4478
cristy3ed852e2009-09-05 21:47:34 +00004479 /*
4480 Initialize blur image attributes.
4481 */
4482 assert(image != (Image *) NULL);
4483 assert(image->signature == MagickSignature);
4484 if (image->debug != MagickFalse)
4485 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4486 assert(exception != (ExceptionInfo *) NULL);
4487 assert(exception->signature == MagickSignature);
4488 width=GetOptimalKernelWidth1D(radius,sigma);
4489 kernel=(double *) AcquireQuantumMemory((size_t) width,width*sizeof(*kernel));
4490 if (kernel == (double *) NULL)
4491 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy47e00502009-12-17 19:19:57 +00004492 j=(long) width/2;
cristy3ed852e2009-09-05 21:47:34 +00004493 i=0;
cristy47e00502009-12-17 19:19:57 +00004494 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00004495 {
cristy47e00502009-12-17 19:19:57 +00004496 for (u=(-j); u <= j; u++)
4497 kernel[i++]=exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
4498 (2.0*MagickPI*MagickSigma*MagickSigma);
cristy3ed852e2009-09-05 21:47:34 +00004499 }
4500 if (image->debug != MagickFalse)
4501 {
4502 char
4503 format[MaxTextExtent],
4504 *message;
4505
4506 long
4507 u,
4508 v;
4509
4510 register const double
4511 *k;
4512
4513 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
4514 " SelectiveBlurImage with %ldx%ld kernel:",width,width);
4515 message=AcquireString("");
4516 k=kernel;
4517 for (v=0; v < (long) width; v++)
4518 {
4519 *message='\0';
4520 (void) FormatMagickString(format,MaxTextExtent,"%ld: ",v);
4521 (void) ConcatenateString(&message,format);
4522 for (u=0; u < (long) width; u++)
4523 {
4524 (void) FormatMagickString(format,MaxTextExtent,"%+f ",*k++);
4525 (void) ConcatenateString(&message,format);
4526 }
4527 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
4528 }
4529 message=DestroyString(message);
4530 }
4531 blur_image=CloneImage(image,0,0,MagickTrue,exception);
4532 if (blur_image == (Image *) NULL)
4533 return((Image *) NULL);
4534 if (SetImageStorageClass(blur_image,DirectClass) == MagickFalse)
4535 {
4536 InheritException(exception,&blur_image->exception);
4537 blur_image=DestroyImage(blur_image);
4538 return((Image *) NULL);
4539 }
4540 /*
4541 Threshold blur image.
4542 */
4543 status=MagickTrue;
4544 progress=0;
cristyddd82202009-11-03 20:14:50 +00004545 GetMagickPixelPacket(image,&bias);
4546 SetMagickPixelPacketBias(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00004547 image_view=AcquireCacheView(image);
4548 blur_view=AcquireCacheView(blur_image);
cristyb5d5f722009-11-04 03:03:49 +00004549#if defined(MAGICKCORE_OPENMP_SUPPORT)
4550 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004551#endif
4552 for (y=0; y < (long) image->rows; y++)
4553 {
4554 MagickBooleanType
4555 sync;
4556
4557 MagickRealType
4558 gamma;
4559
4560 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004561 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00004562
4563 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004564 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00004565
4566 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00004567 *restrict blur_indexes;
cristy3ed852e2009-09-05 21:47:34 +00004568
4569 register long
4570 x;
4571
4572 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004573 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004574
4575 if (status == MagickFalse)
4576 continue;
4577 p=GetCacheViewVirtualPixels(image_view,-((long) width/2L),y-(long) (width/
4578 2L),image->columns+width,width,exception);
4579 q=GetCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
4580 exception);
4581 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
4582 {
4583 status=MagickFalse;
4584 continue;
4585 }
4586 indexes=GetCacheViewVirtualIndexQueue(image_view);
4587 blur_indexes=GetCacheViewAuthenticIndexQueue(blur_view);
4588 for (x=0; x < (long) image->columns; x++)
4589 {
4590 long
4591 j,
4592 v;
4593
4594 MagickPixelPacket
4595 pixel;
4596
4597 register const double
cristyc47d1f82009-11-26 01:44:43 +00004598 *restrict k;
cristy3ed852e2009-09-05 21:47:34 +00004599
4600 register long
4601 u;
4602
cristyddd82202009-11-03 20:14:50 +00004603 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00004604 k=kernel;
4605 gamma=0.0;
4606 j=0;
4607 if (((channel & OpacityChannel) == 0) || (image->matte == MagickFalse))
4608 {
4609 for (v=0; v < (long) width; v++)
4610 {
4611 for (u=0; u < (long) width; u++)
4612 {
4613 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4614 {
4615 pixel.red+=(*k)*(p+u+j)->red;
4616 pixel.green+=(*k)*(p+u+j)->green;
4617 pixel.blue+=(*k)*(p+u+j)->blue;
4618 gamma+=(*k);
4619 k++;
4620 }
4621 }
4622 j+=image->columns+width;
4623 }
4624 if (gamma != 0.0)
4625 {
4626 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
4627 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004628 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004629 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004630 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004631 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004632 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004633 }
4634 if ((channel & OpacityChannel) != 0)
4635 {
4636 gamma=0.0;
4637 j=0;
4638 for (v=0; v < (long) width; v++)
4639 {
4640 for (u=0; u < (long) width; u++)
4641 {
4642 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4643 {
4644 pixel.opacity+=(*k)*(p+u+j)->opacity;
4645 gamma+=(*k);
4646 k++;
4647 }
4648 }
4649 j+=image->columns+width;
4650 }
4651 if (gamma != 0.0)
4652 {
4653 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4654 gamma);
cristyce70c172010-01-07 17:15:30 +00004655 SetOpacityPixelComponent(q,ClampToQuantum(gamma*
4656 GetOpacityPixelComponent(&pixel)));
cristy3ed852e2009-09-05 21:47:34 +00004657 }
4658 }
4659 if (((channel & IndexChannel) != 0) &&
4660 (image->colorspace == CMYKColorspace))
4661 {
4662 gamma=0.0;
4663 j=0;
4664 for (v=0; v < (long) width; v++)
4665 {
4666 for (u=0; u < (long) width; u++)
4667 {
4668 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4669 {
4670 pixel.index+=(*k)*indexes[x+u+j];
4671 gamma+=(*k);
4672 k++;
4673 }
4674 }
4675 j+=image->columns+width;
4676 }
4677 if (gamma != 0.0)
4678 {
4679 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4680 gamma);
cristyce70c172010-01-07 17:15:30 +00004681 blur_indexes[x]=ClampToQuantum(gamma*GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004682 }
4683 }
4684 }
4685 else
4686 {
4687 MagickRealType
4688 alpha;
4689
4690 for (v=0; v < (long) width; v++)
4691 {
4692 for (u=0; u < (long) width; u++)
4693 {
4694 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4695 {
4696 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
4697 (p+u+j)->opacity));
4698 pixel.red+=(*k)*alpha*(p+u+j)->red;
4699 pixel.green+=(*k)*alpha*(p+u+j)->green;
4700 pixel.blue+=(*k)*alpha*(p+u+j)->blue;
4701 pixel.opacity+=(*k)*(p+u+j)->opacity;
4702 gamma+=(*k)*alpha;
4703 k++;
4704 }
4705 }
4706 j+=image->columns+width;
4707 }
4708 if (gamma != 0.0)
4709 {
4710 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 : gamma);
4711 if ((channel & RedChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004712 q->red=ClampToQuantum(gamma*GetRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004713 if ((channel & GreenChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004714 q->green=ClampToQuantum(gamma*GetGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004715 if ((channel & BlueChannel) != 0)
cristyce70c172010-01-07 17:15:30 +00004716 q->blue=ClampToQuantum(gamma*GetBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004717 }
4718 if ((channel & OpacityChannel) != 0)
4719 {
4720 gamma=0.0;
4721 j=0;
4722 for (v=0; v < (long) width; v++)
4723 {
4724 for (u=0; u < (long) width; u++)
4725 {
4726 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4727 {
4728 pixel.opacity+=(*k)*(p+u+j)->opacity;
4729 gamma+=(*k);
4730 k++;
4731 }
4732 }
4733 j+=image->columns+width;
4734 }
4735 if (gamma != 0.0)
4736 {
4737 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4738 gamma);
cristyce70c172010-01-07 17:15:30 +00004739 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004740 }
4741 }
4742 if (((channel & IndexChannel) != 0) &&
4743 (image->colorspace == CMYKColorspace))
4744 {
4745 gamma=0.0;
4746 j=0;
4747 for (v=0; v < (long) width; v++)
4748 {
4749 for (u=0; u < (long) width; u++)
4750 {
4751 if (SelectiveContrast(p+u+j,q,threshold) != MagickFalse)
4752 {
4753 alpha=(MagickRealType) (QuantumScale*(QuantumRange-
4754 (p+u+j)->opacity));
4755 pixel.index+=(*k)*alpha*indexes[x+u+j];
4756 gamma+=(*k);
4757 k++;
4758 }
4759 }
4760 j+=image->columns+width;
4761 }
4762 if (gamma != 0.0)
4763 {
4764 gamma=1.0/(fabs((double) gamma) <= MagickEpsilon ? 1.0 :
4765 gamma);
cristyce70c172010-01-07 17:15:30 +00004766 blur_indexes[x]=ClampToQuantum(gamma*GetIndexPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00004767 }
4768 }
4769 }
4770 p++;
4771 q++;
4772 }
4773 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
4774 if (sync == MagickFalse)
4775 status=MagickFalse;
4776 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4777 {
4778 MagickBooleanType
4779 proceed;
4780
cristyb5d5f722009-11-04 03:03:49 +00004781#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004782 #pragma omp critical (MagickCore_SelectiveBlurImageChannel)
4783#endif
4784 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress++,
4785 image->rows);
4786 if (proceed == MagickFalse)
4787 status=MagickFalse;
4788 }
4789 }
4790 blur_image->type=image->type;
4791 blur_view=DestroyCacheView(blur_view);
4792 image_view=DestroyCacheView(image_view);
4793 kernel=(double *) RelinquishMagickMemory(kernel);
4794 if (status == MagickFalse)
4795 blur_image=DestroyImage(blur_image);
4796 return(blur_image);
4797}
4798
4799/*
4800%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4801% %
4802% %
4803% %
4804% S h a d e I m a g e %
4805% %
4806% %
4807% %
4808%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4809%
4810% ShadeImage() shines a distant light on an image to create a
4811% three-dimensional effect. You control the positioning of the light with
4812% azimuth and elevation; azimuth is measured in degrees off the x axis
4813% and elevation is measured in pixels above the Z axis.
4814%
4815% The format of the ShadeImage method is:
4816%
4817% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
4818% const double azimuth,const double elevation,ExceptionInfo *exception)
4819%
4820% A description of each parameter follows:
4821%
4822% o image: the image.
4823%
4824% o gray: A value other than zero shades the intensity of each pixel.
4825%
4826% o azimuth, elevation: Define the light source direction.
4827%
4828% o exception: return any errors or warnings in this structure.
4829%
4830*/
4831MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
4832 const double azimuth,const double elevation,ExceptionInfo *exception)
4833{
4834#define ShadeImageTag "Shade/Image"
4835
cristyc4c8d132010-01-07 01:58:38 +00004836 CacheView
4837 *image_view,
4838 *shade_view;
4839
cristy3ed852e2009-09-05 21:47:34 +00004840 Image
4841 *shade_image;
4842
4843 long
4844 progress,
4845 y;
4846
4847 MagickBooleanType
4848 status;
4849
4850 PrimaryInfo
4851 light;
4852
cristy3ed852e2009-09-05 21:47:34 +00004853 /*
4854 Initialize shaded image attributes.
4855 */
4856 assert(image != (const Image *) NULL);
4857 assert(image->signature == MagickSignature);
4858 if (image->debug != MagickFalse)
4859 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4860 assert(exception != (ExceptionInfo *) NULL);
4861 assert(exception->signature == MagickSignature);
4862 shade_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
4863 if (shade_image == (Image *) NULL)
4864 return((Image *) NULL);
4865 if (SetImageStorageClass(shade_image,DirectClass) == MagickFalse)
4866 {
4867 InheritException(exception,&shade_image->exception);
4868 shade_image=DestroyImage(shade_image);
4869 return((Image *) NULL);
4870 }
4871 /*
4872 Compute the light vector.
4873 */
4874 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
4875 cos(DegreesToRadians(elevation));
4876 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
4877 cos(DegreesToRadians(elevation));
4878 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
4879 /*
4880 Shade image.
4881 */
4882 status=MagickTrue;
4883 progress=0;
4884 image_view=AcquireCacheView(image);
4885 shade_view=AcquireCacheView(shade_image);
cristyb5d5f722009-11-04 03:03:49 +00004886#if defined(MAGICKCORE_OPENMP_SUPPORT)
4887 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00004888#endif
4889 for (y=0; y < (long) image->rows; y++)
4890 {
4891 MagickRealType
4892 distance,
4893 normal_distance,
4894 shade;
4895
4896 PrimaryInfo
4897 normal;
4898
4899 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004900 *restrict p,
4901 *restrict s0,
4902 *restrict s1,
4903 *restrict s2;
cristy3ed852e2009-09-05 21:47:34 +00004904
4905 register long
4906 x;
4907
4908 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00004909 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00004910
4911 if (status == MagickFalse)
4912 continue;
4913 p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception);
4914 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
4915 exception);
4916 if ((p == (PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
4917 {
4918 status=MagickFalse;
4919 continue;
4920 }
4921 /*
4922 Shade this row of pixels.
4923 */
4924 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
4925 s0=p+1;
4926 s1=s0+image->columns+2;
4927 s2=s1+image->columns+2;
4928 for (x=0; x < (long) image->columns; x++)
4929 {
4930 /*
4931 Determine the surface normal and compute shading.
4932 */
4933 normal.x=(double) (PixelIntensity(s0-1)+PixelIntensity(s1-1)+
4934 PixelIntensity(s2-1)-PixelIntensity(s0+1)-PixelIntensity(s1+1)-
4935 PixelIntensity(s2+1));
4936 normal.y=(double) (PixelIntensity(s2-1)+PixelIntensity(s2)+
4937 PixelIntensity(s2+1)-PixelIntensity(s0-1)-PixelIntensity(s0)-
4938 PixelIntensity(s0+1));
4939 if ((normal.x == 0.0) && (normal.y == 0.0))
4940 shade=light.z;
4941 else
4942 {
4943 shade=0.0;
4944 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
4945 if (distance > MagickEpsilon)
4946 {
4947 normal_distance=
4948 normal.x*normal.x+normal.y*normal.y+normal.z*normal.z;
4949 if (normal_distance > (MagickEpsilon*MagickEpsilon))
4950 shade=distance/sqrt((double) normal_distance);
4951 }
4952 }
4953 if (gray != MagickFalse)
4954 {
4955 q->red=(Quantum) shade;
4956 q->green=(Quantum) shade;
4957 q->blue=(Quantum) shade;
4958 }
4959 else
4960 {
cristyce70c172010-01-07 17:15:30 +00004961 q->red=ClampToQuantum(QuantumScale*shade*s1->red);
4962 q->green=ClampToQuantum(QuantumScale*shade*s1->green);
4963 q->blue=ClampToQuantum(QuantumScale*shade*s1->blue);
cristy3ed852e2009-09-05 21:47:34 +00004964 }
4965 q->opacity=s1->opacity;
4966 s0++;
4967 s1++;
4968 s2++;
4969 q++;
4970 }
4971 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
4972 status=MagickFalse;
4973 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4974 {
4975 MagickBooleanType
4976 proceed;
4977
cristyb5d5f722009-11-04 03:03:49 +00004978#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00004979 #pragma omp critical (MagickCore_ShadeImage)
4980#endif
4981 proceed=SetImageProgress(image,ShadeImageTag,progress++,image->rows);
4982 if (proceed == MagickFalse)
4983 status=MagickFalse;
4984 }
4985 }
4986 shade_view=DestroyCacheView(shade_view);
4987 image_view=DestroyCacheView(image_view);
4988 if (status == MagickFalse)
4989 shade_image=DestroyImage(shade_image);
4990 return(shade_image);
4991}
4992
4993/*
4994%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4995% %
4996% %
4997% %
4998% S h a r p e n I m a g e %
4999% %
5000% %
5001% %
5002%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5003%
5004% SharpenImage() sharpens the image. We convolve the image with a Gaussian
5005% operator of the given radius and standard deviation (sigma). For
5006% reasonable results, radius should be larger than sigma. Use a radius of 0
5007% and SharpenImage() selects a suitable radius for you.
5008%
5009% Using a separable kernel would be faster, but the negative weights cancel
5010% out on the corners of the kernel producing often undesirable ringing in the
5011% filtered result; this can be avoided by using a 2D gaussian shaped image
5012% sharpening kernel instead.
5013%
5014% The format of the SharpenImage method is:
5015%
5016% Image *SharpenImage(const Image *image,const double radius,
5017% const double sigma,ExceptionInfo *exception)
5018% Image *SharpenImageChannel(const Image *image,const ChannelType channel,
5019% const double radius,const double sigma,ExceptionInfo *exception)
5020%
5021% A description of each parameter follows:
5022%
5023% o image: the image.
5024%
5025% o channel: the channel type.
5026%
5027% o radius: the radius of the Gaussian, in pixels, not counting the center
5028% pixel.
5029%
5030% o sigma: the standard deviation of the Laplacian, in pixels.
5031%
5032% o exception: return any errors or warnings in this structure.
5033%
5034*/
5035
5036MagickExport Image *SharpenImage(const Image *image,const double radius,
5037 const double sigma,ExceptionInfo *exception)
5038{
5039 Image
5040 *sharp_image;
5041
5042 sharp_image=SharpenImageChannel(image,DefaultChannels,radius,sigma,exception);
5043 return(sharp_image);
5044}
5045
5046MagickExport Image *SharpenImageChannel(const Image *image,
5047 const ChannelType channel,const double radius,const double sigma,
5048 ExceptionInfo *exception)
5049{
5050 double
cristy47e00502009-12-17 19:19:57 +00005051 *kernel,
5052 normalize;
cristy3ed852e2009-09-05 21:47:34 +00005053
5054 Image
5055 *sharp_image;
5056
cristy47e00502009-12-17 19:19:57 +00005057 long
5058 j,
cristy3ed852e2009-09-05 21:47:34 +00005059 u,
5060 v;
5061
cristy47e00502009-12-17 19:19:57 +00005062 register long
5063 i;
5064
cristy3ed852e2009-09-05 21:47:34 +00005065 unsigned long
5066 width;
5067
5068 assert(image != (const Image *) NULL);
5069 assert(image->signature == MagickSignature);
5070 if (image->debug != MagickFalse)
5071 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5072 assert(exception != (ExceptionInfo *) NULL);
5073 assert(exception->signature == MagickSignature);
5074 width=GetOptimalKernelWidth2D(radius,sigma);
5075 kernel=(double *) AcquireQuantumMemory((size_t) width*width,sizeof(*kernel));
5076 if (kernel == (double *) NULL)
5077 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy3ed852e2009-09-05 21:47:34 +00005078 normalize=0.0;
cristy47e00502009-12-17 19:19:57 +00005079 j=(long) width/2;
5080 i=0;
5081 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00005082 {
cristy47e00502009-12-17 19:19:57 +00005083 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00005084 {
cristy47e00502009-12-17 19:19:57 +00005085 kernel[i]=(-exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
5086 (2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00005087 normalize+=kernel[i];
5088 i++;
5089 }
5090 }
5091 kernel[i/2]=(double) ((-2.0)*normalize);
5092 sharp_image=ConvolveImageChannel(image,channel,width,kernel,exception);
5093 kernel=(double *) RelinquishMagickMemory(kernel);
5094 return(sharp_image);
5095}
5096
5097/*
5098%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5099% %
5100% %
5101% %
5102% S p r e a d I m a g e %
5103% %
5104% %
5105% %
5106%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5107%
5108% SpreadImage() is a special effects method that randomly displaces each
5109% pixel in a block defined by the radius parameter.
5110%
5111% The format of the SpreadImage method is:
5112%
5113% Image *SpreadImage(const Image *image,const double radius,
5114% ExceptionInfo *exception)
5115%
5116% A description of each parameter follows:
5117%
5118% o image: the image.
5119%
5120% o radius: Choose a random pixel in a neighborhood of this extent.
5121%
5122% o exception: return any errors or warnings in this structure.
5123%
5124*/
5125MagickExport Image *SpreadImage(const Image *image,const double radius,
5126 ExceptionInfo *exception)
5127{
5128#define SpreadImageTag "Spread/Image"
5129
cristyfa112112010-01-04 17:48:07 +00005130 CacheView
5131 *image_view;
5132
cristy3ed852e2009-09-05 21:47:34 +00005133 Image
5134 *spread_image;
5135
5136 long
5137 progress,
5138 y;
5139
5140 MagickBooleanType
5141 status;
5142
5143 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00005144 bias;
cristy3ed852e2009-09-05 21:47:34 +00005145
5146 RandomInfo
cristyfa112112010-01-04 17:48:07 +00005147 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00005148
5149 ResampleFilter
cristyfa112112010-01-04 17:48:07 +00005150 **restrict resample_filter;
cristy3ed852e2009-09-05 21:47:34 +00005151
5152 unsigned long
5153 width;
5154
cristy3ed852e2009-09-05 21:47:34 +00005155 /*
5156 Initialize spread image attributes.
5157 */
5158 assert(image != (Image *) NULL);
5159 assert(image->signature == MagickSignature);
5160 if (image->debug != MagickFalse)
5161 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5162 assert(exception != (ExceptionInfo *) NULL);
5163 assert(exception->signature == MagickSignature);
5164 spread_image=CloneImage(image,image->columns,image->rows,MagickTrue,
5165 exception);
5166 if (spread_image == (Image *) NULL)
5167 return((Image *) NULL);
5168 if (SetImageStorageClass(spread_image,DirectClass) == MagickFalse)
5169 {
5170 InheritException(exception,&spread_image->exception);
5171 spread_image=DestroyImage(spread_image);
5172 return((Image *) NULL);
5173 }
5174 /*
5175 Spread image.
5176 */
5177 status=MagickTrue;
5178 progress=0;
cristyddd82202009-11-03 20:14:50 +00005179 GetMagickPixelPacket(spread_image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00005180 width=GetOptimalKernelWidth1D(radius,0.5);
5181 resample_filter=AcquireResampleFilterThreadSet(image,MagickTrue,exception);
5182 random_info=AcquireRandomInfoThreadSet();
5183 image_view=AcquireCacheView(spread_image);
cristyb5d5f722009-11-04 03:03:49 +00005184#if defined(MAGICKCORE_OPENMP_SUPPORT)
5185 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00005186#endif
5187 for (y=0; y < (long) spread_image->rows; y++)
5188 {
5189 MagickPixelPacket
5190 pixel;
5191
5192 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00005193 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00005194
5195 register long
5196 id,
5197 x;
5198
5199 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00005200 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00005201
5202 if (status == MagickFalse)
5203 continue;
5204 q=QueueCacheViewAuthenticPixels(image_view,0,y,spread_image->columns,1,
5205 exception);
5206 if (q == (PixelPacket *) NULL)
5207 {
5208 status=MagickFalse;
5209 continue;
5210 }
5211 indexes=GetCacheViewAuthenticIndexQueue(image_view);
cristyddd82202009-11-03 20:14:50 +00005212 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00005213 id=GetOpenMPThreadId();
5214 for (x=0; x < (long) spread_image->columns; x++)
5215 {
5216 (void) ResamplePixelColor(resample_filter[id],(double) x+width*
5217 (GetPseudoRandomValue(random_info[id])-0.5),(double) y+width*
5218 (GetPseudoRandomValue(random_info[id])-0.5),&pixel);
5219 SetPixelPacket(spread_image,&pixel,q,indexes+x);
5220 q++;
5221 }
5222 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
5223 status=MagickFalse;
5224 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5225 {
5226 MagickBooleanType
5227 proceed;
5228
cristyb5d5f722009-11-04 03:03:49 +00005229#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00005230 #pragma omp critical (MagickCore_SpreadImage)
5231#endif
5232 proceed=SetImageProgress(image,SpreadImageTag,progress++,image->rows);
5233 if (proceed == MagickFalse)
5234 status=MagickFalse;
5235 }
5236 }
5237 image_view=DestroyCacheView(image_view);
5238 random_info=DestroyRandomInfoThreadSet(random_info);
5239 resample_filter=DestroyResampleFilterThreadSet(resample_filter);
5240 return(spread_image);
5241}
5242
5243/*
5244%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5245% %
5246% %
5247% %
5248% U n s h a r p M a s k I m a g e %
5249% %
5250% %
5251% %
5252%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
5253%
5254% UnsharpMaskImage() sharpens one or more image channels. We convolve the
5255% image with a Gaussian operator of the given radius and standard deviation
5256% (sigma). For reasonable results, radius should be larger than sigma. Use a
5257% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
5258%
5259% The format of the UnsharpMaskImage method is:
5260%
5261% Image *UnsharpMaskImage(const Image *image,const double radius,
5262% const double sigma,const double amount,const double threshold,
5263% ExceptionInfo *exception)
5264% Image *UnsharpMaskImageChannel(const Image *image,
5265% const ChannelType channel,const double radius,const double sigma,
5266% const double amount,const double threshold,ExceptionInfo *exception)
5267%
5268% A description of each parameter follows:
5269%
5270% o image: the image.
5271%
5272% o channel: the channel type.
5273%
5274% o radius: the radius of the Gaussian, in pixels, not counting the center
5275% pixel.
5276%
5277% o sigma: the standard deviation of the Gaussian, in pixels.
5278%
5279% o amount: the percentage of the difference between the original and the
5280% blur image that is added back into the original.
5281%
5282% o threshold: the threshold in pixels needed to apply the diffence amount.
5283%
5284% o exception: return any errors or warnings in this structure.
5285%
5286*/
5287
5288MagickExport Image *UnsharpMaskImage(const Image *image,const double radius,
5289 const double sigma,const double amount,const double threshold,
5290 ExceptionInfo *exception)
5291{
5292 Image
5293 *sharp_image;
5294
5295 sharp_image=UnsharpMaskImageChannel(image,DefaultChannels,radius,sigma,amount,
5296 threshold,exception);
5297 return(sharp_image);
5298}
5299
5300MagickExport Image *UnsharpMaskImageChannel(const Image *image,
5301 const ChannelType channel,const double radius,const double sigma,
5302 const double amount,const double threshold,ExceptionInfo *exception)
5303{
5304#define SharpenImageTag "Sharpen/Image"
5305
cristyc4c8d132010-01-07 01:58:38 +00005306 CacheView
5307 *image_view,
5308 *unsharp_view;
5309
cristy3ed852e2009-09-05 21:47:34 +00005310 Image
5311 *unsharp_image;
5312
5313 long
5314 progress,
5315 y;
5316
5317 MagickBooleanType
5318 status;
5319
5320 MagickPixelPacket
cristyddd82202009-11-03 20:14:50 +00005321 bias;
cristy3ed852e2009-09-05 21:47:34 +00005322
5323 MagickRealType
5324 quantum_threshold;
5325
cristy3ed852e2009-09-05 21:47:34 +00005326 assert(image != (const Image *) NULL);
5327 assert(image->signature == MagickSignature);
5328 if (image->debug != MagickFalse)
5329 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
5330 assert(exception != (ExceptionInfo *) NULL);
5331 unsharp_image=BlurImageChannel(image,channel,radius,sigma,exception);
5332 if (unsharp_image == (Image *) NULL)
5333 return((Image *) NULL);
5334 quantum_threshold=(MagickRealType) QuantumRange*threshold;
5335 /*
5336 Unsharp-mask image.
5337 */
5338 status=MagickTrue;
5339 progress=0;
cristyddd82202009-11-03 20:14:50 +00005340 GetMagickPixelPacket(image,&bias);
cristy3ed852e2009-09-05 21:47:34 +00005341 image_view=AcquireCacheView(image);
5342 unsharp_view=AcquireCacheView(unsharp_image);
cristyb5d5f722009-11-04 03:03:49 +00005343#if defined(MAGICKCORE_OPENMP_SUPPORT)
5344 #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
cristy3ed852e2009-09-05 21:47:34 +00005345#endif
5346 for (y=0; y < (long) image->rows; y++)
5347 {
5348 MagickPixelPacket
5349 pixel;
5350
5351 register const IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00005352 *restrict indexes;
cristy3ed852e2009-09-05 21:47:34 +00005353
5354 register const PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00005355 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00005356
5357 register IndexPacket
cristyc47d1f82009-11-26 01:44:43 +00005358 *restrict unsharp_indexes;
cristy3ed852e2009-09-05 21:47:34 +00005359
5360 register long
5361 x;
5362
5363 register PixelPacket
cristyc47d1f82009-11-26 01:44:43 +00005364 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00005365
5366 if (status == MagickFalse)
5367 continue;
5368 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
5369 q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
5370 exception);
5371 if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
5372 {
5373 status=MagickFalse;
5374 continue;
5375 }
5376 indexes=GetCacheViewVirtualIndexQueue(image_view);
5377 unsharp_indexes=GetCacheViewAuthenticIndexQueue(unsharp_view);
cristyddd82202009-11-03 20:14:50 +00005378 pixel=bias;
cristy3ed852e2009-09-05 21:47:34 +00005379 for (x=0; x < (long) image->columns; x++)
5380 {
5381 if ((channel & RedChannel) != 0)
5382 {
5383 pixel.red=p->red-(MagickRealType) q->red;
5384 if (fabs(2.0*pixel.red) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005385 pixel.red=(MagickRealType) GetRedPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005386 else
5387 pixel.red=(MagickRealType) p->red+(pixel.red*amount);
cristyce70c172010-01-07 17:15:30 +00005388 SetRedPixelComponent(q,ClampRedPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00005389 }
5390 if ((channel & GreenChannel) != 0)
5391 {
5392 pixel.green=p->green-(MagickRealType) q->green;
5393 if (fabs(2.0*pixel.green) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005394 pixel.green=(MagickRealType) GetGreenPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005395 else
5396 pixel.green=(MagickRealType) p->green+(pixel.green*amount);
cristyce70c172010-01-07 17:15:30 +00005397 SetGreenPixelComponent(q,ClampGreenPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00005398 }
5399 if ((channel & BlueChannel) != 0)
5400 {
5401 pixel.blue=p->blue-(MagickRealType) q->blue;
5402 if (fabs(2.0*pixel.blue) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005403 pixel.blue=(MagickRealType) GetBluePixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005404 else
5405 pixel.blue=(MagickRealType) p->blue+(pixel.blue*amount);
cristyce70c172010-01-07 17:15:30 +00005406 SetBluePixelComponent(q,ClampBluePixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00005407 }
5408 if ((channel & OpacityChannel) != 0)
5409 {
5410 pixel.opacity=p->opacity-(MagickRealType) q->opacity;
5411 if (fabs(2.0*pixel.opacity) < quantum_threshold)
cristyce70c172010-01-07 17:15:30 +00005412 pixel.opacity=(MagickRealType) GetOpacityPixelComponent(p);
cristy3ed852e2009-09-05 21:47:34 +00005413 else
5414 pixel.opacity=p->opacity+(pixel.opacity*amount);
cristyce70c172010-01-07 17:15:30 +00005415 SetOpacityPixelComponent(q,ClampOpacityPixelComponent(&pixel));
cristy3ed852e2009-09-05 21:47:34 +00005416 }
5417 if (((channel & IndexChannel) != 0) &&
5418 (image->colorspace == CMYKColorspace))
5419 {
5420 pixel.index=unsharp_indexes[x]-(MagickRealType) indexes[x];
5421 if (fabs(2.0*pixel.index) < quantum_threshold)
5422 pixel.index=(MagickRealType) unsharp_indexes[x];
5423 else
5424 pixel.index=(MagickRealType) unsharp_indexes[x]+(pixel.index*
5425 amount);
cristyce70c172010-01-07 17:15:30 +00005426 unsharp_indexes[x]=ClampToQuantum(pixel.index);
cristy3ed852e2009-09-05 21:47:34 +00005427 }
5428 p++;
5429 q++;
5430 }
5431 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
5432 status=MagickFalse;
5433 if (image->progress_monitor != (MagickProgressMonitor) NULL)
5434 {
5435 MagickBooleanType
5436 proceed;
5437
cristyb5d5f722009-11-04 03:03:49 +00005438#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy3ed852e2009-09-05 21:47:34 +00005439 #pragma omp critical (MagickCore_UnsharpMaskImageChannel)
5440#endif
5441 proceed=SetImageProgress(image,SharpenImageTag,progress++,image->rows);
5442 if (proceed == MagickFalse)
5443 status=MagickFalse;
5444 }
5445 }
5446 unsharp_image->type=image->type;
5447 unsharp_view=DestroyCacheView(unsharp_view);
5448 image_view=DestroyCacheView(image_view);
5449 if (status == MagickFalse)
5450 unsharp_image=DestroyImage(unsharp_image);
5451 return(unsharp_image);
5452}