blob: 9dccbcdf6a1a7e969db1bf291bd96417802fa9d9 [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);
cristy3d2287c2012-06-04 21:26:19 +0000219 if (fabs(sigma) < MagickEpsilon)
cristy3ed852e2009-09-05 21:47:34 +0000220 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 }
cristy3d2287c2012-06-04 21:26:19 +0000274 if (fabs(normalize) < MagickEpsilon)
275 normalize=MagickEpsilon;
cristy35f15302012-06-07 14:59:02 +0000276 normalize=MagickEpsilonReciprocal(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);
cristy4c11c2b2011-09-05 20:17:07 +0000349 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy3ed852e2009-09-05 21:47:34 +0000350 {
cristya19f1d72012-08-07 18:24:38 +0000351 double
cristy4c11c2b2011-09-05 20:17:07 +0000352 alpha,
353 gamma,
354 pixel;
355
356 PixelChannel
357 channel;
358
359 PixelTrait
360 blur_traits,
361 traits;
362
363 register const double
364 *restrict k;
365
366 register const Quantum
367 *restrict pixels;
368
369 register ssize_t
370 u;
371
372 ssize_t
373 v;
374
cristye2a912b2011-12-05 20:02:07 +0000375 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +0000376 traits=GetPixelChannelMapTraits(image,channel);
cristy4c11c2b2011-09-05 20:17:07 +0000377 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
378 if ((traits == UndefinedPixelTrait) ||
379 (blur_traits == UndefinedPixelTrait))
380 continue;
cristy1eced092012-08-10 23:10:56 +0000381 if (((blur_traits & CopyPixelTrait) != 0) ||
382 (GetPixelMask(image,p) != 0))
cristy4c11c2b2011-09-05 20:17:07 +0000383 {
cristy0beccfa2011-09-25 20:47:53 +0000384 SetPixelChannel(blur_image,channel,p[center+i],q);
cristy4c11c2b2011-09-05 20:17:07 +0000385 continue;
386 }
387 k=kernel[j];
388 pixels=p;
cristyaa2c16c2012-03-25 22:21:35 +0000389 pixel=0.0;
cristy4c11c2b2011-09-05 20:17:07 +0000390 gamma=0.0;
391 if ((blur_traits & BlendPixelTrait) == 0)
392 {
393 /*
394 No alpha blending.
395 */
396 for (v=0; v < (ssize_t) (width-j); v++)
397 {
398 for (u=0; u < (ssize_t) (width-j); u++)
399 {
400 pixel+=(*k)*pixels[i];
401 gamma+=(*k);
402 k++;
403 pixels+=GetPixelChannels(image);
404 }
405 }
cristyc58380a2012-06-03 15:12:30 +0000406 gamma=MagickEpsilonReciprocal(gamma);
cristy0beccfa2011-09-25 20:47:53 +0000407 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy4c11c2b2011-09-05 20:17:07 +0000408 continue;
409 }
410 /*
411 Alpha blending.
412 */
413 for (v=0; v < (ssize_t) (width-j); v++)
cristy3ed852e2009-09-05 21:47:34 +0000414 {
cristy4c11c2b2011-09-05 20:17:07 +0000415 for (u=0; u < (ssize_t) (width-j); u++)
416 {
cristya19f1d72012-08-07 18:24:38 +0000417 alpha=(double) (QuantumScale*GetPixelAlpha(image,pixels));
cristy4c11c2b2011-09-05 20:17:07 +0000418 pixel+=(*k)*alpha*pixels[i];
419 gamma+=(*k)*alpha;
420 k++;
421 pixels+=GetPixelChannels(image);
422 }
cristy3ed852e2009-09-05 21:47:34 +0000423 }
cristyc58380a2012-06-03 15:12:30 +0000424 gamma=MagickEpsilonReciprocal(gamma);
cristy0beccfa2011-09-25 20:47:53 +0000425 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy3ed852e2009-09-05 21:47:34 +0000426 }
cristyed231572011-07-14 02:18:59 +0000427 q+=GetPixelChannels(blur_image);
428 r+=GetPixelChannels(edge_image);
cristy3ed852e2009-09-05 21:47:34 +0000429 }
430 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
431 status=MagickFalse;
432 if (image->progress_monitor != (MagickProgressMonitor) NULL)
433 {
434 MagickBooleanType
435 proceed;
436
cristyb5d5f722009-11-04 03:03:49 +0000437#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf8561542012-01-24 00:26:46 +0000438 #pragma omp critical (MagickCore_AdaptiveBlurImage)
cristy3ed852e2009-09-05 21:47:34 +0000439#endif
440 proceed=SetImageProgress(image,AdaptiveBlurImageTag,progress++,
441 image->rows);
442 if (proceed == MagickFalse)
443 status=MagickFalse;
444 }
445 }
446 blur_image->type=image->type;
447 blur_view=DestroyCacheView(blur_view);
448 edge_view=DestroyCacheView(edge_view);
449 image_view=DestroyCacheView(image_view);
450 edge_image=DestroyImage(edge_image);
cristybb503372010-05-27 20:51:26 +0000451 for (i=0; i < (ssize_t) width; i+=2)
cristyf5d0f3e2012-01-17 03:20:33 +0000452 kernel[i]=(double *) RelinquishAlignedMemory(kernel[i]);
cristy219a63a2012-01-17 12:30:33 +0000453 kernel=(double **) RelinquishAlignedMemory(kernel);
cristy3ed852e2009-09-05 21:47:34 +0000454 if (status == MagickFalse)
455 blur_image=DestroyImage(blur_image);
456 return(blur_image);
457}
458
459/*
460%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
461% %
462% %
463% %
464% A d a p t i v e S h a r p e n I m a g e %
465% %
466% %
467% %
468%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
469%
470% AdaptiveSharpenImage() adaptively sharpens the image by sharpening more
471% intensely near image edges and less intensely far from edges. We sharpen the
472% image with a Gaussian operator of the given radius and standard deviation
473% (sigma). For reasonable results, radius should be larger than sigma. Use a
474% radius of 0 and AdaptiveSharpenImage() selects a suitable radius for you.
475%
476% The format of the AdaptiveSharpenImage method is:
477%
478% Image *AdaptiveSharpenImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +0000479% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000480%
481% A description of each parameter follows:
482%
483% o image: the image.
484%
cristy3ed852e2009-09-05 21:47:34 +0000485% o radius: the radius of the Gaussian, in pixels, not counting the center
486% pixel.
487%
488% o sigma: the standard deviation of the Laplacian, in pixels.
489%
490% o exception: return any errors or warnings in this structure.
491%
492*/
cristy3ed852e2009-09-05 21:47:34 +0000493MagickExport Image *AdaptiveSharpenImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +0000494 const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000495{
cristy3ed852e2009-09-05 21:47:34 +0000496#define AdaptiveSharpenImageTag "Convolve/Image"
cristy9b528342012-06-02 00:59:20 +0000497#define MagickSigma (fabs(sigma) < MagickEpsilon ? MagickEpsilon : sigma)
cristy3ed852e2009-09-05 21:47:34 +0000498
cristyc4c8d132010-01-07 01:58:38 +0000499 CacheView
500 *sharp_view,
501 *edge_view,
502 *image_view;
503
cristy3ed852e2009-09-05 21:47:34 +0000504 double
cristy47e00502009-12-17 19:19:57 +0000505 **kernel,
506 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000507
508 Image
509 *sharp_image,
510 *edge_image,
511 *gaussian_image;
512
cristy3ed852e2009-09-05 21:47:34 +0000513 MagickBooleanType
514 status;
515
cristybb503372010-05-27 20:51:26 +0000516 MagickOffsetType
517 progress;
518
cristybb503372010-05-27 20:51:26 +0000519 register ssize_t
cristy47e00502009-12-17 19:19:57 +0000520 i;
cristy3ed852e2009-09-05 21:47:34 +0000521
cristybb503372010-05-27 20:51:26 +0000522 size_t
cristy3ed852e2009-09-05 21:47:34 +0000523 width;
524
cristybb503372010-05-27 20:51:26 +0000525 ssize_t
526 j,
527 k,
528 u,
529 v,
530 y;
531
cristy3ed852e2009-09-05 21:47:34 +0000532 assert(image != (const Image *) NULL);
533 assert(image->signature == MagickSignature);
534 if (image->debug != MagickFalse)
535 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
536 assert(exception != (ExceptionInfo *) NULL);
537 assert(exception->signature == MagickSignature);
538 sharp_image=CloneImage(image,0,0,MagickTrue,exception);
539 if (sharp_image == (Image *) NULL)
540 return((Image *) NULL);
cristye6180ff2012-06-04 22:19:16 +0000541 if (fabs(sigma) < MagickEpsilon)
cristy3ed852e2009-09-05 21:47:34 +0000542 return(sharp_image);
cristy574cc262011-08-05 01:23:58 +0000543 if (SetImageStorageClass(sharp_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000544 {
cristy3ed852e2009-09-05 21:47:34 +0000545 sharp_image=DestroyImage(sharp_image);
546 return((Image *) NULL);
547 }
548 /*
549 Edge detect the image brighness channel, level, sharp, and level again.
550 */
cristy8ae632d2011-09-05 17:29:53 +0000551 edge_image=EdgeImage(image,radius,sigma,exception);
cristy3ed852e2009-09-05 21:47:34 +0000552 if (edge_image == (Image *) NULL)
553 {
554 sharp_image=DestroyImage(sharp_image);
555 return((Image *) NULL);
556 }
cristy051718b2011-08-28 22:49:25 +0000557 (void) AdaptiveLevelImage(edge_image,"20%,95%",exception);
cristyd89705a2012-01-20 02:52:24 +0000558 gaussian_image=GaussianBlurImage(edge_image,radius,sigma,exception);
cristy3ed852e2009-09-05 21:47:34 +0000559 if (gaussian_image != (Image *) NULL)
560 {
561 edge_image=DestroyImage(edge_image);
562 edge_image=gaussian_image;
563 }
cristy051718b2011-08-28 22:49:25 +0000564 (void) AdaptiveLevelImage(edge_image,"10%,95%",exception);
cristy3ed852e2009-09-05 21:47:34 +0000565 /*
566 Create a set of kernels from maximum (radius,sigma) to minimum.
567 */
568 width=GetOptimalKernelWidth2D(radius,sigma);
cristy219a63a2012-01-17 12:30:33 +0000569 kernel=(double **) AcquireAlignedMemory((size_t) width,sizeof(*kernel));
cristy3ed852e2009-09-05 21:47:34 +0000570 if (kernel == (double **) NULL)
571 {
572 edge_image=DestroyImage(edge_image);
573 sharp_image=DestroyImage(sharp_image);
574 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
575 }
576 (void) ResetMagickMemory(kernel,0,(size_t) width*sizeof(*kernel));
cristybb503372010-05-27 20:51:26 +0000577 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000578 {
cristy59d1f6d2012-01-17 03:17:40 +0000579 kernel[i]=(double *) AcquireAlignedMemory((size_t) (width-i),(width-i)*
cristy3ed852e2009-09-05 21:47:34 +0000580 sizeof(**kernel));
581 if (kernel[i] == (double *) NULL)
582 break;
cristy47e00502009-12-17 19:19:57 +0000583 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000584 j=(ssize_t) (width-i)/2;
cristy47e00502009-12-17 19:19:57 +0000585 k=0;
586 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000587 {
cristy47e00502009-12-17 19:19:57 +0000588 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000589 {
cristy4205a3c2010-09-12 20:19:59 +0000590 kernel[i][k]=(double) (-exp(-((double) u*u+v*v)/(2.0*MagickSigma*
591 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +0000592 normalize+=kernel[i][k];
593 k++;
cristy3ed852e2009-09-05 21:47:34 +0000594 }
595 }
cristy3d2287c2012-06-04 21:26:19 +0000596 if (fabs(normalize) < MagickEpsilon)
597 normalize=MagickEpsilon;
cristy35f15302012-06-07 14:59:02 +0000598 normalize=MagickEpsilonReciprocal(normalize);
cristy47e00502009-12-17 19:19:57 +0000599 for (k=0; k < (j*j); k++)
600 kernel[i][k]=normalize*kernel[i][k];
cristy3ed852e2009-09-05 21:47:34 +0000601 }
cristybb503372010-05-27 20:51:26 +0000602 if (i < (ssize_t) width)
cristy3ed852e2009-09-05 21:47:34 +0000603 {
604 for (i-=2; i >= 0; i-=2)
cristy59d1f6d2012-01-17 03:17:40 +0000605 kernel[i]=(double *) RelinquishAlignedMemory(kernel[i]);
cristy219a63a2012-01-17 12:30:33 +0000606 kernel=(double **) RelinquishAlignedMemory(kernel);
cristy3ed852e2009-09-05 21:47:34 +0000607 edge_image=DestroyImage(edge_image);
608 sharp_image=DestroyImage(sharp_image);
609 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
610 }
611 /*
612 Adaptively sharpen image.
613 */
614 status=MagickTrue;
615 progress=0;
cristydb070952012-04-20 14:33:00 +0000616 image_view=AcquireVirtualCacheView(image,exception);
617 edge_view=AcquireVirtualCacheView(edge_image,exception);
618 sharp_view=AcquireAuthenticCacheView(sharp_image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000619#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000620 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy4ee2b0c2012-05-15 00:30:35 +0000621 dynamic_number_threads(image,image->columns,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +0000622#endif
cristybb503372010-05-27 20:51:26 +0000623 for (y=0; y < (ssize_t) sharp_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000624 {
cristy4c08aed2011-07-01 19:47:50 +0000625 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000626 *restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000627
cristy4c08aed2011-07-01 19:47:50 +0000628 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000629 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000630
cristy117ff172010-08-15 21:35:32 +0000631 register ssize_t
632 x;
633
cristy3ed852e2009-09-05 21:47:34 +0000634 if (status == MagickFalse)
635 continue;
636 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
637 q=QueueCacheViewAuthenticPixels(sharp_view,0,y,sharp_image->columns,1,
638 exception);
cristy4c08aed2011-07-01 19:47:50 +0000639 if ((r == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000640 {
641 status=MagickFalse;
642 continue;
643 }
cristybb503372010-05-27 20:51:26 +0000644 for (x=0; x < (ssize_t) sharp_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000645 {
cristy4c11c2b2011-09-05 20:17:07 +0000646 register const Quantum
647 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000648
cristybb503372010-05-27 20:51:26 +0000649 register ssize_t
cristy4c11c2b2011-09-05 20:17:07 +0000650 i;
cristy3ed852e2009-09-05 21:47:34 +0000651
cristy4c11c2b2011-09-05 20:17:07 +0000652 ssize_t
653 center,
654 j;
655
656 j=(ssize_t) ceil((double) width*QuantumScale*
cristy4c08aed2011-07-01 19:47:50 +0000657 GetPixelIntensity(edge_image,r)-0.5);
cristy4c11c2b2011-09-05 20:17:07 +0000658 if (j < 0)
659 j=0;
cristy3ed852e2009-09-05 21:47:34 +0000660 else
cristy4c11c2b2011-09-05 20:17:07 +0000661 if (j > (ssize_t) width)
662 j=(ssize_t) width;
663 if ((j & 0x01) != 0)
664 j--;
665 p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) (width-j)/2L),y-
666 (ssize_t) ((width-j)/2L),width-j,width-j,exception);
cristy4c08aed2011-07-01 19:47:50 +0000667 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000668 break;
cristyd09f8802012-02-04 16:44:10 +0000669 center=(ssize_t) GetPixelChannels(image)*(width-j)*((width-j)/2L)+
670 GetPixelChannels(image)*((width-j)/2);
cristyc94ba6f2012-01-29 23:19:58 +0000671 for (i=0; i < (ssize_t) GetPixelChannels(sharp_image); i++)
cristy3ed852e2009-09-05 21:47:34 +0000672 {
cristya19f1d72012-08-07 18:24:38 +0000673 double
cristy4c11c2b2011-09-05 20:17:07 +0000674 alpha,
675 gamma,
676 pixel;
677
678 PixelChannel
679 channel;
680
681 PixelTrait
682 sharp_traits,
683 traits;
684
685 register const double
686 *restrict k;
687
688 register const Quantum
689 *restrict pixels;
690
691 register ssize_t
692 u;
693
694 ssize_t
695 v;
696
cristye2a912b2011-12-05 20:02:07 +0000697 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +0000698 traits=GetPixelChannelMapTraits(image,channel);
cristy4c11c2b2011-09-05 20:17:07 +0000699 sharp_traits=GetPixelChannelMapTraits(sharp_image,channel);
700 if ((traits == UndefinedPixelTrait) ||
701 (sharp_traits == UndefinedPixelTrait))
702 continue;
cristy1eced092012-08-10 23:10:56 +0000703 if (((sharp_traits & CopyPixelTrait) != 0) ||
704 (GetPixelMask(image,p) != 0))
cristy4c11c2b2011-09-05 20:17:07 +0000705 {
cristy0beccfa2011-09-25 20:47:53 +0000706 SetPixelChannel(sharp_image,channel,p[center+i],q);
cristy4c11c2b2011-09-05 20:17:07 +0000707 continue;
708 }
709 k=kernel[j];
710 pixels=p;
cristyaa2c16c2012-03-25 22:21:35 +0000711 pixel=0.0;
cristy4c11c2b2011-09-05 20:17:07 +0000712 gamma=0.0;
713 if ((sharp_traits & BlendPixelTrait) == 0)
714 {
715 /*
716 No alpha blending.
717 */
718 for (v=0; v < (ssize_t) (width-j); v++)
719 {
720 for (u=0; u < (ssize_t) (width-j); u++)
721 {
722 pixel+=(*k)*pixels[i];
723 gamma+=(*k);
724 k++;
725 pixels+=GetPixelChannels(image);
726 }
727 }
cristyc58380a2012-06-03 15:12:30 +0000728 gamma=MagickEpsilonReciprocal(gamma);
cristy0beccfa2011-09-25 20:47:53 +0000729 SetPixelChannel(sharp_image,channel,ClampToQuantum(gamma*pixel),q);
cristy4c11c2b2011-09-05 20:17:07 +0000730 continue;
731 }
732 /*
733 Alpha blending.
734 */
735 for (v=0; v < (ssize_t) (width-j); v++)
cristy3ed852e2009-09-05 21:47:34 +0000736 {
cristy4c11c2b2011-09-05 20:17:07 +0000737 for (u=0; u < (ssize_t) (width-j); u++)
738 {
cristya19f1d72012-08-07 18:24:38 +0000739 alpha=(double) (QuantumScale*GetPixelAlpha(image,pixels));
cristy4c11c2b2011-09-05 20:17:07 +0000740 pixel+=(*k)*alpha*pixels[i];
741 gamma+=(*k)*alpha;
742 k++;
743 pixels+=GetPixelChannels(image);
744 }
cristy3ed852e2009-09-05 21:47:34 +0000745 }
cristyc58380a2012-06-03 15:12:30 +0000746 gamma=MagickEpsilonReciprocal(gamma);
cristy0beccfa2011-09-25 20:47:53 +0000747 SetPixelChannel(sharp_image,channel,ClampToQuantum(gamma*pixel),q);
cristy3ed852e2009-09-05 21:47:34 +0000748 }
cristyed231572011-07-14 02:18:59 +0000749 q+=GetPixelChannels(sharp_image);
750 r+=GetPixelChannels(edge_image);
cristy3ed852e2009-09-05 21:47:34 +0000751 }
752 if (SyncCacheViewAuthenticPixels(sharp_view,exception) == MagickFalse)
753 status=MagickFalse;
754 if (image->progress_monitor != (MagickProgressMonitor) NULL)
755 {
756 MagickBooleanType
757 proceed;
758
cristyb5d5f722009-11-04 03:03:49 +0000759#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf8561542012-01-24 00:26:46 +0000760 #pragma omp critical (MagickCore_AdaptiveSharpenImage)
cristy3ed852e2009-09-05 21:47:34 +0000761#endif
762 proceed=SetImageProgress(image,AdaptiveSharpenImageTag,progress++,
763 image->rows);
764 if (proceed == MagickFalse)
765 status=MagickFalse;
766 }
767 }
768 sharp_image->type=image->type;
769 sharp_view=DestroyCacheView(sharp_view);
770 edge_view=DestroyCacheView(edge_view);
771 image_view=DestroyCacheView(image_view);
772 edge_image=DestroyImage(edge_image);
cristybb503372010-05-27 20:51:26 +0000773 for (i=0; i < (ssize_t) width; i+=2)
cristy59d1f6d2012-01-17 03:17:40 +0000774 kernel[i]=(double *) RelinquishAlignedMemory(kernel[i]);
cristy219a63a2012-01-17 12:30:33 +0000775 kernel=(double **) RelinquishAlignedMemory(kernel);
cristy3ed852e2009-09-05 21:47:34 +0000776 if (status == MagickFalse)
777 sharp_image=DestroyImage(sharp_image);
778 return(sharp_image);
779}
780
781/*
782%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
783% %
784% %
785% %
786% B l u r I m a g e %
787% %
788% %
789% %
790%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
791%
792% BlurImage() blurs an image. We convolve the image with a Gaussian operator
793% of the given radius and standard deviation (sigma). For reasonable results,
794% the radius should be larger than sigma. Use a radius of 0 and BlurImage()
795% selects a suitable radius for you.
796%
797% BlurImage() differs from GaussianBlurImage() in that it uses a separable
798% kernel which is faster but mathematically equivalent to the non-separable
799% kernel.
800%
801% The format of the BlurImage method is:
802%
803% Image *BlurImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +0000804% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000805%
806% A description of each parameter follows:
807%
808% o image: the image.
809%
cristy3ed852e2009-09-05 21:47:34 +0000810% o radius: the radius of the Gaussian, in pixels, not counting the center
811% pixel.
812%
813% o sigma: the standard deviation of the Gaussian, in pixels.
814%
815% o exception: return any errors or warnings in this structure.
816%
817*/
818
cristybb503372010-05-27 20:51:26 +0000819static double *GetBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +0000820{
cristy3ed852e2009-09-05 21:47:34 +0000821 double
cristy47e00502009-12-17 19:19:57 +0000822 *kernel,
823 normalize;
cristy3ed852e2009-09-05 21:47:34 +0000824
cristy117ff172010-08-15 21:35:32 +0000825 register ssize_t
826 i;
827
cristybb503372010-05-27 20:51:26 +0000828 ssize_t
cristy47e00502009-12-17 19:19:57 +0000829 j,
830 k;
cristy3ed852e2009-09-05 21:47:34 +0000831
cristy3ed852e2009-09-05 21:47:34 +0000832 /*
833 Generate a 1-D convolution kernel.
834 */
835 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
cristy8b49f382012-01-17 03:11:03 +0000836 kernel=(double *) AcquireAlignedMemory((size_t) width,sizeof(*kernel));
cristy3ed852e2009-09-05 21:47:34 +0000837 if (kernel == (double *) NULL)
838 return(0);
cristy3ed852e2009-09-05 21:47:34 +0000839 normalize=0.0;
cristybb503372010-05-27 20:51:26 +0000840 j=(ssize_t) width/2;
cristy47e00502009-12-17 19:19:57 +0000841 i=0;
842 for (k=(-j); k <= j; k++)
843 {
cristy4205a3c2010-09-12 20:19:59 +0000844 kernel[i]=(double) (exp(-((double) k*k)/(2.0*MagickSigma*MagickSigma))/
845 (MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +0000846 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +0000847 i++;
848 }
cristybb503372010-05-27 20:51:26 +0000849 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000850 kernel[i]/=normalize;
851 return(kernel);
852}
853
cristyf4ad9df2011-07-08 16:49:03 +0000854MagickExport Image *BlurImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +0000855 const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000856{
857#define BlurImageTag "Blur/Image"
858
cristyc4c8d132010-01-07 01:58:38 +0000859 CacheView
860 *blur_view,
861 *image_view;
862
cristy3ed852e2009-09-05 21:47:34 +0000863 double
864 *kernel;
865
866 Image
867 *blur_image;
868
cristy3ed852e2009-09-05 21:47:34 +0000869 MagickBooleanType
870 status;
871
cristybb503372010-05-27 20:51:26 +0000872 MagickOffsetType
873 progress;
874
cristybb503372010-05-27 20:51:26 +0000875 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000876 i;
877
cristybb503372010-05-27 20:51:26 +0000878 size_t
cristy3ed852e2009-09-05 21:47:34 +0000879 width;
880
cristybb503372010-05-27 20:51:26 +0000881 ssize_t
cristyb41a1172011-09-06 00:55:14 +0000882 center,
cristybb503372010-05-27 20:51:26 +0000883 x,
884 y;
885
cristy3ed852e2009-09-05 21:47:34 +0000886 /*
887 Initialize blur image attributes.
888 */
889 assert(image != (Image *) NULL);
890 assert(image->signature == MagickSignature);
891 if (image->debug != MagickFalse)
892 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
893 assert(exception != (ExceptionInfo *) NULL);
894 assert(exception->signature == MagickSignature);
cristyd25c77e2011-09-06 00:10:24 +0000895 blur_image=CloneImage(image,0,0,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +0000896 if (blur_image == (Image *) NULL)
897 return((Image *) NULL);
cristy3d2287c2012-06-04 21:26:19 +0000898 if (fabs(sigma) < MagickEpsilon)
cristy3ed852e2009-09-05 21:47:34 +0000899 return(blur_image);
cristy574cc262011-08-05 01:23:58 +0000900 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000901 {
cristy3ed852e2009-09-05 21:47:34 +0000902 blur_image=DestroyImage(blur_image);
903 return((Image *) NULL);
904 }
905 width=GetOptimalKernelWidth1D(radius,sigma);
906 kernel=GetBlurKernel(width,sigma);
907 if (kernel == (double *) NULL)
908 {
909 blur_image=DestroyImage(blur_image);
910 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
911 }
912 if (image->debug != MagickFalse)
913 {
914 char
915 format[MaxTextExtent],
916 *message;
917
918 register const double
919 *k;
920
921 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristyef1f9072012-01-20 14:43:06 +0000922 " blur image with kernel width %.20g:",(double) width);
cristy3ed852e2009-09-05 21:47:34 +0000923 message=AcquireString("");
924 k=kernel;
cristybb503372010-05-27 20:51:26 +0000925 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +0000926 {
927 *message='\0';
cristyb51dff52011-05-19 16:55:47 +0000928 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) i);
cristy3ed852e2009-09-05 21:47:34 +0000929 (void) ConcatenateString(&message,format);
cristyb51dff52011-05-19 16:55:47 +0000930 (void) FormatLocaleString(format,MaxTextExtent,"%g ",*k++);
cristy3ed852e2009-09-05 21:47:34 +0000931 (void) ConcatenateString(&message,format);
932 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
933 }
934 message=DestroyString(message);
935 }
936 /*
937 Blur rows.
938 */
939 status=MagickTrue;
940 progress=0;
cristyb41a1172011-09-06 00:55:14 +0000941 center=(ssize_t) GetPixelChannels(image)*(width/2L);
cristydb070952012-04-20 14:33:00 +0000942 image_view=AcquireVirtualCacheView(image,exception);
943 blur_view=AcquireAuthenticCacheView(blur_image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000944#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000945 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy4ee2b0c2012-05-15 00:30:35 +0000946 dynamic_number_threads(image,image->columns,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +0000947#endif
cristyb41a1172011-09-06 00:55:14 +0000948 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000949 {
cristy4c08aed2011-07-01 19:47:50 +0000950 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +0000951 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000952
cristy4c08aed2011-07-01 19:47:50 +0000953 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000954 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000955
cristy117ff172010-08-15 21:35:32 +0000956 register ssize_t
957 x;
958
cristy3ed852e2009-09-05 21:47:34 +0000959 if (status == MagickFalse)
960 continue;
cristy117ff172010-08-15 21:35:32 +0000961 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y,
962 image->columns+width,1,exception);
cristy8b49f382012-01-17 03:11:03 +0000963 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +0000964 exception);
cristy4c08aed2011-07-01 19:47:50 +0000965 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000966 {
967 status=MagickFalse;
968 continue;
969 }
cristyb41a1172011-09-06 00:55:14 +0000970 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000971 {
cristybb503372010-05-27 20:51:26 +0000972 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000973 i;
974
cristyb41a1172011-09-06 00:55:14 +0000975 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
976 {
cristya19f1d72012-08-07 18:24:38 +0000977 double
cristyb41a1172011-09-06 00:55:14 +0000978 alpha,
979 gamma,
980 pixel;
cristyd25c77e2011-09-06 00:10:24 +0000981
cristyb41a1172011-09-06 00:55:14 +0000982 PixelChannel
983 channel;
984
985 PixelTrait
986 blur_traits,
987 traits;
988
989 register const double
990 *restrict k;
991
992 register const Quantum
993 *restrict pixels;
994
995 register ssize_t
996 u;
997
cristye2a912b2011-12-05 20:02:07 +0000998 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +0000999 traits=GetPixelChannelMapTraits(image,channel);
cristyb41a1172011-09-06 00:55:14 +00001000 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
1001 if ((traits == UndefinedPixelTrait) ||
1002 (blur_traits == UndefinedPixelTrait))
1003 continue;
cristy1eced092012-08-10 23:10:56 +00001004 if (((blur_traits & CopyPixelTrait) != 0) ||
1005 (GetPixelMask(image,p) != 0))
cristyd25c77e2011-09-06 00:10:24 +00001006 {
cristy0beccfa2011-09-25 20:47:53 +00001007 SetPixelChannel(blur_image,channel,p[center+i],q);
cristyb41a1172011-09-06 00:55:14 +00001008 continue;
cristyd25c77e2011-09-06 00:10:24 +00001009 }
cristyb41a1172011-09-06 00:55:14 +00001010 k=kernel;
1011 pixels=p;
1012 pixel=0.0;
1013 if ((blur_traits & BlendPixelTrait) == 0)
1014 {
1015 /*
1016 No alpha blending.
1017 */
1018 for (u=0; u < (ssize_t) width; u++)
cristyd25c77e2011-09-06 00:10:24 +00001019 {
cristyb41a1172011-09-06 00:55:14 +00001020 pixel+=(*k)*pixels[i];
1021 k++;
1022 pixels+=GetPixelChannels(image);
cristyd25c77e2011-09-06 00:10:24 +00001023 }
cristy0beccfa2011-09-25 20:47:53 +00001024 SetPixelChannel(blur_image,channel,ClampToQuantum(pixel),q);
cristyb41a1172011-09-06 00:55:14 +00001025 continue;
1026 }
1027 /*
1028 Alpha blending.
1029 */
1030 gamma=0.0;
1031 for (u=0; u < (ssize_t) width; u++)
1032 {
cristya19f1d72012-08-07 18:24:38 +00001033 alpha=(double) (QuantumScale*GetPixelAlpha(image,pixels));
cristyb41a1172011-09-06 00:55:14 +00001034 pixel+=(*k)*alpha*pixels[i];
1035 gamma+=(*k)*alpha;
1036 k++;
1037 pixels+=GetPixelChannels(image);
cristyd25c77e2011-09-06 00:10:24 +00001038 }
cristyc58380a2012-06-03 15:12:30 +00001039 gamma=MagickEpsilonReciprocal(gamma);
cristy0beccfa2011-09-25 20:47:53 +00001040 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristyb41a1172011-09-06 00:55:14 +00001041 }
cristyed231572011-07-14 02:18:59 +00001042 p+=GetPixelChannels(image);
1043 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001044 }
1045 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1046 status=MagickFalse;
1047 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1048 {
1049 MagickBooleanType
1050 proceed;
1051
cristyb5d5f722009-11-04 03:03:49 +00001052#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyef1f9072012-01-20 14:43:06 +00001053 #pragma omp critical (MagickCore_BlurImage)
cristy3ed852e2009-09-05 21:47:34 +00001054#endif
1055 proceed=SetImageProgress(image,BlurImageTag,progress++,blur_image->rows+
1056 blur_image->columns);
1057 if (proceed == MagickFalse)
1058 status=MagickFalse;
1059 }
1060 }
1061 blur_view=DestroyCacheView(blur_view);
1062 image_view=DestroyCacheView(image_view);
1063 /*
1064 Blur columns.
1065 */
cristyef1f9072012-01-20 14:43:06 +00001066 center=(ssize_t) GetPixelChannels(blur_image)*(width/2L);
cristydb070952012-04-20 14:33:00 +00001067 image_view=AcquireVirtualCacheView(blur_image,exception);
1068 blur_view=AcquireAuthenticCacheView(blur_image,exception);
cristyb5d5f722009-11-04 03:03:49 +00001069#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001070 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy4ee2b0c2012-05-15 00:30:35 +00001071 dynamic_number_threads(image,image->columns,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00001072#endif
cristyb6dc4b72011-12-10 23:29:53 +00001073 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001074 {
cristy4c08aed2011-07-01 19:47:50 +00001075 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001076 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001077
cristy4c08aed2011-07-01 19:47:50 +00001078 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001079 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001080
cristy117ff172010-08-15 21:35:32 +00001081 register ssize_t
1082 y;
1083
cristy3ed852e2009-09-05 21:47:34 +00001084 if (status == MagickFalse)
1085 continue;
cristy117ff172010-08-15 21:35:32 +00001086 p=GetCacheViewVirtualPixels(image_view,x,-((ssize_t) width/2L),1,
cristyb6dc4b72011-12-10 23:29:53 +00001087 blur_image->rows+width,exception);
cristy3ed852e2009-09-05 21:47:34 +00001088 q=GetCacheViewAuthenticPixels(blur_view,x,0,1,blur_image->rows,exception);
cristy4c08aed2011-07-01 19:47:50 +00001089 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001090 {
1091 status=MagickFalse;
1092 continue;
1093 }
cristyb6dc4b72011-12-10 23:29:53 +00001094 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001095 {
cristybb503372010-05-27 20:51:26 +00001096 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001097 i;
1098
cristyb6dc4b72011-12-10 23:29:53 +00001099 for (i=0; i < (ssize_t) GetPixelChannels(blur_image); i++)
cristyb41a1172011-09-06 00:55:14 +00001100 {
cristya19f1d72012-08-07 18:24:38 +00001101 double
cristyb41a1172011-09-06 00:55:14 +00001102 alpha,
1103 gamma,
1104 pixel;
cristyd25c77e2011-09-06 00:10:24 +00001105
cristyb41a1172011-09-06 00:55:14 +00001106 PixelChannel
1107 channel;
1108
1109 PixelTrait
1110 blur_traits,
1111 traits;
1112
1113 register const double
1114 *restrict k;
1115
1116 register const Quantum
1117 *restrict pixels;
1118
1119 register ssize_t
1120 u;
1121
cristye2a912b2011-12-05 20:02:07 +00001122 channel=GetPixelChannelMapChannel(blur_image,i);
cristyabace412011-12-11 15:56:53 +00001123 traits=GetPixelChannelMapTraits(blur_image,channel);
cristyb41a1172011-09-06 00:55:14 +00001124 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
1125 if ((traits == UndefinedPixelTrait) ||
1126 (blur_traits == UndefinedPixelTrait))
1127 continue;
cristy1eced092012-08-10 23:10:56 +00001128 if (((blur_traits & CopyPixelTrait) != 0) ||
1129 (GetPixelMask(image,p) != 0))
cristyd25c77e2011-09-06 00:10:24 +00001130 {
cristy0beccfa2011-09-25 20:47:53 +00001131 SetPixelChannel(blur_image,channel,p[center+i],q);
cristyb41a1172011-09-06 00:55:14 +00001132 continue;
cristyd25c77e2011-09-06 00:10:24 +00001133 }
cristyb41a1172011-09-06 00:55:14 +00001134 k=kernel;
1135 pixels=p;
1136 pixel=0.0;
1137 if ((blur_traits & BlendPixelTrait) == 0)
1138 {
1139 /*
1140 No alpha blending.
1141 */
1142 for (u=0; u < (ssize_t) width; u++)
cristyd25c77e2011-09-06 00:10:24 +00001143 {
cristyb41a1172011-09-06 00:55:14 +00001144 pixel+=(*k)*pixels[i];
1145 k++;
1146 pixels+=GetPixelChannels(blur_image);
cristyd25c77e2011-09-06 00:10:24 +00001147 }
cristy0beccfa2011-09-25 20:47:53 +00001148 SetPixelChannel(blur_image,channel,ClampToQuantum(pixel),q);
cristyb41a1172011-09-06 00:55:14 +00001149 continue;
1150 }
1151 /*
1152 Alpha blending.
1153 */
1154 gamma=0.0;
1155 for (u=0; u < (ssize_t) width; u++)
1156 {
cristya19f1d72012-08-07 18:24:38 +00001157 alpha=(double) (QuantumScale*GetPixelAlpha(blur_image,
cristyb6dc4b72011-12-10 23:29:53 +00001158 pixels));
cristyb41a1172011-09-06 00:55:14 +00001159 pixel+=(*k)*alpha*pixels[i];
1160 gamma+=(*k)*alpha;
1161 k++;
1162 pixels+=GetPixelChannels(blur_image);
cristyd25c77e2011-09-06 00:10:24 +00001163 }
cristyc58380a2012-06-03 15:12:30 +00001164 gamma=MagickEpsilonReciprocal(gamma);
cristy0beccfa2011-09-25 20:47:53 +00001165 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristyb41a1172011-09-06 00:55:14 +00001166 }
cristyd25c77e2011-09-06 00:10:24 +00001167 p+=GetPixelChannels(blur_image);
cristyed231572011-07-14 02:18:59 +00001168 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001169 }
1170 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1171 status=MagickFalse;
cristy4c08aed2011-07-01 19:47:50 +00001172 if (blur_image->progress_monitor != (MagickProgressMonitor) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001173 {
1174 MagickBooleanType
1175 proceed;
1176
cristyb5d5f722009-11-04 03:03:49 +00001177#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyef1f9072012-01-20 14:43:06 +00001178 #pragma omp critical (MagickCore_BlurImage)
cristy3ed852e2009-09-05 21:47:34 +00001179#endif
cristy4c08aed2011-07-01 19:47:50 +00001180 proceed=SetImageProgress(blur_image,BlurImageTag,progress++,
1181 blur_image->rows+blur_image->columns);
cristy3ed852e2009-09-05 21:47:34 +00001182 if (proceed == MagickFalse)
1183 status=MagickFalse;
1184 }
1185 }
1186 blur_view=DestroyCacheView(blur_view);
1187 image_view=DestroyCacheView(image_view);
cristy8b49f382012-01-17 03:11:03 +00001188 kernel=(double *) RelinquishAlignedMemory(kernel);
1189 blur_image->type=image->type;
cristy3ed852e2009-09-05 21:47:34 +00001190 if (status == MagickFalse)
1191 blur_image=DestroyImage(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00001192 return(blur_image);
1193}
1194
1195/*
1196%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1197% %
1198% %
1199% %
cristyfccdab92009-11-30 16:43:57 +00001200% C o n v o l v e I m a g e %
1201% %
1202% %
1203% %
1204%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1205%
1206% ConvolveImage() applies a custom convolution kernel to the image.
1207%
1208% The format of the ConvolveImage method is:
1209%
cristy5e6be1e2011-07-16 01:23:39 +00001210% Image *ConvolveImage(const Image *image,const KernelInfo *kernel,
1211% ExceptionInfo *exception)
1212%
cristyfccdab92009-11-30 16:43:57 +00001213% A description of each parameter follows:
1214%
1215% o image: the image.
1216%
cristy5e6be1e2011-07-16 01:23:39 +00001217% o kernel: the filtering kernel.
cristyfccdab92009-11-30 16:43:57 +00001218%
1219% o exception: return any errors or warnings in this structure.
1220%
1221*/
cristy5e6be1e2011-07-16 01:23:39 +00001222MagickExport Image *ConvolveImage(const Image *image,
1223 const KernelInfo *kernel_info,ExceptionInfo *exception)
cristyfccdab92009-11-30 16:43:57 +00001224{
cristy00f91682012-05-06 19:33:40 +00001225 return(MorphologyImage(image,CorrelateMorphology,1,kernel_info,exception));
cristyfccdab92009-11-30 16:43:57 +00001226}
1227
1228/*
1229%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1230% %
1231% %
1232% %
cristy3ed852e2009-09-05 21:47:34 +00001233% D e s p e c k l e I m a g e %
1234% %
1235% %
1236% %
1237%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1238%
1239% DespeckleImage() reduces the speckle noise in an image while perserving the
cristyf0ae7762012-01-15 22:04:03 +00001240% edges of the original image. A speckle removing filter uses a complementary % hulling technique (raising pixels that are darker than their surrounding
1241% neighbors, then complementarily lowering pixels that are brighter than their
1242% surrounding neighbors) to reduce the speckle index of that image (reference
1243% Crimmins speckle removal).
cristy3ed852e2009-09-05 21:47:34 +00001244%
1245% The format of the DespeckleImage method is:
1246%
1247% Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1248%
1249% A description of each parameter follows:
1250%
1251% o image: the image.
1252%
1253% o exception: return any errors or warnings in this structure.
1254%
1255*/
1256
cristy4ee2b0c2012-05-15 00:30:35 +00001257static void Hull(const Image *image,const ssize_t x_offset,
1258 const ssize_t y_offset,const size_t columns,const size_t rows,
1259 const int polarity,Quantum *restrict f,Quantum *restrict g)
cristy3ed852e2009-09-05 21:47:34 +00001260{
cristy7b4b8862012-01-16 19:52:19 +00001261 register Quantum
cristy5473bf02012-01-16 19:15:08 +00001262 *p,
1263 *q,
1264 *r,
1265 *s;
cristy78f3de22012-01-16 00:43:18 +00001266
cristy5473bf02012-01-16 19:15:08 +00001267 ssize_t
1268 y;
1269
1270 assert(f != (Quantum *) NULL);
1271 assert(g != (Quantum *) NULL);
1272 p=f+(columns+2);
1273 q=g+(columns+2);
1274 r=p+(y_offset*(columns+2)+x_offset);
cristyff8e85a2012-01-18 13:36:20 +00001275#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001276 #pragma omp parallel for schedule(static) \
cristy4ee2b0c2012-05-15 00:30:35 +00001277 dynamic_number_threads(image,columns,rows,1)
cristy5473bf02012-01-16 19:15:08 +00001278#endif
1279 for (y=0; y < (ssize_t) rows; y++)
1280 {
cristy7b4b8862012-01-16 19:52:19 +00001281 register ssize_t
cristy5473bf02012-01-16 19:15:08 +00001282 i,
1283 x;
1284
cristy7b4b8862012-01-16 19:52:19 +00001285 SignedQuantum
1286 v;
1287
cristy5473bf02012-01-16 19:15:08 +00001288 i=(2*y+1)+y*columns;
1289 if (polarity > 0)
1290 for (x=0; x < (ssize_t) columns; x++)
1291 {
1292 v=(SignedQuantum) p[i];
1293 if ((SignedQuantum) r[i] >= (v+ScaleCharToQuantum(2)))
1294 v+=ScaleCharToQuantum(1);
1295 q[i]=(Quantum) v;
1296 i++;
1297 }
1298 else
1299 for (x=0; x < (ssize_t) columns; x++)
1300 {
1301 v=(SignedQuantum) p[i];
1302 if ((SignedQuantum) r[i] <= (v-ScaleCharToQuantum(2)))
1303 v-=ScaleCharToQuantum(1);
1304 q[i]=(Quantum) v;
1305 i++;
1306 }
1307 }
1308 p=f+(columns+2);
1309 q=g+(columns+2);
1310 r=q+(y_offset*(columns+2)+x_offset);
1311 s=q-(y_offset*(columns+2)+x_offset);
cristyff8e85a2012-01-18 13:36:20 +00001312#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001313 #pragma omp parallel for schedule(static) \
cristy4ee2b0c2012-05-15 00:30:35 +00001314 dynamic_number_threads(image,columns,rows,1)
cristy5473bf02012-01-16 19:15:08 +00001315#endif
1316 for (y=0; y < (ssize_t) rows; y++)
1317 {
cristy7b4b8862012-01-16 19:52:19 +00001318 register ssize_t
cristy5473bf02012-01-16 19:15:08 +00001319 i,
1320 x;
1321
cristy7b4b8862012-01-16 19:52:19 +00001322 SignedQuantum
1323 v;
1324
cristy5473bf02012-01-16 19:15:08 +00001325 i=(2*y+1)+y*columns;
1326 if (polarity > 0)
1327 for (x=0; x < (ssize_t) columns; x++)
1328 {
1329 v=(SignedQuantum) q[i];
1330 if (((SignedQuantum) s[i] >= (v+ScaleCharToQuantum(2))) &&
1331 ((SignedQuantum) r[i] > v))
1332 v+=ScaleCharToQuantum(1);
1333 p[i]=(Quantum) v;
1334 i++;
1335 }
1336 else
1337 for (x=0; x < (ssize_t) columns; x++)
1338 {
1339 v=(SignedQuantum) q[i];
1340 if (((SignedQuantum) s[i] <= (v-ScaleCharToQuantum(2))) &&
1341 ((SignedQuantum) r[i] < v))
1342 v-=ScaleCharToQuantum(1);
1343 p[i]=(Quantum) v;
1344 i++;
1345 }
1346 }
cristy3ed852e2009-09-05 21:47:34 +00001347}
1348
1349MagickExport Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1350{
1351#define DespeckleImageTag "Despeckle/Image"
1352
cristy2407fc22009-09-11 00:55:25 +00001353 CacheView
1354 *despeckle_view,
1355 *image_view;
1356
cristy3ed852e2009-09-05 21:47:34 +00001357 Image
1358 *despeckle_image;
1359
cristy3ed852e2009-09-05 21:47:34 +00001360 MagickBooleanType
1361 status;
1362
cristy5473bf02012-01-16 19:15:08 +00001363 Quantum
1364 *restrict buffer,
1365 *restrict pixels;
cristya63e4a92011-09-09 00:47:59 +00001366
cristy5473bf02012-01-16 19:15:08 +00001367 register ssize_t
1368 i;
1369
1370 size_t
1371 length;
cristy117ff172010-08-15 21:35:32 +00001372
cristybb503372010-05-27 20:51:26 +00001373 static const ssize_t
cristy691a29e2009-09-11 00:44:10 +00001374 X[4] = {0, 1, 1,-1},
1375 Y[4] = {1, 0, 1, 1};
cristy3ed852e2009-09-05 21:47:34 +00001376
cristy3ed852e2009-09-05 21:47:34 +00001377 /*
1378 Allocate despeckled image.
1379 */
1380 assert(image != (const Image *) NULL);
1381 assert(image->signature == MagickSignature);
1382 if (image->debug != MagickFalse)
1383 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1384 assert(exception != (ExceptionInfo *) NULL);
1385 assert(exception->signature == MagickSignature);
cristy5473bf02012-01-16 19:15:08 +00001386 despeckle_image=CloneImage(image,0,0,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00001387 if (despeckle_image == (Image *) NULL)
1388 return((Image *) NULL);
cristya63e4a92011-09-09 00:47:59 +00001389 status=SetImageStorageClass(despeckle_image,DirectClass,exception);
1390 if (status == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001391 {
cristy3ed852e2009-09-05 21:47:34 +00001392 despeckle_image=DestroyImage(despeckle_image);
1393 return((Image *) NULL);
1394 }
1395 /*
cristy5473bf02012-01-16 19:15:08 +00001396 Allocate image buffer.
1397 */
1398 length=(size_t) ((image->columns+2)*(image->rows+2));
1399 pixels=(Quantum *) AcquireQuantumMemory(length,sizeof(*pixels));
1400 buffer=(Quantum *) AcquireQuantumMemory(length,sizeof(*buffer));
1401 if ((pixels == (Quantum *) NULL) || (buffer == (Quantum *) NULL))
1402 {
1403 if (buffer != (Quantum *) NULL)
1404 buffer=(Quantum *) RelinquishMagickMemory(buffer);
1405 if (pixels != (Quantum *) NULL)
1406 pixels=(Quantum *) RelinquishMagickMemory(pixels);
1407 despeckle_image=DestroyImage(despeckle_image);
1408 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1409 }
1410 /*
1411 Reduce speckle in the image.
cristy3ed852e2009-09-05 21:47:34 +00001412 */
1413 status=MagickTrue;
cristydb070952012-04-20 14:33:00 +00001414 image_view=AcquireVirtualCacheView(image,exception);
1415 despeckle_view=AcquireAuthenticCacheView(despeckle_image,exception);
cristy5473bf02012-01-16 19:15:08 +00001416 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy3ed852e2009-09-05 21:47:34 +00001417 {
cristy5473bf02012-01-16 19:15:08 +00001418 PixelChannel
1419 channel;
1420
1421 PixelTrait
1422 despeckle_traits,
1423 traits;
cristy3ed852e2009-09-05 21:47:34 +00001424
cristyc1488b52011-02-19 18:54:15 +00001425 register ssize_t
cristy5473bf02012-01-16 19:15:08 +00001426 k,
cristyf0ae7762012-01-15 22:04:03 +00001427 x;
cristyc1488b52011-02-19 18:54:15 +00001428
cristy5473bf02012-01-16 19:15:08 +00001429 ssize_t
1430 j,
1431 y;
1432
1433 if (status == MagickFalse)
1434 continue;
1435 channel=GetPixelChannelMapChannel(image,i);
1436 traits=GetPixelChannelMapTraits(image,channel);
1437 despeckle_traits=GetPixelChannelMapTraits(despeckle_image,channel);
1438 if ((traits == UndefinedPixelTrait) ||
1439 (despeckle_traits == UndefinedPixelTrait))
1440 continue;
1441 if ((despeckle_traits & CopyPixelTrait) != 0)
1442 continue;
1443 (void) ResetMagickMemory(pixels,0,length*sizeof(*pixels));
1444 j=(ssize_t) image->columns+2;
1445 for (y=0; y < (ssize_t) image->rows; y++)
cristyfc830f42012-01-15 02:45:06 +00001446 {
cristy5473bf02012-01-16 19:15:08 +00001447 register const Quantum
1448 *restrict p;
cristyfc830f42012-01-15 02:45:06 +00001449
cristy5473bf02012-01-16 19:15:08 +00001450 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1451 if (p == (const Quantum *) NULL)
cristyf0ae7762012-01-15 22:04:03 +00001452 {
cristy5473bf02012-01-16 19:15:08 +00001453 status=MagickFalse;
1454 continue;
cristyf0ae7762012-01-15 22:04:03 +00001455 }
cristy5473bf02012-01-16 19:15:08 +00001456 j++;
1457 for (x=0; x < (ssize_t) image->columns; x++)
1458 {
1459 pixels[j++]=p[i];
1460 p+=GetPixelChannels(image);
cristyfc830f42012-01-15 02:45:06 +00001461 }
cristy5473bf02012-01-16 19:15:08 +00001462 j++;
cristy3ed852e2009-09-05 21:47:34 +00001463 }
cristy5473bf02012-01-16 19:15:08 +00001464 (void) ResetMagickMemory(buffer,0,length*sizeof(*buffer));
1465 for (k=0; k < 4; k++)
1466 {
cristy4ee2b0c2012-05-15 00:30:35 +00001467 Hull(image,X[k],Y[k],image->columns,image->rows,1,pixels,buffer);
1468 Hull(image,-X[k],-Y[k],image->columns,image->rows,1,pixels,buffer);
1469 Hull(image,-X[k],-Y[k],image->columns,image->rows,-1,pixels,buffer);
1470 Hull(image,X[k],Y[k],image->columns,image->rows,-1,pixels,buffer);
cristy5473bf02012-01-16 19:15:08 +00001471 }
1472 j=(ssize_t) image->columns+2;
1473 for (y=0; y < (ssize_t) image->rows; y++)
1474 {
1475 MagickBooleanType
1476 sync;
1477
1478 register Quantum
1479 *restrict q;
1480
cristyd13e8eb2012-01-17 02:36:42 +00001481 q=QueueCacheViewAuthenticPixels(despeckle_view,0,y,
1482 despeckle_image->columns,1,exception);
cristy5473bf02012-01-16 19:15:08 +00001483 if (q == (Quantum *) NULL)
1484 {
1485 status=MagickFalse;
1486 continue;
1487 }
1488 j++;
1489 for (x=0; x < (ssize_t) image->columns; x++)
1490 {
1491 SetPixelChannel(despeckle_image,channel,pixels[j++],q);
1492 q+=GetPixelChannels(despeckle_image);
1493 }
1494 sync=SyncCacheViewAuthenticPixels(despeckle_view,exception);
1495 if (sync == MagickFalse)
1496 status=MagickFalse;
1497 j++;
1498 }
cristy3ed852e2009-09-05 21:47:34 +00001499 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1500 {
1501 MagickBooleanType
1502 proceed;
1503
cristy5473bf02012-01-16 19:15:08 +00001504 proceed=SetImageProgress(image,DespeckleImageTag,(MagickOffsetType) i,
1505 GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00001506 if (proceed == MagickFalse)
1507 status=MagickFalse;
1508 }
1509 }
1510 despeckle_view=DestroyCacheView(despeckle_view);
1511 image_view=DestroyCacheView(image_view);
cristy5473bf02012-01-16 19:15:08 +00001512 buffer=(Quantum *) RelinquishMagickMemory(buffer);
1513 pixels=(Quantum *) RelinquishMagickMemory(pixels);
cristy3ed852e2009-09-05 21:47:34 +00001514 despeckle_image->type=image->type;
1515 if (status == MagickFalse)
1516 despeckle_image=DestroyImage(despeckle_image);
1517 return(despeckle_image);
1518}
1519
1520/*
1521%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1522% %
1523% %
1524% %
1525% E d g e I m a g e %
1526% %
1527% %
1528% %
1529%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1530%
1531% EdgeImage() finds edges in an image. Radius defines the radius of the
1532% convolution filter. Use a radius of 0 and EdgeImage() selects a suitable
1533% radius for you.
1534%
1535% The format of the EdgeImage method is:
1536%
1537% Image *EdgeImage(const Image *image,const double radius,
cristy8ae632d2011-09-05 17:29:53 +00001538% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001539%
1540% A description of each parameter follows:
1541%
1542% o image: the image.
1543%
1544% o radius: the radius of the pixel neighborhood.
1545%
cristy8ae632d2011-09-05 17:29:53 +00001546% o sigma: the standard deviation of the Gaussian, in pixels.
1547%
cristy3ed852e2009-09-05 21:47:34 +00001548% o exception: return any errors or warnings in this structure.
1549%
1550*/
1551MagickExport Image *EdgeImage(const Image *image,const double radius,
cristy8ae632d2011-09-05 17:29:53 +00001552 const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001553{
1554 Image
1555 *edge_image;
1556
cristy41cbe682011-07-15 19:12:37 +00001557 KernelInfo
1558 *kernel_info;
cristy3ed852e2009-09-05 21:47:34 +00001559
cristybb503372010-05-27 20:51:26 +00001560 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001561 i;
1562
cristybb503372010-05-27 20:51:26 +00001563 size_t
cristy3ed852e2009-09-05 21:47:34 +00001564 width;
1565
cristy41cbe682011-07-15 19:12:37 +00001566 ssize_t
1567 j,
1568 u,
1569 v;
1570
cristy3ed852e2009-09-05 21:47:34 +00001571 assert(image != (const Image *) NULL);
1572 assert(image->signature == MagickSignature);
1573 if (image->debug != MagickFalse)
1574 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1575 assert(exception != (ExceptionInfo *) NULL);
1576 assert(exception->signature == MagickSignature);
cristyfc0ae532011-12-06 15:14:45 +00001577 width=GetOptimalKernelWidth1D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00001578 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00001579 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001580 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00001581 kernel_info->width=width;
1582 kernel_info->height=width;
cristye8d93f62012-05-07 16:45:46 +00001583 kernel_info->values=(double *) AcquireAlignedMemory(kernel_info->width,
1584 kernel_info->width*sizeof(*kernel_info->values));
1585 if (kernel_info->values == (double *) NULL)
cristy41cbe682011-07-15 19:12:37 +00001586 {
1587 kernel_info=DestroyKernelInfo(kernel_info);
1588 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1589 }
1590 j=(ssize_t) kernel_info->width/2;
1591 i=0;
1592 for (v=(-j); v <= j; v++)
1593 {
1594 for (u=(-j); u <= j; u++)
1595 {
1596 kernel_info->values[i]=(-1.0);
1597 i++;
1598 }
1599 }
1600 kernel_info->values[i/2]=(double) (width*width-1.0);
cristy5e6be1e2011-07-16 01:23:39 +00001601 edge_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00001602 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00001603 return(edge_image);
1604}
1605
1606/*
1607%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1608% %
1609% %
1610% %
1611% E m b o s s I m a g e %
1612% %
1613% %
1614% %
1615%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1616%
1617% EmbossImage() returns a grayscale image with a three-dimensional effect.
1618% We convolve the image with a Gaussian operator of the given radius and
1619% standard deviation (sigma). For reasonable results, radius should be
1620% larger than sigma. Use a radius of 0 and Emboss() selects a suitable
1621% radius for you.
1622%
1623% The format of the EmbossImage method is:
1624%
1625% Image *EmbossImage(const Image *image,const double radius,
1626% const double sigma,ExceptionInfo *exception)
1627%
1628% A description of each parameter follows:
1629%
1630% o image: the image.
1631%
1632% o radius: the radius of the pixel neighborhood.
1633%
1634% o sigma: the standard deviation of the Gaussian, in pixels.
1635%
1636% o exception: return any errors or warnings in this structure.
1637%
1638*/
1639MagickExport Image *EmbossImage(const Image *image,const double radius,
1640 const double sigma,ExceptionInfo *exception)
1641{
cristy3ed852e2009-09-05 21:47:34 +00001642 Image
1643 *emboss_image;
1644
cristy41cbe682011-07-15 19:12:37 +00001645 KernelInfo
1646 *kernel_info;
1647
cristybb503372010-05-27 20:51:26 +00001648 register ssize_t
cristy47e00502009-12-17 19:19:57 +00001649 i;
1650
cristybb503372010-05-27 20:51:26 +00001651 size_t
cristy3ed852e2009-09-05 21:47:34 +00001652 width;
1653
cristy117ff172010-08-15 21:35:32 +00001654 ssize_t
1655 j,
1656 k,
1657 u,
1658 v;
1659
cristy41cbe682011-07-15 19:12:37 +00001660 assert(image != (const Image *) NULL);
cristy3ed852e2009-09-05 21:47:34 +00001661 assert(image->signature == MagickSignature);
1662 if (image->debug != MagickFalse)
1663 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1664 assert(exception != (ExceptionInfo *) NULL);
1665 assert(exception->signature == MagickSignature);
cristyfc0ae532011-12-06 15:14:45 +00001666 width=GetOptimalKernelWidth1D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00001667 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00001668 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001669 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00001670 kernel_info->width=width;
1671 kernel_info->height=width;
cristye8d93f62012-05-07 16:45:46 +00001672 kernel_info->values=(double *) AcquireAlignedMemory(kernel_info->width,
1673 kernel_info->width*sizeof(*kernel_info->values));
1674 if (kernel_info->values == (double *) NULL)
cristy41cbe682011-07-15 19:12:37 +00001675 {
1676 kernel_info=DestroyKernelInfo(kernel_info);
1677 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1678 }
1679 j=(ssize_t) kernel_info->width/2;
cristy47e00502009-12-17 19:19:57 +00001680 k=j;
1681 i=0;
1682 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00001683 {
cristy47e00502009-12-17 19:19:57 +00001684 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00001685 {
cristy41cbe682011-07-15 19:12:37 +00001686 kernel_info->values[i]=(double) (((u < 0) || (v < 0) ? -8.0 : 8.0)*
cristy47e00502009-12-17 19:19:57 +00001687 exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
cristy4205a3c2010-09-12 20:19:59 +00001688 (2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +00001689 if (u != k)
cristy41cbe682011-07-15 19:12:37 +00001690 kernel_info->values[i]=0.0;
cristy3ed852e2009-09-05 21:47:34 +00001691 i++;
1692 }
cristy47e00502009-12-17 19:19:57 +00001693 k--;
cristy3ed852e2009-09-05 21:47:34 +00001694 }
cristy5e6be1e2011-07-16 01:23:39 +00001695 emboss_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00001696 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00001697 if (emboss_image != (Image *) NULL)
cristy6d8c3d72011-08-22 01:20:01 +00001698 (void) EqualizeImage(emboss_image,exception);
cristy3ed852e2009-09-05 21:47:34 +00001699 return(emboss_image);
1700}
1701
1702/*
1703%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1704% %
1705% %
1706% %
1707% G a u s s i a n B l u r I m a g e %
1708% %
1709% %
1710% %
1711%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1712%
1713% GaussianBlurImage() blurs an image. We convolve the image with a
1714% Gaussian operator of the given radius and standard deviation (sigma).
1715% For reasonable results, the radius should be larger than sigma. Use a
1716% radius of 0 and GaussianBlurImage() selects a suitable radius for you
1717%
1718% The format of the GaussianBlurImage method is:
1719%
1720% Image *GaussianBlurImage(const Image *image,onst double radius,
cristyd89705a2012-01-20 02:52:24 +00001721% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001722%
1723% A description of each parameter follows:
1724%
1725% o image: the image.
1726%
cristy3ed852e2009-09-05 21:47:34 +00001727% o radius: the radius of the Gaussian, in pixels, not counting the center
1728% pixel.
1729%
1730% o sigma: the standard deviation of the Gaussian, in pixels.
1731%
1732% o exception: return any errors or warnings in this structure.
1733%
1734*/
cristy41cbe682011-07-15 19:12:37 +00001735MagickExport Image *GaussianBlurImage(const Image *image,const double radius,
cristyd89705a2012-01-20 02:52:24 +00001736 const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001737{
cristy3ed852e2009-09-05 21:47:34 +00001738 Image
1739 *blur_image;
1740
cristy41cbe682011-07-15 19:12:37 +00001741 KernelInfo
1742 *kernel_info;
1743
cristybb503372010-05-27 20:51:26 +00001744 register ssize_t
cristy47e00502009-12-17 19:19:57 +00001745 i;
1746
cristybb503372010-05-27 20:51:26 +00001747 size_t
cristy3ed852e2009-09-05 21:47:34 +00001748 width;
1749
cristy117ff172010-08-15 21:35:32 +00001750 ssize_t
1751 j,
1752 u,
1753 v;
1754
cristy3ed852e2009-09-05 21:47:34 +00001755 assert(image != (const Image *) NULL);
1756 assert(image->signature == MagickSignature);
1757 if (image->debug != MagickFalse)
1758 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1759 assert(exception != (ExceptionInfo *) NULL);
1760 assert(exception->signature == MagickSignature);
1761 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00001762 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00001763 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001764 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00001765 (void) ResetMagickMemory(kernel_info,0,sizeof(*kernel_info));
1766 kernel_info->width=width;
1767 kernel_info->height=width;
1768 kernel_info->signature=MagickSignature;
cristye8d93f62012-05-07 16:45:46 +00001769 kernel_info->values=(double *) AcquireAlignedMemory(
cristya96f2492011-12-14 18:25:41 +00001770 kernel_info->width,kernel_info->width*sizeof(*kernel_info->values));
cristye8d93f62012-05-07 16:45:46 +00001771 if (kernel_info->values == (double *) NULL)
cristy41cbe682011-07-15 19:12:37 +00001772 {
1773 kernel_info=DestroyKernelInfo(kernel_info);
1774 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1775 }
1776 j=(ssize_t) kernel_info->width/2;
cristy3ed852e2009-09-05 21:47:34 +00001777 i=0;
cristy47e00502009-12-17 19:19:57 +00001778 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00001779 {
cristy47e00502009-12-17 19:19:57 +00001780 for (u=(-j); u <= j; u++)
cristy41cbe682011-07-15 19:12:37 +00001781 {
1782 kernel_info->values[i]=(double) (exp(-((double) u*u+v*v)/(2.0*
1783 MagickSigma*MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
1784 i++;
1785 }
cristy3ed852e2009-09-05 21:47:34 +00001786 }
cristy5e6be1e2011-07-16 01:23:39 +00001787 blur_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00001788 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00001789 return(blur_image);
1790}
1791
1792/*
1793%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1794% %
1795% %
1796% %
cristy3ed852e2009-09-05 21:47:34 +00001797% M o t i o n B l u r I m a g e %
1798% %
1799% %
1800% %
1801%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1802%
1803% MotionBlurImage() simulates motion blur. We convolve the image with a
1804% Gaussian operator of the given radius and standard deviation (sigma).
1805% For reasonable results, radius should be larger than sigma. Use a
1806% radius of 0 and MotionBlurImage() selects a suitable radius for you.
1807% Angle gives the angle of the blurring motion.
1808%
1809% Andrew Protano contributed this effect.
1810%
1811% The format of the MotionBlurImage method is:
1812%
1813% Image *MotionBlurImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +00001814% const double sigma,const double angle,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001815%
1816% A description of each parameter follows:
1817%
1818% o image: the image.
1819%
cristy3ed852e2009-09-05 21:47:34 +00001820% o radius: the radius of the Gaussian, in pixels, not counting
1821% the center pixel.
1822%
1823% o sigma: the standard deviation of the Gaussian, in pixels.
1824%
cristycee97112010-05-28 00:44:52 +00001825% o angle: Apply the effect along this angle.
cristy3ed852e2009-09-05 21:47:34 +00001826%
1827% o exception: return any errors or warnings in this structure.
1828%
1829*/
1830
cristybb503372010-05-27 20:51:26 +00001831static double *GetMotionBlurKernel(const size_t width,const double sigma)
cristy3ed852e2009-09-05 21:47:34 +00001832{
cristy3ed852e2009-09-05 21:47:34 +00001833 double
cristy47e00502009-12-17 19:19:57 +00001834 *kernel,
cristy3ed852e2009-09-05 21:47:34 +00001835 normalize;
1836
cristybb503372010-05-27 20:51:26 +00001837 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001838 i;
1839
1840 /*
cristy47e00502009-12-17 19:19:57 +00001841 Generate a 1-D convolution kernel.
cristy3ed852e2009-09-05 21:47:34 +00001842 */
1843 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
cristy8b49f382012-01-17 03:11:03 +00001844 kernel=(double *) AcquireAlignedMemory((size_t) width,sizeof(*kernel));
cristy3ed852e2009-09-05 21:47:34 +00001845 if (kernel == (double *) NULL)
1846 return(kernel);
cristy3ed852e2009-09-05 21:47:34 +00001847 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00001848 for (i=0; i < (ssize_t) width; i++)
cristy47e00502009-12-17 19:19:57 +00001849 {
cristy4205a3c2010-09-12 20:19:59 +00001850 kernel[i]=(double) (exp((-((double) i*i)/(double) (2.0*MagickSigma*
1851 MagickSigma)))/(MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00001852 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +00001853 }
cristybb503372010-05-27 20:51:26 +00001854 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001855 kernel[i]/=normalize;
1856 return(kernel);
1857}
1858
cristya63e4a92011-09-09 00:47:59 +00001859MagickExport Image *MotionBlurImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +00001860 const double sigma,const double angle,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001861{
cristyc4c8d132010-01-07 01:58:38 +00001862 CacheView
1863 *blur_view,
cristy8b49f382012-01-17 03:11:03 +00001864 *image_view,
1865 *motion_view;
cristyc4c8d132010-01-07 01:58:38 +00001866
cristy3ed852e2009-09-05 21:47:34 +00001867 double
1868 *kernel;
1869
1870 Image
1871 *blur_image;
1872
cristy3ed852e2009-09-05 21:47:34 +00001873 MagickBooleanType
1874 status;
1875
cristybb503372010-05-27 20:51:26 +00001876 MagickOffsetType
1877 progress;
1878
cristy3ed852e2009-09-05 21:47:34 +00001879 OffsetInfo
1880 *offset;
1881
1882 PointInfo
1883 point;
1884
cristybb503372010-05-27 20:51:26 +00001885 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001886 i;
1887
cristybb503372010-05-27 20:51:26 +00001888 size_t
cristy3ed852e2009-09-05 21:47:34 +00001889 width;
1890
cristybb503372010-05-27 20:51:26 +00001891 ssize_t
1892 y;
1893
cristy3ed852e2009-09-05 21:47:34 +00001894 assert(image != (Image *) NULL);
1895 assert(image->signature == MagickSignature);
1896 if (image->debug != MagickFalse)
1897 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1898 assert(exception != (ExceptionInfo *) NULL);
1899 width=GetOptimalKernelWidth1D(radius,sigma);
1900 kernel=GetMotionBlurKernel(width,sigma);
1901 if (kernel == (double *) NULL)
1902 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1903 offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset));
1904 if (offset == (OffsetInfo *) NULL)
1905 {
cristy8b49f382012-01-17 03:11:03 +00001906 kernel=(double *) RelinquishAlignedMemory(kernel);
cristy3ed852e2009-09-05 21:47:34 +00001907 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1908 }
cristy1e7aa312011-09-10 20:01:36 +00001909 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00001910 if (blur_image == (Image *) NULL)
1911 {
cristy8b49f382012-01-17 03:11:03 +00001912 kernel=(double *) RelinquishAlignedMemory(kernel);
cristy3ed852e2009-09-05 21:47:34 +00001913 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
1914 return((Image *) NULL);
1915 }
cristy574cc262011-08-05 01:23:58 +00001916 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001917 {
cristy8b49f382012-01-17 03:11:03 +00001918 kernel=(double *) RelinquishAlignedMemory(kernel);
cristy3ed852e2009-09-05 21:47:34 +00001919 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
cristy3ed852e2009-09-05 21:47:34 +00001920 blur_image=DestroyImage(blur_image);
1921 return((Image *) NULL);
1922 }
1923 point.x=(double) width*sin(DegreesToRadians(angle));
1924 point.y=(double) width*cos(DegreesToRadians(angle));
cristybb503372010-05-27 20:51:26 +00001925 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00001926 {
cristybb503372010-05-27 20:51:26 +00001927 offset[i].x=(ssize_t) ceil((double) (i*point.y)/hypot(point.x,point.y)-0.5);
1928 offset[i].y=(ssize_t) ceil((double) (i*point.x)/hypot(point.x,point.y)-0.5);
cristy3ed852e2009-09-05 21:47:34 +00001929 }
1930 /*
1931 Motion blur image.
1932 */
1933 status=MagickTrue;
1934 progress=0;
cristydb070952012-04-20 14:33:00 +00001935 image_view=AcquireVirtualCacheView(image,exception);
1936 motion_view=AcquireVirtualCacheView(image,exception);
1937 blur_view=AcquireAuthenticCacheView(blur_image,exception);
cristyb557a152011-02-22 12:14:30 +00001938#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001939 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy4ee2b0c2012-05-15 00:30:35 +00001940 dynamic_number_threads(image,image->columns,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00001941#endif
cristybb503372010-05-27 20:51:26 +00001942 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001943 {
cristyf7ef0252011-09-09 14:50:06 +00001944 register const Quantum
1945 *restrict p;
1946
cristy4c08aed2011-07-01 19:47:50 +00001947 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001948 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001949
cristy117ff172010-08-15 21:35:32 +00001950 register ssize_t
1951 x;
1952
cristy3ed852e2009-09-05 21:47:34 +00001953 if (status == MagickFalse)
1954 continue;
cristy8b49f382012-01-17 03:11:03 +00001955 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1956 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +00001957 exception);
cristyf7ef0252011-09-09 14:50:06 +00001958 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001959 {
1960 status=MagickFalse;
1961 continue;
1962 }
cristybb503372010-05-27 20:51:26 +00001963 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001964 {
cristybb503372010-05-27 20:51:26 +00001965 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001966 i;
1967
cristyf7ef0252011-09-09 14:50:06 +00001968 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1969 {
cristya19f1d72012-08-07 18:24:38 +00001970 double
cristyf7ef0252011-09-09 14:50:06 +00001971 alpha,
1972 gamma,
1973 pixel;
cristy3ed852e2009-09-05 21:47:34 +00001974
cristyf7ef0252011-09-09 14:50:06 +00001975 PixelChannel
1976 channel;
1977
1978 PixelTrait
1979 blur_traits,
1980 traits;
1981
1982 register const Quantum
1983 *restrict r;
1984
1985 register double
1986 *restrict k;
1987
1988 register ssize_t
1989 j;
1990
cristye2a912b2011-12-05 20:02:07 +00001991 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +00001992 traits=GetPixelChannelMapTraits(image,channel);
cristyf7ef0252011-09-09 14:50:06 +00001993 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
1994 if ((traits == UndefinedPixelTrait) ||
1995 (blur_traits == UndefinedPixelTrait))
1996 continue;
cristy1eced092012-08-10 23:10:56 +00001997 if (((blur_traits & CopyPixelTrait) != 0) ||
1998 (GetPixelMask(image,p) != 0))
cristy3ed852e2009-09-05 21:47:34 +00001999 {
cristy0beccfa2011-09-25 20:47:53 +00002000 SetPixelChannel(blur_image,channel,p[i],q);
cristyf7ef0252011-09-09 14:50:06 +00002001 continue;
cristy3ed852e2009-09-05 21:47:34 +00002002 }
cristyf7ef0252011-09-09 14:50:06 +00002003 k=kernel;
cristyaa2c16c2012-03-25 22:21:35 +00002004 pixel=0.0;
cristyf7ef0252011-09-09 14:50:06 +00002005 if ((blur_traits & BlendPixelTrait) == 0)
2006 {
2007 for (j=0; j < (ssize_t) width; j++)
2008 {
cristy8b49f382012-01-17 03:11:03 +00002009 r=GetCacheViewVirtualPixels(motion_view,x+offset[j].x,y+
cristyf7ef0252011-09-09 14:50:06 +00002010 offset[j].y,1,1,exception);
2011 if (r == (const Quantum *) NULL)
2012 {
2013 status=MagickFalse;
2014 continue;
2015 }
2016 pixel+=(*k)*r[i];
2017 k++;
2018 }
cristy0beccfa2011-09-25 20:47:53 +00002019 SetPixelChannel(blur_image,channel,ClampToQuantum(pixel),q);
cristyf7ef0252011-09-09 14:50:06 +00002020 continue;
2021 }
2022 alpha=0.0;
2023 gamma=0.0;
2024 for (j=0; j < (ssize_t) width; j++)
2025 {
cristy8b49f382012-01-17 03:11:03 +00002026 r=GetCacheViewVirtualPixels(motion_view,x+offset[j].x,y+offset[j].y,1,
cristyf7ef0252011-09-09 14:50:06 +00002027 1,exception);
2028 if (r == (const Quantum *) NULL)
2029 {
2030 status=MagickFalse;
2031 continue;
2032 }
cristya19f1d72012-08-07 18:24:38 +00002033 alpha=(double) (QuantumScale*GetPixelAlpha(image,r));
cristyf7ef0252011-09-09 14:50:06 +00002034 pixel+=(*k)*alpha*r[i];
2035 gamma+=(*k)*alpha;
2036 k++;
cristy3ed852e2009-09-05 21:47:34 +00002037 }
cristyc58380a2012-06-03 15:12:30 +00002038 gamma=MagickEpsilonReciprocal(gamma);
cristy0beccfa2011-09-25 20:47:53 +00002039 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristyf7ef0252011-09-09 14:50:06 +00002040 }
2041 p+=GetPixelChannels(image);
cristyed231572011-07-14 02:18:59 +00002042 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00002043 }
2044 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
2045 status=MagickFalse;
2046 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2047 {
2048 MagickBooleanType
2049 proceed;
2050
cristyb557a152011-02-22 12:14:30 +00002051#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf8561542012-01-24 00:26:46 +00002052 #pragma omp critical (MagickCore_MotionBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00002053#endif
2054 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
2055 if (proceed == MagickFalse)
2056 status=MagickFalse;
2057 }
2058 }
2059 blur_view=DestroyCacheView(blur_view);
cristy8b49f382012-01-17 03:11:03 +00002060 motion_view=DestroyCacheView(motion_view);
cristy3ed852e2009-09-05 21:47:34 +00002061 image_view=DestroyCacheView(image_view);
cristy8b49f382012-01-17 03:11:03 +00002062 kernel=(double *) RelinquishAlignedMemory(kernel);
cristy3ed852e2009-09-05 21:47:34 +00002063 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2064 if (status == MagickFalse)
2065 blur_image=DestroyImage(blur_image);
2066 return(blur_image);
2067}
2068
2069/*
2070%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2071% %
2072% %
2073% %
2074% P r e v i e w I m a g e %
2075% %
2076% %
2077% %
2078%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2079%
2080% PreviewImage() tiles 9 thumbnails of the specified image with an image
2081% processing operation applied with varying parameters. This may be helpful
2082% pin-pointing an appropriate parameter for a particular image processing
2083% operation.
2084%
2085% The format of the PreviewImages method is:
2086%
2087% Image *PreviewImages(const Image *image,const PreviewType preview,
2088% ExceptionInfo *exception)
2089%
2090% A description of each parameter follows:
2091%
2092% o image: the image.
2093%
2094% o preview: the image processing operation.
2095%
2096% o exception: return any errors or warnings in this structure.
2097%
2098*/
2099MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
2100 ExceptionInfo *exception)
2101{
2102#define NumberTiles 9
2103#define PreviewImageTag "Preview/Image"
2104#define DefaultPreviewGeometry "204x204+10+10"
2105
2106 char
2107 factor[MaxTextExtent],
2108 label[MaxTextExtent];
2109
2110 double
2111 degrees,
2112 gamma,
2113 percentage,
2114 radius,
2115 sigma,
2116 threshold;
2117
cristybcdf5672012-05-24 22:58:54 +00002118 extern const char
2119 DefaultTileFrame[];
2120
cristy3ed852e2009-09-05 21:47:34 +00002121 Image
2122 *images,
2123 *montage_image,
2124 *preview_image,
2125 *thumbnail;
2126
2127 ImageInfo
2128 *preview_info;
2129
cristy3ed852e2009-09-05 21:47:34 +00002130 MagickBooleanType
2131 proceed;
2132
2133 MontageInfo
2134 *montage_info;
2135
2136 QuantizeInfo
2137 quantize_info;
2138
2139 RectangleInfo
2140 geometry;
2141
cristybb503372010-05-27 20:51:26 +00002142 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002143 i,
2144 x;
2145
cristybb503372010-05-27 20:51:26 +00002146 size_t
cristy3ed852e2009-09-05 21:47:34 +00002147 colors;
2148
cristy117ff172010-08-15 21:35:32 +00002149 ssize_t
2150 y;
2151
cristy3ed852e2009-09-05 21:47:34 +00002152 /*
2153 Open output image file.
2154 */
2155 assert(image != (Image *) NULL);
2156 assert(image->signature == MagickSignature);
2157 if (image->debug != MagickFalse)
2158 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2159 colors=2;
2160 degrees=0.0;
2161 gamma=(-0.2f);
2162 preview_info=AcquireImageInfo();
2163 SetGeometry(image,&geometry);
2164 (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
2165 &geometry.width,&geometry.height);
2166 images=NewImageList();
2167 percentage=12.5;
2168 GetQuantizeInfo(&quantize_info);
2169 radius=0.0;
2170 sigma=1.0;
2171 threshold=0.0;
2172 x=0;
2173 y=0;
2174 for (i=0; i < NumberTiles; i++)
2175 {
2176 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
2177 if (thumbnail == (Image *) NULL)
2178 break;
2179 (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
2180 (void *) NULL);
cristyd15e6592011-10-15 00:13:06 +00002181 (void) SetImageProperty(thumbnail,"label",DefaultTileLabel,exception);
cristy3ed852e2009-09-05 21:47:34 +00002182 if (i == (NumberTiles/2))
2183 {
cristy9950d572011-10-01 18:22:35 +00002184 (void) QueryColorCompliance("#dfdfdf",AllCompliance,
2185 &thumbnail->matte_color,exception);
cristy3ed852e2009-09-05 21:47:34 +00002186 AppendImageToList(&images,thumbnail);
2187 continue;
2188 }
2189 switch (preview)
2190 {
2191 case RotatePreview:
2192 {
2193 degrees+=45.0;
2194 preview_image=RotateImage(thumbnail,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002195 (void) FormatLocaleString(label,MaxTextExtent,"rotate %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002196 break;
2197 }
2198 case ShearPreview:
2199 {
2200 degrees+=5.0;
2201 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
cristyb51dff52011-05-19 16:55:47 +00002202 (void) FormatLocaleString(label,MaxTextExtent,"shear %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002203 degrees,2.0*degrees);
2204 break;
2205 }
2206 case RollPreview:
2207 {
cristybb503372010-05-27 20:51:26 +00002208 x=(ssize_t) ((i+1)*thumbnail->columns)/NumberTiles;
2209 y=(ssize_t) ((i+1)*thumbnail->rows)/NumberTiles;
cristy3ed852e2009-09-05 21:47:34 +00002210 preview_image=RollImage(thumbnail,x,y,exception);
cristyb51dff52011-05-19 16:55:47 +00002211 (void) FormatLocaleString(label,MaxTextExtent,"roll %+.20gx%+.20g",
cristye8c25f92010-06-03 00:53:06 +00002212 (double) x,(double) y);
cristy3ed852e2009-09-05 21:47:34 +00002213 break;
2214 }
2215 case HuePreview:
2216 {
2217 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2218 if (preview_image == (Image *) NULL)
2219 break;
cristyb51dff52011-05-19 16:55:47 +00002220 (void) FormatLocaleString(factor,MaxTextExtent,"100,100,%g",
cristy3ed852e2009-09-05 21:47:34 +00002221 2.0*percentage);
cristy33bd5152011-08-24 01:42:24 +00002222 (void) ModulateImage(preview_image,factor,exception);
cristyb51dff52011-05-19 16:55:47 +00002223 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002224 break;
2225 }
2226 case SaturationPreview:
2227 {
2228 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2229 if (preview_image == (Image *) NULL)
2230 break;
cristyb51dff52011-05-19 16:55:47 +00002231 (void) FormatLocaleString(factor,MaxTextExtent,"100,%g",
cristy8cd5b312010-01-07 01:10:24 +00002232 2.0*percentage);
cristy33bd5152011-08-24 01:42:24 +00002233 (void) ModulateImage(preview_image,factor,exception);
cristyb51dff52011-05-19 16:55:47 +00002234 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002235 break;
2236 }
2237 case BrightnessPreview:
2238 {
2239 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2240 if (preview_image == (Image *) NULL)
2241 break;
cristyb51dff52011-05-19 16:55:47 +00002242 (void) FormatLocaleString(factor,MaxTextExtent,"%g",2.0*percentage);
cristy33bd5152011-08-24 01:42:24 +00002243 (void) ModulateImage(preview_image,factor,exception);
cristyb51dff52011-05-19 16:55:47 +00002244 (void) FormatLocaleString(label,MaxTextExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002245 break;
2246 }
2247 case GammaPreview:
2248 default:
2249 {
2250 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2251 if (preview_image == (Image *) NULL)
2252 break;
2253 gamma+=0.4f;
cristyb3e7c6c2011-07-24 01:43:55 +00002254 (void) GammaImage(preview_image,gamma,exception);
cristyb51dff52011-05-19 16:55:47 +00002255 (void) FormatLocaleString(label,MaxTextExtent,"gamma %g",gamma);
cristy3ed852e2009-09-05 21:47:34 +00002256 break;
2257 }
2258 case SpiffPreview:
2259 {
2260 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2261 if (preview_image != (Image *) NULL)
2262 for (x=0; x < i; x++)
cristye23ec9d2011-08-16 18:15:40 +00002263 (void) ContrastImage(preview_image,MagickTrue,exception);
cristyb51dff52011-05-19 16:55:47 +00002264 (void) FormatLocaleString(label,MaxTextExtent,"contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002265 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002266 break;
2267 }
2268 case DullPreview:
2269 {
2270 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2271 if (preview_image == (Image *) NULL)
2272 break;
2273 for (x=0; x < i; x++)
cristye23ec9d2011-08-16 18:15:40 +00002274 (void) ContrastImage(preview_image,MagickFalse,exception);
cristyb51dff52011-05-19 16:55:47 +00002275 (void) FormatLocaleString(label,MaxTextExtent,"+contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002276 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002277 break;
2278 }
2279 case GrayscalePreview:
2280 {
2281 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2282 if (preview_image == (Image *) NULL)
2283 break;
2284 colors<<=1;
2285 quantize_info.number_colors=colors;
2286 quantize_info.colorspace=GRAYColorspace;
cristy018f07f2011-09-04 21:15:19 +00002287 (void) QuantizeImage(&quantize_info,preview_image,exception);
cristyb51dff52011-05-19 16:55:47 +00002288 (void) FormatLocaleString(label,MaxTextExtent,
cristye8c25f92010-06-03 00:53:06 +00002289 "-colorspace gray -colors %.20g",(double) colors);
cristy3ed852e2009-09-05 21:47:34 +00002290 break;
2291 }
2292 case QuantizePreview:
2293 {
2294 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2295 if (preview_image == (Image *) NULL)
2296 break;
2297 colors<<=1;
2298 quantize_info.number_colors=colors;
cristy018f07f2011-09-04 21:15:19 +00002299 (void) QuantizeImage(&quantize_info,preview_image,exception);
cristyb51dff52011-05-19 16:55:47 +00002300 (void) FormatLocaleString(label,MaxTextExtent,"colors %.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00002301 colors);
cristy3ed852e2009-09-05 21:47:34 +00002302 break;
2303 }
2304 case DespecklePreview:
2305 {
2306 for (x=0; x < (i-1); x++)
2307 {
2308 preview_image=DespeckleImage(thumbnail,exception);
2309 if (preview_image == (Image *) NULL)
2310 break;
2311 thumbnail=DestroyImage(thumbnail);
2312 thumbnail=preview_image;
2313 }
2314 preview_image=DespeckleImage(thumbnail,exception);
2315 if (preview_image == (Image *) NULL)
2316 break;
cristyb51dff52011-05-19 16:55:47 +00002317 (void) FormatLocaleString(label,MaxTextExtent,"despeckle (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002318 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002319 break;
2320 }
2321 case ReduceNoisePreview:
2322 {
cristy95c38342011-03-18 22:39:51 +00002323 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) radius,
2324 (size_t) radius,exception);
cristyb51dff52011-05-19 16:55:47 +00002325 (void) FormatLocaleString(label,MaxTextExtent,"noise %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002326 break;
2327 }
2328 case AddNoisePreview:
2329 {
2330 switch ((int) i)
2331 {
2332 case 0:
2333 {
2334 (void) CopyMagickString(factor,"uniform",MaxTextExtent);
2335 break;
2336 }
2337 case 1:
2338 {
2339 (void) CopyMagickString(factor,"gaussian",MaxTextExtent);
2340 break;
2341 }
2342 case 2:
2343 {
2344 (void) CopyMagickString(factor,"multiplicative",MaxTextExtent);
2345 break;
2346 }
2347 case 3:
2348 {
2349 (void) CopyMagickString(factor,"impulse",MaxTextExtent);
2350 break;
2351 }
2352 case 4:
2353 {
2354 (void) CopyMagickString(factor,"laplacian",MaxTextExtent);
2355 break;
2356 }
2357 case 5:
2358 {
2359 (void) CopyMagickString(factor,"Poisson",MaxTextExtent);
2360 break;
2361 }
2362 default:
2363 {
2364 (void) CopyMagickString(thumbnail->magick,"NULL",MaxTextExtent);
2365 break;
2366 }
2367 }
cristyd76c51e2011-03-26 00:21:26 +00002368 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) i,
2369 (size_t) i,exception);
cristyb51dff52011-05-19 16:55:47 +00002370 (void) FormatLocaleString(label,MaxTextExtent,"+noise %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002371 break;
2372 }
2373 case SharpenPreview:
2374 {
cristyaa2c16c2012-03-25 22:21:35 +00002375 preview_image=SharpenImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00002376 (void) FormatLocaleString(label,MaxTextExtent,"sharpen %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002377 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002378 break;
2379 }
2380 case BlurPreview:
2381 {
cristyaa2c16c2012-03-25 22:21:35 +00002382 preview_image=BlurImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00002383 (void) FormatLocaleString(label,MaxTextExtent,"blur %gx%g",radius,
cristy3ed852e2009-09-05 21:47:34 +00002384 sigma);
2385 break;
2386 }
2387 case ThresholdPreview:
2388 {
2389 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2390 if (preview_image == (Image *) NULL)
2391 break;
cristya19f1d72012-08-07 18:24:38 +00002392 (void) BilevelImage(thumbnail,(double) (percentage*((double)
cristye941a752011-10-15 01:52:48 +00002393 QuantumRange+1.0))/100.0,exception);
cristyb51dff52011-05-19 16:55:47 +00002394 (void) FormatLocaleString(label,MaxTextExtent,"threshold %g",
cristya19f1d72012-08-07 18:24:38 +00002395 (double) (percentage*((double) QuantumRange+1.0))/100.0);
cristy3ed852e2009-09-05 21:47:34 +00002396 break;
2397 }
2398 case EdgeDetectPreview:
2399 {
cristy8ae632d2011-09-05 17:29:53 +00002400 preview_image=EdgeImage(thumbnail,radius,sigma,exception);
cristyb51dff52011-05-19 16:55:47 +00002401 (void) FormatLocaleString(label,MaxTextExtent,"edge %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002402 break;
2403 }
2404 case SpreadPreview:
2405 {
cristy5c4e2582011-09-11 19:21:03 +00002406 preview_image=SpreadImage(thumbnail,radius,thumbnail->interpolate,
2407 exception);
cristyb51dff52011-05-19 16:55:47 +00002408 (void) FormatLocaleString(label,MaxTextExtent,"spread %g",
cristy8cd5b312010-01-07 01:10:24 +00002409 radius+0.5);
cristy3ed852e2009-09-05 21:47:34 +00002410 break;
2411 }
2412 case SolarizePreview:
2413 {
2414 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2415 if (preview_image == (Image *) NULL)
2416 break;
2417 (void) SolarizeImage(preview_image,(double) QuantumRange*
cristy5cbc0162011-08-29 00:36:28 +00002418 percentage/100.0,exception);
cristyb51dff52011-05-19 16:55:47 +00002419 (void) FormatLocaleString(label,MaxTextExtent,"solarize %g",
cristy3ed852e2009-09-05 21:47:34 +00002420 (QuantumRange*percentage)/100.0);
2421 break;
2422 }
2423 case ShadePreview:
2424 {
2425 degrees+=10.0;
2426 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
2427 exception);
cristyb51dff52011-05-19 16:55:47 +00002428 (void) FormatLocaleString(label,MaxTextExtent,"shade %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002429 degrees,degrees);
cristy3ed852e2009-09-05 21:47:34 +00002430 break;
2431 }
2432 case RaisePreview:
2433 {
2434 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2435 if (preview_image == (Image *) NULL)
2436 break;
cristybb503372010-05-27 20:51:26 +00002437 geometry.width=(size_t) (2*i+2);
2438 geometry.height=(size_t) (2*i+2);
cristy3ed852e2009-09-05 21:47:34 +00002439 geometry.x=i/2;
2440 geometry.y=i/2;
cristy6170ac32011-08-28 14:15:37 +00002441 (void) RaiseImage(preview_image,&geometry,MagickTrue,exception);
cristyb51dff52011-05-19 16:55:47 +00002442 (void) FormatLocaleString(label,MaxTextExtent,
cristy6d8abba2010-06-03 01:10:47 +00002443 "raise %.20gx%.20g%+.20g%+.20g",(double) geometry.width,(double)
cristye8c25f92010-06-03 00:53:06 +00002444 geometry.height,(double) geometry.x,(double) geometry.y);
cristy3ed852e2009-09-05 21:47:34 +00002445 break;
2446 }
2447 case SegmentPreview:
2448 {
2449 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2450 if (preview_image == (Image *) NULL)
2451 break;
2452 threshold+=0.4f;
cristyc511e882012-04-16 21:11:14 +00002453 (void) SegmentImage(preview_image,sRGBColorspace,MagickFalse,threshold,
cristy018f07f2011-09-04 21:15:19 +00002454 threshold,exception);
cristyb51dff52011-05-19 16:55:47 +00002455 (void) FormatLocaleString(label,MaxTextExtent,"segment %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002456 threshold,threshold);
2457 break;
2458 }
2459 case SwirlPreview:
2460 {
cristy76f512e2011-09-12 01:26:56 +00002461 preview_image=SwirlImage(thumbnail,degrees,image->interpolate,
2462 exception);
cristyb51dff52011-05-19 16:55:47 +00002463 (void) FormatLocaleString(label,MaxTextExtent,"swirl %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002464 degrees+=45.0;
2465 break;
2466 }
2467 case ImplodePreview:
2468 {
2469 degrees+=0.1f;
cristy76f512e2011-09-12 01:26:56 +00002470 preview_image=ImplodeImage(thumbnail,degrees,image->interpolate,
2471 exception);
cristyb51dff52011-05-19 16:55:47 +00002472 (void) FormatLocaleString(label,MaxTextExtent,"implode %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002473 break;
2474 }
2475 case WavePreview:
2476 {
2477 degrees+=5.0f;
cristy5c4e2582011-09-11 19:21:03 +00002478 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,
2479 image->interpolate,exception);
cristyb51dff52011-05-19 16:55:47 +00002480 (void) FormatLocaleString(label,MaxTextExtent,"wave %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002481 0.5*degrees,2.0*degrees);
cristy3ed852e2009-09-05 21:47:34 +00002482 break;
2483 }
2484 case OilPaintPreview:
2485 {
cristy14973ba2011-08-27 23:48:07 +00002486 preview_image=OilPaintImage(thumbnail,(double) radius,(double) sigma,
2487 exception);
2488 (void) FormatLocaleString(label,MaxTextExtent,"charcoal %gx%g",
2489 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002490 break;
2491 }
2492 case CharcoalDrawingPreview:
2493 {
2494 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
cristyaa2c16c2012-03-25 22:21:35 +00002495 exception);
cristyb51dff52011-05-19 16:55:47 +00002496 (void) FormatLocaleString(label,MaxTextExtent,"charcoal %gx%g",
cristy8cd5b312010-01-07 01:10:24 +00002497 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002498 break;
2499 }
2500 case JPEGPreview:
2501 {
2502 char
2503 filename[MaxTextExtent];
2504
2505 int
2506 file;
2507
2508 MagickBooleanType
2509 status;
2510
2511 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2512 if (preview_image == (Image *) NULL)
2513 break;
cristybb503372010-05-27 20:51:26 +00002514 preview_info->quality=(size_t) percentage;
cristyb51dff52011-05-19 16:55:47 +00002515 (void) FormatLocaleString(factor,MaxTextExtent,"%.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00002516 preview_info->quality);
cristy3ed852e2009-09-05 21:47:34 +00002517 file=AcquireUniqueFileResource(filename);
2518 if (file != -1)
2519 file=close(file)-1;
cristyb51dff52011-05-19 16:55:47 +00002520 (void) FormatLocaleString(preview_image->filename,MaxTextExtent,
cristy3ed852e2009-09-05 21:47:34 +00002521 "jpeg:%s",filename);
cristy6f9e0d32011-08-28 16:32:09 +00002522 status=WriteImage(preview_info,preview_image,exception);
cristy3ed852e2009-09-05 21:47:34 +00002523 if (status != MagickFalse)
2524 {
2525 Image
2526 *quality_image;
2527
2528 (void) CopyMagickString(preview_info->filename,
2529 preview_image->filename,MaxTextExtent);
2530 quality_image=ReadImage(preview_info,exception);
2531 if (quality_image != (Image *) NULL)
2532 {
2533 preview_image=DestroyImage(preview_image);
2534 preview_image=quality_image;
2535 }
2536 }
2537 (void) RelinquishUniqueFileResource(preview_image->filename);
2538 if ((GetBlobSize(preview_image)/1024) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00002539 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%gmb ",
cristy3ed852e2009-09-05 21:47:34 +00002540 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
2541 1024.0/1024.0);
2542 else
2543 if (GetBlobSize(preview_image) >= 1024)
cristyb51dff52011-05-19 16:55:47 +00002544 (void) FormatLocaleString(label,MaxTextExtent,
cristye7f51092010-01-17 00:39:37 +00002545 "quality %s\n%gkb ",factor,(double) ((MagickOffsetType)
cristy8cd5b312010-01-07 01:10:24 +00002546 GetBlobSize(preview_image))/1024.0);
cristy3ed852e2009-09-05 21:47:34 +00002547 else
cristyb51dff52011-05-19 16:55:47 +00002548 (void) FormatLocaleString(label,MaxTextExtent,"quality %s\n%.20gb ",
cristy54ea5732011-06-10 12:39:53 +00002549 factor,(double) ((MagickOffsetType) GetBlobSize(thumbnail)));
cristy3ed852e2009-09-05 21:47:34 +00002550 break;
2551 }
2552 }
2553 thumbnail=DestroyImage(thumbnail);
2554 percentage+=12.5;
2555 radius+=0.5;
2556 sigma+=0.25;
2557 if (preview_image == (Image *) NULL)
2558 break;
2559 (void) DeleteImageProperty(preview_image,"label");
cristyd15e6592011-10-15 00:13:06 +00002560 (void) SetImageProperty(preview_image,"label",label,exception);
cristy3ed852e2009-09-05 21:47:34 +00002561 AppendImageToList(&images,preview_image);
cristybb503372010-05-27 20:51:26 +00002562 proceed=SetImageProgress(image,PreviewImageTag,(MagickOffsetType) i,
2563 NumberTiles);
cristy3ed852e2009-09-05 21:47:34 +00002564 if (proceed == MagickFalse)
2565 break;
2566 }
2567 if (images == (Image *) NULL)
2568 {
2569 preview_info=DestroyImageInfo(preview_info);
2570 return((Image *) NULL);
2571 }
2572 /*
2573 Create the montage.
2574 */
2575 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
2576 (void) CopyMagickString(montage_info->filename,image->filename,MaxTextExtent);
2577 montage_info->shadow=MagickTrue;
2578 (void) CloneString(&montage_info->tile,"3x3");
2579 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
2580 (void) CloneString(&montage_info->frame,DefaultTileFrame);
2581 montage_image=MontageImages(images,montage_info,exception);
2582 montage_info=DestroyMontageInfo(montage_info);
2583 images=DestroyImageList(images);
2584 if (montage_image == (Image *) NULL)
2585 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2586 if (montage_image->montage != (char *) NULL)
2587 {
2588 /*
2589 Free image directory.
2590 */
2591 montage_image->montage=(char *) RelinquishMagickMemory(
2592 montage_image->montage);
2593 if (image->directory != (char *) NULL)
2594 montage_image->directory=(char *) RelinquishMagickMemory(
2595 montage_image->directory);
2596 }
2597 preview_info=DestroyImageInfo(preview_info);
2598 return(montage_image);
2599}
2600
2601/*
2602%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2603% %
2604% %
2605% %
2606% R a d i a l B l u r I m a g e %
2607% %
2608% %
2609% %
2610%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2611%
2612% RadialBlurImage() applies a radial blur to the image.
2613%
2614% Andrew Protano contributed this effect.
2615%
2616% The format of the RadialBlurImage method is:
2617%
2618% Image *RadialBlurImage(const Image *image,const double angle,
cristyaa2c16c2012-03-25 22:21:35 +00002619% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002620%
2621% A description of each parameter follows:
2622%
2623% o image: the image.
2624%
cristy3ed852e2009-09-05 21:47:34 +00002625% o angle: the angle of the radial blur.
2626%
cristy6435bd92011-09-10 02:10:07 +00002627% o blur: the blur.
2628%
cristy3ed852e2009-09-05 21:47:34 +00002629% o exception: return any errors or warnings in this structure.
2630%
2631*/
cristy4282c702011-11-21 00:01:06 +00002632MagickExport Image *RadialBlurImage(const Image *image,const double angle,
cristyaa2c16c2012-03-25 22:21:35 +00002633 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002634{
cristyc4c8d132010-01-07 01:58:38 +00002635 CacheView
2636 *blur_view,
cristy8b49f382012-01-17 03:11:03 +00002637 *image_view,
2638 *radial_view;
cristyc4c8d132010-01-07 01:58:38 +00002639
cristy3ed852e2009-09-05 21:47:34 +00002640 Image
2641 *blur_image;
2642
cristy3ed852e2009-09-05 21:47:34 +00002643 MagickBooleanType
2644 status;
2645
cristybb503372010-05-27 20:51:26 +00002646 MagickOffsetType
2647 progress;
2648
cristya19f1d72012-08-07 18:24:38 +00002649 double
cristy3ed852e2009-09-05 21:47:34 +00002650 blur_radius,
2651 *cos_theta,
2652 offset,
2653 *sin_theta,
2654 theta;
2655
2656 PointInfo
2657 blur_center;
2658
cristybb503372010-05-27 20:51:26 +00002659 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002660 i;
2661
cristybb503372010-05-27 20:51:26 +00002662 size_t
cristy3ed852e2009-09-05 21:47:34 +00002663 n;
2664
cristybb503372010-05-27 20:51:26 +00002665 ssize_t
2666 y;
2667
cristy3ed852e2009-09-05 21:47:34 +00002668 /*
2669 Allocate blur image.
2670 */
2671 assert(image != (Image *) NULL);
2672 assert(image->signature == MagickSignature);
2673 if (image->debug != MagickFalse)
2674 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2675 assert(exception != (ExceptionInfo *) NULL);
2676 assert(exception->signature == MagickSignature);
cristy1e7aa312011-09-10 20:01:36 +00002677 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00002678 if (blur_image == (Image *) NULL)
2679 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00002680 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00002681 {
cristy3ed852e2009-09-05 21:47:34 +00002682 blur_image=DestroyImage(blur_image);
2683 return((Image *) NULL);
2684 }
2685 blur_center.x=(double) image->columns/2.0;
2686 blur_center.y=(double) image->rows/2.0;
2687 blur_radius=hypot(blur_center.x,blur_center.y);
cristy117ff172010-08-15 21:35:32 +00002688 n=(size_t) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+2UL);
cristya19f1d72012-08-07 18:24:38 +00002689 theta=DegreesToRadians(angle)/(double) (n-1);
2690 cos_theta=(double *) AcquireQuantumMemory((size_t) n,
cristy3ed852e2009-09-05 21:47:34 +00002691 sizeof(*cos_theta));
cristya19f1d72012-08-07 18:24:38 +00002692 sin_theta=(double *) AcquireQuantumMemory((size_t) n,
cristy3ed852e2009-09-05 21:47:34 +00002693 sizeof(*sin_theta));
cristya19f1d72012-08-07 18:24:38 +00002694 if ((cos_theta == (double *) NULL) ||
2695 (sin_theta == (double *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00002696 {
2697 blur_image=DestroyImage(blur_image);
2698 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2699 }
cristya19f1d72012-08-07 18:24:38 +00002700 offset=theta*(double) (n-1)/2.0;
cristybb503372010-05-27 20:51:26 +00002701 for (i=0; i < (ssize_t) n; i++)
cristy3ed852e2009-09-05 21:47:34 +00002702 {
2703 cos_theta[i]=cos((double) (theta*i-offset));
2704 sin_theta[i]=sin((double) (theta*i-offset));
2705 }
2706 /*
2707 Radial blur image.
2708 */
2709 status=MagickTrue;
2710 progress=0;
cristydb070952012-04-20 14:33:00 +00002711 image_view=AcquireVirtualCacheView(image,exception);
2712 radial_view=AcquireVirtualCacheView(image,exception);
2713 blur_view=AcquireAuthenticCacheView(blur_image,exception);
cristyb5d5f722009-11-04 03:03:49 +00002714#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00002715 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy4ee2b0c2012-05-15 00:30:35 +00002716 dynamic_number_threads(image,image->columns,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00002717#endif
cristy1e7aa312011-09-10 20:01:36 +00002718 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002719 {
cristy1e7aa312011-09-10 20:01:36 +00002720 register const Quantum
2721 *restrict p;
2722
cristy4c08aed2011-07-01 19:47:50 +00002723 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002724 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002725
cristy117ff172010-08-15 21:35:32 +00002726 register ssize_t
2727 x;
2728
cristy3ed852e2009-09-05 21:47:34 +00002729 if (status == MagickFalse)
2730 continue;
cristy8b49f382012-01-17 03:11:03 +00002731 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
2732 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +00002733 exception);
cristy1e7aa312011-09-10 20:01:36 +00002734 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00002735 {
2736 status=MagickFalse;
2737 continue;
2738 }
cristy1e7aa312011-09-10 20:01:36 +00002739 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002740 {
cristya19f1d72012-08-07 18:24:38 +00002741 double
cristy3ed852e2009-09-05 21:47:34 +00002742 radius;
2743
cristy3ed852e2009-09-05 21:47:34 +00002744 PointInfo
2745 center;
2746
cristybb503372010-05-27 20:51:26 +00002747 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002748 i;
2749
cristybb503372010-05-27 20:51:26 +00002750 size_t
cristy3ed852e2009-09-05 21:47:34 +00002751 step;
2752
2753 center.x=(double) x-blur_center.x;
2754 center.y=(double) y-blur_center.y;
2755 radius=hypot((double) center.x,center.y);
2756 if (radius == 0)
2757 step=1;
2758 else
2759 {
cristybb503372010-05-27 20:51:26 +00002760 step=(size_t) (blur_radius/radius);
cristy3ed852e2009-09-05 21:47:34 +00002761 if (step == 0)
2762 step=1;
2763 else
2764 if (step >= n)
2765 step=n-1;
2766 }
cristy1e7aa312011-09-10 20:01:36 +00002767 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2768 {
cristya19f1d72012-08-07 18:24:38 +00002769 double
cristy1e7aa312011-09-10 20:01:36 +00002770 gamma,
2771 pixel;
cristy3ed852e2009-09-05 21:47:34 +00002772
cristy1e7aa312011-09-10 20:01:36 +00002773 PixelChannel
2774 channel;
2775
2776 PixelTrait
2777 blur_traits,
2778 traits;
2779
2780 register const Quantum
2781 *restrict r;
2782
2783 register ssize_t
2784 j;
2785
cristye2a912b2011-12-05 20:02:07 +00002786 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +00002787 traits=GetPixelChannelMapTraits(image,channel);
cristy1e7aa312011-09-10 20:01:36 +00002788 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
2789 if ((traits == UndefinedPixelTrait) ||
2790 (blur_traits == UndefinedPixelTrait))
2791 continue;
cristy1eced092012-08-10 23:10:56 +00002792 if (((blur_traits & CopyPixelTrait) != 0) ||
2793 (GetPixelMask(image,p) != 0))
cristy3ed852e2009-09-05 21:47:34 +00002794 {
cristy0beccfa2011-09-25 20:47:53 +00002795 SetPixelChannel(blur_image,channel,p[i],q);
cristy1e7aa312011-09-10 20:01:36 +00002796 continue;
cristy3ed852e2009-09-05 21:47:34 +00002797 }
cristy1e7aa312011-09-10 20:01:36 +00002798 gamma=0.0;
cristyaa2c16c2012-03-25 22:21:35 +00002799 pixel=0.0;
cristy1e7aa312011-09-10 20:01:36 +00002800 if ((blur_traits & BlendPixelTrait) == 0)
2801 {
2802 for (j=0; j < (ssize_t) n; j+=(ssize_t) step)
2803 {
cristy8b49f382012-01-17 03:11:03 +00002804 r=GetCacheViewVirtualPixels(radial_view, (ssize_t) (blur_center.x+
cristy1e7aa312011-09-10 20:01:36 +00002805 center.x*cos_theta[j]-center.y*sin_theta[j]+0.5),(ssize_t)
2806 (blur_center.y+center.x*sin_theta[j]+center.y*cos_theta[j]+0.5),
2807 1,1,exception);
2808 if (r == (const Quantum *) NULL)
2809 {
2810 status=MagickFalse;
2811 continue;
2812 }
2813 pixel+=r[i];
2814 gamma++;
2815 }
cristyc58380a2012-06-03 15:12:30 +00002816 gamma=MagickEpsilonReciprocal(gamma);
cristy0beccfa2011-09-25 20:47:53 +00002817 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy1e7aa312011-09-10 20:01:36 +00002818 continue;
2819 }
2820 for (j=0; j < (ssize_t) n; j+=(ssize_t) step)
2821 {
cristy8b49f382012-01-17 03:11:03 +00002822 r=GetCacheViewVirtualPixels(radial_view, (ssize_t) (blur_center.x+
cristy1e7aa312011-09-10 20:01:36 +00002823 center.x*cos_theta[j]-center.y*sin_theta[j]+0.5),(ssize_t)
2824 (blur_center.y+center.x*sin_theta[j]+center.y*cos_theta[j]+0.5),
2825 1,1,exception);
2826 if (r == (const Quantum *) NULL)
2827 {
2828 status=MagickFalse;
2829 continue;
2830 }
2831 pixel+=GetPixelAlpha(image,r)*r[i];
2832 gamma+=GetPixelAlpha(image,r);
cristy3ed852e2009-09-05 21:47:34 +00002833 }
cristyc58380a2012-06-03 15:12:30 +00002834 gamma=MagickEpsilonReciprocal(gamma);
cristy0beccfa2011-09-25 20:47:53 +00002835 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy1e7aa312011-09-10 20:01:36 +00002836 }
2837 p+=GetPixelChannels(image);
cristyed231572011-07-14 02:18:59 +00002838 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00002839 }
2840 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
2841 status=MagickFalse;
2842 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2843 {
2844 MagickBooleanType
2845 proceed;
2846
cristyb5d5f722009-11-04 03:03:49 +00002847#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf8561542012-01-24 00:26:46 +00002848 #pragma omp critical (MagickCore_RadialBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00002849#endif
2850 proceed=SetImageProgress(image,BlurImageTag,progress++,image->rows);
2851 if (proceed == MagickFalse)
2852 status=MagickFalse;
2853 }
2854 }
2855 blur_view=DestroyCacheView(blur_view);
cristy8b49f382012-01-17 03:11:03 +00002856 radial_view=DestroyCacheView(radial_view);
cristy3ed852e2009-09-05 21:47:34 +00002857 image_view=DestroyCacheView(image_view);
cristya19f1d72012-08-07 18:24:38 +00002858 cos_theta=(double *) RelinquishMagickMemory(cos_theta);
2859 sin_theta=(double *) RelinquishMagickMemory(sin_theta);
cristy3ed852e2009-09-05 21:47:34 +00002860 if (status == MagickFalse)
2861 blur_image=DestroyImage(blur_image);
2862 return(blur_image);
2863}
2864
2865/*
2866%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2867% %
2868% %
2869% %
cristy3ed852e2009-09-05 21:47:34 +00002870% S e l e c t i v e B l u r I m a g e %
2871% %
2872% %
2873% %
2874%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2875%
2876% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
2877% It is similar to the unsharpen mask that sharpens everything with contrast
2878% above a certain threshold.
2879%
2880% The format of the SelectiveBlurImage method is:
2881%
2882% Image *SelectiveBlurImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +00002883% const double sigma,const double threshold,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002884%
2885% A description of each parameter follows:
2886%
2887% o image: the image.
2888%
cristy3ed852e2009-09-05 21:47:34 +00002889% o radius: the radius of the Gaussian, in pixels, not counting the center
2890% pixel.
2891%
2892% o sigma: the standard deviation of the Gaussian, in pixels.
2893%
2894% o threshold: only pixels within this contrast threshold are included
2895% in the blur operation.
2896%
2897% o exception: return any errors or warnings in this structure.
2898%
2899*/
cristy4282c702011-11-21 00:01:06 +00002900MagickExport Image *SelectiveBlurImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +00002901 const double sigma,const double threshold,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002902{
2903#define SelectiveBlurImageTag "SelectiveBlur/Image"
2904
cristy47e00502009-12-17 19:19:57 +00002905 CacheView
2906 *blur_view,
cristy9c7cf372012-08-08 22:58:27 +00002907 *image_view,
2908 *luminance_view;
cristy47e00502009-12-17 19:19:57 +00002909
cristy3ed852e2009-09-05 21:47:34 +00002910 double
cristy3ed852e2009-09-05 21:47:34 +00002911 *kernel;
2912
2913 Image
cristy9c7cf372012-08-08 22:58:27 +00002914 *blur_image,
2915 *luminance_image;
cristy3ed852e2009-09-05 21:47:34 +00002916
cristy3ed852e2009-09-05 21:47:34 +00002917 MagickBooleanType
2918 status;
2919
cristybb503372010-05-27 20:51:26 +00002920 MagickOffsetType
2921 progress;
2922
cristybb503372010-05-27 20:51:26 +00002923 register ssize_t
cristy47e00502009-12-17 19:19:57 +00002924 i;
cristy3ed852e2009-09-05 21:47:34 +00002925
cristybb503372010-05-27 20:51:26 +00002926 size_t
cristy3ed852e2009-09-05 21:47:34 +00002927 width;
2928
cristybb503372010-05-27 20:51:26 +00002929 ssize_t
cristyc8523c12011-09-13 00:02:53 +00002930 center,
cristybb503372010-05-27 20:51:26 +00002931 j,
2932 u,
2933 v,
2934 y;
2935
cristy3ed852e2009-09-05 21:47:34 +00002936 /*
2937 Initialize blur image attributes.
2938 */
2939 assert(image != (Image *) NULL);
2940 assert(image->signature == MagickSignature);
2941 if (image->debug != MagickFalse)
2942 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2943 assert(exception != (ExceptionInfo *) NULL);
2944 assert(exception->signature == MagickSignature);
2945 width=GetOptimalKernelWidth1D(radius,sigma);
cristy8b49f382012-01-17 03:11:03 +00002946 kernel=(double *) AcquireAlignedMemory((size_t) width,width*sizeof(*kernel));
cristy3ed852e2009-09-05 21:47:34 +00002947 if (kernel == (double *) NULL)
2948 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristybb503372010-05-27 20:51:26 +00002949 j=(ssize_t) width/2;
cristy3ed852e2009-09-05 21:47:34 +00002950 i=0;
cristy47e00502009-12-17 19:19:57 +00002951 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00002952 {
cristy47e00502009-12-17 19:19:57 +00002953 for (u=(-j); u <= j; u++)
cristy4205a3c2010-09-12 20:19:59 +00002954 kernel[i++]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
2955 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00002956 }
2957 if (image->debug != MagickFalse)
2958 {
2959 char
2960 format[MaxTextExtent],
2961 *message;
2962
cristy117ff172010-08-15 21:35:32 +00002963 register const double
2964 *k;
2965
cristybb503372010-05-27 20:51:26 +00002966 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002967 u,
2968 v;
2969
cristy3ed852e2009-09-05 21:47:34 +00002970 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00002971 " SelectiveBlurImage with %.20gx%.20g kernel:",(double) width,(double)
2972 width);
cristy3ed852e2009-09-05 21:47:34 +00002973 message=AcquireString("");
2974 k=kernel;
cristybb503372010-05-27 20:51:26 +00002975 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00002976 {
2977 *message='\0';
cristyb51dff52011-05-19 16:55:47 +00002978 (void) FormatLocaleString(format,MaxTextExtent,"%.20g: ",(double) v);
cristy3ed852e2009-09-05 21:47:34 +00002979 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00002980 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00002981 {
cristyb51dff52011-05-19 16:55:47 +00002982 (void) FormatLocaleString(format,MaxTextExtent,"%+f ",*k++);
cristy3ed852e2009-09-05 21:47:34 +00002983 (void) ConcatenateString(&message,format);
2984 }
2985 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
2986 }
2987 message=DestroyString(message);
2988 }
cristy1e7aa312011-09-10 20:01:36 +00002989 blur_image=CloneImage(image,image->columns,image->rows,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00002990 if (blur_image == (Image *) NULL)
2991 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00002992 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00002993 {
cristy3ed852e2009-09-05 21:47:34 +00002994 blur_image=DestroyImage(blur_image);
cristy9c7cf372012-08-08 22:58:27 +00002995 kernel=(double *) RelinquishAlignedMemory(kernel);
2996 return((Image *) NULL);
2997 }
2998 luminance_image=CloneImage(image,0,0,MagickTrue,exception);
2999 if (luminance_image == (Image *) NULL)
3000 {
3001 blur_image=DestroyImage(blur_image);
3002 kernel=(double *) RelinquishAlignedMemory(kernel);
3003 return((Image *) NULL);
3004 }
3005 status=TransformImageColorspace(luminance_image,GRAYColorspace,exception);
3006 if (status == MagickFalse)
3007 {
3008 luminance_image=DestroyImage(luminance_image);
3009 blur_image=DestroyImage(blur_image);
3010 kernel=(double *) RelinquishAlignedMemory(kernel);
cristy3ed852e2009-09-05 21:47:34 +00003011 return((Image *) NULL);
3012 }
3013 /*
3014 Threshold blur image.
3015 */
3016 status=MagickTrue;
3017 progress=0;
cristyc8523c12011-09-13 00:02:53 +00003018 center=(ssize_t) (GetPixelChannels(image)*(image->columns+width)*(width/2L)+
3019 GetPixelChannels(image)*(width/2L));
cristydb070952012-04-20 14:33:00 +00003020 image_view=AcquireVirtualCacheView(image,exception);
cristy9c7cf372012-08-08 22:58:27 +00003021 luminance_view=AcquireVirtualCacheView(luminance_image,exception);
cristydb070952012-04-20 14:33:00 +00003022 blur_view=AcquireAuthenticCacheView(blur_image,exception);
cristy9c7cf372012-08-08 22:58:27 +00003023#if defined(MMAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00003024 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy4ee2b0c2012-05-15 00:30:35 +00003025 dynamic_number_threads(image,image->columns,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00003026#endif
cristybb503372010-05-27 20:51:26 +00003027 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003028 {
cristy4c08aed2011-07-01 19:47:50 +00003029 double
3030 contrast;
3031
cristy3ed852e2009-09-05 21:47:34 +00003032 MagickBooleanType
3033 sync;
3034
cristy4c08aed2011-07-01 19:47:50 +00003035 register const Quantum
cristy9c7cf372012-08-08 22:58:27 +00003036 *restrict l,
cristyc47d1f82009-11-26 01:44:43 +00003037 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003038
cristy4c08aed2011-07-01 19:47:50 +00003039 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003040 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003041
cristy117ff172010-08-15 21:35:32 +00003042 register ssize_t
3043 x;
3044
cristy3ed852e2009-09-05 21:47:34 +00003045 if (status == MagickFalse)
3046 continue;
cristy117ff172010-08-15 21:35:32 +00003047 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) width/2L),y-(ssize_t)
3048 (width/2L),image->columns+width,width,exception);
cristy9c7cf372012-08-08 22:58:27 +00003049 l=GetCacheViewVirtualPixels(luminance_view,-((ssize_t) width/2L),y-(ssize_t)
3050 (width/2L),luminance_image->columns+width,width,exception);
cristy8b49f382012-01-17 03:11:03 +00003051 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +00003052 exception);
cristy4c08aed2011-07-01 19:47:50 +00003053 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003054 {
3055 status=MagickFalse;
3056 continue;
3057 }
cristybb503372010-05-27 20:51:26 +00003058 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003059 {
cristy9c7cf372012-08-08 22:58:27 +00003060 double
3061 intensity;
3062
cristybb503372010-05-27 20:51:26 +00003063 register ssize_t
cristy1e7aa312011-09-10 20:01:36 +00003064 i;
cristy3ed852e2009-09-05 21:47:34 +00003065
cristyf13c5942012-08-08 23:50:11 +00003066 intensity=GetPixelIntensity(image,p+center);
cristy1e7aa312011-09-10 20:01:36 +00003067 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3068 {
cristya19f1d72012-08-07 18:24:38 +00003069 double
cristy1e7aa312011-09-10 20:01:36 +00003070 alpha,
3071 gamma,
cristy1e7aa312011-09-10 20:01:36 +00003072 pixel;
cristy117ff172010-08-15 21:35:32 +00003073
cristy1e7aa312011-09-10 20:01:36 +00003074 PixelChannel
3075 channel;
3076
3077 PixelTrait
3078 blur_traits,
3079 traits;
3080
3081 register const double
3082 *restrict k;
3083
3084 register const Quantum
cristy9c7cf372012-08-08 22:58:27 +00003085 *restrict luminance_pixels,
cristy1e7aa312011-09-10 20:01:36 +00003086 *restrict pixels;
3087
3088 register ssize_t
3089 u;
3090
3091 ssize_t
3092 v;
3093
cristye2a912b2011-12-05 20:02:07 +00003094 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +00003095 traits=GetPixelChannelMapTraits(image,channel);
cristy1e7aa312011-09-10 20:01:36 +00003096 blur_traits=GetPixelChannelMapTraits(blur_image,channel);
3097 if ((traits == UndefinedPixelTrait) ||
3098 (blur_traits == UndefinedPixelTrait))
3099 continue;
cristy1eced092012-08-10 23:10:56 +00003100 if (((blur_traits & CopyPixelTrait) != 0) ||
3101 (GetPixelMask(image,p) != 0))
cristy3ed852e2009-09-05 21:47:34 +00003102 {
cristy0beccfa2011-09-25 20:47:53 +00003103 SetPixelChannel(blur_image,channel,p[center+i],q);
cristy1e7aa312011-09-10 20:01:36 +00003104 continue;
cristy3ed852e2009-09-05 21:47:34 +00003105 }
cristy1e7aa312011-09-10 20:01:36 +00003106 k=kernel;
cristyaa2c16c2012-03-25 22:21:35 +00003107 pixel=0.0;
cristy1e7aa312011-09-10 20:01:36 +00003108 pixels=p;
cristy9c7cf372012-08-08 22:58:27 +00003109 luminance_pixels=l;
cristy1e7aa312011-09-10 20:01:36 +00003110 gamma=0.0;
3111 if ((blur_traits & BlendPixelTrait) == 0)
cristy3ed852e2009-09-05 21:47:34 +00003112 {
cristy1e7aa312011-09-10 20:01:36 +00003113 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003114 {
cristy1e7aa312011-09-10 20:01:36 +00003115 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003116 {
cristyf13c5942012-08-08 23:50:11 +00003117 contrast=GetPixelIntensity(luminance_image,luminance_pixels)-
3118 intensity;
cristy1e7aa312011-09-10 20:01:36 +00003119 if (fabs(contrast) < threshold)
3120 {
3121 pixel+=(*k)*pixels[i];
3122 gamma+=(*k);
3123 }
3124 k++;
3125 pixels+=GetPixelChannels(image);
cristy9c7cf372012-08-08 22:58:27 +00003126 luminance_pixels+=GetPixelChannels(luminance_image);
cristy3ed852e2009-09-05 21:47:34 +00003127 }
cristy1e7aa312011-09-10 20:01:36 +00003128 pixels+=image->columns*GetPixelChannels(image);
cristy9c7cf372012-08-08 22:58:27 +00003129 luminance_pixels+=luminance_image->columns*
3130 GetPixelChannels(luminance_image);
cristy3ed852e2009-09-05 21:47:34 +00003131 }
cristy1e7aa312011-09-10 20:01:36 +00003132 if (fabs((double) gamma) < MagickEpsilon)
3133 {
cristy0beccfa2011-09-25 20:47:53 +00003134 SetPixelChannel(blur_image,channel,p[center+i],q);
cristy1e7aa312011-09-10 20:01:36 +00003135 continue;
3136 }
cristyc58380a2012-06-03 15:12:30 +00003137 gamma=MagickEpsilonReciprocal(gamma);
cristy0beccfa2011-09-25 20:47:53 +00003138 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy1e7aa312011-09-10 20:01:36 +00003139 continue;
3140 }
3141 for (v=0; v < (ssize_t) width; v++)
3142 {
3143 for (u=0; u < (ssize_t) width; u++)
3144 {
3145 contrast=GetPixelIntensity(image,pixels)-intensity;
3146 if (fabs(contrast) < threshold)
3147 {
cristya19f1d72012-08-07 18:24:38 +00003148 alpha=(double) (QuantumScale*
cristy1e7aa312011-09-10 20:01:36 +00003149 GetPixelAlpha(image,pixels));
3150 pixel+=(*k)*alpha*pixels[i];
3151 gamma+=(*k)*alpha;
3152 }
3153 k++;
3154 pixels+=GetPixelChannels(image);
cristy9c7cf372012-08-08 22:58:27 +00003155 luminance_pixels+=GetPixelChannels(luminance_image);
cristy1e7aa312011-09-10 20:01:36 +00003156 }
3157 pixels+=image->columns*GetPixelChannels(image);
cristy9c7cf372012-08-08 22:58:27 +00003158 luminance_pixels+=luminance_image->columns*
3159 GetPixelChannels(luminance_image);
cristy3ed852e2009-09-05 21:47:34 +00003160 }
cristy1e7aa312011-09-10 20:01:36 +00003161 if (fabs((double) gamma) < MagickEpsilon)
3162 {
cristy0beccfa2011-09-25 20:47:53 +00003163 SetPixelChannel(blur_image,channel,p[center+i],q);
cristy1e7aa312011-09-10 20:01:36 +00003164 continue;
3165 }
cristyc58380a2012-06-03 15:12:30 +00003166 gamma=MagickEpsilonReciprocal(gamma);
cristy0beccfa2011-09-25 20:47:53 +00003167 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy1e7aa312011-09-10 20:01:36 +00003168 }
cristyed231572011-07-14 02:18:59 +00003169 p+=GetPixelChannels(image);
cristy9c7cf372012-08-08 22:58:27 +00003170 l+=GetPixelChannels(luminance_image);
cristyed231572011-07-14 02:18:59 +00003171 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003172 }
3173 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
3174 if (sync == MagickFalse)
3175 status=MagickFalse;
3176 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3177 {
3178 MagickBooleanType
3179 proceed;
3180
cristyb5d5f722009-11-04 03:03:49 +00003181#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf8561542012-01-24 00:26:46 +00003182 #pragma omp critical (MagickCore_SelectiveBlurImage)
cristy3ed852e2009-09-05 21:47:34 +00003183#endif
3184 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress++,
3185 image->rows);
3186 if (proceed == MagickFalse)
3187 status=MagickFalse;
3188 }
3189 }
3190 blur_image->type=image->type;
3191 blur_view=DestroyCacheView(blur_view);
3192 image_view=DestroyCacheView(image_view);
cristy9c7cf372012-08-08 22:58:27 +00003193 luminance_image=DestroyImage(luminance_image);
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 {
cristya19f1d72012-08-07 18:24:38 +00003294 double
cristy3ed852e2009-09-05 21:47:34 +00003295 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 }
cristy1e7aa312011-09-10 20:01:36 +00003364 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3365 {
3366 PixelChannel
3367 channel;
3368
3369 PixelTrait
3370 shade_traits,
3371 traits;
3372
cristye2a912b2011-12-05 20:02:07 +00003373 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +00003374 traits=GetPixelChannelMapTraits(image,channel);
cristy1e7aa312011-09-10 20:01:36 +00003375 shade_traits=GetPixelChannelMapTraits(shade_image,channel);
3376 if ((traits == UndefinedPixelTrait) ||
3377 (shade_traits == UndefinedPixelTrait))
3378 continue;
cristy1eced092012-08-10 23:10:56 +00003379 if (((shade_traits & CopyPixelTrait) != 0) ||
3380 (GetPixelMask(image,p) != 0))
cristy1e7aa312011-09-10 20:01:36 +00003381 {
cristy0beccfa2011-09-25 20:47:53 +00003382 SetPixelChannel(shade_image,channel,center[i],q);
cristy1e7aa312011-09-10 20:01:36 +00003383 continue;
3384 }
3385 if (gray != MagickFalse)
3386 {
cristy0beccfa2011-09-25 20:47:53 +00003387 SetPixelChannel(shade_image,channel,ClampToQuantum(shade),q);
cristy1e7aa312011-09-10 20:01:36 +00003388 continue;
3389 }
cristy0beccfa2011-09-25 20:47:53 +00003390 SetPixelChannel(shade_image,channel,ClampToQuantum(QuantumScale*shade*
3391 center[i]),q);
cristy1e7aa312011-09-10 20:01:36 +00003392 }
3393 pre+=GetPixelChannels(image);
3394 center+=GetPixelChannels(image);
3395 post+=GetPixelChannels(image);
cristyed231572011-07-14 02:18:59 +00003396 q+=GetPixelChannels(shade_image);
cristy3ed852e2009-09-05 21:47:34 +00003397 }
3398 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
3399 status=MagickFalse;
3400 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3401 {
3402 MagickBooleanType
3403 proceed;
3404
cristyb5d5f722009-11-04 03:03:49 +00003405#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf8561542012-01-24 00:26:46 +00003406 #pragma omp critical (MagickCore_ShadeImage)
cristy3ed852e2009-09-05 21:47:34 +00003407#endif
3408 proceed=SetImageProgress(image,ShadeImageTag,progress++,image->rows);
3409 if (proceed == MagickFalse)
3410 status=MagickFalse;
3411 }
3412 }
3413 shade_view=DestroyCacheView(shade_view);
3414 image_view=DestroyCacheView(image_view);
3415 if (status == MagickFalse)
3416 shade_image=DestroyImage(shade_image);
3417 return(shade_image);
3418}
3419
3420/*
3421%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3422% %
3423% %
3424% %
3425% S h a r p e n I m a g e %
3426% %
3427% %
3428% %
3429%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3430%
3431% SharpenImage() sharpens the image. We convolve the image with a Gaussian
3432% operator of the given radius and standard deviation (sigma). For
3433% reasonable results, radius should be larger than sigma. Use a radius of 0
3434% and SharpenImage() selects a suitable radius for you.
3435%
3436% Using a separable kernel would be faster, but the negative weights cancel
3437% out on the corners of the kernel producing often undesirable ringing in the
3438% filtered result; this can be avoided by using a 2D gaussian shaped image
3439% sharpening kernel instead.
3440%
3441% The format of the SharpenImage method is:
3442%
3443% Image *SharpenImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +00003444% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003445%
3446% A description of each parameter follows:
3447%
3448% o image: the image.
3449%
cristy3ed852e2009-09-05 21:47:34 +00003450% o radius: the radius of the Gaussian, in pixels, not counting the center
3451% pixel.
3452%
3453% o sigma: the standard deviation of the Laplacian, in pixels.
3454%
3455% o exception: return any errors or warnings in this structure.
3456%
3457*/
cristy3ed852e2009-09-05 21:47:34 +00003458MagickExport Image *SharpenImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +00003459 const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003460{
cristy3ed852e2009-09-05 21:47:34 +00003461 double
cristy47e00502009-12-17 19:19:57 +00003462 normalize;
cristy3ed852e2009-09-05 21:47:34 +00003463
3464 Image
3465 *sharp_image;
3466
cristy41cbe682011-07-15 19:12:37 +00003467 KernelInfo
3468 *kernel_info;
3469
cristybb503372010-05-27 20:51:26 +00003470 register ssize_t
cristy47e00502009-12-17 19:19:57 +00003471 i;
3472
cristybb503372010-05-27 20:51:26 +00003473 size_t
cristy3ed852e2009-09-05 21:47:34 +00003474 width;
3475
cristy117ff172010-08-15 21:35:32 +00003476 ssize_t
3477 j,
3478 u,
3479 v;
3480
cristy3ed852e2009-09-05 21:47:34 +00003481 assert(image != (const Image *) NULL);
3482 assert(image->signature == MagickSignature);
3483 if (image->debug != MagickFalse)
3484 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3485 assert(exception != (ExceptionInfo *) NULL);
3486 assert(exception->signature == MagickSignature);
3487 width=GetOptimalKernelWidth2D(radius,sigma);
cristy5e6be1e2011-07-16 01:23:39 +00003488 kernel_info=AcquireKernelInfo((const char *) NULL);
cristy41cbe682011-07-15 19:12:37 +00003489 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003490 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00003491 (void) ResetMagickMemory(kernel_info,0,sizeof(*kernel_info));
3492 kernel_info->width=width;
3493 kernel_info->height=width;
3494 kernel_info->signature=MagickSignature;
cristye8d93f62012-05-07 16:45:46 +00003495 kernel_info->values=(double *) AcquireAlignedMemory(
cristya96f2492011-12-14 18:25:41 +00003496 kernel_info->width,kernel_info->width*sizeof(*kernel_info->values));
cristye8d93f62012-05-07 16:45:46 +00003497 if (kernel_info->values == (double *) NULL)
cristy41cbe682011-07-15 19:12:37 +00003498 {
3499 kernel_info=DestroyKernelInfo(kernel_info);
3500 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3501 }
cristy3ed852e2009-09-05 21:47:34 +00003502 normalize=0.0;
cristy41cbe682011-07-15 19:12:37 +00003503 j=(ssize_t) kernel_info->width/2;
cristy47e00502009-12-17 19:19:57 +00003504 i=0;
3505 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003506 {
cristy47e00502009-12-17 19:19:57 +00003507 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00003508 {
cristy41cbe682011-07-15 19:12:37 +00003509 kernel_info->values[i]=(double) (-exp(-((double) u*u+v*v)/(2.0*
3510 MagickSigma*MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
3511 normalize+=kernel_info->values[i];
cristy3ed852e2009-09-05 21:47:34 +00003512 i++;
3513 }
3514 }
cristy41cbe682011-07-15 19:12:37 +00003515 kernel_info->values[i/2]=(double) ((-2.0)*normalize);
cristy5e6be1e2011-07-16 01:23:39 +00003516 sharp_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00003517 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00003518 return(sharp_image);
3519}
3520
3521/*
3522%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3523% %
3524% %
3525% %
3526% S p r e a d I m a g e %
3527% %
3528% %
3529% %
3530%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3531%
3532% SpreadImage() is a special effects method that randomly displaces each
3533% pixel in a block defined by the radius parameter.
3534%
3535% The format of the SpreadImage method is:
3536%
3537% Image *SpreadImage(const Image *image,const double radius,
cristy5c4e2582011-09-11 19:21:03 +00003538% const PixelInterpolateMethod method,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003539%
3540% A description of each parameter follows:
3541%
3542% o image: the image.
3543%
cristy5c4e2582011-09-11 19:21:03 +00003544% o radius: choose a random pixel in a neighborhood of this extent.
3545%
3546% o method: the pixel interpolation method.
cristy3ed852e2009-09-05 21:47:34 +00003547%
3548% o exception: return any errors or warnings in this structure.
3549%
3550*/
3551MagickExport Image *SpreadImage(const Image *image,const double radius,
cristy5c4e2582011-09-11 19:21:03 +00003552 const PixelInterpolateMethod method,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003553{
3554#define SpreadImageTag "Spread/Image"
3555
cristyfa112112010-01-04 17:48:07 +00003556 CacheView
cristy9f7e7cb2011-03-26 00:49:57 +00003557 *image_view,
3558 *spread_view;
cristyfa112112010-01-04 17:48:07 +00003559
cristy3ed852e2009-09-05 21:47:34 +00003560 Image
3561 *spread_image;
3562
cristy3ed852e2009-09-05 21:47:34 +00003563 MagickBooleanType
3564 status;
3565
cristybb503372010-05-27 20:51:26 +00003566 MagickOffsetType
3567 progress;
3568
cristy3ed852e2009-09-05 21:47:34 +00003569 RandomInfo
cristyfa112112010-01-04 17:48:07 +00003570 **restrict random_info;
cristy3ed852e2009-09-05 21:47:34 +00003571
cristybb503372010-05-27 20:51:26 +00003572 size_t
cristy3ed852e2009-09-05 21:47:34 +00003573 width;
3574
cristybb503372010-05-27 20:51:26 +00003575 ssize_t
3576 y;
3577
cristy57340e02012-05-05 00:53:23 +00003578 unsigned long
3579 key;
3580
cristy3ed852e2009-09-05 21:47:34 +00003581 /*
3582 Initialize spread image attributes.
3583 */
3584 assert(image != (Image *) NULL);
3585 assert(image->signature == MagickSignature);
3586 if (image->debug != MagickFalse)
3587 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3588 assert(exception != (ExceptionInfo *) NULL);
3589 assert(exception->signature == MagickSignature);
3590 spread_image=CloneImage(image,image->columns,image->rows,MagickTrue,
3591 exception);
3592 if (spread_image == (Image *) NULL)
3593 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003594 if (SetImageStorageClass(spread_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003595 {
cristy3ed852e2009-09-05 21:47:34 +00003596 spread_image=DestroyImage(spread_image);
3597 return((Image *) NULL);
3598 }
3599 /*
3600 Spread image.
3601 */
3602 status=MagickTrue;
3603 progress=0;
cristy3ed852e2009-09-05 21:47:34 +00003604 width=GetOptimalKernelWidth1D(radius,0.5);
cristy3ed852e2009-09-05 21:47:34 +00003605 random_info=AcquireRandomInfoThreadSet();
cristy57340e02012-05-05 00:53:23 +00003606 key=GetRandomSecretKey(random_info[0]);
cristydb070952012-04-20 14:33:00 +00003607 image_view=AcquireVirtualCacheView(image,exception);
3608 spread_view=AcquireAuthenticCacheView(spread_image,exception);
cristyb557a152011-02-22 12:14:30 +00003609#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristybd065992012-08-09 15:27:39 +00003610 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy4ee2b0c2012-05-15 00:30:35 +00003611 dynamic_number_threads(image,image->columns,image->rows,key == ~0UL)
cristy3ed852e2009-09-05 21:47:34 +00003612#endif
cristybe82ad52011-09-06 01:17:20 +00003613 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003614 {
cristy5c9e6f22010-09-17 17:31:01 +00003615 const int
3616 id = GetOpenMPThreadId();
cristy6ebe97c2010-07-03 01:17:28 +00003617
cristybe82ad52011-09-06 01:17:20 +00003618 register const Quantum
3619 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003620
cristy4c08aed2011-07-01 19:47:50 +00003621 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003622 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003623
cristy117ff172010-08-15 21:35:32 +00003624 register ssize_t
3625 x;
3626
cristy3ed852e2009-09-05 21:47:34 +00003627 if (status == MagickFalse)
3628 continue;
cristybe82ad52011-09-06 01:17:20 +00003629 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy9f7e7cb2011-03-26 00:49:57 +00003630 q=QueueCacheViewAuthenticPixels(spread_view,0,y,spread_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +00003631 exception);
cristybe82ad52011-09-06 01:17:20 +00003632 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003633 {
3634 status=MagickFalse;
3635 continue;
3636 }
cristybe82ad52011-09-06 01:17:20 +00003637 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003638 {
cristybe82ad52011-09-06 01:17:20 +00003639 PointInfo
3640 point;
3641
cristybe82ad52011-09-06 01:17:20 +00003642 point.x=GetPseudoRandomValue(random_info[id]);
3643 point.y=GetPseudoRandomValue(random_info[id]);
cristy5c4e2582011-09-11 19:21:03 +00003644 status=InterpolatePixelChannels(image,image_view,spread_image,method,
3645 (double) x+width*point.x-0.5,(double) y+width*point.y-0.5,q,exception);
cristyed231572011-07-14 02:18:59 +00003646 q+=GetPixelChannels(spread_image);
cristy3ed852e2009-09-05 21:47:34 +00003647 }
cristy9f7e7cb2011-03-26 00:49:57 +00003648 if (SyncCacheViewAuthenticPixels(spread_view,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003649 status=MagickFalse;
3650 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3651 {
3652 MagickBooleanType
3653 proceed;
3654
cristyb557a152011-02-22 12:14:30 +00003655#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf8561542012-01-24 00:26:46 +00003656 #pragma omp critical (MagickCore_SpreadImage)
cristy3ed852e2009-09-05 21:47:34 +00003657#endif
3658 proceed=SetImageProgress(image,SpreadImageTag,progress++,image->rows);
3659 if (proceed == MagickFalse)
3660 status=MagickFalse;
3661 }
3662 }
cristy9f7e7cb2011-03-26 00:49:57 +00003663 spread_view=DestroyCacheView(spread_view);
cristy3ed852e2009-09-05 21:47:34 +00003664 image_view=DestroyCacheView(image_view);
3665 random_info=DestroyRandomInfoThreadSet(random_info);
cristy3ed852e2009-09-05 21:47:34 +00003666 return(spread_image);
3667}
3668
3669/*
3670%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3671% %
3672% %
3673% %
3674% U n s h a r p M a s k I m a g e %
3675% %
3676% %
3677% %
3678%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3679%
3680% UnsharpMaskImage() sharpens one or more image channels. We convolve the
3681% image with a Gaussian operator of the given radius and standard deviation
3682% (sigma). For reasonable results, radius should be larger than sigma. Use a
3683% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
3684%
3685% The format of the UnsharpMaskImage method is:
3686%
3687% Image *UnsharpMaskImage(const Image *image,const double radius,
3688% const double sigma,const double amount,const double threshold,
3689% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003690%
3691% A description of each parameter follows:
3692%
3693% o image: the image.
3694%
cristy3ed852e2009-09-05 21:47:34 +00003695% o radius: the radius of the Gaussian, in pixels, not counting the center
3696% pixel.
3697%
3698% o sigma: the standard deviation of the Gaussian, in pixels.
3699%
3700% o amount: the percentage of the difference between the original and the
3701% blur image that is added back into the original.
3702%
3703% o threshold: the threshold in pixels needed to apply the diffence amount.
3704%
3705% o exception: return any errors or warnings in this structure.
3706%
3707*/
cristy31bb6272011-11-20 23:57:25 +00003708MagickExport Image *UnsharpMaskImage(const Image *image,const double radius,
3709 const double sigma,const double amount,const double threshold,
3710 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003711{
3712#define SharpenImageTag "Sharpen/Image"
3713
cristyc4c8d132010-01-07 01:58:38 +00003714 CacheView
3715 *image_view,
3716 *unsharp_view;
3717
cristy3ed852e2009-09-05 21:47:34 +00003718 Image
3719 *unsharp_image;
3720
cristy3ed852e2009-09-05 21:47:34 +00003721 MagickBooleanType
3722 status;
3723
cristybb503372010-05-27 20:51:26 +00003724 MagickOffsetType
3725 progress;
3726
cristya19f1d72012-08-07 18:24:38 +00003727 double
cristy3ed852e2009-09-05 21:47:34 +00003728 quantum_threshold;
3729
cristybb503372010-05-27 20:51:26 +00003730 ssize_t
3731 y;
3732
cristy3ed852e2009-09-05 21:47:34 +00003733 assert(image != (const Image *) NULL);
3734 assert(image->signature == MagickSignature);
3735 if (image->debug != MagickFalse)
3736 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3737 assert(exception != (ExceptionInfo *) NULL);
cristyaa2c16c2012-03-25 22:21:35 +00003738 unsharp_image=BlurImage(image,radius,sigma,exception);
cristy3ed852e2009-09-05 21:47:34 +00003739 if (unsharp_image == (Image *) NULL)
3740 return((Image *) NULL);
cristya19f1d72012-08-07 18:24:38 +00003741 quantum_threshold=(double) QuantumRange*threshold;
cristy3ed852e2009-09-05 21:47:34 +00003742 /*
3743 Unsharp-mask image.
3744 */
3745 status=MagickTrue;
3746 progress=0;
cristydb070952012-04-20 14:33:00 +00003747 image_view=AcquireVirtualCacheView(image,exception);
3748 unsharp_view=AcquireAuthenticCacheView(unsharp_image,exception);
cristyb5d5f722009-11-04 03:03:49 +00003749#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00003750 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy4ee2b0c2012-05-15 00:30:35 +00003751 dynamic_number_threads(image,image->columns,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00003752#endif
cristybb503372010-05-27 20:51:26 +00003753 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003754 {
cristy4c08aed2011-07-01 19:47:50 +00003755 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00003756 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003757
cristy4c08aed2011-07-01 19:47:50 +00003758 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003759 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003760
cristy117ff172010-08-15 21:35:32 +00003761 register ssize_t
3762 x;
3763
cristy3ed852e2009-09-05 21:47:34 +00003764 if (status == MagickFalse)
3765 continue;
3766 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy8b49f382012-01-17 03:11:03 +00003767 q=QueueCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +00003768 exception);
cristy4c08aed2011-07-01 19:47:50 +00003769 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003770 {
3771 status=MagickFalse;
3772 continue;
3773 }
cristybb503372010-05-27 20:51:26 +00003774 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003775 {
cristy7f3a0d12011-09-05 23:27:59 +00003776 register ssize_t
3777 i;
3778
3779 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3780 {
cristya19f1d72012-08-07 18:24:38 +00003781 double
cristy7f3a0d12011-09-05 23:27:59 +00003782 pixel;
3783
3784 PixelChannel
3785 channel;
3786
3787 PixelTrait
3788 traits,
3789 unsharp_traits;
3790
cristye2a912b2011-12-05 20:02:07 +00003791 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +00003792 traits=GetPixelChannelMapTraits(image,channel);
cristy7f3a0d12011-09-05 23:27:59 +00003793 unsharp_traits=GetPixelChannelMapTraits(unsharp_image,channel);
3794 if ((traits == UndefinedPixelTrait) ||
3795 (unsharp_traits == UndefinedPixelTrait))
3796 continue;
cristy1eced092012-08-10 23:10:56 +00003797 if (((unsharp_traits & CopyPixelTrait) != 0) ||
3798 (GetPixelMask(image,p) != 0))
cristy7f3a0d12011-09-05 23:27:59 +00003799 {
cristy0beccfa2011-09-25 20:47:53 +00003800 SetPixelChannel(unsharp_image,channel,p[i],q);
cristy7f3a0d12011-09-05 23:27:59 +00003801 continue;
3802 }
cristya19f1d72012-08-07 18:24:38 +00003803 pixel=p[i]-(double) GetPixelChannel(unsharp_image,channel,q);
cristy7f3a0d12011-09-05 23:27:59 +00003804 if (fabs(2.0*pixel) < quantum_threshold)
cristya19f1d72012-08-07 18:24:38 +00003805 pixel=(double) p[i];
cristy7f3a0d12011-09-05 23:27:59 +00003806 else
cristya19f1d72012-08-07 18:24:38 +00003807 pixel=(double) p[i]+amount*pixel;
cristy0beccfa2011-09-25 20:47:53 +00003808 SetPixelChannel(unsharp_image,channel,ClampToQuantum(pixel),q);
cristy7f3a0d12011-09-05 23:27:59 +00003809 }
cristyed231572011-07-14 02:18:59 +00003810 p+=GetPixelChannels(image);
3811 q+=GetPixelChannels(unsharp_image);
cristy3ed852e2009-09-05 21:47:34 +00003812 }
3813 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
3814 status=MagickFalse;
3815 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3816 {
3817 MagickBooleanType
3818 proceed;
3819
cristyb5d5f722009-11-04 03:03:49 +00003820#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyf8561542012-01-24 00:26:46 +00003821 #pragma omp critical (MagickCore_UnsharpMaskImage)
cristy3ed852e2009-09-05 21:47:34 +00003822#endif
3823 proceed=SetImageProgress(image,SharpenImageTag,progress++,image->rows);
3824 if (proceed == MagickFalse)
3825 status=MagickFalse;
3826 }
3827 }
3828 unsharp_image->type=image->type;
3829 unsharp_view=DestroyCacheView(unsharp_view);
3830 image_view=DestroyCacheView(image_view);
3831 if (status == MagickFalse)
3832 unsharp_image=DestroyImage(unsharp_image);
3833 return(unsharp_image);
3834}