blob: 14f3866562149220b8c11e1759cb05085956c894 [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% %
cristy1454be72011-12-19 01:52:48 +000020% Copyright 1999-2012 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*/
cristy4c08aed2011-07-01 19:47:50 +000043#include "MagickCore/studio.h"
44#include "MagickCore/accelerate.h"
45#include "MagickCore/blob.h"
46#include "MagickCore/cache-view.h"
47#include "MagickCore/color.h"
48#include "MagickCore/color-private.h"
49#include "MagickCore/colorspace.h"
50#include "MagickCore/constitute.h"
51#include "MagickCore/decorate.h"
cristyc53413d2011-11-17 13:04:26 +000052#include "MagickCore/distort.h"
cristy4c08aed2011-07-01 19:47:50 +000053#include "MagickCore/draw.h"
54#include "MagickCore/enhance.h"
55#include "MagickCore/exception.h"
56#include "MagickCore/exception-private.h"
57#include "MagickCore/effect.h"
58#include "MagickCore/fx.h"
59#include "MagickCore/gem.h"
cristy8ea81222011-09-04 10:33:32 +000060#include "MagickCore/gem-private.h"
cristy4c08aed2011-07-01 19:47:50 +000061#include "MagickCore/geometry.h"
62#include "MagickCore/image-private.h"
63#include "MagickCore/list.h"
64#include "MagickCore/log.h"
65#include "MagickCore/memory_.h"
66#include "MagickCore/monitor.h"
67#include "MagickCore/monitor-private.h"
68#include "MagickCore/montage.h"
69#include "MagickCore/morphology.h"
70#include "MagickCore/paint.h"
71#include "MagickCore/pixel-accessor.h"
cristy1d1b10f2012-06-02 20:02:55 +000072#include "MagickCore/pixel-private.h"
cristy4c08aed2011-07-01 19:47:50 +000073#include "MagickCore/property.h"
74#include "MagickCore/quantize.h"
75#include "MagickCore/quantum.h"
76#include "MagickCore/quantum-private.h"
77#include "MagickCore/random_.h"
78#include "MagickCore/random-private.h"
79#include "MagickCore/resample.h"
80#include "MagickCore/resample-private.h"
81#include "MagickCore/resize.h"
82#include "MagickCore/resource_.h"
83#include "MagickCore/segment.h"
cristy31bbf2f2011-11-17 13:19:37 +000084#include "MagickCore/shear.h"
cristy4c08aed2011-07-01 19:47:50 +000085#include "MagickCore/signature-private.h"
cristy99bd5232011-12-07 14:38:20 +000086#include "MagickCore/statistic.h"
cristy4c08aed2011-07-01 19:47:50 +000087#include "MagickCore/string_.h"
88#include "MagickCore/thread-private.h"
89#include "MagickCore/transform.h"
90#include "MagickCore/threshold.h"
cristy3ed852e2009-09-05 21:47:34 +000091
92/*
93%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
94% %
95% %
96% %
97% A d a p t i v e B l u r I m a g e %
98% %
99% %
100% %
101%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
102%
103% AdaptiveBlurImage() adaptively blurs the image by blurring less
104% intensely near image edges and more intensely far from edges. We blur the
105% image with a Gaussian operator of the given radius and standard deviation
106% (sigma). For reasonable results, radius should be larger than sigma. Use a
107% radius of 0 and AdaptiveBlurImage() selects a suitable radius for you.
108%
109% The format of the AdaptiveBlurImage method is:
110%
111% Image *AdaptiveBlurImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +0000112% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000113%
114% A description of each parameter follows:
115%
116% o image: the image.
117%
cristy3ed852e2009-09-05 21:47:34 +0000118% o radius: the radius of the Gaussian, in pixels, not counting the center
119% pixel.
120%
121% o sigma: the standard deviation of the Laplacian, in pixels.
122%
123% o exception: return any errors or warnings in this structure.
124%
125*/
126
cristyf89cb1d2011-07-07 01:24:37 +0000127MagickExport MagickBooleanType AdaptiveLevelImage(Image *image,
cristy051718b2011-08-28 22:49:25 +0000128 const char *levels,ExceptionInfo *exception)
cristyf89cb1d2011-07-07 01:24:37 +0000129{
130 double
131 black_point,
132 gamma,
133 white_point;
134
135 GeometryInfo
136 geometry_info;
137
138 MagickBooleanType
139 status;
140
141 MagickStatusType
142 flags;
143
144 /*
145 Parse levels.
146 */
147 if (levels == (char *) NULL)
148 return(MagickFalse);
149 flags=ParseGeometry(levels,&geometry_info);
150 black_point=geometry_info.rho;
151 white_point=(double) QuantumRange;
152 if ((flags & SigmaValue) != 0)
153 white_point=geometry_info.sigma;
154 gamma=1.0;
155 if ((flags & XiValue) != 0)
156 gamma=geometry_info.xi;
157 if ((flags & PercentValue) != 0)
158 {
159 black_point*=(double) image->columns*image->rows/100.0;
160 white_point*=(double) image->columns*image->rows/100.0;
161 }
162 if ((flags & SigmaValue) == 0)
163 white_point=(double) QuantumRange-black_point;
164 if ((flags & AspectValue ) == 0)
cristy7c0a0a42011-08-23 17:57:25 +0000165 status=LevelImage(image,black_point,white_point,gamma,exception);
cristyf89cb1d2011-07-07 01:24:37 +0000166 else
cristy7c0a0a42011-08-23 17:57:25 +0000167 status=LevelizeImage(image,black_point,white_point,gamma,exception);
cristyf89cb1d2011-07-07 01:24:37 +0000168 return(status);
169}
170
cristy4282c702011-11-21 00:01:06 +0000171MagickExport Image *AdaptiveBlurImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +0000172 const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000173{
174#define AdaptiveBlurImageTag "Convolve/Image"
cristy9b528342012-06-02 00:59:20 +0000175#define MagickSigma (fabs(sigma) < MagickEpsilon ? MagickEpsilon : sigma)
cristy3ed852e2009-09-05 21:47:34 +0000176
cristyc4c8d132010-01-07 01:58:38 +0000177 CacheView
178 *blur_view,
179 *edge_view,
180 *image_view;
181
cristy3ed852e2009-09-05 21:47:34 +0000182 double
cristy47e00502009-12-17 19:19:57 +0000183 **kernel,
184 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000185
186 Image
187 *blur_image,
188 *edge_image,
189 *gaussian_image;
190
cristy3ed852e2009-09-05 21:47:34 +0000191 MagickBooleanType
192 status;
193
cristybb503372010-05-27 20:51:26 +0000194 MagickOffsetType
195 progress;
196
cristybb503372010-05-27 20:51:26 +0000197 register ssize_t
cristy47e00502009-12-17 19:19:57 +0000198 i;
cristy3ed852e2009-09-05 21:47:34 +0000199
cristybb503372010-05-27 20:51:26 +0000200 size_t
cristy3ed852e2009-09-05 21:47:34 +0000201 width;
202
cristybb503372010-05-27 20:51:26 +0000203 ssize_t
204 j,
205 k,
206 u,
207 v,
208 y;
209
cristy3ed852e2009-09-05 21:47:34 +0000210 assert(image != (const Image *) NULL);
211 assert(image->signature == MagickSignature);
212 if (image->debug != MagickFalse)
213 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
214 assert(exception != (ExceptionInfo *) NULL);
215 assert(exception->signature == MagickSignature);
216 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
217 if (blur_image == (Image *) NULL)
218 return((Image *) NULL);
219 if (fabs(sigma) <= MagickEpsilon)
220 return(blur_image);
cristy574cc262011-08-05 01:23:58 +0000221 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000222 {
cristy3ed852e2009-09-05 21:47:34 +0000223 blur_image=DestroyImage(blur_image);
224 return((Image *) NULL);
225 }
226 /*
227 Edge detect the image brighness channel, level, blur, and level again.
228 */
cristy8ae632d2011-09-05 17:29:53 +0000229 edge_image=EdgeImage(image,radius,sigma,exception);
cristy3ed852e2009-09-05 21:47:34 +0000230 if (edge_image == (Image *) NULL)
231 {
232 blur_image=DestroyImage(blur_image);
233 return((Image *) NULL);
234 }
cristy051718b2011-08-28 22:49:25 +0000235 (void) AdaptiveLevelImage(edge_image,"20%,95%",exception);
cristyd89705a2012-01-20 02:52:24 +0000236 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,exception);
cristy3ed852e2009-09-05 21:47:34 +0000237 if (gaussian_image != (Image *) NULL)
238 {
239 edge_image=DestroyImage(edge_image);
240 edge_image=gaussian_image;
241 }
cristy051718b2011-08-28 22:49:25 +0000242 (void) AdaptiveLevelImage(edge_image,"10%,95%",exception);
cristy3ed852e2009-09-05 21:47:34 +0000243 /*
244 Create a set of kernels from maximum (radius,sigma) to minimum.
245 */
246 width=GetOptimalKernelWidth2D(radius,sigma);
cristy219a63a2012-01-17 12:30:33 +0000247 kernel=(double **) AcquireAlignedMemory((size_t) width,sizeof(*kernel));
cristy3ed852e2009-09-05 21:47:34 +0000248 if (kernel == (double **) NULL)
249 {
250 edge_image=DestroyImage(edge_image);
251 blur_image=DestroyImage(blur_image);
252 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
253 }
254 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
cristybb503372010-05-27 20:51:26 +0000255 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000256 {
cristyf5d0f3e2012-01-17 03:20:33 +0000257 kernel[i]=(double *) AcquireAlignedMemory((size_t) (width-i),(width-i)*
cristy3ed852e2009-09-05 21:47:34 +0000258 sizeof(**kernel));
259 if (kernel[i] == (double *) NULL)
260 break;
cristy47e00502009-12-17 19:19:57 +0000261 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000262 j=(ssize_t) (width-i)/2;
cristy47e00502009-12-17 19:19:57 +0000263 k=0;
264 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000265 {
cristy47e00502009-12-17 19:19:57 +0000266 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000267 {
cristy4205a3c2010-09-12 20:19:59 +0000268 kernel[i][k]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
269 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +0000270 normalize+=kernel[i][k];
271 k++;
cristy3ed852e2009-09-05 21:47:34 +0000272 }
273 }
cristy3ed852e2009-09-05 21:47:34 +0000274 if (fabs(normalize) <= MagickEpsilon)
275 normalize=1.0;
276 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000277 for (k=0; k < (j*j); k++)
278 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000279 }
cristybb503372010-05-27 20:51:26 +0000280 if (i < (ssize_t) width)
cristy3ed852e2009-09-05 21:47:34 +0000281 {
282 for (i-=2; i >= 0; i-=2)
cristyf5d0f3e2012-01-17 03:20:33 +0000283 kernel[i]=(double *) RelinquishAlignedMemory(kernel[i]);
cristy219a63a2012-01-17 12:30:33 +0000284 kernel=(double **) RelinquishAlignedMemory(kernel);
cristy3ed852e2009-09-05 21:47:34 +0000285 edge_image=DestroyImage(edge_image);
286 blur_image=DestroyImage(blur_image);
287 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
288 }
289 /*
290 Adaptively blur image.
291 */
292 status=MagickTrue;
293 progress=0;
cristydb070952012-04-20 14:33:00 +0000294 image_view=AcquireVirtualCacheView(image,exception);
295 edge_view=AcquireVirtualCacheView(edge_image,exception);
296 blur_view=AcquireAuthenticCacheView(blur_image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000297#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000298 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy4ee2b0c2012-05-15 00:30:35 +0000299 dynamic_number_threads(image,image->columns,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +0000300#endif
cristybb503372010-05-27 20:51:26 +0000301 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000302 {
cristy4c08aed2011-07-01 19:47:50 +0000303 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000304 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000305
cristy4c08aed2011-07-01 19:47:50 +0000306 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000307 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000308
cristy117ff172010-08-15 21:35:32 +0000309 register ssize_t
310 x;
311
cristy3ed852e2009-09-05 21:47:34 +0000312 if (status == MagickFalse)
313 continue;
314 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
315 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
316 exception);
cristyacd2ed22011-08-30 01:44:23 +0000317 if ((r == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000318 {
319 status=MagickFalse;
320 continue;
321 }
cristybb503372010-05-27 20:51:26 +0000322 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000323 {
cristy4c11c2b2011-09-05 20:17:07 +0000324 register const Quantum
325 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000326
cristybb503372010-05-27 20:51:26 +0000327 register ssize_t
cristy4c11c2b2011-09-05 20:17:07 +0000328 i;
cristy3ed852e2009-09-05 21:47:34 +0000329
cristy4c11c2b2011-09-05 20:17:07 +0000330 ssize_t
331 center,
332 j;
333
334 j=(ssize_t) ceil((double) width*QuantumScale*
cristy4c08aed2011-07-01 19:47:50 +0000335 GetPixelIntensity(edge_image,r)-0.5);
cristy4c11c2b2011-09-05 20:17:07 +0000336 if (j < 0)
337 j=0;
cristy3ed852e2009-09-05 21:47:34 +0000338 else
cristy4c11c2b2011-09-05 20:17:07 +0000339 if (j > (ssize_t) width)
340 j=(ssize_t) width;
341 if ((j & 0x01) != 0)
342 j--;
343 p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) (width-j)/2L),y-
344 (ssize_t) ((width-j)/2L),width-j,width-j,exception);
cristy4c08aed2011-07-01 19:47:50 +0000345 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000346 break;
cristyd09f8802012-02-04 16:44:10 +0000347 center=(ssize_t) GetPixelChannels(image)*(width-j)*((width-j)/2L)+
348 GetPixelChannels(image)*((width-j)/2L);
cristy177e41c2012-04-15 15:08:25 +0000349 if (GetPixelMask(image,p) != 0)
350 {
351 q+=GetPixelChannels(blur_image);
352 r+=GetPixelChannels(edge_image);
353 continue;
354 }
cristy4c11c2b2011-09-05 20:17:07 +0000355 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy3ed852e2009-09-05 21:47:34 +0000356 {
cristy4c11c2b2011-09-05 20:17:07 +0000357 MagickRealType
358 alpha,
359 gamma,
360 pixel;
361
362 PixelChannel
363 channel;
364
365 PixelTrait
366 blur_traits,
367 traits;
368
369 register const double
370 *restrict k;
371
372 register const Quantum
373 *restrict pixels;
374
375 register ssize_t
376 u;
377
378 ssize_t
379 v;
380
cristye2a912b2011-12-05 20:02:07 +0000381 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +0000382 traits=GetPixelChannelMapTraits(image,channel);
cristy4c11c2b2011-09-05 20:17:07 +0000383 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
384 if ((traits == UndefinedPixelTrait) ||
385 (blur_traits == UndefinedPixelTrait))
386 continue;
cristy177e41c2012-04-15 15:08:25 +0000387 if ((blur_traits & CopyPixelTrait) != 0)
cristy4c11c2b2011-09-05 20:17:07 +0000388 {
cristy0beccfa2011-09-25 20:47:53 +0000389 SetPixelChannel(blur_image,channel,p[center+i],q);
cristy4c11c2b2011-09-05 20:17:07 +0000390 continue;
391 }
392 k=kernel[j];
393 pixels=p;
cristyaa2c16c2012-03-25 22:21:35 +0000394 pixel=0.0;
cristy4c11c2b2011-09-05 20:17:07 +0000395 gamma=0.0;
396 if ((blur_traits & BlendPixelTrait) == 0)
397 {
398 /*
399 No alpha blending.
400 */
401 for (v=0; v < (ssize_t) (width-j); v++)
402 {
403 for (u=0; u < (ssize_t) (width-j); u++)
404 {
405 pixel+=(*k)*pixels[i];
406 gamma+=(*k);
407 k++;
408 pixels+=GetPixelChannels(image);
409 }
410 }
cristyc58380a2012-06-03 15:12:30 +0000411 gamma=MagickEpsilonReciprocal(gamma);
cristy0beccfa2011-09-25 20:47:53 +0000412 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy4c11c2b2011-09-05 20:17:07 +0000413 continue;
414 }
415 /*
416 Alpha blending.
417 */
418 for (v=0; v < (ssize_t) (width-j); v++)
cristy3ed852e2009-09-05 21:47:34 +0000419 {
cristy4c11c2b2011-09-05 20:17:07 +0000420 for (u=0; u < (ssize_t) (width-j); u++)
421 {
422 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,pixels));
423 pixel+=(*k)*alpha*pixels[i];
424 gamma+=(*k)*alpha;
425 k++;
426 pixels+=GetPixelChannels(image);
427 }
cristy3ed852e2009-09-05 21:47:34 +0000428 }
cristyc58380a2012-06-03 15:12:30 +0000429 gamma=MagickEpsilonReciprocal(gamma);
cristy0beccfa2011-09-25 20:47:53 +0000430 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy3ed852e2009-09-05 21:47:34 +0000431 }
cristyed231572011-07-14 02:18:59 +0000432 q+=GetPixelChannels(blur_image);
433 r+=GetPixelChannels(edge_image);
cristy3ed852e2009-09-05 21:47:34 +0000434 }
435 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
436 status=MagickFalse;
437 if (image->progress_monitor != (MagickProgressMonitor) NULL)
438 {
439 MagickBooleanType
440 proceed;
441
cristyb5d5f722009-11-04 03:03:49 +0000442#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf8561542012-01-24 00:26:46 +0000443 #pragma omp critical (MagickCore_AdaptiveBlurImage)
cristy3ed852e2009-09-05 21:47:34 +0000444#endif
445 proceed=SetImageProgress(image,AdaptiveBlurImageTag,progress++,
446 image->rows);
447 if (proceed == MagickFalse)
448 status=MagickFalse;
449 }
450 }
451 blur_image->type=image->type;
452 blur_view=DestroyCacheView(blur_view);
453 edge_view=DestroyCacheView(edge_view);
454 image_view=DestroyCacheView(image_view);
455 edge_image=DestroyImage(edge_image);
cristybb503372010-05-27 20:51:26 +0000456 for (i=0; i < (ssize_t) width; i+=2)
cristyf5d0f3e2012-01-17 03:20:33 +0000457 kernel[i]=(double *) RelinquishAlignedMemory(kernel[i]);
cristy219a63a2012-01-17 12:30:33 +0000458 kernel=(double **) RelinquishAlignedMemory(kernel);
cristy3ed852e2009-09-05 21:47:34 +0000459 if (status == MagickFalse)
460 blur_image=DestroyImage(blur_image);
461 return(blur_image);
462}
463
464/*
465%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
466% %
467% %
468% %
469% A d a p t i v e S h a r p e n I m a g e %
470% %
471% %
472% %
473%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
474%
475% AdaptiveSharpenImage() adaptively sharpens the image by sharpening more
476% intensely near image edges and less intensely far from edges. We sharpen the
477% image with a Gaussian operator of the given radius and standard deviation
478% (sigma). For reasonable results, radius should be larger than sigma. Use a
479% radius of 0 and AdaptiveSharpenImage() selects a suitable radius for you.
480%
481% The format of the AdaptiveSharpenImage method is:
482%
483% Image *AdaptiveSharpenImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +0000484% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000485%
486% A description of each parameter follows:
487%
488% o image: the image.
489%
cristy3ed852e2009-09-05 21:47:34 +0000490% o radius: the radius of the Gaussian, in pixels, not counting the center
491% pixel.
492%
493% o sigma: the standard deviation of the Laplacian, in pixels.
494%
495% o exception: return any errors or warnings in this structure.
496%
497*/
cristy3ed852e2009-09-05 21:47:34 +0000498MagickExport Image *AdaptiveSharpenImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +0000499 const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000500{
cristy3ed852e2009-09-05 21:47:34 +0000501#define AdaptiveSharpenImageTag "Convolve/Image"
cristy9b528342012-06-02 00:59:20 +0000502#define MagickSigma (fabs(sigma) < MagickEpsilon ? MagickEpsilon : sigma)
cristy3ed852e2009-09-05 21:47:34 +0000503
cristyc4c8d132010-01-07 01:58:38 +0000504 CacheView
505 *sharp_view,
506 *edge_view,
507 *image_view;
508
cristy3ed852e2009-09-05 21:47:34 +0000509 double
cristy47e00502009-12-17 19:19:57 +0000510 **kernel,
511 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000512
513 Image
514 *sharp_image,
515 *edge_image,
516 *gaussian_image;
517
cristy3ed852e2009-09-05 21:47:34 +0000518 MagickBooleanType
519 status;
520
cristybb503372010-05-27 20:51:26 +0000521 MagickOffsetType
522 progress;
523
cristybb503372010-05-27 20:51:26 +0000524 register ssize_t
cristy47e00502009-12-17 19:19:57 +0000525 i;
cristy3ed852e2009-09-05 21:47:34 +0000526
cristybb503372010-05-27 20:51:26 +0000527 size_t
cristy3ed852e2009-09-05 21:47:34 +0000528 width;
529
cristybb503372010-05-27 20:51:26 +0000530 ssize_t
531 j,
532 k,
533 u,
534 v,
535 y;
536
cristy3ed852e2009-09-05 21:47:34 +0000537 assert(image != (const Image *) NULL);
538 assert(image->signature == MagickSignature);
539 if (image->debug != MagickFalse)
540 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
541 assert(exception != (ExceptionInfo *) NULL);
542 assert(exception->signature == MagickSignature);
543 sharp_image=CloneImage(image,0,0,MagickTrue,exception);
544 if (sharp_image == (Image *) NULL)
545 return((Image *) NULL);
546 if (fabs(sigma) <= MagickEpsilon)
547 return(sharp_image);
cristy574cc262011-08-05 01:23:58 +0000548 if (SetImageStorageClass(sharp_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000549 {
cristy3ed852e2009-09-05 21:47:34 +0000550 sharp_image=DestroyImage(sharp_image);
551 return((Image *) NULL);
552 }
553 /*
554 Edge detect the image brighness channel, level, sharp, and level again.
555 */
cristy8ae632d2011-09-05 17:29:53 +0000556 edge_image=EdgeImage(image,radius,sigma,exception);
cristy3ed852e2009-09-05 21:47:34 +0000557 if (edge_image == (Image *) NULL)
558 {
559 sharp_image=DestroyImage(sharp_image);
560 return((Image *) NULL);
561 }
cristy051718b2011-08-28 22:49:25 +0000562 (void) AdaptiveLevelImage(edge_image,"20%,95%",exception);
cristyd89705a2012-01-20 02:52:24 +0000563 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,exception);
cristy3ed852e2009-09-05 21:47:34 +0000564 if (gaussian_image != (Image *) NULL)
565 {
566 edge_image=DestroyImage(edge_image);
567 edge_image=gaussian_image;
568 }
cristy051718b2011-08-28 22:49:25 +0000569 (void) AdaptiveLevelImage(edge_image,"10%,95%",exception);
cristy3ed852e2009-09-05 21:47:34 +0000570 /*
571 Create a set of kernels from maximum (radius,sigma) to minimum.
572 */
573 width=GetOptimalKernelWidth2D(radius,sigma);
cristy219a63a2012-01-17 12:30:33 +0000574 kernel=(double **) AcquireAlignedMemory((size_t) width,sizeof(*kernel));
cristy3ed852e2009-09-05 21:47:34 +0000575 if (kernel == (double **) NULL)
576 {
577 edge_image=DestroyImage(edge_image);
578 sharp_image=DestroyImage(sharp_image);
579 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
580 }
581 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
cristybb503372010-05-27 20:51:26 +0000582 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000583 {
cristy59d1f6d2012-01-17 03:17:40 +0000584 kernel[i]=(double *) AcquireAlignedMemory((size_t) (width-i),(width-i)*
cristy3ed852e2009-09-05 21:47:34 +0000585 sizeof(**kernel));
586 if (kernel[i] == (double *) NULL)
587 break;
cristy47e00502009-12-17 19:19:57 +0000588 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000589 j=(ssize_t) (width-i)/2;
cristy47e00502009-12-17 19:19:57 +0000590 k=0;
591 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000592 {
cristy47e00502009-12-17 19:19:57 +0000593 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000594 {
cristy4205a3c2010-09-12 20:19:59 +0000595 kernel[i][k]=(double) (-exp(-((double) u*u+v*v)/(2.0*MagickSigma*
596 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +0000597 normalize+=kernel[i][k];
598 k++;
cristy3ed852e2009-09-05 21:47:34 +0000599 }
600 }
cristy3ed852e2009-09-05 21:47:34 +0000601 if (fabs(normalize) <= MagickEpsilon)
602 normalize=1.0;
603 normalize=1.0/normalize;
cristy47e00502009-12-17 19:19:57 +0000604 for (k=0; k < (j*j); k++)
605 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000606 }
cristybb503372010-05-27 20:51:26 +0000607 if (i < (ssize_t) width)
cristy3ed852e2009-09-05 21:47:34 +0000608 {
609 for (i-=2; i >= 0; i-=2)
cristy59d1f6d2012-01-17 03:17:40 +0000610 kernel[i]=(double *) RelinquishAlignedMemory(kernel[i]);
cristy219a63a2012-01-17 12:30:33 +0000611 kernel=(double **) RelinquishAlignedMemory(kernel);
cristy3ed852e2009-09-05 21:47:34 +0000612 edge_image=DestroyImage(edge_image);
613 sharp_image=DestroyImage(sharp_image);
614 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
615 }
616 /*
617 Adaptively sharpen image.
618 */
619 status=MagickTrue;
620 progress=0;
cristydb070952012-04-20 14:33:00 +0000621 image_view=AcquireVirtualCacheView(image,exception);
622 edge_view=AcquireVirtualCacheView(edge_image,exception);
623 sharp_view=AcquireAuthenticCacheView(sharp_image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000624#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000625 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy4ee2b0c2012-05-15 00:30:35 +0000626 dynamic_number_threads(image,image->columns,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +0000627#endif
cristybb503372010-05-27 20:51:26 +0000628 for (y=0; y < (ssize_t) sharp_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000629 {
cristy4c08aed2011-07-01 19:47:50 +0000630 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000631 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000632
cristy4c08aed2011-07-01 19:47:50 +0000633 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000634 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000635
cristy117ff172010-08-15 21:35:32 +0000636 register ssize_t
637 x;
638
cristy3ed852e2009-09-05 21:47:34 +0000639 if (status == MagickFalse)
640 continue;
641 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
642 q=QueueCacheViewAuthenticPixels(sharp_view,0,y,sharp_image->columns,1,
643 exception);
cristy4c08aed2011-07-01 19:47:50 +0000644 if ((r == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000645 {
646 status=MagickFalse;
647 continue;
648 }
cristybb503372010-05-27 20:51:26 +0000649 for (x=0; x < (ssize_t) sharp_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000650 {
cristy4c11c2b2011-09-05 20:17:07 +0000651 register const Quantum
652 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000653
cristybb503372010-05-27 20:51:26 +0000654 register ssize_t
cristy4c11c2b2011-09-05 20:17:07 +0000655 i;
cristy3ed852e2009-09-05 21:47:34 +0000656
cristy4c11c2b2011-09-05 20:17:07 +0000657 ssize_t
658 center,
659 j;
660
661 j=(ssize_t) ceil((double) width*QuantumScale*
cristy4c08aed2011-07-01 19:47:50 +0000662 GetPixelIntensity(edge_image,r)-0.5);
cristy4c11c2b2011-09-05 20:17:07 +0000663 if (j < 0)
664 j=0;
cristy3ed852e2009-09-05 21:47:34 +0000665 else
cristy4c11c2b2011-09-05 20:17:07 +0000666 if (j > (ssize_t) width)
667 j=(ssize_t) width;
668 if ((j & 0x01) != 0)
669 j--;
670 p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) (width-j)/2L),y-
671 (ssize_t) ((width-j)/2L),width-j,width-j,exception);
cristy4c08aed2011-07-01 19:47:50 +0000672 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000673 break;
cristyd09f8802012-02-04 16:44:10 +0000674 center=(ssize_t) GetPixelChannels(image)*(width-j)*((width-j)/2L)+
675 GetPixelChannels(image)*((width-j)/2);
cristy177e41c2012-04-15 15:08:25 +0000676 if (GetPixelMask(image,p) != 0)
677 {
678 q+=GetPixelChannels(sharp_image);
679 r+=GetPixelChannels(edge_image);
680 continue;
681 }
cristyc94ba6f2012-01-29 23:19:58 +0000682 for (i=0; i < (ssize_t) GetPixelChannels(sharp_image); i++)
cristy3ed852e2009-09-05 21:47:34 +0000683 {
cristy4c11c2b2011-09-05 20:17:07 +0000684 MagickRealType
685 alpha,
686 gamma,
687 pixel;
688
689 PixelChannel
690 channel;
691
692 PixelTrait
693 sharp_traits,
694 traits;
695
696 register const double
697 *restrict k;
698
699 register const Quantum
700 *restrict pixels;
701
702 register ssize_t
703 u;
704
705 ssize_t
706 v;
707
cristye2a912b2011-12-05 20:02:07 +0000708 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +0000709 traits=GetPixelChannelMapTraits(image,channel);
cristy4c11c2b2011-09-05 20:17:07 +0000710 sharp_traits=GetPixelChannelMapTraits(sharp_image,channel);
711 if ((traits == UndefinedPixelTrait) ||
712 (sharp_traits == UndefinedPixelTrait))
713 continue;
cristy177e41c2012-04-15 15:08:25 +0000714 if ((sharp_traits & CopyPixelTrait) != 0)
cristy4c11c2b2011-09-05 20:17:07 +0000715 {
cristy0beccfa2011-09-25 20:47:53 +0000716 SetPixelChannel(sharp_image,channel,p[center+i],q);
cristy4c11c2b2011-09-05 20:17:07 +0000717 continue;
718 }
719 k=kernel[j];
720 pixels=p;
cristyaa2c16c2012-03-25 22:21:35 +0000721 pixel=0.0;
cristy4c11c2b2011-09-05 20:17:07 +0000722 gamma=0.0;
723 if ((sharp_traits & BlendPixelTrait) == 0)
724 {
725 /*
726 No alpha blending.
727 */
728 for (v=0; v < (ssize_t) (width-j); v++)
729 {
730 for (u=0; u < (ssize_t) (width-j); u++)
731 {
732 pixel+=(*k)*pixels[i];
733 gamma+=(*k);
734 k++;
735 pixels+=GetPixelChannels(image);
736 }
737 }
cristyc58380a2012-06-03 15:12:30 +0000738 gamma=MagickEpsilonReciprocal(gamma);
cristy0beccfa2011-09-25 20:47:53 +0000739 SetPixelChannel(sharp_image,channel,ClampToQuantum(gamma*pixel),q);
cristy4c11c2b2011-09-05 20:17:07 +0000740 continue;
741 }
742 /*
743 Alpha blending.
744 */
745 for (v=0; v < (ssize_t) (width-j); v++)
cristy3ed852e2009-09-05 21:47:34 +0000746 {
cristy4c11c2b2011-09-05 20:17:07 +0000747 for (u=0; u < (ssize_t) (width-j); u++)
748 {
749 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,pixels));
750 pixel+=(*k)*alpha*pixels[i];
751 gamma+=(*k)*alpha;
752 k++;
753 pixels+=GetPixelChannels(image);
754 }
cristy3ed852e2009-09-05 21:47:34 +0000755 }
cristyc58380a2012-06-03 15:12:30 +0000756 gamma=MagickEpsilonReciprocal(gamma);
cristy0beccfa2011-09-25 20:47:53 +0000757 SetPixelChannel(sharp_image,channel,ClampToQuantum(gamma*pixel),q);
cristy3ed852e2009-09-05 21:47:34 +0000758 }
cristyed231572011-07-14 02:18:59 +0000759 q+=GetPixelChannels(sharp_image);
760 r+=GetPixelChannels(edge_image);
cristy3ed852e2009-09-05 21:47:34 +0000761 }
762 if (SyncCacheViewAuthenticPixels(sharp_view,exception) == MagickFalse)
763 status=MagickFalse;
764 if (image->progress_monitor != (MagickProgressMonitor) NULL)
765 {
766 MagickBooleanType
767 proceed;
768
cristyb5d5f722009-11-04 03:03:49 +0000769#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf8561542012-01-24 00:26:46 +0000770 #pragma omp critical (MagickCore_AdaptiveSharpenImage)
cristy3ed852e2009-09-05 21:47:34 +0000771#endif
772 proceed=SetImageProgress(image,AdaptiveSharpenImageTag,progress++,
773 image->rows);
774 if (proceed == MagickFalse)
775 status=MagickFalse;
776 }
777 }
778 sharp_image->type=image->type;
779 sharp_view=DestroyCacheView(sharp_view);
780 edge_view=DestroyCacheView(edge_view);
781 image_view=DestroyCacheView(image_view);
782 edge_image=DestroyImage(edge_image);
cristybb503372010-05-27 20:51:26 +0000783 for (i=0; i < (ssize_t) width; i+=2)
cristy59d1f6d2012-01-17 03:17:40 +0000784 kernel[i]=(double *) RelinquishAlignedMemory(kernel[i]);
cristy219a63a2012-01-17 12:30:33 +0000785 kernel=(double **) RelinquishAlignedMemory(kernel);
cristy3ed852e2009-09-05 21:47:34 +0000786 if (status == MagickFalse)
787 sharp_image=DestroyImage(sharp_image);
788 return(sharp_image);
789}
790
791/*
792%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
793% %
794% %
795% %
796% B l u r I m a g e %
797% %
798% %
799% %
800%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
801%
802% BlurImage() blurs an image. We convolve the image with a Gaussian operator
803% of the given radius and standard deviation (sigma). For reasonable results,
804% the radius should be larger than sigma. Use a radius of 0 and BlurImage()
805% selects a suitable radius for you.
806%
807% BlurImage() differs from GaussianBlurImage() in that it uses a separable
808% kernel which is faster but mathematically equivalent to the non-separable
809% kernel.
810%
811% The format of the BlurImage method is:
812%
813% Image *BlurImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +0000814% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000815%
816% A description of each parameter follows:
817%
818% o image: the image.
819%
cristy3ed852e2009-09-05 21:47:34 +0000820% o radius: the radius of the Gaussian, in pixels, not counting the center
821% pixel.
822%
823% o sigma: the standard deviation of the Gaussian, in pixels.
824%
825% o exception: return any errors or warnings in this structure.
826%
827*/
828
cristybb503372010-05-27 20:51:26 +0000829static double *GetBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +0000830{
cristy3ed852e2009-09-05 21:47:34 +0000831 double
cristy47e00502009-12-17 19:19:57 +0000832 *kernel,
833 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000834
cristy117ff172010-08-15 21:35:32 +0000835 register ssize_t
836 i;
837
cristybb503372010-05-27 20:51:26 +0000838 ssize_t
cristy47e00502009-12-17 19:19:57 +0000839 j,
840 k;
cristy3ed852e2009-09-05 21:47:34 +0000841
cristy3ed852e2009-09-05 21:47:34 +0000842 /*
843 Generate a 1-D convolution kernel.
844 */
845 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
cristy8b49f382012-01-17 03:11:03 +0000846 kernel=(double *) AcquireAlignedMemory((size_t) width,sizeof(*kernel));
cristy3ed852e2009-09-05 21:47:34 +0000847 if (kernel == (double *) NULL)
848 return(0);
cristy3ed852e2009-09-05 21:47:34 +0000849 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000850 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +0000851 i=0;
852 for (k=(-j); k <= j; k++)
853 {
cristy4205a3c2010-09-12 20:19:59 +0000854 kernel[i]=(double) (exp(-((double) k*k)/(2.0*MagickSigma*MagickSigma))/
855 (MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +0000856 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +0000857 i++;
858 }
cristybb503372010-05-27 20:51:26 +0000859 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000860 kernel[i]/=normalize;
861 return(kernel);
862}
863
cristyf4ad9df2011-07-08 16:49:03 +0000864MagickExport Image *BlurImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +0000865 const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000866{
867#define BlurImageTag "Blur/Image"
868
cristyc4c8d132010-01-07 01:58:38 +0000869 CacheView
870 *blur_view,
871 *image_view;
872
cristy3ed852e2009-09-05 21:47:34 +0000873 double
874 *kernel;
875
876 Image
877 *blur_image;
878
cristy3ed852e2009-09-05 21:47:34 +0000879 MagickBooleanType
880 status;
881
cristybb503372010-05-27 20:51:26 +0000882 MagickOffsetType
883 progress;
884
cristybb503372010-05-27 20:51:26 +0000885 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000886 i;
887
cristybb503372010-05-27 20:51:26 +0000888 size_t
cristy3ed852e2009-09-05 21:47:34 +0000889 width;
890
cristybb503372010-05-27 20:51:26 +0000891 ssize_t
cristyb41a1172011-09-06 00:55:14 +0000892 center,
cristybb503372010-05-27 20:51:26 +0000893 x,
894 y;
895
cristy3ed852e2009-09-05 21:47:34 +0000896 /*
897 Initialize blur image attributes.
898 */
899 assert(image != (Image *) NULL);
900 assert(image->signature == MagickSignature);
901 if (image->debug != MagickFalse)
902 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
903 assert(exception != (ExceptionInfo *) NULL);
904 assert(exception->signature == MagickSignature);
cristyd25c77e2011-09-06 00:10:24 +0000905 blur_image=CloneImage(image,0,0,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +0000906 if (blur_image == (Image *) NULL)
907 return((Image *) NULL);
908 if (fabs(sigma) <= MagickEpsilon)
909 return(blur_image);
cristy574cc262011-08-05 01:23:58 +0000910 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000911 {
cristy3ed852e2009-09-05 21:47:34 +0000912 blur_image=DestroyImage(blur_image);
913 return((Image *) NULL);
914 }
915 width=GetOptimalKernelWidth1D(radius,sigma);
916 kernel=GetBlurKernel(width,sigma);
917 if (kernel == (double *) NULL)
918 {
919 blur_image=DestroyImage(blur_image);
920 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
921 }
922 if (image->debug != MagickFalse)
923 {
924 char
925 format[MaxTextExtent],
926 *message;
927
928 register const double
929 *k;
930
931 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristyef1f9072012-01-20 14:43:06 +0000932 " blur image with kernel width %.20g:",(double) width);
cristy3ed852e2009-09-05 21:47:34 +0000933 message=AcquireString("");
934 k=kernel;
cristybb503372010-05-27 20:51:26 +0000935 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000936 {
937 *message='\0';
cristyb51dff52011-05-19 16:55:47 +0000938 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) i);
cristy3ed852e2009-09-05 21:47:34 +0000939 (void) ConcatenateString(&message,format);
cristyb51dff52011-05-19 16:55:47 +0000940 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristy3ed852e2009-09-05 21:47:34 +0000941 (void) ConcatenateString(&message,format);
942 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
943 }
944 message=DestroyString(message);
945 }
946 /*
947 Blur rows.
948 */
949 status=MagickTrue;
950 progress=0;
cristyb41a1172011-09-06 00:55:14 +0000951 center=(ssize_t) GetPixelChannels(image)*(width/2L);
cristydb070952012-04-20 14:33:00 +0000952 image_view=AcquireVirtualCacheView(image,exception);
953 blur_view=AcquireAuthenticCacheView(blur_image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000954#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000955 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy4ee2b0c2012-05-15 00:30:35 +0000956 dynamic_number_threads(image,image->columns,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +0000957#endif
cristyb41a1172011-09-06 00:55:14 +0000958 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000959 {
cristy4c08aed2011-07-01 19:47:50 +0000960 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000961 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000962
cristy4c08aed2011-07-01 19:47:50 +0000963 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000964 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000965
cristy117ff172010-08-15 21:35:32 +0000966 register ssize_t
967 x;
968
cristy3ed852e2009-09-05 21:47:34 +0000969 if (status == MagickFalse)
970 continue;
cristy117ff172010-08-15 21:35:32 +0000971 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y,
972 image->columns+width,1,exception);
cristy8b49f382012-01-17 03:11:03 +0000973 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +0000974 exception);
cristy4c08aed2011-07-01 19:47:50 +0000975 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000976 {
977 status=MagickFalse;
978 continue;
979 }
cristyb41a1172011-09-06 00:55:14 +0000980 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000981 {
cristybb503372010-05-27 20:51:26 +0000982 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000983 i;
984
cristy177e41c2012-04-15 15:08:25 +0000985 if (GetPixelMask(image,p) != 0)
986 {
987 p+=GetPixelChannels(image);
988 q+=GetPixelChannels(blur_image);
989 continue;
990 }
cristyb41a1172011-09-06 00:55:14 +0000991 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
992 {
993 MagickRealType
994 alpha,
995 gamma,
996 pixel;
cristyd25c77e2011-09-06 00:10:24 +0000997
cristyb41a1172011-09-06 00:55:14 +0000998 PixelChannel
999 channel;
1000
1001 PixelTrait
1002 blur_traits,
1003 traits;
1004
1005 register const double
1006 *restrict k;
1007
1008 register const Quantum
1009 *restrict pixels;
1010
1011 register ssize_t
1012 u;
1013
cristye2a912b2011-12-05 20:02:07 +00001014 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +00001015 traits=GetPixelChannelMapTraits(image,channel);
cristyb41a1172011-09-06 00:55:14 +00001016 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
1017 if ((traits == UndefinedPixelTrait) ||
1018 (blur_traits == UndefinedPixelTrait))
1019 continue;
cristy177e41c2012-04-15 15:08:25 +00001020 if ((blur_traits & CopyPixelTrait) != 0)
cristyd25c77e2011-09-06 00:10:24 +00001021 {
cristy0beccfa2011-09-25 20:47:53 +00001022 SetPixelChannel(blur_image,channel,p[center+i],q);
cristyb41a1172011-09-06 00:55:14 +00001023 continue;
cristyd25c77e2011-09-06 00:10:24 +00001024 }
cristyb41a1172011-09-06 00:55:14 +00001025 k=kernel;
1026 pixels=p;
1027 pixel=0.0;
1028 if ((blur_traits & BlendPixelTrait) == 0)
1029 {
1030 /*
1031 No alpha blending.
1032 */
1033 for (u=0; u < (ssize_t) width; u++)
cristyd25c77e2011-09-06 00:10:24 +00001034 {
cristyb41a1172011-09-06 00:55:14 +00001035 pixel+=(*k)*pixels[i];
1036 k++;
1037 pixels+=GetPixelChannels(image);
cristyd25c77e2011-09-06 00:10:24 +00001038 }
cristy0beccfa2011-09-25 20:47:53 +00001039 SetPixelChannel(blur_image,channel,ClampToQuantum(pixel),q);
cristyb41a1172011-09-06 00:55:14 +00001040 continue;
1041 }
1042 /*
1043 Alpha blending.
1044 */
1045 gamma=0.0;
1046 for (u=0; u < (ssize_t) width; u++)
1047 {
1048 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,pixels));
1049 pixel+=(*k)*alpha*pixels[i];
1050 gamma+=(*k)*alpha;
1051 k++;
1052 pixels+=GetPixelChannels(image);
cristyd25c77e2011-09-06 00:10:24 +00001053 }
cristyc58380a2012-06-03 15:12:30 +00001054 gamma=MagickEpsilonReciprocal(gamma);
cristy0beccfa2011-09-25 20:47:53 +00001055 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristyb41a1172011-09-06 00:55:14 +00001056 }
cristyed231572011-07-14 02:18:59 +00001057 p+=GetPixelChannels(image);
1058 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001059 }
1060 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1061 status=MagickFalse;
1062 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1063 {
1064 MagickBooleanType
1065 proceed;
1066
cristyb5d5f722009-11-04 03:03:49 +00001067#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyef1f9072012-01-20 14:43:06 +00001068 #pragma omp critical (MagickCore_BlurImage)
cristy3ed852e2009-09-05 21:47:34 +00001069#endif
1070 proceed=SetImageProgress(image,BlurImageTag,progress++,blur_image->rows+
1071 blur_image->columns);
1072 if (proceed == MagickFalse)
1073 status=MagickFalse;
1074 }
1075 }
1076 blur_view=DestroyCacheView(blur_view);
1077 image_view=DestroyCacheView(image_view);
1078 /*
1079 Blur columns.
1080 */
cristyef1f9072012-01-20 14:43:06 +00001081 center=(ssize_t) GetPixelChannels(blur_image)*(width/2L);
cristydb070952012-04-20 14:33:00 +00001082 image_view=AcquireVirtualCacheView(blur_image,exception);
1083 blur_view=AcquireAuthenticCacheView(blur_image,exception);
cristyb5d5f722009-11-04 03:03:49 +00001084#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001085 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy4ee2b0c2012-05-15 00:30:35 +00001086 dynamic_number_threads(image,image->columns,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00001087#endif
cristyb6dc4b72011-12-10 23:29:53 +00001088 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001089 {
cristy4c08aed2011-07-01 19:47:50 +00001090 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001091 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001092
cristy4c08aed2011-07-01 19:47:50 +00001093 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001094 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001095
cristy117ff172010-08-15 21:35:32 +00001096 register ssize_t
1097 y;
1098
cristy3ed852e2009-09-05 21:47:34 +00001099 if (status == MagickFalse)
1100 continue;
cristy117ff172010-08-15 21:35:32 +00001101 p=GetCacheViewVirtualPixels(image_view,x,-((ssize_t) width/2L),1,
cristyb6dc4b72011-12-10 23:29:53 +00001102 blur_image->rows+width,exception);
cristy3ed852e2009-09-05 21:47:34 +00001103 q=GetCacheViewAuthenticPixels(blur_view,x,0,1,blur_image->rows,exception);
cristy4c08aed2011-07-01 19:47:50 +00001104 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001105 {
1106 status=MagickFalse;
1107 continue;
1108 }
cristyb6dc4b72011-12-10 23:29:53 +00001109 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001110 {
cristybb503372010-05-27 20:51:26 +00001111 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001112 i;
1113
cristy177e41c2012-04-15 15:08:25 +00001114 if (GetPixelMask(image,p) != 0)
1115 {
1116 p+=GetPixelChannels(blur_image);
1117 q+=GetPixelChannels(blur_image);
1118 continue;
1119 }
cristyb6dc4b72011-12-10 23:29:53 +00001120 for (i=0; i < (ssize_t) GetPixelChannels(blur_image); i++)
cristyb41a1172011-09-06 00:55:14 +00001121 {
1122 MagickRealType
1123 alpha,
1124 gamma,
1125 pixel;
cristyd25c77e2011-09-06 00:10:24 +00001126
cristyb41a1172011-09-06 00:55:14 +00001127 PixelChannel
1128 channel;
1129
1130 PixelTrait
1131 blur_traits,
1132 traits;
1133
1134 register const double
1135 *restrict k;
1136
1137 register const Quantum
1138 *restrict pixels;
1139
1140 register ssize_t
1141 u;
1142
cristye2a912b2011-12-05 20:02:07 +00001143 channel=GetPixelChannelMapChannel(blur_image,i);
cristyabace412011-12-11 15:56:53 +00001144 traits=GetPixelChannelMapTraits(blur_image,channel);
cristyb41a1172011-09-06 00:55:14 +00001145 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
1146 if ((traits == UndefinedPixelTrait) ||
1147 (blur_traits == UndefinedPixelTrait))
1148 continue;
cristy177e41c2012-04-15 15:08:25 +00001149 if ((blur_traits & CopyPixelTrait) != 0)
cristyd25c77e2011-09-06 00:10:24 +00001150 {
cristy0beccfa2011-09-25 20:47:53 +00001151 SetPixelChannel(blur_image,channel,p[center+i],q);
cristyb41a1172011-09-06 00:55:14 +00001152 continue;
cristyd25c77e2011-09-06 00:10:24 +00001153 }
cristyb41a1172011-09-06 00:55:14 +00001154 k=kernel;
1155 pixels=p;
1156 pixel=0.0;
1157 if ((blur_traits & BlendPixelTrait) == 0)
1158 {
1159 /*
1160 No alpha blending.
1161 */
1162 for (u=0; u < (ssize_t) width; u++)
cristyd25c77e2011-09-06 00:10:24 +00001163 {
cristyb41a1172011-09-06 00:55:14 +00001164 pixel+=(*k)*pixels[i];
1165 k++;
1166 pixels+=GetPixelChannels(blur_image);
cristyd25c77e2011-09-06 00:10:24 +00001167 }
cristy0beccfa2011-09-25 20:47:53 +00001168 SetPixelChannel(blur_image,channel,ClampToQuantum(pixel),q);
cristyb41a1172011-09-06 00:55:14 +00001169 continue;
1170 }
1171 /*
1172 Alpha blending.
1173 */
1174 gamma=0.0;
1175 for (u=0; u < (ssize_t) width; u++)
1176 {
cristyb6dc4b72011-12-10 23:29:53 +00001177 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(blur_image,
1178 pixels));
cristyb41a1172011-09-06 00:55:14 +00001179 pixel+=(*k)*alpha*pixels[i];
1180 gamma+=(*k)*alpha;
1181 k++;
1182 pixels+=GetPixelChannels(blur_image);
cristyd25c77e2011-09-06 00:10:24 +00001183 }
cristyc58380a2012-06-03 15:12:30 +00001184 gamma=MagickEpsilonReciprocal(gamma);
cristy0beccfa2011-09-25 20:47:53 +00001185 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristyb41a1172011-09-06 00:55:14 +00001186 }
cristyd25c77e2011-09-06 00:10:24 +00001187 p+=GetPixelChannels(blur_image);
cristyed231572011-07-14 02:18:59 +00001188 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001189 }
1190 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1191 status=MagickFalse;
cristy4c08aed2011-07-01 19:47:50 +00001192 if (blur_image->progress_monitor != (MagickProgressMonitor) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001193 {
1194 MagickBooleanType
1195 proceed;
1196
cristyb5d5f722009-11-04 03:03:49 +00001197#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyef1f9072012-01-20 14:43:06 +00001198 #pragma omp critical (MagickCore_BlurImage)
cristy3ed852e2009-09-05 21:47:34 +00001199#endif
cristy4c08aed2011-07-01 19:47:50 +00001200 proceed=SetImageProgress(blur_image,BlurImageTag,progress++,
1201 blur_image->rows+blur_image->columns);
cristy3ed852e2009-09-05 21:47:34 +00001202 if (proceed == MagickFalse)
1203 status=MagickFalse;
1204 }
1205 }
1206 blur_view=DestroyCacheView(blur_view);
1207 image_view=DestroyCacheView(image_view);
cristy8b49f382012-01-17 03:11:03 +00001208 kernel=(double *) RelinquishAlignedMemory(kernel);
1209 blur_image->type=image->type;
cristy3ed852e2009-09-05 21:47:34 +00001210 if (status == MagickFalse)
1211 blur_image=DestroyImage(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001212 return(blur_image);
1213}
1214
1215/*
1216%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1217% %
1218% %
1219% %
cristyfccdab92009-11-30 16:43:57 +00001220% C o n v o l v e I m a g e %
1221% %
1222% %
1223% %
1224%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1225%
1226% ConvolveImage() applies a custom convolution kernel to the image.
1227%
1228% The format of the ConvolveImage method is:
1229%
cristy5e6be1e2011-07-16 01:23:39 +00001230% Image *ConvolveImage(const Image *image,const KernelInfo *kernel,
1231% ExceptionInfo *exception)
1232%
cristyfccdab92009-11-30 16:43:57 +00001233% A description of each parameter follows:
1234%
1235% o image: the image.
1236%
cristy5e6be1e2011-07-16 01:23:39 +00001237% o kernel: the filtering kernel.
cristyfccdab92009-11-30 16:43:57 +00001238%
1239% o exception: return any errors or warnings in this structure.
1240%
1241*/
cristy5e6be1e2011-07-16 01:23:39 +00001242MagickExport Image *ConvolveImage(const Image *image,
1243 const KernelInfo *kernel_info,ExceptionInfo *exception)
cristyfccdab92009-11-30 16:43:57 +00001244{
cristy00f91682012-05-06 19:33:40 +00001245 return(MorphologyImage(image,CorrelateMorphology,1,kernel_info,exception));
cristyfccdab92009-11-30 16:43:57 +00001246}
1247
1248/*
1249%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1250% %
1251% %
1252% %
cristy3ed852e2009-09-05 21:47:34 +00001253% D e s p e c k l e I m a g e %
1254% %
1255% %
1256% %
1257%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1258%
1259% DespeckleImage() reduces the speckle noise in an image while perserving the
cristyf0ae7762012-01-15 22:04:03 +00001260% edges of the original image. A speckle removing filter uses a complementary % hulling technique (raising pixels that are darker than their surrounding
1261% neighbors, then complementarily lowering pixels that are brighter than their
1262% surrounding neighbors) to reduce the speckle index of that image (reference
1263% Crimmins speckle removal).
cristy3ed852e2009-09-05 21:47:34 +00001264%
1265% The format of the DespeckleImage method is:
1266%
1267% Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1268%
1269% A description of each parameter follows:
1270%
1271% o image: the image.
1272%
1273% o exception: return any errors or warnings in this structure.
1274%
1275*/
1276
cristy4ee2b0c2012-05-15 00:30:35 +00001277static void Hull(const Image *image,const ssize_t x_offset,
1278 const ssize_t y_offset,const size_t columns,const size_t rows,
1279 const int polarity,Quantum *restrict f,Quantum *restrict g)
cristy3ed852e2009-09-05 21:47:34 +00001280{
cristy7b4b8862012-01-16 19:52:19 +00001281 register Quantum
cristy5473bf02012-01-16 19:15:08 +00001282 *p,
1283 *q,
1284 *r,
1285 *s;
cristy78f3de22012-01-16 00:43:18 +00001286
cristy5473bf02012-01-16 19:15:08 +00001287 ssize_t
1288 y;
1289
1290 assert(f != (Quantum *) NULL);
1291 assert(g != (Quantum *) NULL);
1292 p=f+(columns+2);
1293 q=g+(columns+2);
1294 r=p+(y_offset*(columns+2)+x_offset);
cristyff8e85a2012-01-18 13:36:20 +00001295#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001296 #pragma omp parallel for schedule(static) \
cristy4ee2b0c2012-05-15 00:30:35 +00001297 dynamic_number_threads(image,columns,rows,1)
cristy5473bf02012-01-16 19:15:08 +00001298#endif
1299 for (y=0; y < (ssize_t) rows; y++)
1300 {
cristy7b4b8862012-01-16 19:52:19 +00001301 register ssize_t
cristy5473bf02012-01-16 19:15:08 +00001302 i,
1303 x;
1304
cristy7b4b8862012-01-16 19:52:19 +00001305 SignedQuantum
1306 v;
1307
cristy5473bf02012-01-16 19:15:08 +00001308 i=(2*y+1)+y*columns;
1309 if (polarity > 0)
1310 for (x=0; x < (ssize_t) columns; x++)
1311 {
1312 v=(SignedQuantum) p[i];
1313 if ((SignedQuantum) r[i] >= (v+ScaleCharToQuantum(2)))
1314 v+=ScaleCharToQuantum(1);
1315 q[i]=(Quantum) v;
1316 i++;
1317 }
1318 else
1319 for (x=0; x < (ssize_t) columns; x++)
1320 {
1321 v=(SignedQuantum) p[i];
1322 if ((SignedQuantum) r[i] <= (v-ScaleCharToQuantum(2)))
1323 v-=ScaleCharToQuantum(1);
1324 q[i]=(Quantum) v;
1325 i++;
1326 }
1327 }
1328 p=f+(columns+2);
1329 q=g+(columns+2);
1330 r=q+(y_offset*(columns+2)+x_offset);
1331 s=q-(y_offset*(columns+2)+x_offset);
cristyff8e85a2012-01-18 13:36:20 +00001332#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001333 #pragma omp parallel for schedule(static) \
cristy4ee2b0c2012-05-15 00:30:35 +00001334 dynamic_number_threads(image,columns,rows,1)
cristy5473bf02012-01-16 19:15:08 +00001335#endif
1336 for (y=0; y < (ssize_t) rows; y++)
1337 {
cristy7b4b8862012-01-16 19:52:19 +00001338 register ssize_t
cristy5473bf02012-01-16 19:15:08 +00001339 i,
1340 x;
1341
cristy7b4b8862012-01-16 19:52:19 +00001342 SignedQuantum
1343 v;
1344
cristy5473bf02012-01-16 19:15:08 +00001345 i=(2*y+1)+y*columns;
1346 if (polarity > 0)
1347 for (x=0; x < (ssize_t) columns; x++)
1348 {
1349 v=(SignedQuantum) q[i];
1350 if (((SignedQuantum) s[i] >= (v+ScaleCharToQuantum(2))) &&
1351 ((SignedQuantum) r[i] > v))
1352 v+=ScaleCharToQuantum(1);
1353 p[i]=(Quantum) v;
1354 i++;
1355 }
1356 else
1357 for (x=0; x < (ssize_t) columns; x++)
1358 {
1359 v=(SignedQuantum) q[i];
1360 if (((SignedQuantum) s[i] <= (v-ScaleCharToQuantum(2))) &&
1361 ((SignedQuantum) r[i] < v))
1362 v-=ScaleCharToQuantum(1);
1363 p[i]=(Quantum) v;
1364 i++;
1365 }
1366 }
cristy3ed852e2009-09-05 21:47:34 +00001367}
1368
1369MagickExport Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1370{
1371#define DespeckleImageTag "Despeckle/Image"
1372
cristy2407fc22009-09-11 00:55:25 +00001373 CacheView
1374 *despeckle_view,
1375 *image_view;
1376
cristy3ed852e2009-09-05 21:47:34 +00001377 Image
1378 *despeckle_image;
1379
cristy3ed852e2009-09-05 21:47:34 +00001380 MagickBooleanType
1381 status;
1382
cristy5473bf02012-01-16 19:15:08 +00001383 Quantum
1384 *restrict buffer,
1385 *restrict pixels;
cristya63e4a92011-09-09 00:47:59 +00001386
cristy5473bf02012-01-16 19:15:08 +00001387 register ssize_t
1388 i;
1389
1390 size_t
1391 length;
cristy117ff172010-08-15 21:35:32 +00001392
cristybb503372010-05-27 20:51:26 +00001393 static const ssize_t
cristy691a29e2009-09-11 00:44:10 +00001394 X[4] = {0, 1, 1,-1},
1395 Y[4] = {1, 0, 1, 1};
cristy3ed852e2009-09-05 21:47:34 +00001396
cristy3ed852e2009-09-05 21:47:34 +00001397 /*
1398 Allocate despeckled image.
1399 */
1400 assert(image != (const Image *) NULL);
1401 assert(image->signature == MagickSignature);
1402 if (image->debug != MagickFalse)
1403 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1404 assert(exception != (ExceptionInfo *) NULL);
1405 assert(exception->signature == MagickSignature);
cristy5473bf02012-01-16 19:15:08 +00001406 despeckle_image=CloneImage(image,0,0,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00001407 if (despeckle_image == (Image *) NULL)
1408 return((Image *) NULL);
cristya63e4a92011-09-09 00:47:59 +00001409 status=SetImageStorageClass(despeckle_image,DirectClass,exception);
1410 if (status == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001411 {
cristy3ed852e2009-09-05 21:47:34 +00001412 despeckle_image=DestroyImage(despeckle_image);
1413 return((Image *) NULL);
1414 }
1415 /*
cristy5473bf02012-01-16 19:15:08 +00001416 Allocate image buffer.
1417 */
1418 length=(size_t) ((image->columns+2)*(image->rows+2));
1419 pixels=(Quantum *) AcquireQuantumMemory(length,sizeof(*pixels));
1420 buffer=(Quantum *) AcquireQuantumMemory(length,sizeof(*buffer));
1421 if ((pixels == (Quantum *) NULL) || (buffer == (Quantum *) NULL))
1422 {
1423 if (buffer != (Quantum *) NULL)
1424 buffer=(Quantum *) RelinquishMagickMemory(buffer);
1425 if (pixels != (Quantum *) NULL)
1426 pixels=(Quantum *) RelinquishMagickMemory(pixels);
1427 despeckle_image=DestroyImage(despeckle_image);
1428 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1429 }
1430 /*
1431 Reduce speckle in the image.
cristy3ed852e2009-09-05 21:47:34 +00001432 */
1433 status=MagickTrue;
cristydb070952012-04-20 14:33:00 +00001434 image_view=AcquireVirtualCacheView(image,exception);
1435 despeckle_view=AcquireAuthenticCacheView(despeckle_image,exception);
cristy5473bf02012-01-16 19:15:08 +00001436 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy3ed852e2009-09-05 21:47:34 +00001437 {
cristy5473bf02012-01-16 19:15:08 +00001438 PixelChannel
1439 channel;
1440
1441 PixelTrait
1442 despeckle_traits,
1443 traits;
cristy3ed852e2009-09-05 21:47:34 +00001444
cristyc1488b52011-02-19 18:54:15 +00001445 register ssize_t
cristy5473bf02012-01-16 19:15:08 +00001446 k,
cristyf0ae7762012-01-15 22:04:03 +00001447 x;
cristyc1488b52011-02-19 18:54:15 +00001448
cristy5473bf02012-01-16 19:15:08 +00001449 ssize_t
1450 j,
1451 y;
1452
1453 if (status == MagickFalse)
1454 continue;
1455 channel=GetPixelChannelMapChannel(image,i);
1456 traits=GetPixelChannelMapTraits(image,channel);
1457 despeckle_traits=GetPixelChannelMapTraits(despeckle_image,channel);
1458 if ((traits == UndefinedPixelTrait) ||
1459 (despeckle_traits == UndefinedPixelTrait))
1460 continue;
1461 if ((despeckle_traits & CopyPixelTrait) != 0)
1462 continue;
1463 (void) ResetMagickMemory(pixels,0,length*sizeof(*pixels));
1464 j=(ssize_t) image->columns+2;
1465 for (y=0; y < (ssize_t) image->rows; y++)
cristyfc830f42012-01-15 02:45:06 +00001466 {
cristy5473bf02012-01-16 19:15:08 +00001467 register const Quantum
1468 *restrict p;
cristyfc830f42012-01-15 02:45:06 +00001469
cristy5473bf02012-01-16 19:15:08 +00001470 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1471 if (p == (const Quantum *) NULL)
cristyf0ae7762012-01-15 22:04:03 +00001472 {
cristy5473bf02012-01-16 19:15:08 +00001473 status=MagickFalse;
1474 continue;
cristyf0ae7762012-01-15 22:04:03 +00001475 }
cristy5473bf02012-01-16 19:15:08 +00001476 j++;
1477 for (x=0; x < (ssize_t) image->columns; x++)
1478 {
1479 pixels[j++]=p[i];
1480 p+=GetPixelChannels(image);
cristyfc830f42012-01-15 02:45:06 +00001481 }
cristy5473bf02012-01-16 19:15:08 +00001482 j++;
cristy3ed852e2009-09-05 21:47:34 +00001483 }
cristy5473bf02012-01-16 19:15:08 +00001484 (void) ResetMagickMemory(buffer,0,length*sizeof(*buffer));
1485 for (k=0; k < 4; k++)
1486 {
cristy4ee2b0c2012-05-15 00:30:35 +00001487 Hull(image,X[k],Y[k],image->columns,image->rows,1,pixels,buffer);
1488 Hull(image,-X[k],-Y[k],image->columns,image->rows,1,pixels,buffer);
1489 Hull(image,-X[k],-Y[k],image->columns,image->rows,-1,pixels,buffer);
1490 Hull(image,X[k],Y[k],image->columns,image->rows,-1,pixels,buffer);
cristy5473bf02012-01-16 19:15:08 +00001491 }
1492 j=(ssize_t) image->columns+2;
1493 for (y=0; y < (ssize_t) image->rows; y++)
1494 {
1495 MagickBooleanType
1496 sync;
1497
1498 register Quantum
1499 *restrict q;
1500
cristyd13e8eb2012-01-17 02:36:42 +00001501 q=QueueCacheViewAuthenticPixels(despeckle_view,0,y,
1502 despeckle_image->columns,1,exception);
cristy5473bf02012-01-16 19:15:08 +00001503 if (q == (Quantum *) NULL)
1504 {
1505 status=MagickFalse;
1506 continue;
1507 }
1508 j++;
1509 for (x=0; x < (ssize_t) image->columns; x++)
1510 {
1511 SetPixelChannel(despeckle_image,channel,pixels[j++],q);
1512 q+=GetPixelChannels(despeckle_image);
1513 }
1514 sync=SyncCacheViewAuthenticPixels(despeckle_view,exception);
1515 if (sync == MagickFalse)
1516 status=MagickFalse;
1517 j++;
1518 }
cristy3ed852e2009-09-05 21:47:34 +00001519 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1520 {
1521 MagickBooleanType
1522 proceed;
1523
cristy5473bf02012-01-16 19:15:08 +00001524 proceed=SetImageProgress(image,DespeckleImageTag,(MagickOffsetType) i,
1525 GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00001526 if (proceed == MagickFalse)
1527 status=MagickFalse;
1528 }
1529 }
1530 despeckle_view=DestroyCacheView(despeckle_view);
1531 image_view=DestroyCacheView(image_view);
cristy5473bf02012-01-16 19:15:08 +00001532 buffer=(Quantum *) RelinquishMagickMemory(buffer);
1533 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001534 despeckle_image->type=image->type;
1535 if (status == MagickFalse)
1536 despeckle_image=DestroyImage(despeckle_image);
1537 return(despeckle_image);
1538}
1539
1540/*
1541%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1542% %
1543% %
1544% %
1545% E d g e I m a g e %
1546% %
1547% %
1548% %
1549%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1550%
1551% EdgeImage() finds edges in an image. Radius defines the radius of the
1552% convolution filter. Use a radius of 0 and EdgeImage() selects a suitable
1553% radius for you.
1554%
1555% The format of the EdgeImage method is:
1556%
1557% Image *EdgeImage(const Image *image,const double radius,
cristy8ae632d2011-09-05 17:29:53 +00001558% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001559%
1560% A description of each parameter follows:
1561%
1562% o image: the image.
1563%
1564% o radius: the radius of the pixel neighborhood.
1565%
cristy8ae632d2011-09-05 17:29:53 +00001566% o sigma: the standard deviation of the Gaussian, in pixels.
1567%
cristy3ed852e2009-09-05 21:47:34 +00001568% o exception: return any errors or warnings in this structure.
1569%
1570*/
1571MagickExport Image *EdgeImage(const Image *image,const double radius,
cristy8ae632d2011-09-05 17:29:53 +00001572 const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001573{
1574 Image
1575 *edge_image;
1576
cristy41cbe682011-07-15 19:12:37 +00001577 KernelInfo
1578 *kernel_info;
cristy3ed852e2009-09-05 21:47:34 +00001579
cristybb503372010-05-27 20:51:26 +00001580 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001581 i;
1582
cristybb503372010-05-27 20:51:26 +00001583 size_t
cristy3ed852e2009-09-05 21:47:34 +00001584 width;
1585
cristy41cbe682011-07-15 19:12:37 +00001586 ssize_t
1587 j,
1588 u,
1589 v;
1590
cristy3ed852e2009-09-05 21:47:34 +00001591 assert(image != (const Image *) NULL);
1592 assert(image->signature == MagickSignature);
1593 if (image->debug != MagickFalse)
1594 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1595 assert(exception != (ExceptionInfo *) NULL);
1596 assert(exception->signature == MagickSignature);
cristyfc0ae532011-12-06 15:14:45 +00001597 width=GetOptimalKernelWidth1D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00001598 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00001599 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001600 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00001601 kernel_info->width=width;
1602 kernel_info->height=width;
cristye8d93f62012-05-07 16:45:46 +00001603 kernel_info->values=(double *) AcquireAlignedMemory(kernel_info->width,
1604 kernel_info->width*sizeof(*kernel_info->values));
1605 if (kernel_info->values == (double *) NULL)
cristy41cbe682011-07-15 19:12:37 +00001606 {
1607 kernel_info=DestroyKernelInfo(kernel_info);
1608 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1609 }
1610 j=(ssize_t) kernel_info->width/2;
1611 i=0;
1612 for (v=(-j); v <= j; v++)
1613 {
1614 for (u=(-j); u <= j; u++)
1615 {
1616 kernel_info->values[i]=(-1.0);
1617 i++;
1618 }
1619 }
1620 kernel_info->values[i/2]=(double) (width*width-1.0);
cristy5e6be1e2011-07-16 01:23:39 +00001621 edge_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00001622 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00001623 return(edge_image);
1624}
1625
1626/*
1627%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1628% %
1629% %
1630% %
1631% E m b o s s I m a g e %
1632% %
1633% %
1634% %
1635%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1636%
1637% EmbossImage() returns a grayscale image with a three-dimensional effect.
1638% We convolve the image with a Gaussian operator of the given radius and
1639% standard deviation (sigma). For reasonable results, radius should be
1640% larger than sigma. Use a radius of 0 and Emboss() selects a suitable
1641% radius for you.
1642%
1643% The format of the EmbossImage method is:
1644%
1645% Image *EmbossImage(const Image *image,const double radius,
1646% const double sigma,ExceptionInfo *exception)
1647%
1648% A description of each parameter follows:
1649%
1650% o image: the image.
1651%
1652% o radius: the radius of the pixel neighborhood.
1653%
1654% o sigma: the standard deviation of the Gaussian, in pixels.
1655%
1656% o exception: return any errors or warnings in this structure.
1657%
1658*/
1659MagickExport Image *EmbossImage(const Image *image,const double radius,
1660 const double sigma,ExceptionInfo *exception)
1661{
cristy3ed852e2009-09-05 21:47:34 +00001662 Image
1663 *emboss_image;
1664
cristy41cbe682011-07-15 19:12:37 +00001665 KernelInfo
1666 *kernel_info;
1667
cristybb503372010-05-27 20:51:26 +00001668 register ssize_t
cristy47e00502009-12-17 19:19:57 +00001669 i;
1670
cristybb503372010-05-27 20:51:26 +00001671 size_t
cristy3ed852e2009-09-05 21:47:34 +00001672 width;
1673
cristy117ff172010-08-15 21:35:32 +00001674 ssize_t
1675 j,
1676 k,
1677 u,
1678 v;
1679
cristy41cbe682011-07-15 19:12:37 +00001680 assert(image != (const Image *) NULL);
cristy3ed852e2009-09-05 21:47:34 +00001681 assert(image->signature == MagickSignature);
1682 if (image->debug != MagickFalse)
1683 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1684 assert(exception != (ExceptionInfo *) NULL);
1685 assert(exception->signature == MagickSignature);
cristyfc0ae532011-12-06 15:14:45 +00001686 width=GetOptimalKernelWidth1D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00001687 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00001688 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001689 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00001690 kernel_info->width=width;
1691 kernel_info->height=width;
cristye8d93f62012-05-07 16:45:46 +00001692 kernel_info->values=(double *) AcquireAlignedMemory(kernel_info->width,
1693 kernel_info->width*sizeof(*kernel_info->values));
1694 if (kernel_info->values == (double *) NULL)
cristy41cbe682011-07-15 19:12:37 +00001695 {
1696 kernel_info=DestroyKernelInfo(kernel_info);
1697 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1698 }
1699 j=(ssize_t) kernel_info->width/2;
cristy47e00502009-12-17 19:19:57 +00001700 k=j;
1701 i=0;
1702 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00001703 {
cristy47e00502009-12-17 19:19:57 +00001704 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00001705 {
cristy41cbe682011-07-15 19:12:37 +00001706 kernel_info->values[i]=(double) (((u < 0) || (v < 0) ? -8.0 : 8.0)*
cristy47e00502009-12-17 19:19:57 +00001707 exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
cristy4205a3c2010-09-12 20:19:59 +00001708 (2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +00001709 if (u != k)
cristy41cbe682011-07-15 19:12:37 +00001710 kernel_info->values[i]=0.0;
cristy3ed852e2009-09-05 21:47:34 +00001711 i++;
1712 }
cristy47e00502009-12-17 19:19:57 +00001713 k--;
cristy3ed852e2009-09-05 21:47:34 +00001714 }
cristy5e6be1e2011-07-16 01:23:39 +00001715 emboss_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00001716 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00001717 if (emboss_image != (Image *) NULL)
cristy6d8c3d72011-08-22 01:20:01 +00001718 (void) EqualizeImage(emboss_image,exception);
cristy3ed852e2009-09-05 21:47:34 +00001719 return(emboss_image);
1720}
1721
1722/*
1723%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1724% %
1725% %
1726% %
1727% G a u s s i a n B l u r I m a g e %
1728% %
1729% %
1730% %
1731%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1732%
1733% GaussianBlurImage() blurs an image. We convolve the image with a
1734% Gaussian operator of the given radius and standard deviation (sigma).
1735% For reasonable results, the radius should be larger than sigma. Use a
1736% radius of 0 and GaussianBlurImage() selects a suitable radius for you
1737%
1738% The format of the GaussianBlurImage method is:
1739%
1740% Image *GaussianBlurImage(const Image *image,onst double radius,
cristyd89705a2012-01-20 02:52:24 +00001741% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001742%
1743% A description of each parameter follows:
1744%
1745% o image: the image.
1746%
cristy3ed852e2009-09-05 21:47:34 +00001747% o radius: the radius of the Gaussian, in pixels, not counting the center
1748% pixel.
1749%
1750% o sigma: the standard deviation of the Gaussian, in pixels.
1751%
1752% o exception: return any errors or warnings in this structure.
1753%
1754*/
cristy41cbe682011-07-15 19:12:37 +00001755MagickExport Image *GaussianBlurImage(const Image *image,const double radius,
cristyd89705a2012-01-20 02:52:24 +00001756 const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001757{
cristy3ed852e2009-09-05 21:47:34 +00001758 Image
1759 *blur_image;
1760
cristy41cbe682011-07-15 19:12:37 +00001761 KernelInfo
1762 *kernel_info;
1763
cristybb503372010-05-27 20:51:26 +00001764 register ssize_t
cristy47e00502009-12-17 19:19:57 +00001765 i;
1766
cristybb503372010-05-27 20:51:26 +00001767 size_t
cristy3ed852e2009-09-05 21:47:34 +00001768 width;
1769
cristy117ff172010-08-15 21:35:32 +00001770 ssize_t
1771 j,
1772 u,
1773 v;
1774
cristy3ed852e2009-09-05 21:47:34 +00001775 assert(image != (const Image *) NULL);
1776 assert(image->signature == MagickSignature);
1777 if (image->debug != MagickFalse)
1778 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1779 assert(exception != (ExceptionInfo *) NULL);
1780 assert(exception->signature == MagickSignature);
1781 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00001782 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00001783 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001784 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00001785 (void) ResetMagickMemory(kernel_info,0,sizeof(*kernel_info));
1786 kernel_info->width=width;
1787 kernel_info->height=width;
1788 kernel_info->signature=MagickSignature;
cristye8d93f62012-05-07 16:45:46 +00001789 kernel_info->values=(double *) AcquireAlignedMemory(
cristya96f2492011-12-14 18:25:41 +00001790 kernel_info->width,kernel_info->width*sizeof(*kernel_info->values));
cristye8d93f62012-05-07 16:45:46 +00001791 if (kernel_info->values == (double *) NULL)
cristy41cbe682011-07-15 19:12:37 +00001792 {
1793 kernel_info=DestroyKernelInfo(kernel_info);
1794 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1795 }
1796 j=(ssize_t) kernel_info->width/2;
cristy3ed852e2009-09-05 21:47:34 +00001797 i=0;
cristy47e00502009-12-17 19:19:57 +00001798 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00001799 {
cristy47e00502009-12-17 19:19:57 +00001800 for (u=(-j); u <= j; u++)
cristy41cbe682011-07-15 19:12:37 +00001801 {
1802 kernel_info->values[i]=(double) (exp(-((double) u*u+v*v)/(2.0*
1803 MagickSigma*MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
1804 i++;
1805 }
cristy3ed852e2009-09-05 21:47:34 +00001806 }
cristy5e6be1e2011-07-16 01:23:39 +00001807 blur_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00001808 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00001809 return(blur_image);
1810}
1811
1812/*
1813%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1814% %
1815% %
1816% %
cristy3ed852e2009-09-05 21:47:34 +00001817% M o t i o n B l u r I m a g e %
1818% %
1819% %
1820% %
1821%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1822%
1823% MotionBlurImage() simulates motion blur. We convolve the image with a
1824% Gaussian operator of the given radius and standard deviation (sigma).
1825% For reasonable results, radius should be larger than sigma. Use a
1826% radius of 0 and MotionBlurImage() selects a suitable radius for you.
1827% Angle gives the angle of the blurring motion.
1828%
1829% Andrew Protano contributed this effect.
1830%
1831% The format of the MotionBlurImage method is:
1832%
1833% Image *MotionBlurImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +00001834% const double sigma,const double angle,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001835%
1836% A description of each parameter follows:
1837%
1838% o image: the image.
1839%
cristy3ed852e2009-09-05 21:47:34 +00001840% o radius: the radius of the Gaussian, in pixels, not counting
1841% the center pixel.
1842%
1843% o sigma: the standard deviation of the Gaussian, in pixels.
1844%
cristycee97112010-05-28 00:44:52 +00001845% o angle: Apply the effect along this angle.
cristy3ed852e2009-09-05 21:47:34 +00001846%
1847% o exception: return any errors or warnings in this structure.
1848%
1849*/
1850
cristybb503372010-05-27 20:51:26 +00001851static double *GetMotionBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +00001852{
cristy3ed852e2009-09-05 21:47:34 +00001853 double
cristy47e00502009-12-17 19:19:57 +00001854 *kernel,
cristy3ed852e2009-09-05 21:47:34 +00001855 normalize;
1856
cristybb503372010-05-27 20:51:26 +00001857 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001858 i;
1859
1860 /*
cristy47e00502009-12-17 19:19:57 +00001861 Generate a 1-D convolution kernel.
cristy3ed852e2009-09-05 21:47:34 +00001862 */
1863 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
cristy8b49f382012-01-17 03:11:03 +00001864 kernel=(double *) AcquireAlignedMemory((size_t) width,sizeof(*kernel));
cristy3ed852e2009-09-05 21:47:34 +00001865 if (kernel == (double *) NULL)
1866 return(kernel);
cristy3ed852e2009-09-05 21:47:34 +00001867 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00001868 for (i=0; i < (ssize_t) width; i++)
cristy47e00502009-12-17 19:19:57 +00001869 {
cristy4205a3c2010-09-12 20:19:59 +00001870 kernel[i]=(double) (exp((-((double) i*i)/(double) (2.0*MagickSigma*
1871 MagickSigma)))/(MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00001872 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +00001873 }
cristybb503372010-05-27 20:51:26 +00001874 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001875 kernel[i]/=normalize;
1876 return(kernel);
1877}
1878
cristya63e4a92011-09-09 00:47:59 +00001879MagickExport Image *MotionBlurImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +00001880 const double sigma,const double angle,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001881{
cristyc4c8d132010-01-07 01:58:38 +00001882 CacheView
1883 *blur_view,
cristy8b49f382012-01-17 03:11:03 +00001884 *image_view,
1885 *motion_view;
cristyc4c8d132010-01-07 01:58:38 +00001886
cristy3ed852e2009-09-05 21:47:34 +00001887 double
1888 *kernel;
1889
1890 Image
1891 *blur_image;
1892
cristy3ed852e2009-09-05 21:47:34 +00001893 MagickBooleanType
1894 status;
1895
cristybb503372010-05-27 20:51:26 +00001896 MagickOffsetType
1897 progress;
1898
cristy3ed852e2009-09-05 21:47:34 +00001899 OffsetInfo
1900 *offset;
1901
1902 PointInfo
1903 point;
1904
cristybb503372010-05-27 20:51:26 +00001905 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001906 i;
1907
cristybb503372010-05-27 20:51:26 +00001908 size_t
cristy3ed852e2009-09-05 21:47:34 +00001909 width;
1910
cristybb503372010-05-27 20:51:26 +00001911 ssize_t
1912 y;
1913
cristy3ed852e2009-09-05 21:47:34 +00001914 assert(image != (Image *) NULL);
1915 assert(image->signature == MagickSignature);
1916 if (image->debug != MagickFalse)
1917 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1918 assert(exception != (ExceptionInfo *) NULL);
1919 width=GetOptimalKernelWidth1D(radius,sigma);
1920 kernel=GetMotionBlurKernel(width,sigma);
1921 if (kernel == (double *) NULL)
1922 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1923 offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset));
1924 if (offset == (OffsetInfo *) NULL)
1925 {
cristy8b49f382012-01-17 03:11:03 +00001926 kernel=(double *) RelinquishAlignedMemory(kernel);
cristy3ed852e2009-09-05 21:47:34 +00001927 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1928 }
cristy1e7aa312011-09-10 20:01:36 +00001929 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00001930 if (blur_image == (Image *) NULL)
1931 {
cristy8b49f382012-01-17 03:11:03 +00001932 kernel=(double *) RelinquishAlignedMemory(kernel);
cristy3ed852e2009-09-05 21:47:34 +00001933 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
1934 return((Image *) NULL);
1935 }
cristy574cc262011-08-05 01:23:58 +00001936 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001937 {
cristy8b49f382012-01-17 03:11:03 +00001938 kernel=(double *) RelinquishAlignedMemory(kernel);
cristy3ed852e2009-09-05 21:47:34 +00001939 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
cristy3ed852e2009-09-05 21:47:34 +00001940 blur_image=DestroyImage(blur_image);
1941 return((Image *) NULL);
1942 }
1943 point.x=(double) width*sin(DegreesToRadians(angle));
1944 point.y=(double) width*cos(DegreesToRadians(angle));
cristybb503372010-05-27 20:51:26 +00001945 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001946 {
cristybb503372010-05-27 20:51:26 +00001947 offset[i].x=(ssize_t) ceil((double) (i*point.y)/hypot(point.x,point.y)-0.5);
1948 offset[i].y=(ssize_t) ceil((double) (i*point.x)/hypot(point.x,point.y)-0.5);
cristy3ed852e2009-09-05 21:47:34 +00001949 }
1950 /*
1951 Motion blur image.
1952 */
1953 status=MagickTrue;
1954 progress=0;
cristydb070952012-04-20 14:33:00 +00001955 image_view=AcquireVirtualCacheView(image,exception);
1956 motion_view=AcquireVirtualCacheView(image,exception);
1957 blur_view=AcquireAuthenticCacheView(blur_image,exception);
cristyb557a152011-02-22 12:14:30 +00001958#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001959 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy4ee2b0c2012-05-15 00:30:35 +00001960 dynamic_number_threads(image,image->columns,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00001961#endif
cristybb503372010-05-27 20:51:26 +00001962 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001963 {
cristyf7ef0252011-09-09 14:50:06 +00001964 register const Quantum
1965 *restrict p;
1966
cristy4c08aed2011-07-01 19:47:50 +00001967 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001968 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001969
cristy117ff172010-08-15 21:35:32 +00001970 register ssize_t
1971 x;
1972
cristy3ed852e2009-09-05 21:47:34 +00001973 if (status == MagickFalse)
1974 continue;
cristy8b49f382012-01-17 03:11:03 +00001975 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1976 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +00001977 exception);
cristyf7ef0252011-09-09 14:50:06 +00001978 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001979 {
1980 status=MagickFalse;
1981 continue;
1982 }
cristybb503372010-05-27 20:51:26 +00001983 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001984 {
cristybb503372010-05-27 20:51:26 +00001985 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001986 i;
1987
cristy177e41c2012-04-15 15:08:25 +00001988 if (GetPixelMask(image,p) != 0)
1989 {
1990 p+=GetPixelChannels(image);
1991 q+=GetPixelChannels(blur_image);
1992 continue;
1993 }
cristyf7ef0252011-09-09 14:50:06 +00001994 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1995 {
1996 MagickRealType
1997 alpha,
1998 gamma,
1999 pixel;
cristy3ed852e2009-09-05 21:47:34 +00002000
cristyf7ef0252011-09-09 14:50:06 +00002001 PixelChannel
2002 channel;
2003
2004 PixelTrait
2005 blur_traits,
2006 traits;
2007
2008 register const Quantum
2009 *restrict r;
2010
2011 register double
2012 *restrict k;
2013
2014 register ssize_t
2015 j;
2016
cristye2a912b2011-12-05 20:02:07 +00002017 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +00002018 traits=GetPixelChannelMapTraits(image,channel);
cristyf7ef0252011-09-09 14:50:06 +00002019 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
2020 if ((traits == UndefinedPixelTrait) ||
2021 (blur_traits == UndefinedPixelTrait))
2022 continue;
cristy177e41c2012-04-15 15:08:25 +00002023 if ((blur_traits & CopyPixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002024 {
cristy0beccfa2011-09-25 20:47:53 +00002025 SetPixelChannel(blur_image,channel,p[i],q);
cristyf7ef0252011-09-09 14:50:06 +00002026 continue;
cristy3ed852e2009-09-05 21:47:34 +00002027 }
cristyf7ef0252011-09-09 14:50:06 +00002028 k=kernel;
cristyaa2c16c2012-03-25 22:21:35 +00002029 pixel=0.0;
cristyf7ef0252011-09-09 14:50:06 +00002030 if ((blur_traits & BlendPixelTrait) == 0)
2031 {
2032 for (j=0; j < (ssize_t) width; j++)
2033 {
cristy8b49f382012-01-17 03:11:03 +00002034 r=GetCacheViewVirtualPixels(motion_view,x+offset[j].x,y+
cristyf7ef0252011-09-09 14:50:06 +00002035 offset[j].y,1,1,exception);
2036 if (r == (const Quantum *) NULL)
2037 {
2038 status=MagickFalse;
2039 continue;
2040 }
2041 pixel+=(*k)*r[i];
2042 k++;
2043 }
cristy0beccfa2011-09-25 20:47:53 +00002044 SetPixelChannel(blur_image,channel,ClampToQuantum(pixel),q);
cristyf7ef0252011-09-09 14:50:06 +00002045 continue;
2046 }
2047 alpha=0.0;
2048 gamma=0.0;
2049 for (j=0; j < (ssize_t) width; j++)
2050 {
cristy8b49f382012-01-17 03:11:03 +00002051 r=GetCacheViewVirtualPixels(motion_view,x+offset[j].x,y+offset[j].y,1,
cristyf7ef0252011-09-09 14:50:06 +00002052 1,exception);
2053 if (r == (const Quantum *) NULL)
2054 {
2055 status=MagickFalse;
2056 continue;
2057 }
2058 alpha=(MagickRealType) (QuantumScale*GetPixelAlpha(image,r));
2059 pixel+=(*k)*alpha*r[i];
2060 gamma+=(*k)*alpha;
2061 k++;
cristy3ed852e2009-09-05 21:47:34 +00002062 }
cristyc58380a2012-06-03 15:12:30 +00002063 gamma=MagickEpsilonReciprocal(gamma);
cristy0beccfa2011-09-25 20:47:53 +00002064 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristyf7ef0252011-09-09 14:50:06 +00002065 }
2066 p+=GetPixelChannels(image);
cristyed231572011-07-14 02:18:59 +00002067 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00002068 }
2069 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
2070 status=MagickFalse;
2071 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2072 {
2073 MagickBooleanType
2074 proceed;
2075
cristyb557a152011-02-22 12:14:30 +00002076#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf8561542012-01-24 00:26:46 +00002077 #pragma omp critical (MagickCore_MotionBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00002078#endif
2079 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
2080 if (proceed == MagickFalse)
2081 status=MagickFalse;
2082 }
2083 }
2084 blur_view=DestroyCacheView(blur_view);
cristy8b49f382012-01-17 03:11:03 +00002085 motion_view=DestroyCacheView(motion_view);
cristy3ed852e2009-09-05 21:47:34 +00002086 image_view=DestroyCacheView(image_view);
cristy8b49f382012-01-17 03:11:03 +00002087 kernel=(double *) RelinquishAlignedMemory(kernel);
cristy3ed852e2009-09-05 21:47:34 +00002088 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2089 if (status == MagickFalse)
2090 blur_image=DestroyImage(blur_image);
2091 return(blur_image);
2092}
2093
2094/*
2095%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2096% %
2097% %
2098% %
2099% P r e v i e w I m a g e %
2100% %
2101% %
2102% %
2103%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2104%
2105% PreviewImage() tiles 9 thumbnails of the specified image with an image
2106% processing operation applied with varying parameters. This may be helpful
2107% pin-pointing an appropriate parameter for a particular image processing
2108% operation.
2109%
2110% The format of the PreviewImages method is:
2111%
2112% Image *PreviewImages(const Image *image,const PreviewType preview,
2113% ExceptionInfo *exception)
2114%
2115% A description of each parameter follows:
2116%
2117% o image: the image.
2118%
2119% o preview: the image processing operation.
2120%
2121% o exception: return any errors or warnings in this structure.
2122%
2123*/
2124MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
2125 ExceptionInfo *exception)
2126{
2127#define NumberTiles 9
2128#define PreviewImageTag "Preview/Image"
2129#define DefaultPreviewGeometry "204x204+10+10"
2130
2131 char
2132 factor[MaxTextExtent],
2133 label[MaxTextExtent];
2134
2135 double
2136 degrees,
2137 gamma,
2138 percentage,
2139 radius,
2140 sigma,
2141 threshold;
2142
cristybcdf5672012-05-24 22:58:54 +00002143 extern const char
2144 DefaultTileFrame[];
2145
cristy3ed852e2009-09-05 21:47:34 +00002146 Image
2147 *images,
2148 *montage_image,
2149 *preview_image,
2150 *thumbnail;
2151
2152 ImageInfo
2153 *preview_info;
2154
cristy3ed852e2009-09-05 21:47:34 +00002155 MagickBooleanType
2156 proceed;
2157
2158 MontageInfo
2159 *montage_info;
2160
2161 QuantizeInfo
2162 quantize_info;
2163
2164 RectangleInfo
2165 geometry;
2166
cristybb503372010-05-27 20:51:26 +00002167 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002168 i,
2169 x;
2170
cristybb503372010-05-27 20:51:26 +00002171 size_t
cristy3ed852e2009-09-05 21:47:34 +00002172 colors;
2173
cristy117ff172010-08-15 21:35:32 +00002174 ssize_t
2175 y;
2176
cristy3ed852e2009-09-05 21:47:34 +00002177 /*
2178 Open output image file.
2179 */
2180 assert(image != (Image *) NULL);
2181 assert(image->signature == MagickSignature);
2182 if (image->debug != MagickFalse)
2183 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2184 colors=2;
2185 degrees=0.0;
2186 gamma=(-0.2f);
2187 preview_info=AcquireImageInfo();
2188 SetGeometry(image,&geometry);
2189 (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
2190 &geometry.width,&geometry.height);
2191 images=NewImageList();
2192 percentage=12.5;
2193 GetQuantizeInfo(&quantize_info);
2194 radius=0.0;
2195 sigma=1.0;
2196 threshold=0.0;
2197 x=0;
2198 y=0;
2199 for (i=0; i < NumberTiles; i++)
2200 {
2201 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
2202 if (thumbnail == (Image *) NULL)
2203 break;
2204 (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
2205 (void *) NULL);
cristyd15e6592011-10-15 00:13:06 +00002206 (void) SetImageProperty(thumbnail,"label",DefaultTileLabel,exception);
cristy3ed852e2009-09-05 21:47:34 +00002207 if (i == (NumberTiles/2))
2208 {
cristy9950d572011-10-01 18:22:35 +00002209 (void) QueryColorCompliance("#dfdfdf",AllCompliance,
2210 &thumbnail->matte_color,exception);
cristy3ed852e2009-09-05 21:47:34 +00002211 AppendImageToList(&images,thumbnail);
2212 continue;
2213 }
2214 switch (preview)
2215 {
2216 case RotatePreview:
2217 {
2218 degrees+=45.0;
2219 preview_image=RotateImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002220 (void) FormatLocaleString(label,MaxTextExtent,"rotate %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002221 break;
2222 }
2223 case ShearPreview:
2224 {
2225 degrees+=5.0;
2226 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002227 (void) FormatLocaleString(label,MaxTextExtent,"shear %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002228 degrees,2.0*degrees);
2229 break;
2230 }
2231 case RollPreview:
2232 {
cristybb503372010-05-27 20:51:26 +00002233 x=(ssize_t) ((i+1)*thumbnail->columns)/NumberTiles;
2234 y=(ssize_t) ((i+1)*thumbnail->rows)/NumberTiles;
cristy3ed852e2009-09-05 21:47:34 +00002235 preview_image=RollImage(thumbnail,x,y,exception);
cristyb51dff52011-05-19 16:55:47 +00002236 (void) FormatLocaleString(label,MaxTextExtent,"roll %+.20gx%+.20g",
cristye8c25f92010-06-03 00:53:06 +00002237 (double) x,(double) y);
cristy3ed852e2009-09-05 21:47:34 +00002238 break;
2239 }
2240 case HuePreview:
2241 {
2242 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2243 if (preview_image == (Image *) NULL)
2244 break;
cristyb51dff52011-05-19 16:55:47 +00002245 (void) FormatLocaleString(factor,MaxTextExtent,"100,100,%g",
cristy3ed852e2009-09-05 21:47:34 +00002246 2.0*percentage);
cristy33bd5152011-08-24 01:42:24 +00002247 (void) ModulateImage(preview_image,factor,exception);
cristyb51dff52011-05-19 16:55:47 +00002248 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002249 break;
2250 }
2251 case SaturationPreview:
2252 {
2253 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2254 if (preview_image == (Image *) NULL)
2255 break;
cristyb51dff52011-05-19 16:55:47 +00002256 (void) FormatLocaleString(factor,MaxTextExtent,"100,%g",
cristy8cd5b312010-01-07 01:10:24 +00002257 2.0*percentage);
cristy33bd5152011-08-24 01:42:24 +00002258 (void) ModulateImage(preview_image,factor,exception);
cristyb51dff52011-05-19 16:55:47 +00002259 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002260 break;
2261 }
2262 case BrightnessPreview:
2263 {
2264 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2265 if (preview_image == (Image *) NULL)
2266 break;
cristyb51dff52011-05-19 16:55:47 +00002267 (void) FormatLocaleString(factor,MaxTextExtent,"%g",2.0*percentage);
cristy33bd5152011-08-24 01:42:24 +00002268 (void) ModulateImage(preview_image,factor,exception);
cristyb51dff52011-05-19 16:55:47 +00002269 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002270 break;
2271 }
2272 case GammaPreview:
2273 default:
2274 {
2275 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2276 if (preview_image == (Image *) NULL)
2277 break;
2278 gamma+=0.4f;
cristyb3e7c6c2011-07-24 01:43:55 +00002279 (void) GammaImage(preview_image,gamma,exception);
cristyb51dff52011-05-19 16:55:47 +00002280 (void) FormatLocaleString(label,MaxTextExtent,"gamma %g",gamma);
cristy3ed852e2009-09-05 21:47:34 +00002281 break;
2282 }
2283 case SpiffPreview:
2284 {
2285 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2286 if (preview_image != (Image *) NULL)
2287 for (x=0; x < i; x++)
cristye23ec9d2011-08-16 18:15:40 +00002288 (void) ContrastImage(preview_image,MagickTrue,exception);
cristyb51dff52011-05-19 16:55:47 +00002289 (void) FormatLocaleString(label,MaxTextExtent,"contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002290 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002291 break;
2292 }
2293 case DullPreview:
2294 {
2295 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2296 if (preview_image == (Image *) NULL)
2297 break;
2298 for (x=0; x < i; x++)
cristye23ec9d2011-08-16 18:15:40 +00002299 (void) ContrastImage(preview_image,MagickFalse,exception);
cristyb51dff52011-05-19 16:55:47 +00002300 (void) FormatLocaleString(label,MaxTextExtent,"+contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002301 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002302 break;
2303 }
2304 case GrayscalePreview:
2305 {
2306 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2307 if (preview_image == (Image *) NULL)
2308 break;
2309 colors<<=1;
2310 quantize_info.number_colors=colors;
2311 quantize_info.colorspace=GRAYColorspace;
cristy018f07f2011-09-04 21:15:19 +00002312 (void) QuantizeImage(&quantize_info,preview_image,exception);
cristyb51dff52011-05-19 16:55:47 +00002313 (void) FormatLocaleString(label,MaxTextExtent,
cristye8c25f92010-06-03 00:53:06 +00002314 "-colorspace gray -colors %.20g",(double) colors);
cristy3ed852e2009-09-05 21:47:34 +00002315 break;
2316 }
2317 case QuantizePreview:
2318 {
2319 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2320 if (preview_image == (Image *) NULL)
2321 break;
2322 colors<<=1;
2323 quantize_info.number_colors=colors;
cristy018f07f2011-09-04 21:15:19 +00002324 (void) QuantizeImage(&quantize_info,preview_image,exception);
cristyb51dff52011-05-19 16:55:47 +00002325 (void) FormatLocaleString(label,MaxTextExtent,"colors %.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00002326 colors);
cristy3ed852e2009-09-05 21:47:34 +00002327 break;
2328 }
2329 case DespecklePreview:
2330 {
2331 for (x=0; x < (i-1); x++)
2332 {
2333 preview_image=DespeckleImage(thumbnail,exception);
2334 if (preview_image == (Image *) NULL)
2335 break;
2336 thumbnail=DestroyImage(thumbnail);
2337 thumbnail=preview_image;
2338 }
2339 preview_image=DespeckleImage(thumbnail,exception);
2340 if (preview_image == (Image *) NULL)
2341 break;
cristyb51dff52011-05-19 16:55:47 +00002342 (void) FormatLocaleString(label,MaxTextExtent,"despeckle (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002343 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002344 break;
2345 }
2346 case ReduceNoisePreview:
2347 {
cristy95c38342011-03-18 22:39:51 +00002348 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) radius,
2349 (size_t) radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002350 (void) FormatLocaleString(label,MaxTextExtent,"noise %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002351 break;
2352 }
2353 case AddNoisePreview:
2354 {
2355 switch ((int) i)
2356 {
2357 case 0:
2358 {
2359 (void) CopyMagickString(factor,"uniform",MaxTextExtent);
2360 break;
2361 }
2362 case 1:
2363 {
2364 (void) CopyMagickString(factor,"gaussian",MaxTextExtent);
2365 break;
2366 }
2367 case 2:
2368 {
2369 (void) CopyMagickString(factor,"multiplicative",MaxTextExtent);
2370 break;
2371 }
2372 case 3:
2373 {
2374 (void) CopyMagickString(factor,"impulse",MaxTextExtent);
2375 break;
2376 }
2377 case 4:
2378 {
2379 (void) CopyMagickString(factor,"laplacian",MaxTextExtent);
2380 break;
2381 }
2382 case 5:
2383 {
2384 (void) CopyMagickString(factor,"Poisson",MaxTextExtent);
2385 break;
2386 }
2387 default:
2388 {
2389 (void) CopyMagickString(thumbnail->magick,"NULL",MaxTextExtent);
2390 break;
2391 }
2392 }
cristyd76c51e2011-03-26 00:21:26 +00002393 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) i,
2394 (size_t) i,exception);
cristyb51dff52011-05-19 16:55:47 +00002395 (void) FormatLocaleString(label,MaxTextExtent,"+noise %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002396 break;
2397 }
2398 case SharpenPreview:
2399 {
cristyaa2c16c2012-03-25 22:21:35 +00002400 preview_image=SharpenImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00002401 (void) FormatLocaleString(label,MaxTextExtent,"sharpen %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002402 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002403 break;
2404 }
2405 case BlurPreview:
2406 {
cristyaa2c16c2012-03-25 22:21:35 +00002407 preview_image=BlurImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00002408 (void) FormatLocaleString(label,MaxTextExtent,"blur %gx%g",radius,
cristy3ed852e2009-09-05 21:47:34 +00002409 sigma);
2410 break;
2411 }
2412 case ThresholdPreview:
2413 {
2414 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2415 if (preview_image == (Image *) NULL)
2416 break;
cristye941a752011-10-15 01:52:48 +00002417 (void) BilevelImage(thumbnail,(double) (percentage*((MagickRealType)
2418 QuantumRange+1.0))/100.0,exception);
cristyb51dff52011-05-19 16:55:47 +00002419 (void) FormatLocaleString(label,MaxTextExtent,"threshold %g",
cristy3ed852e2009-09-05 21:47:34 +00002420 (double) (percentage*((MagickRealType) QuantumRange+1.0))/100.0);
2421 break;
2422 }
2423 case EdgeDetectPreview:
2424 {
cristy8ae632d2011-09-05 17:29:53 +00002425 preview_image=EdgeImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00002426 (void) FormatLocaleString(label,MaxTextExtent,"edge %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002427 break;
2428 }
2429 case SpreadPreview:
2430 {
cristy5c4e2582011-09-11 19:21:03 +00002431 preview_image=SpreadImage(thumbnail,radius,thumbnail->interpolate,
2432 exception);
cristyb51dff52011-05-19 16:55:47 +00002433 (void) FormatLocaleString(label,MaxTextExtent,"spread %g",
cristy8cd5b312010-01-07 01:10:24 +00002434 radius+0.5);
cristy3ed852e2009-09-05 21:47:34 +00002435 break;
2436 }
2437 case SolarizePreview:
2438 {
2439 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2440 if (preview_image == (Image *) NULL)
2441 break;
2442 (void) SolarizeImage(preview_image,(double) QuantumRange*
cristy5cbc0162011-08-29 00:36:28 +00002443 percentage/100.0,exception);
cristyb51dff52011-05-19 16:55:47 +00002444 (void) FormatLocaleString(label,MaxTextExtent,"solarize %g",
cristy3ed852e2009-09-05 21:47:34 +00002445 (QuantumRange*percentage)/100.0);
2446 break;
2447 }
2448 case ShadePreview:
2449 {
2450 degrees+=10.0;
2451 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
2452 exception);
cristyb51dff52011-05-19 16:55:47 +00002453 (void) FormatLocaleString(label,MaxTextExtent,"shade %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002454 degrees,degrees);
cristy3ed852e2009-09-05 21:47:34 +00002455 break;
2456 }
2457 case RaisePreview:
2458 {
2459 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2460 if (preview_image == (Image *) NULL)
2461 break;
cristybb503372010-05-27 20:51:26 +00002462 geometry.width=(size_t) (2*i+2);
2463 geometry.height=(size_t) (2*i+2);
cristy3ed852e2009-09-05 21:47:34 +00002464 geometry.x=i/2;
2465 geometry.y=i/2;
cristy6170ac32011-08-28 14:15:37 +00002466 (void) RaiseImage(preview_image,&geometry,MagickTrue,exception);
cristyb51dff52011-05-19 16:55:47 +00002467 (void) FormatLocaleString(label,MaxTextExtent,
cristy6d8abba2010-06-03 01:10:47 +00002468 "raise %.20gx%.20g%+.20g%+.20g",(double) geometry.width,(double)
cristye8c25f92010-06-03 00:53:06 +00002469 geometry.height,(double) geometry.x,(double) geometry.y);
cristy3ed852e2009-09-05 21:47:34 +00002470 break;
2471 }
2472 case SegmentPreview:
2473 {
2474 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2475 if (preview_image == (Image *) NULL)
2476 break;
2477 threshold+=0.4f;
cristyc511e882012-04-16 21:11:14 +00002478 (void) SegmentImage(preview_image,sRGBColorspace,MagickFalse,threshold,
cristy018f07f2011-09-04 21:15:19 +00002479 threshold,exception);
cristyb51dff52011-05-19 16:55:47 +00002480 (void) FormatLocaleString(label,MaxTextExtent,"segment %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002481 threshold,threshold);
2482 break;
2483 }
2484 case SwirlPreview:
2485 {
cristy76f512e2011-09-12 01:26:56 +00002486 preview_image=SwirlImage(thumbnail,degrees,image->interpolate,
2487 exception);
cristyb51dff52011-05-19 16:55:47 +00002488 (void) FormatLocaleString(label,MaxTextExtent,"swirl %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002489 degrees+=45.0;
2490 break;
2491 }
2492 case ImplodePreview:
2493 {
2494 degrees+=0.1f;
cristy76f512e2011-09-12 01:26:56 +00002495 preview_image=ImplodeImage(thumbnail,degrees,image->interpolate,
2496 exception);
cristyb51dff52011-05-19 16:55:47 +00002497 (void) FormatLocaleString(label,MaxTextExtent,"implode %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002498 break;
2499 }
2500 case WavePreview:
2501 {
2502 degrees+=5.0f;
cristy5c4e2582011-09-11 19:21:03 +00002503 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,
2504 image->interpolate,exception);
cristyb51dff52011-05-19 16:55:47 +00002505 (void) FormatLocaleString(label,MaxTextExtent,"wave %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002506 0.5*degrees,2.0*degrees);
cristy3ed852e2009-09-05 21:47:34 +00002507 break;
2508 }
2509 case OilPaintPreview:
2510 {
cristy14973ba2011-08-27 23:48:07 +00002511 preview_image=OilPaintImage(thumbnail,(double) radius,(double) sigma,
2512 exception);
2513 (void) FormatLocaleString(label,MaxTextExtent,"charcoal %gx%g",
2514 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002515 break;
2516 }
2517 case CharcoalDrawingPreview:
2518 {
2519 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
cristyaa2c16c2012-03-25 22:21:35 +00002520 exception);
cristyb51dff52011-05-19 16:55:47 +00002521 (void) FormatLocaleString(label,MaxTextExtent,"charcoal %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002522 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002523 break;
2524 }
2525 case JPEGPreview:
2526 {
2527 char
2528 filename[MaxTextExtent];
2529
2530 int
2531 file;
2532
2533 MagickBooleanType
2534 status;
2535
2536 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2537 if (preview_image == (Image *) NULL)
2538 break;
cristybb503372010-05-27 20:51:26 +00002539 preview_info->quality=(size_t) percentage;
cristyb51dff52011-05-19 16:55:47 +00002540 (void) FormatLocaleString(factor,MaxTextExtent,"%.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00002541 preview_info->quality);
cristy3ed852e2009-09-05 21:47:34 +00002542 file=AcquireUniqueFileResource(filename);
2543 if (file != -1)
2544 file=close(file)-1;
cristyb51dff52011-05-19 16:55:47 +00002545 (void) FormatLocaleString(preview_image->filename,MaxTextExtent,
cristy3ed852e2009-09-05 21:47:34 +00002546 "jpeg:%s",filename);
cristy6f9e0d32011-08-28 16:32:09 +00002547 status=WriteImage(preview_info,preview_image,exception);
cristy3ed852e2009-09-05 21:47:34 +00002548 if (status != MagickFalse)
2549 {
2550 Image
2551 *quality_image;
2552
2553 (void) CopyMagickString(preview_info->filename,
2554 preview_image->filename,MaxTextExtent);
2555 quality_image=ReadImage(preview_info,exception);
2556 if (quality_image != (Image *) NULL)
2557 {
2558 preview_image=DestroyImage(preview_image);
2559 preview_image=quality_image;
2560 }
2561 }
2562 (void) RelinquishUniqueFileResource(preview_image->filename);
2563 if ((GetBlobSize(preview_image)/1024) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00002564 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%gmb ",
cristy3ed852e2009-09-05 21:47:34 +00002565 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
2566 1024.0/1024.0);
2567 else
2568 if (GetBlobSize(preview_image) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00002569 (void) FormatLocaleString(label,MaxTextExtent,
cristye7f51092010-01-17 00:39:37 +00002570 "quality %s\n%gkb ",factor,(double) ((MagickOffsetType)
cristy8cd5b312010-01-07 01:10:24 +00002571 GetBlobSize(preview_image))/1024.0);
cristy3ed852e2009-09-05 21:47:34 +00002572 else
cristyb51dff52011-05-19 16:55:47 +00002573 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%.20gb ",
cristy54ea5732011-06-10 12:39:53 +00002574 factor,(double) ((MagickOffsetType) GetBlobSize(thumbnail)));
cristy3ed852e2009-09-05 21:47:34 +00002575 break;
2576 }
2577 }
2578 thumbnail=DestroyImage(thumbnail);
2579 percentage+=12.5;
2580 radius+=0.5;
2581 sigma+=0.25;
2582 if (preview_image == (Image *) NULL)
2583 break;
2584 (void) DeleteImageProperty(preview_image,"label");
cristyd15e6592011-10-15 00:13:06 +00002585 (void) SetImageProperty(preview_image,"label",label,exception);
cristy3ed852e2009-09-05 21:47:34 +00002586 AppendImageToList(&images,preview_image);
cristybb503372010-05-27 20:51:26 +00002587 proceed=SetImageProgress(image,PreviewImageTag,(MagickOffsetType) i,
2588 NumberTiles);
cristy3ed852e2009-09-05 21:47:34 +00002589 if (proceed == MagickFalse)
2590 break;
2591 }
2592 if (images == (Image *) NULL)
2593 {
2594 preview_info=DestroyImageInfo(preview_info);
2595 return((Image *) NULL);
2596 }
2597 /*
2598 Create the montage.
2599 */
2600 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
2601 (void) CopyMagickString(montage_info->filename,image->filename,MaxTextExtent);
2602 montage_info->shadow=MagickTrue;
2603 (void) CloneString(&montage_info->tile,"3x3");
2604 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
2605 (void) CloneString(&montage_info->frame,DefaultTileFrame);
2606 montage_image=MontageImages(images,montage_info,exception);
2607 montage_info=DestroyMontageInfo(montage_info);
2608 images=DestroyImageList(images);
2609 if (montage_image == (Image *) NULL)
2610 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2611 if (montage_image->montage != (char *) NULL)
2612 {
2613 /*
2614 Free image directory.
2615 */
2616 montage_image->montage=(char *) RelinquishMagickMemory(
2617 montage_image->montage);
2618 if (image->directory != (char *) NULL)
2619 montage_image->directory=(char *) RelinquishMagickMemory(
2620 montage_image->directory);
2621 }
2622 preview_info=DestroyImageInfo(preview_info);
2623 return(montage_image);
2624}
2625
2626/*
2627%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2628% %
2629% %
2630% %
2631% R a d i a l B l u r I m a g e %
2632% %
2633% %
2634% %
2635%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2636%
2637% RadialBlurImage() applies a radial blur to the image.
2638%
2639% Andrew Protano contributed this effect.
2640%
2641% The format of the RadialBlurImage method is:
2642%
2643% Image *RadialBlurImage(const Image *image,const double angle,
cristyaa2c16c2012-03-25 22:21:35 +00002644% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002645%
2646% A description of each parameter follows:
2647%
2648% o image: the image.
2649%
cristy3ed852e2009-09-05 21:47:34 +00002650% o angle: the angle of the radial blur.
2651%
cristy6435bd92011-09-10 02:10:07 +00002652% o blur: the blur.
2653%
cristy3ed852e2009-09-05 21:47:34 +00002654% o exception: return any errors or warnings in this structure.
2655%
2656*/
cristy4282c702011-11-21 00:01:06 +00002657MagickExport Image *RadialBlurImage(const Image *image,const double angle,
cristyaa2c16c2012-03-25 22:21:35 +00002658 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002659{
cristyc4c8d132010-01-07 01:58:38 +00002660 CacheView
2661 *blur_view,
cristy8b49f382012-01-17 03:11:03 +00002662 *image_view,
2663 *radial_view;
cristyc4c8d132010-01-07 01:58:38 +00002664
cristy3ed852e2009-09-05 21:47:34 +00002665 Image
2666 *blur_image;
2667
cristy3ed852e2009-09-05 21:47:34 +00002668 MagickBooleanType
2669 status;
2670
cristybb503372010-05-27 20:51:26 +00002671 MagickOffsetType
2672 progress;
2673
cristy3ed852e2009-09-05 21:47:34 +00002674 MagickRealType
2675 blur_radius,
2676 *cos_theta,
2677 offset,
2678 *sin_theta,
2679 theta;
2680
2681 PointInfo
2682 blur_center;
2683
cristybb503372010-05-27 20:51:26 +00002684 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002685 i;
2686
cristybb503372010-05-27 20:51:26 +00002687 size_t
cristy3ed852e2009-09-05 21:47:34 +00002688 n;
2689
cristybb503372010-05-27 20:51:26 +00002690 ssize_t
2691 y;
2692
cristy3ed852e2009-09-05 21:47:34 +00002693 /*
2694 Allocate blur image.
2695 */
2696 assert(image != (Image *) NULL);
2697 assert(image->signature == MagickSignature);
2698 if (image->debug != MagickFalse)
2699 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2700 assert(exception != (ExceptionInfo *) NULL);
2701 assert(exception->signature == MagickSignature);
cristy1e7aa312011-09-10 20:01:36 +00002702 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00002703 if (blur_image == (Image *) NULL)
2704 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00002705 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00002706 {
cristy3ed852e2009-09-05 21:47:34 +00002707 blur_image=DestroyImage(blur_image);
2708 return((Image *) NULL);
2709 }
2710 blur_center.x=(double) image->columns/2.0;
2711 blur_center.y=(double) image->rows/2.0;
2712 blur_radius=hypot(blur_center.x,blur_center.y);
cristy117ff172010-08-15 21:35:32 +00002713 n=(size_t) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+2UL);
cristy3ed852e2009-09-05 21:47:34 +00002714 theta=DegreesToRadians(angle)/(MagickRealType) (n-1);
2715 cos_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
2716 sizeof(*cos_theta));
2717 sin_theta=(MagickRealType *) AcquireQuantumMemory((size_t) n,
2718 sizeof(*sin_theta));
2719 if ((cos_theta == (MagickRealType *) NULL) ||
2720 (sin_theta == (MagickRealType *) NULL))
2721 {
2722 blur_image=DestroyImage(blur_image);
2723 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2724 }
2725 offset=theta*(MagickRealType) (n-1)/2.0;
cristybb503372010-05-27 20:51:26 +00002726 for (i=0; i < (ssize_t) n; i++)
cristy3ed852e2009-09-05 21:47:34 +00002727 {
2728 cos_theta[i]=cos((double) (theta*i-offset));
2729 sin_theta[i]=sin((double) (theta*i-offset));
2730 }
2731 /*
2732 Radial blur image.
2733 */
2734 status=MagickTrue;
2735 progress=0;
cristydb070952012-04-20 14:33:00 +00002736 image_view=AcquireVirtualCacheView(image,exception);
2737 radial_view=AcquireVirtualCacheView(image,exception);
2738 blur_view=AcquireAuthenticCacheView(blur_image,exception);
cristyb5d5f722009-11-04 03:03:49 +00002739#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00002740 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy4ee2b0c2012-05-15 00:30:35 +00002741 dynamic_number_threads(image,image->columns,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00002742#endif
cristy1e7aa312011-09-10 20:01:36 +00002743 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002744 {
cristy1e7aa312011-09-10 20:01:36 +00002745 register const Quantum
2746 *restrict p;
2747
cristy4c08aed2011-07-01 19:47:50 +00002748 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002749 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002750
cristy117ff172010-08-15 21:35:32 +00002751 register ssize_t
2752 x;
2753
cristy3ed852e2009-09-05 21:47:34 +00002754 if (status == MagickFalse)
2755 continue;
cristy8b49f382012-01-17 03:11:03 +00002756 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
2757 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +00002758 exception);
cristy1e7aa312011-09-10 20:01:36 +00002759 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00002760 {
2761 status=MagickFalse;
2762 continue;
2763 }
cristy1e7aa312011-09-10 20:01:36 +00002764 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002765 {
cristy3ed852e2009-09-05 21:47:34 +00002766 MagickRealType
cristy3ed852e2009-09-05 21:47:34 +00002767 radius;
2768
cristy3ed852e2009-09-05 21:47:34 +00002769 PointInfo
2770 center;
2771
cristybb503372010-05-27 20:51:26 +00002772 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002773 i;
2774
cristybb503372010-05-27 20:51:26 +00002775 size_t
cristy3ed852e2009-09-05 21:47:34 +00002776 step;
2777
2778 center.x=(double) x-blur_center.x;
2779 center.y=(double) y-blur_center.y;
2780 radius=hypot((double) center.x,center.y);
2781 if (radius == 0)
2782 step=1;
2783 else
2784 {
cristybb503372010-05-27 20:51:26 +00002785 step=(size_t) (blur_radius/radius);
cristy3ed852e2009-09-05 21:47:34 +00002786 if (step == 0)
2787 step=1;
2788 else
2789 if (step >= n)
2790 step=n-1;
2791 }
cristy177e41c2012-04-15 15:08:25 +00002792 if (GetPixelMask(image,p) != 0)
2793 {
2794 p+=GetPixelChannels(image);
2795 q+=GetPixelChannels(blur_image);
2796 continue;
2797 }
cristy1e7aa312011-09-10 20:01:36 +00002798 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2799 {
2800 MagickRealType
2801 gamma,
2802 pixel;
cristy3ed852e2009-09-05 21:47:34 +00002803
cristy1e7aa312011-09-10 20:01:36 +00002804 PixelChannel
2805 channel;
2806
2807 PixelTrait
2808 blur_traits,
2809 traits;
2810
2811 register const Quantum
2812 *restrict r;
2813
2814 register ssize_t
2815 j;
2816
cristye2a912b2011-12-05 20:02:07 +00002817 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +00002818 traits=GetPixelChannelMapTraits(image,channel);
cristy1e7aa312011-09-10 20:01:36 +00002819 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
2820 if ((traits == UndefinedPixelTrait) ||
2821 (blur_traits == UndefinedPixelTrait))
2822 continue;
cristy177e41c2012-04-15 15:08:25 +00002823 if ((blur_traits & CopyPixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002824 {
cristy0beccfa2011-09-25 20:47:53 +00002825 SetPixelChannel(blur_image,channel,p[i],q);
cristy1e7aa312011-09-10 20:01:36 +00002826 continue;
cristy3ed852e2009-09-05 21:47:34 +00002827 }
cristy1e7aa312011-09-10 20:01:36 +00002828 gamma=0.0;
cristyaa2c16c2012-03-25 22:21:35 +00002829 pixel=0.0;
cristy1e7aa312011-09-10 20:01:36 +00002830 if ((blur_traits & BlendPixelTrait) == 0)
2831 {
2832 for (j=0; j < (ssize_t) n; j+=(ssize_t) step)
2833 {
cristy8b49f382012-01-17 03:11:03 +00002834 r=GetCacheViewVirtualPixels(radial_view, (ssize_t) (blur_center.x+
cristy1e7aa312011-09-10 20:01:36 +00002835 center.x*cos_theta[j]-center.y*sin_theta[j]+0.5),(ssize_t)
2836 (blur_center.y+center.x*sin_theta[j]+center.y*cos_theta[j]+0.5),
2837 1,1,exception);
2838 if (r == (const Quantum *) NULL)
2839 {
2840 status=MagickFalse;
2841 continue;
2842 }
2843 pixel+=r[i];
2844 gamma++;
2845 }
cristyc58380a2012-06-03 15:12:30 +00002846 gamma=MagickEpsilonReciprocal(gamma);
cristy0beccfa2011-09-25 20:47:53 +00002847 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy1e7aa312011-09-10 20:01:36 +00002848 continue;
2849 }
2850 for (j=0; j < (ssize_t) n; j+=(ssize_t) step)
2851 {
cristy8b49f382012-01-17 03:11:03 +00002852 r=GetCacheViewVirtualPixels(radial_view, (ssize_t) (blur_center.x+
cristy1e7aa312011-09-10 20:01:36 +00002853 center.x*cos_theta[j]-center.y*sin_theta[j]+0.5),(ssize_t)
2854 (blur_center.y+center.x*sin_theta[j]+center.y*cos_theta[j]+0.5),
2855 1,1,exception);
2856 if (r == (const Quantum *) NULL)
2857 {
2858 status=MagickFalse;
2859 continue;
2860 }
2861 pixel+=GetPixelAlpha(image,r)*r[i];
2862 gamma+=GetPixelAlpha(image,r);
cristy3ed852e2009-09-05 21:47:34 +00002863 }
cristyc58380a2012-06-03 15:12:30 +00002864 gamma=MagickEpsilonReciprocal(gamma);
cristy0beccfa2011-09-25 20:47:53 +00002865 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy1e7aa312011-09-10 20:01:36 +00002866 }
2867 p+=GetPixelChannels(image);
cristyed231572011-07-14 02:18:59 +00002868 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00002869 }
2870 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
2871 status=MagickFalse;
2872 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2873 {
2874 MagickBooleanType
2875 proceed;
2876
cristyb5d5f722009-11-04 03:03:49 +00002877#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf8561542012-01-24 00:26:46 +00002878 #pragma omp critical (MagickCore_RadialBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00002879#endif
2880 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
2881 if (proceed == MagickFalse)
2882 status=MagickFalse;
2883 }
2884 }
2885 blur_view=DestroyCacheView(blur_view);
cristy8b49f382012-01-17 03:11:03 +00002886 radial_view=DestroyCacheView(radial_view);
cristy3ed852e2009-09-05 21:47:34 +00002887 image_view=DestroyCacheView(image_view);
2888 cos_theta=(MagickRealType *) RelinquishMagickMemory(cos_theta);
2889 sin_theta=(MagickRealType *) RelinquishMagickMemory(sin_theta);
2890 if (status == MagickFalse)
2891 blur_image=DestroyImage(blur_image);
2892 return(blur_image);
2893}
2894
2895/*
2896%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2897% %
2898% %
2899% %
cristy3ed852e2009-09-05 21:47:34 +00002900% S e l e c t i v e B l u r I m a g e %
2901% %
2902% %
2903% %
2904%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2905%
2906% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
2907% It is similar to the unsharpen mask that sharpens everything with contrast
2908% above a certain threshold.
2909%
2910% The format of the SelectiveBlurImage method is:
2911%
2912% Image *SelectiveBlurImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +00002913% const double sigma,const double threshold,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002914%
2915% A description of each parameter follows:
2916%
2917% o image: the image.
2918%
cristy3ed852e2009-09-05 21:47:34 +00002919% o radius: the radius of the Gaussian, in pixels, not counting the center
2920% pixel.
2921%
2922% o sigma: the standard deviation of the Gaussian, in pixels.
2923%
2924% o threshold: only pixels within this contrast threshold are included
2925% in the blur operation.
2926%
2927% o exception: return any errors or warnings in this structure.
2928%
2929*/
cristy4282c702011-11-21 00:01:06 +00002930MagickExport Image *SelectiveBlurImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +00002931 const double sigma,const double threshold,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002932{
2933#define SelectiveBlurImageTag "SelectiveBlur/Image"
2934
cristy47e00502009-12-17 19:19:57 +00002935 CacheView
2936 *blur_view,
2937 *image_view;
2938
cristy3ed852e2009-09-05 21:47:34 +00002939 double
cristy3ed852e2009-09-05 21:47:34 +00002940 *kernel;
2941
2942 Image
2943 *blur_image;
2944
cristy3ed852e2009-09-05 21:47:34 +00002945 MagickBooleanType
2946 status;
2947
cristybb503372010-05-27 20:51:26 +00002948 MagickOffsetType
2949 progress;
2950
cristybb503372010-05-27 20:51:26 +00002951 register ssize_t
cristy47e00502009-12-17 19:19:57 +00002952 i;
cristy3ed852e2009-09-05 21:47:34 +00002953
cristybb503372010-05-27 20:51:26 +00002954 size_t
cristy3ed852e2009-09-05 21:47:34 +00002955 width;
2956
cristybb503372010-05-27 20:51:26 +00002957 ssize_t
cristyc8523c12011-09-13 00:02:53 +00002958 center,
cristybb503372010-05-27 20:51:26 +00002959 j,
2960 u,
2961 v,
2962 y;
2963
cristy3ed852e2009-09-05 21:47:34 +00002964 /*
2965 Initialize blur image attributes.
2966 */
2967 assert(image != (Image *) NULL);
2968 assert(image->signature == MagickSignature);
2969 if (image->debug != MagickFalse)
2970 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2971 assert(exception != (ExceptionInfo *) NULL);
2972 assert(exception->signature == MagickSignature);
2973 width=GetOptimalKernelWidth1D(radius,sigma);
cristy8b49f382012-01-17 03:11:03 +00002974 kernel=(double *) AcquireAlignedMemory((size_t) width,width*sizeof(*kernel));
cristy3ed852e2009-09-05 21:47:34 +00002975 if (kernel == (double *) NULL)
2976 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00002977 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00002978 i=0;
cristy47e00502009-12-17 19:19:57 +00002979 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002980 {
cristy47e00502009-12-17 19:19:57 +00002981 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00002982 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
2983 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00002984 }
2985 if (image->debug != MagickFalse)
2986 {
2987 char
2988 format[MaxTextExtent],
2989 *message;
2990
cristy117ff172010-08-15 21:35:32 +00002991 register const double
2992 *k;
2993
cristybb503372010-05-27 20:51:26 +00002994 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002995 u,
2996 v;
2997
cristy3ed852e2009-09-05 21:47:34 +00002998 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00002999 " SelectiveBlurImage with %.20gx%.20g kernel:",(double) width,(double)
3000 width);
cristy3ed852e2009-09-05 21:47:34 +00003001 message=AcquireString("");
3002 k=kernel;
cristybb503372010-05-27 20:51:26 +00003003 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003004 {
3005 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00003006 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy3ed852e2009-09-05 21:47:34 +00003007 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00003008 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003009 {
cristyb51dff52011-05-19 16:55:47 +00003010 (void) FormatLocaleString(format,MaxTextExtent,"%+f ",*k++);
cristy3ed852e2009-09-05 21:47:34 +00003011 (void) ConcatenateString(&message,format);
3012 }
3013 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
3014 }
3015 message=DestroyString(message);
3016 }
cristy1e7aa312011-09-10 20:01:36 +00003017 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00003018 if (blur_image == (Image *) NULL)
3019 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003020 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003021 {
cristy3ed852e2009-09-05 21:47:34 +00003022 blur_image=DestroyImage(blur_image);
3023 return((Image *) NULL);
3024 }
3025 /*
3026 Threshold blur image.
3027 */
3028 status=MagickTrue;
3029 progress=0;
cristyc8523c12011-09-13 00:02:53 +00003030 center=(ssize_t) (GetPixelChannels(image)*(image->columns+width)*(width/2L)+
3031 GetPixelChannels(image)*(width/2L));
cristydb070952012-04-20 14:33:00 +00003032 image_view=AcquireVirtualCacheView(image,exception);
3033 blur_view=AcquireAuthenticCacheView(blur_image,exception);
cristyb5d5f722009-11-04 03:03:49 +00003034#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00003035 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy4ee2b0c2012-05-15 00:30:35 +00003036 dynamic_number_threads(image,image->columns,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00003037#endif
cristybb503372010-05-27 20:51:26 +00003038 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003039 {
cristy4c08aed2011-07-01 19:47:50 +00003040 double
3041 contrast;
3042
cristy3ed852e2009-09-05 21:47:34 +00003043 MagickBooleanType
3044 sync;
3045
cristy4c08aed2011-07-01 19:47:50 +00003046 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003047 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003048
cristy4c08aed2011-07-01 19:47:50 +00003049 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003050 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003051
cristy117ff172010-08-15 21:35:32 +00003052 register ssize_t
3053 x;
3054
cristy3ed852e2009-09-05 21:47:34 +00003055 if (status == MagickFalse)
3056 continue;
cristy117ff172010-08-15 21:35:32 +00003057 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
3058 (width/2L),image->columns+width,width,exception);
cristy8b49f382012-01-17 03:11:03 +00003059 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +00003060 exception);
cristy4c08aed2011-07-01 19:47:50 +00003061 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003062 {
3063 status=MagickFalse;
3064 continue;
3065 }
cristybb503372010-05-27 20:51:26 +00003066 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003067 {
cristybb503372010-05-27 20:51:26 +00003068 register ssize_t
cristy1e7aa312011-09-10 20:01:36 +00003069 i;
cristy3ed852e2009-09-05 21:47:34 +00003070
cristy177e41c2012-04-15 15:08:25 +00003071 if (GetPixelMask(image,p) != 0)
3072 {
3073 p+=GetPixelChannels(image);
3074 q+=GetPixelChannels(blur_image);
3075 continue;
3076 }
cristy1e7aa312011-09-10 20:01:36 +00003077 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3078 {
3079 MagickRealType
3080 alpha,
3081 gamma,
3082 intensity,
3083 pixel;
cristy117ff172010-08-15 21:35:32 +00003084
cristy1e7aa312011-09-10 20:01:36 +00003085 PixelChannel
3086 channel;
3087
3088 PixelTrait
3089 blur_traits,
3090 traits;
3091
3092 register const double
3093 *restrict k;
3094
3095 register const Quantum
3096 *restrict pixels;
3097
3098 register ssize_t
3099 u;
3100
3101 ssize_t
3102 v;
3103
cristye2a912b2011-12-05 20:02:07 +00003104 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +00003105 traits=GetPixelChannelMapTraits(image,channel);
cristy1e7aa312011-09-10 20:01:36 +00003106 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
3107 if ((traits == UndefinedPixelTrait) ||
3108 (blur_traits == UndefinedPixelTrait))
3109 continue;
cristy177e41c2012-04-15 15:08:25 +00003110 if ((blur_traits & CopyPixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003111 {
cristy0beccfa2011-09-25 20:47:53 +00003112 SetPixelChannel(blur_image,channel,p[center+i],q);
cristy1e7aa312011-09-10 20:01:36 +00003113 continue;
cristy3ed852e2009-09-05 21:47:34 +00003114 }
cristy1e7aa312011-09-10 20:01:36 +00003115 k=kernel;
cristyaa2c16c2012-03-25 22:21:35 +00003116 pixel=0.0;
cristy1e7aa312011-09-10 20:01:36 +00003117 pixels=p;
cristyc8523c12011-09-13 00:02:53 +00003118 intensity=(MagickRealType) GetPixelIntensity(image,p+center);
cristy1e7aa312011-09-10 20:01:36 +00003119 gamma=0.0;
3120 if ((blur_traits & BlendPixelTrait) == 0)
cristy3ed852e2009-09-05 21:47:34 +00003121 {
cristy1e7aa312011-09-10 20:01:36 +00003122 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003123 {
cristy1e7aa312011-09-10 20:01:36 +00003124 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003125 {
cristy1e7aa312011-09-10 20:01:36 +00003126 contrast=GetPixelIntensity(image,pixels)-intensity;
3127 if (fabs(contrast) < threshold)
3128 {
3129 pixel+=(*k)*pixels[i];
3130 gamma+=(*k);
3131 }
3132 k++;
3133 pixels+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003134 }
cristy1e7aa312011-09-10 20:01:36 +00003135 pixels+=image->columns*GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003136 }
cristy1e7aa312011-09-10 20:01:36 +00003137 if (fabs((double) gamma) < MagickEpsilon)
3138 {
cristy0beccfa2011-09-25 20:47:53 +00003139 SetPixelChannel(blur_image,channel,p[center+i],q);
cristy1e7aa312011-09-10 20:01:36 +00003140 continue;
3141 }
cristyc58380a2012-06-03 15:12:30 +00003142 gamma=MagickEpsilonReciprocal(gamma);
cristy0beccfa2011-09-25 20:47:53 +00003143 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy1e7aa312011-09-10 20:01:36 +00003144 continue;
3145 }
3146 for (v=0; v < (ssize_t) width; v++)
3147 {
3148 for (u=0; u < (ssize_t) width; u++)
3149 {
3150 contrast=GetPixelIntensity(image,pixels)-intensity;
3151 if (fabs(contrast) < threshold)
3152 {
3153 alpha=(MagickRealType) (QuantumScale*
3154 GetPixelAlpha(image,pixels));
3155 pixel+=(*k)*alpha*pixels[i];
3156 gamma+=(*k)*alpha;
3157 }
3158 k++;
3159 pixels+=GetPixelChannels(image);
3160 }
3161 pixels+=image->columns*GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003162 }
cristy1e7aa312011-09-10 20:01:36 +00003163 if (fabs((double) gamma) < MagickEpsilon)
3164 {
cristy0beccfa2011-09-25 20:47:53 +00003165 SetPixelChannel(blur_image,channel,p[center+i],q);
cristy1e7aa312011-09-10 20:01:36 +00003166 continue;
3167 }
cristyc58380a2012-06-03 15:12:30 +00003168 gamma=MagickEpsilonReciprocal(gamma);
cristy0beccfa2011-09-25 20:47:53 +00003169 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy1e7aa312011-09-10 20:01:36 +00003170 }
cristyed231572011-07-14 02:18:59 +00003171 p+=GetPixelChannels(image);
3172 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003173 }
3174 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
3175 if (sync == MagickFalse)
3176 status=MagickFalse;
3177 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3178 {
3179 MagickBooleanType
3180 proceed;
3181
cristyb5d5f722009-11-04 03:03:49 +00003182#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf8561542012-01-24 00:26:46 +00003183 #pragma omp critical (MagickCore_SelectiveBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00003184#endif
3185 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress++,
3186 image->rows);
3187 if (proceed == MagickFalse)
3188 status=MagickFalse;
3189 }
3190 }
3191 blur_image->type=image->type;
3192 blur_view=DestroyCacheView(blur_view);
3193 image_view=DestroyCacheView(image_view);
cristy8b49f382012-01-17 03:11:03 +00003194 kernel=(double *) RelinquishAlignedMemory(kernel);
cristy3ed852e2009-09-05 21:47:34 +00003195 if (status == MagickFalse)
3196 blur_image=DestroyImage(blur_image);
3197 return(blur_image);
3198}
3199
3200/*
3201%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3202% %
3203% %
3204% %
3205% S h a d e I m a g e %
3206% %
3207% %
3208% %
3209%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3210%
3211% ShadeImage() shines a distant light on an image to create a
3212% three-dimensional effect. You control the positioning of the light with
3213% azimuth and elevation; azimuth is measured in degrees off the x axis
3214% and elevation is measured in pixels above the Z axis.
3215%
3216% The format of the ShadeImage method is:
3217%
3218% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3219% const double azimuth,const double elevation,ExceptionInfo *exception)
3220%
3221% A description of each parameter follows:
3222%
3223% o image: the image.
3224%
3225% o gray: A value other than zero shades the intensity of each pixel.
3226%
3227% o azimuth, elevation: Define the light source direction.
3228%
3229% o exception: return any errors or warnings in this structure.
3230%
3231*/
3232MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3233 const double azimuth,const double elevation,ExceptionInfo *exception)
3234{
3235#define ShadeImageTag "Shade/Image"
3236
cristyc4c8d132010-01-07 01:58:38 +00003237 CacheView
3238 *image_view,
3239 *shade_view;
3240
cristy3ed852e2009-09-05 21:47:34 +00003241 Image
3242 *shade_image;
3243
cristy3ed852e2009-09-05 21:47:34 +00003244 MagickBooleanType
3245 status;
3246
cristybb503372010-05-27 20:51:26 +00003247 MagickOffsetType
3248 progress;
3249
cristy3ed852e2009-09-05 21:47:34 +00003250 PrimaryInfo
3251 light;
3252
cristybb503372010-05-27 20:51:26 +00003253 ssize_t
3254 y;
3255
cristy3ed852e2009-09-05 21:47:34 +00003256 /*
3257 Initialize shaded image attributes.
3258 */
3259 assert(image != (const Image *) NULL);
3260 assert(image->signature == MagickSignature);
3261 if (image->debug != MagickFalse)
3262 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3263 assert(exception != (ExceptionInfo *) NULL);
3264 assert(exception->signature == MagickSignature);
3265 shade_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
3266 if (shade_image == (Image *) NULL)
3267 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003268 if (SetImageStorageClass(shade_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003269 {
cristy3ed852e2009-09-05 21:47:34 +00003270 shade_image=DestroyImage(shade_image);
3271 return((Image *) NULL);
3272 }
3273 /*
3274 Compute the light vector.
3275 */
3276 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
3277 cos(DegreesToRadians(elevation));
3278 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
3279 cos(DegreesToRadians(elevation));
3280 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
3281 /*
3282 Shade image.
3283 */
3284 status=MagickTrue;
3285 progress=0;
cristydb070952012-04-20 14:33:00 +00003286 image_view=AcquireVirtualCacheView(image,exception);
3287 shade_view=AcquireAuthenticCacheView(shade_image,exception);
cristyb5d5f722009-11-04 03:03:49 +00003288#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00003289 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy4ee2b0c2012-05-15 00:30:35 +00003290 dynamic_number_threads(image,image->columns,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00003291#endif
cristybb503372010-05-27 20:51:26 +00003292 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003293 {
3294 MagickRealType
3295 distance,
3296 normal_distance,
3297 shade;
3298
3299 PrimaryInfo
3300 normal;
3301
cristy4c08aed2011-07-01 19:47:50 +00003302 register const Quantum
cristy1e7aa312011-09-10 20:01:36 +00003303 *restrict center,
cristyc47d1f82009-11-26 01:44:43 +00003304 *restrict p,
cristy1e7aa312011-09-10 20:01:36 +00003305 *restrict post,
3306 *restrict pre;
cristy3ed852e2009-09-05 21:47:34 +00003307
cristy4c08aed2011-07-01 19:47:50 +00003308 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003309 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003310
cristy117ff172010-08-15 21:35:32 +00003311 register ssize_t
3312 x;
3313
cristy3ed852e2009-09-05 21:47:34 +00003314 if (status == MagickFalse)
3315 continue;
3316 p=GetCacheViewVirtualPixels(image_view,-1,y-1,image->columns+2,3,exception);
3317 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
3318 exception);
cristy4c08aed2011-07-01 19:47:50 +00003319 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003320 {
3321 status=MagickFalse;
3322 continue;
3323 }
3324 /*
3325 Shade this row of pixels.
3326 */
3327 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
cristy1e7aa312011-09-10 20:01:36 +00003328 pre=p+GetPixelChannels(image);
3329 center=pre+(image->columns+2)*GetPixelChannels(image);
3330 post=center+(image->columns+2)*GetPixelChannels(image);
cristybb503372010-05-27 20:51:26 +00003331 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003332 {
cristy1e7aa312011-09-10 20:01:36 +00003333 register ssize_t
3334 i;
3335
cristy3ed852e2009-09-05 21:47:34 +00003336 /*
3337 Determine the surface normal and compute shading.
3338 */
cristy1e7aa312011-09-10 20:01:36 +00003339 normal.x=(double) (GetPixelIntensity(image,pre-GetPixelChannels(image))+
3340 GetPixelIntensity(image,center-GetPixelChannels(image))+
3341 GetPixelIntensity(image,post-GetPixelChannels(image))-
3342 GetPixelIntensity(image,pre+GetPixelChannels(image))-
3343 GetPixelIntensity(image,center+GetPixelChannels(image))-
3344 GetPixelIntensity(image,post+GetPixelChannels(image)));
3345 normal.y=(double) (GetPixelIntensity(image,post-GetPixelChannels(image))+
3346 GetPixelIntensity(image,post)+GetPixelIntensity(image,post+
3347 GetPixelChannels(image))-GetPixelIntensity(image,pre-
3348 GetPixelChannels(image))-GetPixelIntensity(image,pre)-
3349 GetPixelIntensity(image,pre+GetPixelChannels(image)));
cristy3ed852e2009-09-05 21:47:34 +00003350 if ((normal.x == 0.0) && (normal.y == 0.0))
3351 shade=light.z;
3352 else
3353 {
3354 shade=0.0;
3355 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
3356 if (distance > MagickEpsilon)
3357 {
3358 normal_distance=
3359 normal.x*normal.x+normal.y*normal.y+normal.z*normal.z;
3360 if (normal_distance > (MagickEpsilon*MagickEpsilon))
3361 shade=distance/sqrt((double) normal_distance);
3362 }
3363 }
cristy177e41c2012-04-15 15:08:25 +00003364 if (GetPixelMask(image,p) != 0)
3365 {
3366 pre+=GetPixelChannels(image);
3367 center+=GetPixelChannels(image);
3368 post+=GetPixelChannels(image);
3369 q+=GetPixelChannels(shade_image);
3370 continue;
3371 }
cristy1e7aa312011-09-10 20:01:36 +00003372 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3373 {
3374 PixelChannel
3375 channel;
3376
3377 PixelTrait
3378 shade_traits,
3379 traits;
3380
cristye2a912b2011-12-05 20:02:07 +00003381 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +00003382 traits=GetPixelChannelMapTraits(image,channel);
cristy1e7aa312011-09-10 20:01:36 +00003383 shade_traits=GetPixelChannelMapTraits(shade_image,channel);
3384 if ((traits == UndefinedPixelTrait) ||
3385 (shade_traits == UndefinedPixelTrait))
3386 continue;
cristy177e41c2012-04-15 15:08:25 +00003387 if ((shade_traits & CopyPixelTrait) != 0)
cristy1e7aa312011-09-10 20:01:36 +00003388 {
cristy0beccfa2011-09-25 20:47:53 +00003389 SetPixelChannel(shade_image,channel,center[i],q);
cristy1e7aa312011-09-10 20:01:36 +00003390 continue;
3391 }
3392 if (gray != MagickFalse)
3393 {
cristy0beccfa2011-09-25 20:47:53 +00003394 SetPixelChannel(shade_image,channel,ClampToQuantum(shade),q);
cristy1e7aa312011-09-10 20:01:36 +00003395 continue;
3396 }
cristy0beccfa2011-09-25 20:47:53 +00003397 SetPixelChannel(shade_image,channel,ClampToQuantum(QuantumScale*shade*
3398 center[i]),q);
cristy1e7aa312011-09-10 20:01:36 +00003399 }
3400 pre+=GetPixelChannels(image);
3401 center+=GetPixelChannels(image);
3402 post+=GetPixelChannels(image);
cristyed231572011-07-14 02:18:59 +00003403 q+=GetPixelChannels(shade_image);
cristy3ed852e2009-09-05 21:47:34 +00003404 }
3405 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
3406 status=MagickFalse;
3407 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3408 {
3409 MagickBooleanType
3410 proceed;
3411
cristyb5d5f722009-11-04 03:03:49 +00003412#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf8561542012-01-24 00:26:46 +00003413 #pragma omp critical (MagickCore_ShadeImage)
cristy3ed852e2009-09-05 21:47:34 +00003414#endif
3415 proceed=SetImageProgress(image,ShadeImageTag,progress++,image->rows);
3416 if (proceed == MagickFalse)
3417 status=MagickFalse;
3418 }
3419 }
3420 shade_view=DestroyCacheView(shade_view);
3421 image_view=DestroyCacheView(image_view);
3422 if (status == MagickFalse)
3423 shade_image=DestroyImage(shade_image);
3424 return(shade_image);
3425}
3426
3427/*
3428%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3429% %
3430% %
3431% %
3432% S h a r p e n I m a g e %
3433% %
3434% %
3435% %
3436%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3437%
3438% SharpenImage() sharpens the image. We convolve the image with a Gaussian
3439% operator of the given radius and standard deviation (sigma). For
3440% reasonable results, radius should be larger than sigma. Use a radius of 0
3441% and SharpenImage() selects a suitable radius for you.
3442%
3443% Using a separable kernel would be faster, but the negative weights cancel
3444% out on the corners of the kernel producing often undesirable ringing in the
3445% filtered result; this can be avoided by using a 2D gaussian shaped image
3446% sharpening kernel instead.
3447%
3448% The format of the SharpenImage method is:
3449%
3450% Image *SharpenImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +00003451% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003452%
3453% A description of each parameter follows:
3454%
3455% o image: the image.
3456%
cristy3ed852e2009-09-05 21:47:34 +00003457% o radius: the radius of the Gaussian, in pixels, not counting the center
3458% pixel.
3459%
3460% o sigma: the standard deviation of the Laplacian, in pixels.
3461%
3462% o exception: return any errors or warnings in this structure.
3463%
3464*/
cristy3ed852e2009-09-05 21:47:34 +00003465MagickExport Image *SharpenImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +00003466 const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003467{
cristy3ed852e2009-09-05 21:47:34 +00003468 double
cristy47e00502009-12-17 19:19:57 +00003469 normalize;
cristy3ed852e2009-09-05 21:47:34 +00003470
3471 Image
3472 *sharp_image;
3473
cristy41cbe682011-07-15 19:12:37 +00003474 KernelInfo
3475 *kernel_info;
3476
cristybb503372010-05-27 20:51:26 +00003477 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003478 i;
3479
cristybb503372010-05-27 20:51:26 +00003480 size_t
cristy3ed852e2009-09-05 21:47:34 +00003481 width;
3482
cristy117ff172010-08-15 21:35:32 +00003483 ssize_t
3484 j,
3485 u,
3486 v;
3487
cristy3ed852e2009-09-05 21:47:34 +00003488 assert(image != (const Image *) NULL);
3489 assert(image->signature == MagickSignature);
3490 if (image->debug != MagickFalse)
3491 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3492 assert(exception != (ExceptionInfo *) NULL);
3493 assert(exception->signature == MagickSignature);
3494 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00003495 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00003496 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003497 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00003498 (void) ResetMagickMemory(kernel_info,0,sizeof(*kernel_info));
3499 kernel_info->width=width;
3500 kernel_info->height=width;
3501 kernel_info->signature=MagickSignature;
cristye8d93f62012-05-07 16:45:46 +00003502 kernel_info->values=(double *) AcquireAlignedMemory(
cristya96f2492011-12-14 18:25:41 +00003503 kernel_info->width,kernel_info->width*sizeof(*kernel_info->values));
cristye8d93f62012-05-07 16:45:46 +00003504 if (kernel_info->values == (double *) NULL)
cristy41cbe682011-07-15 19:12:37 +00003505 {
3506 kernel_info=DestroyKernelInfo(kernel_info);
3507 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3508 }
cristy3ed852e2009-09-05 21:47:34 +00003509 normalize=0.0;
cristy41cbe682011-07-15 19:12:37 +00003510 j=(ssize_t) kernel_info->width/2;
cristy47e00502009-12-17 19:19:57 +00003511 i=0;
3512 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003513 {
cristy47e00502009-12-17 19:19:57 +00003514 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00003515 {
cristy41cbe682011-07-15 19:12:37 +00003516 kernel_info->values[i]=(double) (-exp(-((double) u*u+v*v)/(2.0*
3517 MagickSigma*MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
3518 normalize+=kernel_info->values[i];
cristy3ed852e2009-09-05 21:47:34 +00003519 i++;
3520 }
3521 }
cristy41cbe682011-07-15 19:12:37 +00003522 kernel_info->values[i/2]=(double) ((-2.0)*normalize);
cristy5e6be1e2011-07-16 01:23:39 +00003523 sharp_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00003524 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00003525 return(sharp_image);
3526}
3527
3528/*
3529%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3530% %
3531% %
3532% %
3533% S p r e a d I m a g e %
3534% %
3535% %
3536% %
3537%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3538%
3539% SpreadImage() is a special effects method that randomly displaces each
3540% pixel in a block defined by the radius parameter.
3541%
3542% The format of the SpreadImage method is:
3543%
3544% Image *SpreadImage(const Image *image,const double radius,
cristy5c4e2582011-09-11 19:21:03 +00003545% const PixelInterpolateMethod method,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003546%
3547% A description of each parameter follows:
3548%
3549% o image: the image.
3550%
cristy5c4e2582011-09-11 19:21:03 +00003551% o radius: choose a random pixel in a neighborhood of this extent.
3552%
3553% o method: the pixel interpolation method.
cristy3ed852e2009-09-05 21:47:34 +00003554%
3555% o exception: return any errors or warnings in this structure.
3556%
3557*/
3558MagickExport Image *SpreadImage(const Image *image,const double radius,
cristy5c4e2582011-09-11 19:21:03 +00003559 const PixelInterpolateMethod method,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003560{
3561#define SpreadImageTag "Spread/Image"
3562
cristyfa112112010-01-04 17:48:07 +00003563 CacheView
cristy9f7e7cb2011-03-26 00:49:57 +00003564 *image_view,
3565 *spread_view;
cristyfa112112010-01-04 17:48:07 +00003566
cristy3ed852e2009-09-05 21:47:34 +00003567 Image
3568 *spread_image;
3569
cristy3ed852e2009-09-05 21:47:34 +00003570 MagickBooleanType
3571 status;
3572
cristybb503372010-05-27 20:51:26 +00003573 MagickOffsetType
3574 progress;
3575
cristy3ed852e2009-09-05 21:47:34 +00003576 RandomInfo
cristyfa112112010-01-04 17:48:07 +00003577 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00003578
cristybb503372010-05-27 20:51:26 +00003579 size_t
cristy3ed852e2009-09-05 21:47:34 +00003580 width;
3581
cristybb503372010-05-27 20:51:26 +00003582 ssize_t
3583 y;
3584
cristy57340e02012-05-05 00:53:23 +00003585 unsigned long
3586 key;
3587
cristy3ed852e2009-09-05 21:47:34 +00003588 /*
3589 Initialize spread image attributes.
3590 */
3591 assert(image != (Image *) NULL);
3592 assert(image->signature == MagickSignature);
3593 if (image->debug != MagickFalse)
3594 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3595 assert(exception != (ExceptionInfo *) NULL);
3596 assert(exception->signature == MagickSignature);
3597 spread_image=CloneImage(image,image->columns,image->rows,MagickTrue,
3598 exception);
3599 if (spread_image == (Image *) NULL)
3600 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003601 if (SetImageStorageClass(spread_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003602 {
cristy3ed852e2009-09-05 21:47:34 +00003603 spread_image=DestroyImage(spread_image);
3604 return((Image *) NULL);
3605 }
3606 /*
3607 Spread image.
3608 */
3609 status=MagickTrue;
3610 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00003611 width=GetOptimalKernelWidth1D(radius,0.5);
cristy3ed852e2009-09-05 21:47:34 +00003612 random_info=AcquireRandomInfoThreadSet();
cristy57340e02012-05-05 00:53:23 +00003613 key=GetRandomSecretKey(random_info[0]);
cristydb070952012-04-20 14:33:00 +00003614 image_view=AcquireVirtualCacheView(image,exception);
3615 spread_view=AcquireAuthenticCacheView(spread_image,exception);
cristyb557a152011-02-22 12:14:30 +00003616#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy57340e02012-05-05 00:53:23 +00003617 #pragma omp parallel for schedule(static,8) shared(progress,status) \
cristy4ee2b0c2012-05-15 00:30:35 +00003618 dynamic_number_threads(image,image->columns,image->rows,key == ~0UL)
cristy3ed852e2009-09-05 21:47:34 +00003619#endif
cristybe82ad52011-09-06 01:17:20 +00003620 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003621 {
cristy5c9e6f22010-09-17 17:31:01 +00003622 const int
3623 id = GetOpenMPThreadId();
cristy6ebe97c2010-07-03 01:17:28 +00003624
cristybe82ad52011-09-06 01:17:20 +00003625 register const Quantum
3626 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003627
cristy4c08aed2011-07-01 19:47:50 +00003628 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003629 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003630
cristy117ff172010-08-15 21:35:32 +00003631 register ssize_t
3632 x;
3633
cristy3ed852e2009-09-05 21:47:34 +00003634 if (status == MagickFalse)
3635 continue;
cristybe82ad52011-09-06 01:17:20 +00003636 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy9f7e7cb2011-03-26 00:49:57 +00003637 q=QueueCacheViewAuthenticPixels(spread_view,0,y,spread_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +00003638 exception);
cristybe82ad52011-09-06 01:17:20 +00003639 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003640 {
3641 status=MagickFalse;
3642 continue;
3643 }
cristybe82ad52011-09-06 01:17:20 +00003644 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003645 {
cristybe82ad52011-09-06 01:17:20 +00003646 PointInfo
3647 point;
3648
cristybe82ad52011-09-06 01:17:20 +00003649 point.x=GetPseudoRandomValue(random_info[id]);
3650 point.y=GetPseudoRandomValue(random_info[id]);
cristy5c4e2582011-09-11 19:21:03 +00003651 status=InterpolatePixelChannels(image,image_view,spread_image,method,
3652 (double) x+width*point.x-0.5,(double) y+width*point.y-0.5,q,exception);
cristyed231572011-07-14 02:18:59 +00003653 q+=GetPixelChannels(spread_image);
cristy3ed852e2009-09-05 21:47:34 +00003654 }
cristy9f7e7cb2011-03-26 00:49:57 +00003655 if (SyncCacheViewAuthenticPixels(spread_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003656 status=MagickFalse;
3657 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3658 {
3659 MagickBooleanType
3660 proceed;
3661
cristyb557a152011-02-22 12:14:30 +00003662#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf8561542012-01-24 00:26:46 +00003663 #pragma omp critical (MagickCore_SpreadImage)
cristy3ed852e2009-09-05 21:47:34 +00003664#endif
3665 proceed=SetImageProgress(image,SpreadImageTag,progress++,image->rows);
3666 if (proceed == MagickFalse)
3667 status=MagickFalse;
3668 }
3669 }
cristy9f7e7cb2011-03-26 00:49:57 +00003670 spread_view=DestroyCacheView(spread_view);
cristy3ed852e2009-09-05 21:47:34 +00003671 image_view=DestroyCacheView(image_view);
3672 random_info=DestroyRandomInfoThreadSet(random_info);
cristy3ed852e2009-09-05 21:47:34 +00003673 return(spread_image);
3674}
3675
3676/*
3677%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3678% %
3679% %
3680% %
3681% U n s h a r p M a s k I m a g e %
3682% %
3683% %
3684% %
3685%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3686%
3687% UnsharpMaskImage() sharpens one or more image channels. We convolve the
3688% image with a Gaussian operator of the given radius and standard deviation
3689% (sigma). For reasonable results, radius should be larger than sigma. Use a
3690% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
3691%
3692% The format of the UnsharpMaskImage method is:
3693%
3694% Image *UnsharpMaskImage(const Image *image,const double radius,
3695% const double sigma,const double amount,const double threshold,
3696% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003697%
3698% A description of each parameter follows:
3699%
3700% o image: the image.
3701%
cristy3ed852e2009-09-05 21:47:34 +00003702% o radius: the radius of the Gaussian, in pixels, not counting the center
3703% pixel.
3704%
3705% o sigma: the standard deviation of the Gaussian, in pixels.
3706%
3707% o amount: the percentage of the difference between the original and the
3708% blur image that is added back into the original.
3709%
3710% o threshold: the threshold in pixels needed to apply the diffence amount.
3711%
3712% o exception: return any errors or warnings in this structure.
3713%
3714*/
cristy31bb6272011-11-20 23:57:25 +00003715MagickExport Image *UnsharpMaskImage(const Image *image,const double radius,
3716 const double sigma,const double amount,const double threshold,
3717 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003718{
3719#define SharpenImageTag "Sharpen/Image"
3720
cristyc4c8d132010-01-07 01:58:38 +00003721 CacheView
3722 *image_view,
3723 *unsharp_view;
3724
cristy3ed852e2009-09-05 21:47:34 +00003725 Image
3726 *unsharp_image;
3727
cristy3ed852e2009-09-05 21:47:34 +00003728 MagickBooleanType
3729 status;
3730
cristybb503372010-05-27 20:51:26 +00003731 MagickOffsetType
3732 progress;
3733
cristy3ed852e2009-09-05 21:47:34 +00003734 MagickRealType
3735 quantum_threshold;
3736
cristybb503372010-05-27 20:51:26 +00003737 ssize_t
3738 y;
3739
cristy3ed852e2009-09-05 21:47:34 +00003740 assert(image != (const Image *) NULL);
3741 assert(image->signature == MagickSignature);
3742 if (image->debug != MagickFalse)
3743 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3744 assert(exception != (ExceptionInfo *) NULL);
cristyaa2c16c2012-03-25 22:21:35 +00003745 unsharp_image=BlurImage(image,radius,sigma,exception);
cristy3ed852e2009-09-05 21:47:34 +00003746 if (unsharp_image == (Image *) NULL)
3747 return((Image *) NULL);
3748 quantum_threshold=(MagickRealType) QuantumRange*threshold;
3749 /*
3750 Unsharp-mask image.
3751 */
3752 status=MagickTrue;
3753 progress=0;
cristydb070952012-04-20 14:33:00 +00003754 image_view=AcquireVirtualCacheView(image,exception);
3755 unsharp_view=AcquireAuthenticCacheView(unsharp_image,exception);
cristyb5d5f722009-11-04 03:03:49 +00003756#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00003757 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy4ee2b0c2012-05-15 00:30:35 +00003758 dynamic_number_threads(image,image->columns,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00003759#endif
cristybb503372010-05-27 20:51:26 +00003760 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003761 {
cristy4c08aed2011-07-01 19:47:50 +00003762 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003763 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003764
cristy4c08aed2011-07-01 19:47:50 +00003765 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003766 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003767
cristy117ff172010-08-15 21:35:32 +00003768 register ssize_t
3769 x;
3770
cristy3ed852e2009-09-05 21:47:34 +00003771 if (status == MagickFalse)
3772 continue;
3773 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy8b49f382012-01-17 03:11:03 +00003774 q=QueueCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +00003775 exception);
cristy4c08aed2011-07-01 19:47:50 +00003776 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003777 {
3778 status=MagickFalse;
3779 continue;
3780 }
cristybb503372010-05-27 20:51:26 +00003781 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003782 {
cristy7f3a0d12011-09-05 23:27:59 +00003783 register ssize_t
3784 i;
3785
cristy177e41c2012-04-15 15:08:25 +00003786 if (GetPixelMask(image,p) != 0)
3787 {
3788 p+=GetPixelChannels(image);
3789 q+=GetPixelChannels(unsharp_image);
3790 continue;
3791 }
cristy7f3a0d12011-09-05 23:27:59 +00003792 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3793 {
3794 MagickRealType
3795 pixel;
3796
3797 PixelChannel
3798 channel;
3799
3800 PixelTrait
3801 traits,
3802 unsharp_traits;
3803
cristye2a912b2011-12-05 20:02:07 +00003804 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +00003805 traits=GetPixelChannelMapTraits(image,channel);
cristy7f3a0d12011-09-05 23:27:59 +00003806 unsharp_traits=GetPixelChannelMapTraits(unsharp_image,channel);
3807 if ((traits == UndefinedPixelTrait) ||
3808 (unsharp_traits == UndefinedPixelTrait))
3809 continue;
cristy177e41c2012-04-15 15:08:25 +00003810 if ((unsharp_traits & CopyPixelTrait) != 0)
cristy7f3a0d12011-09-05 23:27:59 +00003811 {
cristy0beccfa2011-09-25 20:47:53 +00003812 SetPixelChannel(unsharp_image,channel,p[i],q);
cristy7f3a0d12011-09-05 23:27:59 +00003813 continue;
3814 }
cristy0beccfa2011-09-25 20:47:53 +00003815 pixel=p[i]-(MagickRealType) GetPixelChannel(unsharp_image,channel,q);
cristy7f3a0d12011-09-05 23:27:59 +00003816 if (fabs(2.0*pixel) < quantum_threshold)
3817 pixel=(MagickRealType) p[i];
3818 else
3819 pixel=(MagickRealType) p[i]+amount*pixel;
cristy0beccfa2011-09-25 20:47:53 +00003820 SetPixelChannel(unsharp_image,channel,ClampToQuantum(pixel),q);
cristy7f3a0d12011-09-05 23:27:59 +00003821 }
cristyed231572011-07-14 02:18:59 +00003822 p+=GetPixelChannels(image);
3823 q+=GetPixelChannels(unsharp_image);
cristy3ed852e2009-09-05 21:47:34 +00003824 }
3825 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
3826 status=MagickFalse;
3827 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3828 {
3829 MagickBooleanType
3830 proceed;
3831
cristyb5d5f722009-11-04 03:03:49 +00003832#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf8561542012-01-24 00:26:46 +00003833 #pragma omp critical (MagickCore_UnsharpMaskImage)
cristy3ed852e2009-09-05 21:47:34 +00003834#endif
3835 proceed=SetImageProgress(image,SharpenImageTag,progress++,image->rows);
3836 if (proceed == MagickFalse)
3837 status=MagickFalse;
3838 }
3839 }
3840 unsharp_image->type=image->type;
3841 unsharp_view=DestroyCacheView(unsharp_view);
3842 image_view=DestroyCacheView(image_view);
3843 if (status == MagickFalse)
3844 unsharp_image=DestroyImage(unsharp_image);
3845 return(unsharp_image);
3846}