blob: bfb1363f27c1c52f42529aebcc6db98ef3c13f80 [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 %
cristyde984cd2013-12-01 14:49:27 +000016% Cristy %
cristy3ed852e2009-09-05 21:47:34 +000017% October 1996 %
18% %
19% %
Cristyd8420112021-01-01 14:52:00 -050020% Copyright 1999-2021 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% %
Cristy9ddfcca2018-09-09 19:46:34 -040026% https://imagemagick.org/script/license.php %
cristy3ed852e2009-09-05 21:47:34 +000027% %
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"
dirkeeec14f2016-05-29 10:37:29 +020044#include "MagickCore/accelerate-private.h"
cristy4c08aed2011-07-01 19:47:50 +000045#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"
cristydde3f7a2014-04-04 00:55:24 +000065#include "MagickCore/matrix.h"
cristy4c08aed2011-07-01 19:47:50 +000066#include "MagickCore/memory_.h"
cristye42639a2012-08-23 01:53:24 +000067#include "MagickCore/memory-private.h"
cristy4c08aed2011-07-01 19:47:50 +000068#include "MagickCore/monitor.h"
69#include "MagickCore/monitor-private.h"
70#include "MagickCore/montage.h"
71#include "MagickCore/morphology.h"
cristy7f391ce2013-03-27 11:03:11 +000072#include "MagickCore/morphology-private.h"
cristy4c08aed2011-07-01 19:47:50 +000073#include "MagickCore/paint.h"
74#include "MagickCore/pixel-accessor.h"
cristy1d1b10f2012-06-02 20:02:55 +000075#include "MagickCore/pixel-private.h"
cristy4c08aed2011-07-01 19:47:50 +000076#include "MagickCore/property.h"
77#include "MagickCore/quantize.h"
78#include "MagickCore/quantum.h"
79#include "MagickCore/quantum-private.h"
80#include "MagickCore/random_.h"
81#include "MagickCore/random-private.h"
82#include "MagickCore/resample.h"
83#include "MagickCore/resample-private.h"
84#include "MagickCore/resize.h"
85#include "MagickCore/resource_.h"
86#include "MagickCore/segment.h"
cristy31bbf2f2011-11-17 13:19:37 +000087#include "MagickCore/shear.h"
cristy4c08aed2011-07-01 19:47:50 +000088#include "MagickCore/signature-private.h"
cristy99bd5232011-12-07 14:38:20 +000089#include "MagickCore/statistic.h"
cristy4c08aed2011-07-01 19:47:50 +000090#include "MagickCore/string_.h"
91#include "MagickCore/thread-private.h"
92#include "MagickCore/transform.h"
93#include "MagickCore/threshold.h"
cristy3ed852e2009-09-05 21:47:34 +000094
95/*
96%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
97% %
98% %
99% %
100% A d a p t i v e B l u r I m a g e %
101% %
102% %
103% %
104%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
105%
106% AdaptiveBlurImage() adaptively blurs the image by blurring less
107% intensely near image edges and more intensely far from edges. We blur the
108% image with a Gaussian operator of the given radius and standard deviation
109% (sigma). For reasonable results, radius should be larger than sigma. Use a
110% radius of 0 and AdaptiveBlurImage() selects a suitable radius for you.
111%
112% The format of the AdaptiveBlurImage method is:
113%
114% Image *AdaptiveBlurImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +0000115% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000116%
117% A description of each parameter follows:
118%
119% o image: the image.
120%
cristy3ed852e2009-09-05 21:47:34 +0000121% o radius: the radius of the Gaussian, in pixels, not counting the center
122% pixel.
123%
124% o sigma: the standard deviation of the Laplacian, in pixels.
125%
126% o exception: return any errors or warnings in this structure.
127%
128*/
cristy4282c702011-11-21 00:01:06 +0000129MagickExport Image *AdaptiveBlurImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +0000130 const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000131{
132#define AdaptiveBlurImageTag "Convolve/Image"
cristy9b528342012-06-02 00:59:20 +0000133#define MagickSigma (fabs(sigma) < MagickEpsilon ? MagickEpsilon : sigma)
cristy3ed852e2009-09-05 21:47:34 +0000134
cristyc4c8d132010-01-07 01:58:38 +0000135 CacheView
136 *blur_view,
137 *edge_view,
138 *image_view;
139
cristyf4bae2c2012-10-17 12:19:38 +0000140 double
Cristy5962ba12016-01-10 20:16:49 -0500141 normalize,
142 **kernel;
cristyf4bae2c2012-10-17 12:19:38 +0000143
cristy3ed852e2009-09-05 21:47:34 +0000144 Image
145 *blur_image,
146 *edge_image,
147 *gaussian_image;
148
cristy3ed852e2009-09-05 21:47:34 +0000149 MagickBooleanType
150 status;
151
cristybb503372010-05-27 20:51:26 +0000152 MagickOffsetType
153 progress;
154
Cristyf2dc1dd2020-12-28 13:59:26 -0500155 ssize_t
cristy47e00502009-12-17 19:19:57 +0000156 i;
cristy3ed852e2009-09-05 21:47:34 +0000157
cristybb503372010-05-27 20:51:26 +0000158 size_t
cristy3ed852e2009-09-05 21:47:34 +0000159 width;
160
cristybb503372010-05-27 20:51:26 +0000161 ssize_t
162 j,
163 k,
164 u,
165 v,
166 y;
167
cristy3ed852e2009-09-05 21:47:34 +0000168 assert(image != (const Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +0000169 assert(image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +0000170 if (image->debug != MagickFalse)
171 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
172 assert(exception != (ExceptionInfo *) NULL);
cristye1c94d92015-06-28 12:16:33 +0000173 assert(exception->signature == MagickCoreSignature);
Cristy590a11f2018-06-10 16:25:53 -0400174 blur_image=CloneImage(image,0,0,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +0000175 if (blur_image == (Image *) NULL)
176 return((Image *) NULL);
cristy3d2287c2012-06-04 21:26:19 +0000177 if (fabs(sigma) < MagickEpsilon)
cristy3ed852e2009-09-05 21:47:34 +0000178 return(blur_image);
cristy574cc262011-08-05 01:23:58 +0000179 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000180 {
cristy3ed852e2009-09-05 21:47:34 +0000181 blur_image=DestroyImage(blur_image);
182 return((Image *) NULL);
183 }
184 /*
Cristy5962ba12016-01-10 20:16:49 -0500185 Edge detect the image brightness channel, level, blur, and level again.
cristy3ed852e2009-09-05 21:47:34 +0000186 */
cristy9dc4c512013-03-24 01:38:00 +0000187 edge_image=EdgeImage(image,radius,exception);
cristy3ed852e2009-09-05 21:47:34 +0000188 if (edge_image == (Image *) NULL)
189 {
190 blur_image=DestroyImage(blur_image);
191 return((Image *) NULL);
192 }
cristyd8dd86c2014-05-28 10:58:46 +0000193 (void) AutoLevelImage(edge_image,exception);
cristyb202ff32013-03-24 15:16:46 +0000194 gaussian_image=BlurImage(edge_image,radius,sigma,exception);
cristy3ed852e2009-09-05 21:47:34 +0000195 if (gaussian_image != (Image *) NULL)
196 {
197 edge_image=DestroyImage(edge_image);
198 edge_image=gaussian_image;
199 }
cristyd8dd86c2014-05-28 10:58:46 +0000200 (void) AutoLevelImage(edge_image,exception);
cristy3ed852e2009-09-05 21:47:34 +0000201 /*
202 Create a set of kernels from maximum (radius,sigma) to minimum.
203 */
204 width=GetOptimalKernelWidth2D(radius,sigma);
Cristy5962ba12016-01-10 20:16:49 -0500205 kernel=(double **) MagickAssumeAligned(AcquireAlignedMemory((size_t) width,
206 sizeof(*kernel)));
207 if (kernel == (double **) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000208 {
209 edge_image=DestroyImage(edge_image);
210 blur_image=DestroyImage(blur_image);
211 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
212 }
Cristy81bfff22018-03-10 07:58:31 -0500213 (void) memset(kernel,0,(size_t) width*sizeof(*kernel));
cristybb503372010-05-27 20:51:26 +0000214 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000215 {
Cristy5962ba12016-01-10 20:16:49 -0500216 kernel[i]=(double *) MagickAssumeAligned(AcquireAlignedMemory(
cristye42639a2012-08-23 01:53:24 +0000217 (size_t) (width-i),(width-i)*sizeof(**kernel)));
Cristy5962ba12016-01-10 20:16:49 -0500218 if (kernel[i] == (double *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000219 break;
cristy47e00502009-12-17 19:19:57 +0000220 normalize=0.0;
cristy6eec5202013-04-16 23:33:08 +0000221 j=(ssize_t) (width-i-1)/2;
cristy47e00502009-12-17 19:19:57 +0000222 k=0;
223 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000224 {
cristy47e00502009-12-17 19:19:57 +0000225 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000226 {
Cristy5962ba12016-01-10 20:16:49 -0500227 kernel[i][k]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
cristy4205a3c2010-09-12 20:19:59 +0000228 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +0000229 normalize+=kernel[i][k];
230 k++;
cristy3ed852e2009-09-05 21:47:34 +0000231 }
232 }
Cristy5962ba12016-01-10 20:16:49 -0500233 kernel[i][(k-1)/2]+=(double) (1.0-normalize);
cristy6eec5202013-04-16 23:33:08 +0000234 if (sigma < MagickEpsilon)
Cristy5962ba12016-01-10 20:16:49 -0500235 kernel[i][(k-1)/2]=1.0;
cristy3ed852e2009-09-05 21:47:34 +0000236 }
cristybb503372010-05-27 20:51:26 +0000237 if (i < (ssize_t) width)
cristy3ed852e2009-09-05 21:47:34 +0000238 {
239 for (i-=2; i >= 0; i-=2)
Cristy5962ba12016-01-10 20:16:49 -0500240 kernel[i]=(double *) RelinquishAlignedMemory(kernel[i]);
241 kernel=(double **) RelinquishAlignedMemory(kernel);
cristy3ed852e2009-09-05 21:47:34 +0000242 edge_image=DestroyImage(edge_image);
243 blur_image=DestroyImage(blur_image);
244 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
245 }
246 /*
247 Adaptively blur image.
248 */
249 status=MagickTrue;
250 progress=0;
cristy46ff2672012-12-14 15:32:26 +0000251 image_view=AcquireVirtualCacheView(image,exception);
252 edge_view=AcquireVirtualCacheView(edge_image,exception);
253 blur_view=AcquireAuthenticCacheView(blur_image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000254#if defined(MAGICKCORE_OPENMP_SUPPORT)
Cristy38b42e12018-03-18 10:13:01 -0400255 #pragma omp parallel for schedule(static) shared(progress,status) \
Cristy8255ca92017-10-09 08:37:35 -0400256 magick_number_threads(image,blur_image,blur_image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +0000257#endif
cristybb503372010-05-27 20:51:26 +0000258 for (y=0; y < (ssize_t) blur_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000259 {
Cristyf2dc1dd2020-12-28 13:59:26 -0500260 const Quantum
dirk05d2ff72015-11-18 23:13:43 +0100261 *magick_restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000262
Cristyf2dc1dd2020-12-28 13:59:26 -0500263 Quantum
dirk05d2ff72015-11-18 23:13:43 +0100264 *magick_restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000265
Cristyf2dc1dd2020-12-28 13:59:26 -0500266 ssize_t
cristy117ff172010-08-15 21:35:32 +0000267 x;
268
cristy3ed852e2009-09-05 21:47:34 +0000269 if (status == MagickFalse)
270 continue;
271 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
272 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
273 exception);
cristyacd2ed22011-08-30 01:44:23 +0000274 if ((r == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000275 {
276 status=MagickFalse;
277 continue;
278 }
cristybb503372010-05-27 20:51:26 +0000279 for (x=0; x < (ssize_t) blur_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000280 {
Cristyf2dc1dd2020-12-28 13:59:26 -0500281 const Quantum
dirk05d2ff72015-11-18 23:13:43 +0100282 *magick_restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000283
Cristyf2dc1dd2020-12-28 13:59:26 -0500284 ssize_t
cristy4c11c2b2011-09-05 20:17:07 +0000285 i;
cristy3ed852e2009-09-05 21:47:34 +0000286
cristy4c11c2b2011-09-05 20:17:07 +0000287 ssize_t
288 center,
289 j;
290
Cristy20ab4502020-12-25 11:15:13 -0500291 j=CastDoubleToLong(ceil((double) width*(1.0-QuantumScale*
Cristy705a60a2020-12-25 11:00:42 -0500292 GetPixelIntensity(edge_image,r))-0.5));
cristy4c11c2b2011-09-05 20:17:07 +0000293 if (j < 0)
294 j=0;
cristy3ed852e2009-09-05 21:47:34 +0000295 else
cristy4c11c2b2011-09-05 20:17:07 +0000296 if (j > (ssize_t) width)
297 j=(ssize_t) width;
298 if ((j & 0x01) != 0)
299 j--;
300 p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) (width-j)/2L),y-
301 (ssize_t) ((width-j)/2L),width-j,width-j,exception);
cristy4c08aed2011-07-01 19:47:50 +0000302 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000303 break;
cristyd09f8802012-02-04 16:44:10 +0000304 center=(ssize_t) GetPixelChannels(image)*(width-j)*((width-j)/2L)+
Cristy5962ba12016-01-10 20:16:49 -0500305 GetPixelChannels(image)*((width-j)/2);
306 for (i=0; i < (ssize_t) GetPixelChannels(blur_image); i++)
cristy3ed852e2009-09-05 21:47:34 +0000307 {
cristya19f1d72012-08-07 18:24:38 +0000308 double
cristy4c11c2b2011-09-05 20:17:07 +0000309 alpha,
310 gamma,
311 pixel;
312
cristy633067b2013-02-21 01:15:12 +0000313 PixelChannel
314 channel;
315
316 PixelTrait
317 blur_traits,
318 traits;
319
Cristyf2dc1dd2020-12-28 13:59:26 -0500320 const double
dirk05d2ff72015-11-18 23:13:43 +0100321 *magick_restrict k;
cristy4c11c2b2011-09-05 20:17:07 +0000322
Cristyf2dc1dd2020-12-28 13:59:26 -0500323 const Quantum
dirk05d2ff72015-11-18 23:13:43 +0100324 *magick_restrict pixels;
cristy4c11c2b2011-09-05 20:17:07 +0000325
Cristyf2dc1dd2020-12-28 13:59:26 -0500326 ssize_t
cristy4c11c2b2011-09-05 20:17:07 +0000327 u;
328
329 ssize_t
330 v;
331
cristy633067b2013-02-21 01:15:12 +0000332 channel=GetPixelChannelChannel(image,i);
333 traits=GetPixelChannelTraits(image,channel);
334 blur_traits=GetPixelChannelTraits(blur_image,channel);
cristy4c11c2b2011-09-05 20:17:07 +0000335 if ((traits == UndefinedPixelTrait) ||
336 (blur_traits == UndefinedPixelTrait))
337 continue;
Cristydaf00192018-05-12 13:47:19 -0400338 if ((blur_traits & CopyPixelTrait) != 0)
cristy4c11c2b2011-09-05 20:17:07 +0000339 {
cristy0beccfa2011-09-25 20:47:53 +0000340 SetPixelChannel(blur_image,channel,p[center+i],q);
cristy4c11c2b2011-09-05 20:17:07 +0000341 continue;
342 }
343 k=kernel[j];
344 pixels=p;
cristyaa2c16c2012-03-25 22:21:35 +0000345 pixel=0.0;
cristy4c11c2b2011-09-05 20:17:07 +0000346 gamma=0.0;
cristy633067b2013-02-21 01:15:12 +0000347 if ((blur_traits & BlendPixelTrait) == 0)
cristy4c11c2b2011-09-05 20:17:07 +0000348 {
349 /*
350 No alpha blending.
351 */
352 for (v=0; v < (ssize_t) (width-j); v++)
353 {
354 for (u=0; u < (ssize_t) (width-j); u++)
355 {
356 pixel+=(*k)*pixels[i];
357 gamma+=(*k);
358 k++;
359 pixels+=GetPixelChannels(image);
360 }
361 }
cristy3e3ec3a2012-11-03 23:11:06 +0000362 gamma=PerceptibleReciprocal(gamma);
cristy0beccfa2011-09-25 20:47:53 +0000363 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy4c11c2b2011-09-05 20:17:07 +0000364 continue;
365 }
366 /*
367 Alpha blending.
368 */
369 for (v=0; v < (ssize_t) (width-j); v++)
cristy3ed852e2009-09-05 21:47:34 +0000370 {
cristy4c11c2b2011-09-05 20:17:07 +0000371 for (u=0; u < (ssize_t) (width-j); u++)
372 {
cristya19f1d72012-08-07 18:24:38 +0000373 alpha=(double) (QuantumScale*GetPixelAlpha(image,pixels));
cristy4c11c2b2011-09-05 20:17:07 +0000374 pixel+=(*k)*alpha*pixels[i];
375 gamma+=(*k)*alpha;
376 k++;
377 pixels+=GetPixelChannels(image);
378 }
cristy3ed852e2009-09-05 21:47:34 +0000379 }
cristy3e3ec3a2012-11-03 23:11:06 +0000380 gamma=PerceptibleReciprocal(gamma);
cristy0beccfa2011-09-25 20:47:53 +0000381 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy3ed852e2009-09-05 21:47:34 +0000382 }
cristyed231572011-07-14 02:18:59 +0000383 q+=GetPixelChannels(blur_image);
384 r+=GetPixelChannels(edge_image);
cristy3ed852e2009-09-05 21:47:34 +0000385 }
386 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
387 status=MagickFalse;
388 if (image->progress_monitor != (MagickProgressMonitor) NULL)
389 {
390 MagickBooleanType
391 proceed;
392
Cristyfc89eec2018-11-11 21:10:06 -0500393#if defined(MAGICKCORE_OPENMP_SUPPORT)
394 #pragma omp atomic
395#endif
396 progress++;
397 proceed=SetImageProgress(image,AdaptiveBlurImageTag,progress,
cristy3ed852e2009-09-05 21:47:34 +0000398 image->rows);
399 if (proceed == MagickFalse)
400 status=MagickFalse;
401 }
402 }
403 blur_image->type=image->type;
404 blur_view=DestroyCacheView(blur_view);
405 edge_view=DestroyCacheView(edge_view);
406 image_view=DestroyCacheView(image_view);
407 edge_image=DestroyImage(edge_image);
Cristy5962ba12016-01-10 20:16:49 -0500408 for (i=0; i < (ssize_t) width; i+=2)
409 kernel[i]=(double *) RelinquishAlignedMemory(kernel[i]);
410 kernel=(double **) RelinquishAlignedMemory(kernel);
cristy3ed852e2009-09-05 21:47:34 +0000411 if (status == MagickFalse)
412 blur_image=DestroyImage(blur_image);
413 return(blur_image);
414}
415
416/*
417%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
418% %
419% %
420% %
421% A d a p t i v e S h a r p e n I m a g e %
422% %
423% %
424% %
425%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
426%
427% AdaptiveSharpenImage() adaptively sharpens the image by sharpening more
428% intensely near image edges and less intensely far from edges. We sharpen the
429% image with a Gaussian operator of the given radius and standard deviation
430% (sigma). For reasonable results, radius should be larger than sigma. Use a
431% radius of 0 and AdaptiveSharpenImage() selects a suitable radius for you.
432%
433% The format of the AdaptiveSharpenImage method is:
434%
435% Image *AdaptiveSharpenImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +0000436% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000437%
438% A description of each parameter follows:
439%
440% o image: the image.
441%
cristy3ed852e2009-09-05 21:47:34 +0000442% o radius: the radius of the Gaussian, in pixels, not counting the center
443% pixel.
444%
445% o sigma: the standard deviation of the Laplacian, in pixels.
446%
447% o exception: return any errors or warnings in this structure.
448%
449*/
cristy3ed852e2009-09-05 21:47:34 +0000450MagickExport Image *AdaptiveSharpenImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +0000451 const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000452{
cristy3ed852e2009-09-05 21:47:34 +0000453#define AdaptiveSharpenImageTag "Convolve/Image"
cristy9b528342012-06-02 00:59:20 +0000454#define MagickSigma (fabs(sigma) < MagickEpsilon ? MagickEpsilon : sigma)
cristy3ed852e2009-09-05 21:47:34 +0000455
cristyc4c8d132010-01-07 01:58:38 +0000456 CacheView
457 *sharp_view,
458 *edge_view,
459 *image_view;
460
cristyf4bae2c2012-10-17 12:19:38 +0000461 double
Cristy5962ba12016-01-10 20:16:49 -0500462 normalize,
463 **kernel;
cristyf4bae2c2012-10-17 12:19:38 +0000464
cristy3ed852e2009-09-05 21:47:34 +0000465 Image
466 *sharp_image,
467 *edge_image,
468 *gaussian_image;
469
cristy3ed852e2009-09-05 21:47:34 +0000470 MagickBooleanType
471 status;
472
cristybb503372010-05-27 20:51:26 +0000473 MagickOffsetType
474 progress;
475
Cristyf2dc1dd2020-12-28 13:59:26 -0500476 ssize_t
cristy47e00502009-12-17 19:19:57 +0000477 i;
cristy3ed852e2009-09-05 21:47:34 +0000478
cristybb503372010-05-27 20:51:26 +0000479 size_t
cristy3ed852e2009-09-05 21:47:34 +0000480 width;
481
cristybb503372010-05-27 20:51:26 +0000482 ssize_t
483 j,
484 k,
485 u,
486 v,
487 y;
488
cristy3ed852e2009-09-05 21:47:34 +0000489 assert(image != (const Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +0000490 assert(image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +0000491 if (image->debug != MagickFalse)
492 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
493 assert(exception != (ExceptionInfo *) NULL);
cristye1c94d92015-06-28 12:16:33 +0000494 assert(exception->signature == MagickCoreSignature);
Cristy590a11f2018-06-10 16:25:53 -0400495 sharp_image=CloneImage(image,0,0,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +0000496 if (sharp_image == (Image *) NULL)
497 return((Image *) NULL);
cristye6180ff2012-06-04 22:19:16 +0000498 if (fabs(sigma) < MagickEpsilon)
cristy3ed852e2009-09-05 21:47:34 +0000499 return(sharp_image);
cristy574cc262011-08-05 01:23:58 +0000500 if (SetImageStorageClass(sharp_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +0000501 {
cristy3ed852e2009-09-05 21:47:34 +0000502 sharp_image=DestroyImage(sharp_image);
503 return((Image *) NULL);
504 }
505 /*
cristyd8dd86c2014-05-28 10:58:46 +0000506 Edge detect the image brightness channel, level, sharp, and level again.
cristy3ed852e2009-09-05 21:47:34 +0000507 */
cristy9dc4c512013-03-24 01:38:00 +0000508 edge_image=EdgeImage(image,radius,exception);
cristy3ed852e2009-09-05 21:47:34 +0000509 if (edge_image == (Image *) NULL)
510 {
511 sharp_image=DestroyImage(sharp_image);
512 return((Image *) NULL);
513 }
cristyd8dd86c2014-05-28 10:58:46 +0000514 (void) AutoLevelImage(edge_image,exception);
cristyb202ff32013-03-24 15:16:46 +0000515 gaussian_image=BlurImage(edge_image,radius,sigma,exception);
cristy3ed852e2009-09-05 21:47:34 +0000516 if (gaussian_image != (Image *) NULL)
517 {
518 edge_image=DestroyImage(edge_image);
519 edge_image=gaussian_image;
520 }
cristyd8dd86c2014-05-28 10:58:46 +0000521 (void) AutoLevelImage(edge_image,exception);
cristy3ed852e2009-09-05 21:47:34 +0000522 /*
523 Create a set of kernels from maximum (radius,sigma) to minimum.
524 */
525 width=GetOptimalKernelWidth2D(radius,sigma);
Cristy5962ba12016-01-10 20:16:49 -0500526 kernel=(double **) MagickAssumeAligned(AcquireAlignedMemory((size_t)
cristye42639a2012-08-23 01:53:24 +0000527 width,sizeof(*kernel)));
Cristy5962ba12016-01-10 20:16:49 -0500528 if (kernel == (double **) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000529 {
530 edge_image=DestroyImage(edge_image);
531 sharp_image=DestroyImage(sharp_image);
532 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
533 }
Cristy81bfff22018-03-10 07:58:31 -0500534 (void) memset(kernel,0,(size_t) width*sizeof(*kernel));
cristybb503372010-05-27 20:51:26 +0000535 for (i=0; i < (ssize_t) width; i+=2)
cristy3ed852e2009-09-05 21:47:34 +0000536 {
Cristy5962ba12016-01-10 20:16:49 -0500537 kernel[i]=(double *) MagickAssumeAligned(AcquireAlignedMemory((size_t)
538 (width-i),(width-i)*sizeof(**kernel)));
539 if (kernel[i] == (double *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000540 break;
cristy47e00502009-12-17 19:19:57 +0000541 normalize=0.0;
Cristy5962ba12016-01-10 20:16:49 -0500542 j=(ssize_t) (width-i-1)/2;
cristy47e00502009-12-17 19:19:57 +0000543 k=0;
544 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +0000545 {
cristy47e00502009-12-17 19:19:57 +0000546 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +0000547 {
Cristy5962ba12016-01-10 20:16:49 -0500548 kernel[i][k]=(double) (-exp(-((double) u*u+v*v)/(2.0*MagickSigma*
549 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy47e00502009-12-17 19:19:57 +0000550 normalize+=kernel[i][k];
551 k++;
cristy3ed852e2009-09-05 21:47:34 +0000552 }
553 }
cristy6eec5202013-04-16 23:33:08 +0000554 kernel[i][(k-1)/2]=(double) ((-2.0)*normalize);
555 if (sigma < MagickEpsilon)
556 kernel[i][(k-1)/2]=1.0;
cristy3ed852e2009-09-05 21:47:34 +0000557 }
cristybb503372010-05-27 20:51:26 +0000558 if (i < (ssize_t) width)
cristy3ed852e2009-09-05 21:47:34 +0000559 {
560 for (i-=2; i >= 0; i-=2)
Cristy5962ba12016-01-10 20:16:49 -0500561 kernel[i]=(double *) RelinquishAlignedMemory(kernel[i]);
562 kernel=(double **) RelinquishAlignedMemory(kernel);
cristy3ed852e2009-09-05 21:47:34 +0000563 edge_image=DestroyImage(edge_image);
564 sharp_image=DestroyImage(sharp_image);
565 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
566 }
567 /*
568 Adaptively sharpen image.
569 */
570 status=MagickTrue;
571 progress=0;
cristy46ff2672012-12-14 15:32:26 +0000572 image_view=AcquireVirtualCacheView(image,exception);
573 edge_view=AcquireVirtualCacheView(edge_image,exception);
574 sharp_view=AcquireAuthenticCacheView(sharp_image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000575#if defined(MAGICKCORE_OPENMP_SUPPORT)
Cristy38b42e12018-03-18 10:13:01 -0400576 #pragma omp parallel for schedule(static) shared(progress,status) \
Cristy8255ca92017-10-09 08:37:35 -0400577 magick_number_threads(image,sharp_image,sharp_image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +0000578#endif
cristybb503372010-05-27 20:51:26 +0000579 for (y=0; y < (ssize_t) sharp_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000580 {
Cristyf2dc1dd2020-12-28 13:59:26 -0500581 const Quantum
dirk05d2ff72015-11-18 23:13:43 +0100582 *magick_restrict r;
cristy3ed852e2009-09-05 21:47:34 +0000583
Cristyf2dc1dd2020-12-28 13:59:26 -0500584 Quantum
dirk05d2ff72015-11-18 23:13:43 +0100585 *magick_restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000586
Cristyf2dc1dd2020-12-28 13:59:26 -0500587 ssize_t
cristy117ff172010-08-15 21:35:32 +0000588 x;
589
cristy3ed852e2009-09-05 21:47:34 +0000590 if (status == MagickFalse)
591 continue;
592 r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
593 q=QueueCacheViewAuthenticPixels(sharp_view,0,y,sharp_image->columns,1,
594 exception);
cristy4c08aed2011-07-01 19:47:50 +0000595 if ((r == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +0000596 {
597 status=MagickFalse;
598 continue;
599 }
cristybb503372010-05-27 20:51:26 +0000600 for (x=0; x < (ssize_t) sharp_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000601 {
Cristyf2dc1dd2020-12-28 13:59:26 -0500602 const Quantum
dirk05d2ff72015-11-18 23:13:43 +0100603 *magick_restrict p;
cristy3ed852e2009-09-05 21:47:34 +0000604
Cristyf2dc1dd2020-12-28 13:59:26 -0500605 ssize_t
cristy4c11c2b2011-09-05 20:17:07 +0000606 i;
cristy3ed852e2009-09-05 21:47:34 +0000607
cristy4c11c2b2011-09-05 20:17:07 +0000608 ssize_t
609 center,
610 j;
611
Cristy20ab4502020-12-25 11:15:13 -0500612 j=CastDoubleToLong(ceil((double) width*(1.0-QuantumScale*
Cristy705a60a2020-12-25 11:00:42 -0500613 GetPixelIntensity(edge_image,r))-0.5));
cristy4c11c2b2011-09-05 20:17:07 +0000614 if (j < 0)
615 j=0;
cristy3ed852e2009-09-05 21:47:34 +0000616 else
cristy4c11c2b2011-09-05 20:17:07 +0000617 if (j > (ssize_t) width)
618 j=(ssize_t) width;
619 if ((j & 0x01) != 0)
620 j--;
621 p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) (width-j)/2L),y-
622 (ssize_t) ((width-j)/2L),width-j,width-j,exception);
cristy4c08aed2011-07-01 19:47:50 +0000623 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000624 break;
cristyd09f8802012-02-04 16:44:10 +0000625 center=(ssize_t) GetPixelChannels(image)*(width-j)*((width-j)/2L)+
626 GetPixelChannels(image)*((width-j)/2);
cristyc94ba6f2012-01-29 23:19:58 +0000627 for (i=0; i < (ssize_t) GetPixelChannels(sharp_image); i++)
cristy3ed852e2009-09-05 21:47:34 +0000628 {
cristya19f1d72012-08-07 18:24:38 +0000629 double
cristy4c11c2b2011-09-05 20:17:07 +0000630 alpha,
631 gamma,
632 pixel;
633
cristy633067b2013-02-21 01:15:12 +0000634 PixelChannel
635 channel;
636
637 PixelTrait
638 sharp_traits,
639 traits;
640
Cristyf2dc1dd2020-12-28 13:59:26 -0500641 const double
dirk05d2ff72015-11-18 23:13:43 +0100642 *magick_restrict k;
cristy4c11c2b2011-09-05 20:17:07 +0000643
Cristyf2dc1dd2020-12-28 13:59:26 -0500644 const Quantum
dirk05d2ff72015-11-18 23:13:43 +0100645 *magick_restrict pixels;
cristy4c11c2b2011-09-05 20:17:07 +0000646
Cristyf2dc1dd2020-12-28 13:59:26 -0500647 ssize_t
cristy4c11c2b2011-09-05 20:17:07 +0000648 u;
649
650 ssize_t
651 v;
652
cristy633067b2013-02-21 01:15:12 +0000653 channel=GetPixelChannelChannel(image,i);
654 traits=GetPixelChannelTraits(image,channel);
655 sharp_traits=GetPixelChannelTraits(sharp_image,channel);
cristy4c11c2b2011-09-05 20:17:07 +0000656 if ((traits == UndefinedPixelTrait) ||
657 (sharp_traits == UndefinedPixelTrait))
658 continue;
Cristydaf00192018-05-12 13:47:19 -0400659 if ((sharp_traits & CopyPixelTrait) != 0)
cristy4c11c2b2011-09-05 20:17:07 +0000660 {
cristy0beccfa2011-09-25 20:47:53 +0000661 SetPixelChannel(sharp_image,channel,p[center+i],q);
cristy4c11c2b2011-09-05 20:17:07 +0000662 continue;
663 }
664 k=kernel[j];
665 pixels=p;
cristyaa2c16c2012-03-25 22:21:35 +0000666 pixel=0.0;
cristy4c11c2b2011-09-05 20:17:07 +0000667 gamma=0.0;
cristy633067b2013-02-21 01:15:12 +0000668 if ((sharp_traits & BlendPixelTrait) == 0)
cristy4c11c2b2011-09-05 20:17:07 +0000669 {
670 /*
671 No alpha blending.
672 */
673 for (v=0; v < (ssize_t) (width-j); v++)
674 {
675 for (u=0; u < (ssize_t) (width-j); u++)
676 {
677 pixel+=(*k)*pixels[i];
678 gamma+=(*k);
679 k++;
680 pixels+=GetPixelChannels(image);
681 }
682 }
cristy3e3ec3a2012-11-03 23:11:06 +0000683 gamma=PerceptibleReciprocal(gamma);
cristy0beccfa2011-09-25 20:47:53 +0000684 SetPixelChannel(sharp_image,channel,ClampToQuantum(gamma*pixel),q);
cristy4c11c2b2011-09-05 20:17:07 +0000685 continue;
686 }
687 /*
688 Alpha blending.
689 */
690 for (v=0; v < (ssize_t) (width-j); v++)
cristy3ed852e2009-09-05 21:47:34 +0000691 {
cristy4c11c2b2011-09-05 20:17:07 +0000692 for (u=0; u < (ssize_t) (width-j); u++)
693 {
cristya19f1d72012-08-07 18:24:38 +0000694 alpha=(double) (QuantumScale*GetPixelAlpha(image,pixels));
cristy4c11c2b2011-09-05 20:17:07 +0000695 pixel+=(*k)*alpha*pixels[i];
696 gamma+=(*k)*alpha;
697 k++;
698 pixels+=GetPixelChannels(image);
699 }
cristy3ed852e2009-09-05 21:47:34 +0000700 }
cristy3e3ec3a2012-11-03 23:11:06 +0000701 gamma=PerceptibleReciprocal(gamma);
cristy0beccfa2011-09-25 20:47:53 +0000702 SetPixelChannel(sharp_image,channel,ClampToQuantum(gamma*pixel),q);
cristy3ed852e2009-09-05 21:47:34 +0000703 }
cristyed231572011-07-14 02:18:59 +0000704 q+=GetPixelChannels(sharp_image);
705 r+=GetPixelChannels(edge_image);
cristy3ed852e2009-09-05 21:47:34 +0000706 }
707 if (SyncCacheViewAuthenticPixels(sharp_view,exception) == MagickFalse)
708 status=MagickFalse;
709 if (image->progress_monitor != (MagickProgressMonitor) NULL)
710 {
711 MagickBooleanType
712 proceed;
713
Cristyfc89eec2018-11-11 21:10:06 -0500714#if defined(MAGICKCORE_OPENMP_SUPPORT)
715 #pragma omp atomic
716#endif
717 progress++;
718 proceed=SetImageProgress(image,AdaptiveSharpenImageTag,progress,
cristy3ed852e2009-09-05 21:47:34 +0000719 image->rows);
720 if (proceed == MagickFalse)
721 status=MagickFalse;
722 }
723 }
724 sharp_image->type=image->type;
725 sharp_view=DestroyCacheView(sharp_view);
726 edge_view=DestroyCacheView(edge_view);
727 image_view=DestroyCacheView(image_view);
728 edge_image=DestroyImage(edge_image);
Cristy5962ba12016-01-10 20:16:49 -0500729 for (i=0; i < (ssize_t) width; i+=2)
730 kernel[i]=(double *) RelinquishAlignedMemory(kernel[i]);
731 kernel=(double **) RelinquishAlignedMemory(kernel);
cristy3ed852e2009-09-05 21:47:34 +0000732 if (status == MagickFalse)
733 sharp_image=DestroyImage(sharp_image);
734 return(sharp_image);
735}
736
737/*
738%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
739% %
740% %
741% %
742% B l u r I m a g e %
743% %
744% %
745% %
746%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
747%
748% BlurImage() blurs an image. We convolve the image with a Gaussian operator
749% of the given radius and standard deviation (sigma). For reasonable results,
750% the radius should be larger than sigma. Use a radius of 0 and BlurImage()
751% selects a suitable radius for you.
752%
cristy3ed852e2009-09-05 21:47:34 +0000753% The format of the BlurImage method is:
754%
755% Image *BlurImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +0000756% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000757%
758% A description of each parameter follows:
759%
760% o image: the image.
761%
cristy3ed852e2009-09-05 21:47:34 +0000762% o radius: the radius of the Gaussian, in pixels, not counting the center
763% pixel.
764%
765% o sigma: the standard deviation of the Gaussian, in pixels.
766%
767% o exception: return any errors or warnings in this structure.
768%
769*/
cristyf4ad9df2011-07-08 16:49:03 +0000770MagickExport Image *BlurImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +0000771 const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000772{
cristye6bdb5e2013-03-23 18:39:24 +0000773 char
cristy151b66d2015-04-15 10:50:31 +0000774 geometry[MagickPathExtent];
cristy3ed852e2009-09-05 21:47:34 +0000775
cristy590c8292013-02-21 01:37:36 +0000776 KernelInfo
777 *kernel_info;
cristy0a887dc2012-08-15 22:58:36 +0000778
cristye6bdb5e2013-03-23 18:39:24 +0000779 Image
cristyd42fab62013-03-26 16:34:20 +0000780 *blur_image;
cristybb503372010-05-27 20:51:26 +0000781
cristy590c8292013-02-21 01:37:36 +0000782 assert(image != (const Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +0000783 assert(image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +0000784 if (image->debug != MagickFalse)
785 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
786 assert(exception != (ExceptionInfo *) NULL);
cristye1c94d92015-06-28 12:16:33 +0000787 assert(exception->signature == MagickCoreSignature);
dirk21dc0312016-06-13 22:30:26 +0200788#if defined(MAGICKCORE_OPENCL_SUPPORT)
dirk06c4f032016-02-11 23:00:56 +0100789 blur_image=AccelerateBlurImage(image,radius,sigma,exception);
Dusan Veljko94190b72015-11-09 14:45:26 +0100790 if (blur_image != (Image *) NULL)
791 return(blur_image);
dirk21dc0312016-06-13 22:30:26 +0200792#endif
cristy151b66d2015-04-15 10:50:31 +0000793 (void) FormatLocaleString(geometry,MagickPathExtent,
cristyd42fab62013-03-26 16:34:20 +0000794 "blur:%.20gx%.20g;blur:%.20gx%.20g+90",radius,sigma,radius,sigma);
cristy2c57b742014-10-31 00:40:34 +0000795 kernel_info=AcquireKernelInfo(geometry,exception);
cristy590c8292013-02-21 01:37:36 +0000796 if (kernel_info == (KernelInfo *) NULL)
797 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
dirk38aff572015-11-11 15:32:32 +0100798 blur_image=ConvolveImage(image,kernel_info,exception);
cristyda1231c2013-03-24 19:11:49 +0000799 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +0000800 return(blur_image);
801}
802
803/*
804%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
805% %
806% %
807% %
Cristy2e4e0022020-11-26 19:29:45 +0000808% B i l a t e r a l B l u r I m a g e %
Cristyd4b1a4b2020-11-25 09:17:22 -0500809% %
810% %
811% %
812%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
813%
Cristy2e4e0022020-11-26 19:29:45 +0000814% BilateralBlurImage() is a non-linear, edge-preserving, and noise-reducing
815% smoothing filter for images. It replaces the intensity of each pixel with
816% a weighted average of intensity values from nearby pixels. This weight is
817% based on a Gaussian distribution. The weights depend not only on Euclidean
818% distance of pixels, but also on the radiometric differences (e.g., range
819% differences, such as color intensity, depth distance, etc.). This preserves
820% sharp edges.
Cristyd4b1a4b2020-11-25 09:17:22 -0500821%
Cristy2e4e0022020-11-26 19:29:45 +0000822% The format of the BilateralBlurImage method is:
Cristyd4b1a4b2020-11-25 09:17:22 -0500823%
Cristy0cf0d812020-11-29 22:51:48 +0000824% Image *BilateralBlurImage(const Image *image,const size_t width,
825% const size_t height,const double intensity_sigma,
Cristyada21742020-11-26 12:27:24 +0000826% const double spatial_sigma,ExceptionInfo *exception)
Cristyd4b1a4b2020-11-25 09:17:22 -0500827%
828% A description of each parameter follows:
829%
830% o image: the image.
831%
Cristy00bb7ed2020-11-29 22:13:43 +0000832% o width: the width of the neighborhood in pixels.
Cristyd4b1a4b2020-11-25 09:17:22 -0500833%
Cristy00bb7ed2020-11-29 22:13:43 +0000834% o height: the height of the neighborhood in pixels.
Cristyd4b1a4b2020-11-25 09:17:22 -0500835%
Cristy7180c102020-11-26 19:06:02 +0000836% o intensity_sigma: sigma in the intensity space. A larger value means
837% that farther colors within the pixel neighborhood (see spatial_sigma)
838% will be mixed together, resulting in larger areas of semi-equal color.
Cristyada21742020-11-26 12:27:24 +0000839%
Cristy7180c102020-11-26 19:06:02 +0000840% o spatial_sigma: sigma in the coordinate space. A larger value means that
841% farther pixels influence each other as long as their colors are close
842% enough (see intensity_sigma ). When the neigborhood diameter is greater
843% than zero, it specifies the neighborhood size regardless of
844% spatial_sigma. Otherwise, the neigborhood diameter is proportional to
845% spatial_sigma.
Cristyada21742020-11-26 12:27:24 +0000846%
Cristyd4b1a4b2020-11-25 09:17:22 -0500847% o exception: return any errors or warnings in this structure.
848%
849*/
Cristyada21742020-11-26 12:27:24 +0000850
Cristyff4bf142020-11-26 19:56:43 +0000851static inline double BlurDistance(const ssize_t x,const ssize_t y,
Cristyada21742020-11-26 12:27:24 +0000852 const ssize_t u,const ssize_t v)
853{
854 return(sqrt(((double) x-u)*((double) x-u)+((double) y-v)*((double) y-v)));
855}
856
Cristyff4bf142020-11-26 19:56:43 +0000857static inline double BlurGaussian(const double x,const double sigma)
Cristyada21742020-11-26 12:27:24 +0000858{
Cristy923851b2020-11-26 18:10:29 +0000859 return(exp(-((double) x*x)*PerceptibleReciprocal(2.0*sigma*sigma))*
Cristyd7f58092020-11-26 19:19:56 +0000860 PerceptibleReciprocal(Magick2PI*sigma*sigma));
Cristyada21742020-11-26 12:27:24 +0000861}
862
Cristybd2b3512020-12-05 01:45:50 +0000863static double **DestroyBilateralThreadSet(const ssize_t number_threads,
864 double **weights)
Cristy7e96e9f2020-11-28 15:38:02 +0000865{
Cristyf2dc1dd2020-12-28 13:59:26 -0500866 ssize_t
Cristy7e96e9f2020-11-28 15:38:02 +0000867 i;
868
869 assert(weights != (double **) NULL);
Cristybd2b3512020-12-05 01:45:50 +0000870 for (i=0; i <= (ssize_t) number_threads; i++)
Cristy7e96e9f2020-11-28 15:38:02 +0000871 if (weights[i] != (double *) NULL)
872 weights[i]=(double *) RelinquishMagickMemory(weights[i]);
873 weights=(double **) RelinquishMagickMemory(weights);
874 return(weights);
875}
876
Cristybd2b3512020-12-05 01:45:50 +0000877static double **AcquireBilateralThreadSet(const size_t number_threads,
878 const size_t width,const size_t height)
Cristy7e96e9f2020-11-28 15:38:02 +0000879{
880 double
881 **weights;
882
Cristyf2dc1dd2020-12-28 13:59:26 -0500883 ssize_t
Cristy7e96e9f2020-11-28 15:38:02 +0000884 i;
885
Cristybd2b3512020-12-05 01:45:50 +0000886 weights=(double **) AcquireQuantumMemory(number_threads+1,sizeof(*weights));
Cristy7e96e9f2020-11-28 15:38:02 +0000887 if (weights == (double **) NULL)
888 return((double **) NULL);
889 (void) memset(weights,0,number_threads*sizeof(*weights));
Cristybd2b3512020-12-05 01:45:50 +0000890 for (i=0; i <= (ssize_t) number_threads; i++)
Cristy7e96e9f2020-11-28 15:38:02 +0000891 {
Cristy00bb7ed2020-11-29 22:13:43 +0000892 weights[i]=(double *) AcquireQuantumMemory(width,height*sizeof(**weights));
Cristy7e96e9f2020-11-28 15:38:02 +0000893 if (weights[i] == (double *) NULL)
Cristybd2b3512020-12-05 01:45:50 +0000894 return(DestroyBilateralThreadSet(number_threads,weights));
Cristy7e96e9f2020-11-28 15:38:02 +0000895 }
896 return(weights);
897}
898
Cristy0cf0d812020-11-29 22:51:48 +0000899MagickExport Image *BilateralBlurImage(const Image *image,const size_t width,
900 const size_t height,const double intensity_sigma,const double spatial_sigma,
Cristy2e4e0022020-11-26 19:29:45 +0000901 ExceptionInfo *exception)
Cristyd4b1a4b2020-11-25 09:17:22 -0500902{
Cristybd2b3512020-12-05 01:45:50 +0000903#define MaxIntensity (255)
Cristy89f20de2020-12-05 02:58:48 +0000904#define BilateralBlurImageTag "Blur/Image"
Cristyd4b1a4b2020-11-25 09:17:22 -0500905
906 CacheView
Cristyc7062302020-11-27 14:51:30 +0000907 *blur_view,
Cristyd4b1a4b2020-11-25 09:17:22 -0500908 *image_view;
909
Cristy7e96e9f2020-11-28 15:38:02 +0000910 double
Cristybd2b3512020-12-05 01:45:50 +0000911 intensity_gaussian[2*(MaxIntensity+1)],
912 *spatial_gaussian,
Cristy7e96e9f2020-11-28 15:38:02 +0000913 **weights;
914
Cristyd4b1a4b2020-11-25 09:17:22 -0500915 Image
Cristyc7062302020-11-27 14:51:30 +0000916 *blur_image;
Cristyd4b1a4b2020-11-25 09:17:22 -0500917
918 MagickBooleanType
919 status;
920
921 MagickOffsetType
922 progress;
923
Cristy6b1e8132020-11-30 01:01:58 +0000924 OffsetInfo
925 mid;
926
Cristyf2dc1dd2020-12-28 13:59:26 -0500927 ssize_t
Cristybd2b3512020-12-05 01:45:50 +0000928 u;
929
Cristyd4b1a4b2020-11-25 09:17:22 -0500930 ssize_t
Cristybd2b3512020-12-05 01:45:50 +0000931 n,
932 number_threads,
933 v;
934
935 ssize_t
936 i,
Cristyd4b1a4b2020-11-25 09:17:22 -0500937 y;
938
939 assert(image != (const Image *) NULL);
940 assert(image->signature == MagickCoreSignature);
941 if (image->debug != MagickFalse)
942 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
943 assert(exception != (ExceptionInfo *) NULL);
944 assert(exception->signature == MagickCoreSignature);
Cristyc7062302020-11-27 14:51:30 +0000945 blur_image=CloneImage(image,0,0,MagickTrue,exception);
946 if (blur_image == (Image *) NULL)
Cristyd4b1a4b2020-11-25 09:17:22 -0500947 return((Image *) NULL);
Cristyc7062302020-11-27 14:51:30 +0000948 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
Cristyd4b1a4b2020-11-25 09:17:22 -0500949 {
Cristyc7062302020-11-27 14:51:30 +0000950 blur_image=DestroyImage(blur_image);
Cristyd4b1a4b2020-11-25 09:17:22 -0500951 return((Image *) NULL);
952 }
Cristybd2b3512020-12-05 01:45:50 +0000953 number_threads=(size_t) GetMagickResourceLimit(ThreadResource);
954 weights=AcquireBilateralThreadSet(number_threads,width,height);
Cristy7e96e9f2020-11-28 15:38:02 +0000955 if (weights == (double **) NULL)
956 {
957 blur_image=DestroyImage(blur_image);
958 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
959 }
Cristybd2b3512020-12-05 01:45:50 +0000960 for (i=(-MaxIntensity); i < MaxIntensity; i++)
961 intensity_gaussian[i+MaxIntensity]=BlurGaussian((double) i,intensity_sigma);
962 spatial_gaussian=weights[number_threads];
963 n=0;
964 mid.x=(ssize_t) (width/2L);
965 mid.y=(ssize_t) (height/2L);
966 for (v=0; v < (ssize_t) height; v++)
967 for (u=0; u < (ssize_t) width; u++)
968 spatial_gaussian[n++]=BlurGaussian(BlurDistance(0,0,u-mid.x,v-mid.y),
969 spatial_sigma);
Cristyd4b1a4b2020-11-25 09:17:22 -0500970 /*
Cristyc7062302020-11-27 14:51:30 +0000971 Bilateral blur image.
Cristyd4b1a4b2020-11-25 09:17:22 -0500972 */
973 status=MagickTrue;
974 progress=0;
Cristyd4b1a4b2020-11-25 09:17:22 -0500975 image_view=AcquireVirtualCacheView(image,exception);
Cristyc7062302020-11-27 14:51:30 +0000976 blur_view=AcquireAuthenticCacheView(blur_image,exception);
Cristyd4b1a4b2020-11-25 09:17:22 -0500977#if defined(MAGICKCORE_OPENMP_SUPPORT)
978 #pragma omp parallel for schedule(static) shared(progress,status) \
Cristyc7062302020-11-27 14:51:30 +0000979 magick_number_threads(image,blur_image,blur_image->rows,1)
Cristyd4b1a4b2020-11-25 09:17:22 -0500980#endif
Cristyc7062302020-11-27 14:51:30 +0000981 for (y=0; y < (ssize_t) blur_image->rows; y++)
Cristyd4b1a4b2020-11-25 09:17:22 -0500982 {
Cristy7e96e9f2020-11-28 15:38:02 +0000983 const int
984 id = GetOpenMPThreadId();
985
Cristyf2dc1dd2020-12-28 13:59:26 -0500986 Quantum
Cristyd4b1a4b2020-11-25 09:17:22 -0500987 *magick_restrict q;
988
Cristyf2dc1dd2020-12-28 13:59:26 -0500989 ssize_t
Cristyd4b1a4b2020-11-25 09:17:22 -0500990 x;
991
992 if (status == MagickFalse)
993 continue;
Cristyc7062302020-11-27 14:51:30 +0000994 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
Cristy57d08c92020-11-27 02:30:10 +0000995 exception);
996 if (q == (Quantum *) NULL)
Cristyd4b1a4b2020-11-25 09:17:22 -0500997 {
998 status=MagickFalse;
999 continue;
1000 }
Cristyc7062302020-11-27 14:51:30 +00001001 for (x=0; x < (ssize_t) blur_image->columns; x++)
Cristyd4b1a4b2020-11-25 09:17:22 -05001002 {
Cristy7e96e9f2020-11-28 15:38:02 +00001003 double
1004 gamma,
1005 pixel;
1006
Cristyf2dc1dd2020-12-28 13:59:26 -05001007 const Quantum
Cristy4ea9f992020-11-28 15:48:33 +00001008 *magick_restrict p,
1009 *magick_restrict r;
Cristyd4b1a4b2020-11-25 09:17:22 -05001010
Cristyf2dc1dd2020-12-28 13:59:26 -05001011 ssize_t
Cristy7e96e9f2020-11-28 15:38:02 +00001012 i,
1013 u;
Cristyd4b1a4b2020-11-25 09:17:22 -05001014
Cristy7e96e9f2020-11-28 15:38:02 +00001015 ssize_t
1016 n,
1017 v;
1018
1019 /*
1020 Tonal weighting preserves edges while smoothing in the flat regions.
1021 */
Cristy6b1e8132020-11-30 01:01:58 +00001022 p=GetCacheViewVirtualPixels(image_view,x-mid.x,y-mid.y,width,height,
1023 exception);
Cristyd4b1a4b2020-11-25 09:17:22 -05001024 if (p == (const Quantum *) NULL)
1025 break;
Cristybd2b3512020-12-05 01:45:50 +00001026 p+=(ssize_t) GetPixelChannels(image)*width*mid.y+GetPixelChannels(image)*
1027 mid.x;
Cristy7e96e9f2020-11-28 15:38:02 +00001028 n=0;
Cristy00bb7ed2020-11-29 22:13:43 +00001029 for (v=0; v < (ssize_t) height; v++)
Cristy7e96e9f2020-11-28 15:38:02 +00001030 {
1031 for (u=0; u < (ssize_t) width; u++)
1032 {
Cristybd2b3512020-12-05 01:45:50 +00001033 double
1034 intensity;
1035
Cristy6b1e8132020-11-30 01:01:58 +00001036 r=p+(ssize_t) GetPixelChannels(image)*(ssize_t) width*(mid.y-v)+
1037 GetPixelChannels(image)*(mid.x-u);
Cristybd2b3512020-12-05 01:45:50 +00001038 intensity=ScaleQuantumToChar(GetPixelIntensity(image,r))-
1039 (double) ScaleQuantumToChar(GetPixelIntensity(image,p));
1040 if ((intensity >= -MaxIntensity) && (intensity <= MaxIntensity))
1041 weights[id][n]=intensity_gaussian[(ssize_t) intensity+MaxIntensity]*
1042 spatial_gaussian[n];
1043 else
1044 weights[id][n]=BlurGaussian(intensity,intensity_sigma)*
1045 BlurGaussian(BlurDistance(x,y,x+u-mid.x,y+v-mid.y),spatial_sigma);
Cristy7e96e9f2020-11-28 15:38:02 +00001046 n++;
1047 }
1048 }
Cristyc7062302020-11-27 14:51:30 +00001049 for (i=0; i < (ssize_t) GetPixelChannels(blur_image); i++)
Cristyd4b1a4b2020-11-25 09:17:22 -05001050 {
Cristyd4b1a4b2020-11-25 09:17:22 -05001051 PixelChannel
1052 channel;
1053
1054 PixelTrait
Cristyc7062302020-11-27 14:51:30 +00001055 blur_traits,
Cristyd4b1a4b2020-11-25 09:17:22 -05001056 traits;
1057
Cristyd4b1a4b2020-11-25 09:17:22 -05001058 channel=GetPixelChannelChannel(image,i);
1059 traits=GetPixelChannelTraits(image,channel);
Cristyc7062302020-11-27 14:51:30 +00001060 blur_traits=GetPixelChannelTraits(blur_image,channel);
Cristyd4b1a4b2020-11-25 09:17:22 -05001061 if ((traits == UndefinedPixelTrait) ||
Cristyc7062302020-11-27 14:51:30 +00001062 (blur_traits == UndefinedPixelTrait))
Cristyd4b1a4b2020-11-25 09:17:22 -05001063 continue;
Cristyc7062302020-11-27 14:51:30 +00001064 if ((blur_traits & CopyPixelTrait) != 0)
Cristyd4b1a4b2020-11-25 09:17:22 -05001065 {
Cristyc7062302020-11-27 14:51:30 +00001066 SetPixelChannel(blur_image,channel,p[i],q);
Cristyd4b1a4b2020-11-25 09:17:22 -05001067 continue;
1068 }
Cristyd4b1a4b2020-11-25 09:17:22 -05001069 pixel=0.0;
1070 gamma=0.0;
Cristy7e96e9f2020-11-28 15:38:02 +00001071 n=0;
Cristyc7062302020-11-27 14:51:30 +00001072 if ((blur_traits & BlendPixelTrait) == 0)
Cristyd4b1a4b2020-11-25 09:17:22 -05001073 {
1074 /*
1075 No alpha blending.
Cristydb246c32020-11-26 20:27:19 +00001076 */
Cristy00bb7ed2020-11-29 22:13:43 +00001077 for (v=0; v < (ssize_t) height; v++)
Cristyd4b1a4b2020-11-25 09:17:22 -05001078 {
1079 for (u=0; u < (ssize_t) width; u++)
1080 {
Cristy6b1e8132020-11-30 01:01:58 +00001081 r=p+(ssize_t) GetPixelChannels(image)*width*(mid.y-v)+
1082 GetPixelChannels(image)*(mid.x-u);
Cristy7e96e9f2020-11-28 15:38:02 +00001083 pixel+=weights[id][n]*r[i];
1084 gamma+=weights[id][n];
1085 n++;
Cristyd4b1a4b2020-11-25 09:17:22 -05001086 }
1087 }
Cristyc7062302020-11-27 14:51:30 +00001088 SetPixelChannel(blur_image,channel,ClampToQuantum(
Cristydfff9252020-11-28 13:50:38 +00001089 PerceptibleReciprocal(gamma)*pixel),q);
Cristyd4b1a4b2020-11-25 09:17:22 -05001090 continue;
1091 }
1092 /*
1093 Alpha blending.
1094 */
Cristy00bb7ed2020-11-29 22:13:43 +00001095 for (v=0; v < (ssize_t) height; v++)
Cristyd4b1a4b2020-11-25 09:17:22 -05001096 {
1097 for (u=0; u < (ssize_t) width; u++)
1098 {
Cristyc7062302020-11-27 14:51:30 +00001099 double
1100 alpha,
1101 beta;
1102
Cristy6b1e8132020-11-30 01:01:58 +00001103 r=p+(ssize_t) GetPixelChannels(image)*width*(mid.y-v)+
1104 GetPixelChannels(image)*(mid.x-u);
Cristyc7062302020-11-27 14:51:30 +00001105 alpha=(double) (QuantumScale*GetPixelAlpha(image,p));
Cristy7e96e9f2020-11-28 15:38:02 +00001106 beta=(double) (QuantumScale*GetPixelAlpha(image,r));
1107 pixel+=weights[id][n]*r[i];
1108 gamma+=weights[id][n]*alpha*beta;
1109 n++;
Cristyd4b1a4b2020-11-25 09:17:22 -05001110 }
1111 }
Cristydfff9252020-11-28 13:50:38 +00001112 SetPixelChannel(blur_image,channel,ClampToQuantum(
Cristy3dcc66f2020-11-26 20:18:32 +00001113 PerceptibleReciprocal(gamma)*pixel),q);
Cristyd4b1a4b2020-11-25 09:17:22 -05001114 }
Cristyc7062302020-11-27 14:51:30 +00001115 q+=GetPixelChannels(blur_image);
Cristyd4b1a4b2020-11-25 09:17:22 -05001116 }
Cristyc7062302020-11-27 14:51:30 +00001117 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
Cristyd4b1a4b2020-11-25 09:17:22 -05001118 status=MagickFalse;
1119 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1120 {
1121 MagickBooleanType
1122 proceed;
1123
1124#if defined(MAGICKCORE_OPENMP_SUPPORT)
1125 #pragma omp atomic
1126#endif
1127 progress++;
Cristy2e4e0022020-11-26 19:29:45 +00001128 proceed=SetImageProgress(image,BilateralBlurImageTag,progress,
Cristyd4b1a4b2020-11-25 09:17:22 -05001129 image->rows);
1130 if (proceed == MagickFalse)
1131 status=MagickFalse;
1132 }
1133 }
Cristyc7062302020-11-27 14:51:30 +00001134 blur_image->type=image->type;
1135 blur_view=DestroyCacheView(blur_view);
Cristyd4b1a4b2020-11-25 09:17:22 -05001136 image_view=DestroyCacheView(image_view);
Cristybd2b3512020-12-05 01:45:50 +00001137 weights=DestroyBilateralThreadSet(number_threads,weights);
Cristyd4b1a4b2020-11-25 09:17:22 -05001138 if (status == MagickFalse)
Cristyc7062302020-11-27 14:51:30 +00001139 blur_image=DestroyImage(blur_image);
1140 return(blur_image);
Cristyd4b1a4b2020-11-25 09:17:22 -05001141}
1142
1143/*
1144%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1145% %
1146% %
1147% %
cristyfccdab92009-11-30 16:43:57 +00001148% C o n v o l v e I m a g e %
1149% %
1150% %
1151% %
1152%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1153%
1154% ConvolveImage() applies a custom convolution kernel to the image.
1155%
1156% The format of the ConvolveImage method is:
1157%
cristy5e6be1e2011-07-16 01:23:39 +00001158% Image *ConvolveImage(const Image *image,const KernelInfo *kernel,
1159% ExceptionInfo *exception)
1160%
cristyfccdab92009-11-30 16:43:57 +00001161% A description of each parameter follows:
1162%
1163% o image: the image.
1164%
cristy5e6be1e2011-07-16 01:23:39 +00001165% o kernel: the filtering kernel.
cristyfccdab92009-11-30 16:43:57 +00001166%
1167% o exception: return any errors or warnings in this structure.
1168%
1169*/
cristy5e6be1e2011-07-16 01:23:39 +00001170MagickExport Image *ConvolveImage(const Image *image,
1171 const KernelInfo *kernel_info,ExceptionInfo *exception)
cristyfccdab92009-11-30 16:43:57 +00001172{
cristy7f391ce2013-03-27 11:03:11 +00001173 Image
1174 *convolve_image;
1175
dirk21dc0312016-06-13 22:30:26 +02001176#if defined(MAGICKCORE_OPENCL_SUPPORT)
dirk06c4f032016-02-11 23:00:56 +01001177 convolve_image=AccelerateConvolveImage(image,kernel_info,exception);
Dusan Veljko94190b72015-11-09 14:45:26 +01001178 if (convolve_image != (Image *) NULL)
1179 return(convolve_image);
dirk21dc0312016-06-13 22:30:26 +02001180#endif
Dusan Veljko94190b72015-11-09 14:45:26 +01001181
Cristy1a86bf62016-02-05 20:22:03 -05001182 convolve_image=MorphologyImage(image,ConvolveMorphology,1,kernel_info,
1183 exception);
cristy7f391ce2013-03-27 11:03:11 +00001184 return(convolve_image);
cristyfccdab92009-11-30 16:43:57 +00001185}
1186
1187/*
1188%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1189% %
1190% %
1191% %
cristy3ed852e2009-09-05 21:47:34 +00001192% D e s p e c k l e I m a g e %
1193% %
1194% %
1195% %
1196%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1197%
1198% DespeckleImage() reduces the speckle noise in an image while perserving the
dirk05d2ff72015-11-18 23:13:43 +01001199% edges of the original image. A speckle removing filter uses a complementary
1200% hulling technique (raising pixels that are darker than their surrounding
cristyf0ae7762012-01-15 22:04:03 +00001201% neighbors, then complementarily lowering pixels that are brighter than their
1202% surrounding neighbors) to reduce the speckle index of that image (reference
1203% Crimmins speckle removal).
cristy3ed852e2009-09-05 21:47:34 +00001204%
1205% The format of the DespeckleImage method is:
1206%
1207% Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1208%
1209% A description of each parameter follows:
1210%
1211% o image: the image.
1212%
1213% o exception: return any errors or warnings in this structure.
1214%
1215*/
1216
cristy4ee2b0c2012-05-15 00:30:35 +00001217static void Hull(const Image *image,const ssize_t x_offset,
1218 const ssize_t y_offset,const size_t columns,const size_t rows,
dirk05d2ff72015-11-18 23:13:43 +01001219 const int polarity,Quantum *magick_restrict f,Quantum *magick_restrict g)
cristy3ed852e2009-09-05 21:47:34 +00001220{
Cristyf2dc1dd2020-12-28 13:59:26 -05001221 Quantum
cristy5473bf02012-01-16 19:15:08 +00001222 *p,
1223 *q,
1224 *r,
1225 *s;
cristy78f3de22012-01-16 00:43:18 +00001226
cristy5473bf02012-01-16 19:15:08 +00001227 ssize_t
1228 y;
1229
cristyb0de93f2013-05-03 13:39:25 +00001230 assert(image != (const Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +00001231 assert(image->signature == MagickCoreSignature);
cristyb0de93f2013-05-03 13:39:25 +00001232 if (image->debug != MagickFalse)
1233 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy5473bf02012-01-16 19:15:08 +00001234 assert(f != (Quantum *) NULL);
1235 assert(g != (Quantum *) NULL);
1236 p=f+(columns+2);
1237 q=g+(columns+2);
Cristy82945212019-04-20 16:04:40 -04001238 r=p+(y_offset*((ssize_t) columns+2)+x_offset);
cristyff8e85a2012-01-18 13:36:20 +00001239#if defined(MAGICKCORE_OPENMP_SUPPORT)
Cristy38b42e12018-03-18 10:13:01 -04001240 #pragma omp parallel for schedule(static) \
Cristydce076e2017-10-10 19:45:45 -04001241 magick_number_threads(image,image,rows,1)
cristy5473bf02012-01-16 19:15:08 +00001242#endif
1243 for (y=0; y < (ssize_t) rows; y++)
1244 {
cristyc058afd2012-10-31 11:26:53 +00001245 MagickRealType
1246 v;
1247
Cristyf2dc1dd2020-12-28 13:59:26 -05001248 ssize_t
cristy5473bf02012-01-16 19:15:08 +00001249 i,
1250 x;
1251
1252 i=(2*y+1)+y*columns;
1253 if (polarity > 0)
1254 for (x=0; x < (ssize_t) columns; x++)
1255 {
cristyc058afd2012-10-31 11:26:53 +00001256 v=(MagickRealType) p[i];
1257 if ((MagickRealType) r[i] >= (v+ScaleCharToQuantum(2)))
cristy5473bf02012-01-16 19:15:08 +00001258 v+=ScaleCharToQuantum(1);
1259 q[i]=(Quantum) v;
1260 i++;
1261 }
1262 else
1263 for (x=0; x < (ssize_t) columns; x++)
1264 {
cristyc058afd2012-10-31 11:26:53 +00001265 v=(MagickRealType) p[i];
1266 if ((MagickRealType) r[i] <= (v-ScaleCharToQuantum(2)))
cristy5473bf02012-01-16 19:15:08 +00001267 v-=ScaleCharToQuantum(1);
1268 q[i]=(Quantum) v;
1269 i++;
1270 }
1271 }
1272 p=f+(columns+2);
1273 q=g+(columns+2);
Cristy82945212019-04-20 16:04:40 -04001274 r=q+(y_offset*((ssize_t) columns+2)+x_offset);
1275 s=q-(y_offset*((ssize_t) columns+2)+x_offset);
cristyff8e85a2012-01-18 13:36:20 +00001276#if defined(MAGICKCORE_OPENMP_SUPPORT)
Cristy38b42e12018-03-18 10:13:01 -04001277 #pragma omp parallel for schedule(static) \
Cristydce076e2017-10-10 19:45:45 -04001278 magick_number_threads(image,image,rows,1)
cristy5473bf02012-01-16 19:15:08 +00001279#endif
1280 for (y=0; y < (ssize_t) rows; y++)
1281 {
Cristyf2dc1dd2020-12-28 13:59:26 -05001282 ssize_t
cristy5473bf02012-01-16 19:15:08 +00001283 i,
1284 x;
1285
cristyc058afd2012-10-31 11:26:53 +00001286 MagickRealType
cristy7b4b8862012-01-16 19:52:19 +00001287 v;
1288
cristy5473bf02012-01-16 19:15:08 +00001289 i=(2*y+1)+y*columns;
1290 if (polarity > 0)
1291 for (x=0; x < (ssize_t) columns; x++)
1292 {
cristyc058afd2012-10-31 11:26:53 +00001293 v=(MagickRealType) q[i];
1294 if (((MagickRealType) s[i] >= (v+ScaleCharToQuantum(2))) &&
1295 ((MagickRealType) r[i] > v))
cristy5473bf02012-01-16 19:15:08 +00001296 v+=ScaleCharToQuantum(1);
1297 p[i]=(Quantum) v;
1298 i++;
1299 }
1300 else
1301 for (x=0; x < (ssize_t) columns; x++)
1302 {
cristyc058afd2012-10-31 11:26:53 +00001303 v=(MagickRealType) q[i];
1304 if (((MagickRealType) s[i] <= (v-ScaleCharToQuantum(2))) &&
1305 ((MagickRealType) r[i] < v))
cristy5473bf02012-01-16 19:15:08 +00001306 v-=ScaleCharToQuantum(1);
1307 p[i]=(Quantum) v;
1308 i++;
1309 }
1310 }
cristy3ed852e2009-09-05 21:47:34 +00001311}
1312
1313MagickExport Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1314{
1315#define DespeckleImageTag "Despeckle/Image"
1316
cristy2407fc22009-09-11 00:55:25 +00001317 CacheView
1318 *despeckle_view,
1319 *image_view;
1320
cristy3ed852e2009-09-05 21:47:34 +00001321 Image
1322 *despeckle_image;
1323
cristy3ed852e2009-09-05 21:47:34 +00001324 MagickBooleanType
1325 status;
1326
cristy7d4aa382013-06-30 01:59:39 +00001327 MemoryInfo
1328 *buffer_info,
1329 *pixel_info;
1330
cristy5473bf02012-01-16 19:15:08 +00001331 Quantum
dirk05d2ff72015-11-18 23:13:43 +01001332 *magick_restrict buffer,
1333 *magick_restrict pixels;
cristya63e4a92011-09-09 00:47:59 +00001334
Cristyf2dc1dd2020-12-28 13:59:26 -05001335 ssize_t
cristy5473bf02012-01-16 19:15:08 +00001336 i;
1337
1338 size_t
1339 length;
cristy117ff172010-08-15 21:35:32 +00001340
cristybb503372010-05-27 20:51:26 +00001341 static const ssize_t
cristy691a29e2009-09-11 00:44:10 +00001342 X[4] = {0, 1, 1,-1},
1343 Y[4] = {1, 0, 1, 1};
cristy3ed852e2009-09-05 21:47:34 +00001344
cristy3ed852e2009-09-05 21:47:34 +00001345 /*
1346 Allocate despeckled image.
1347 */
1348 assert(image != (const Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +00001349 assert(image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00001350 if (image->debug != MagickFalse)
1351 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1352 assert(exception != (ExceptionInfo *) NULL);
cristye1c94d92015-06-28 12:16:33 +00001353 assert(exception->signature == MagickCoreSignature);
dirk21dc0312016-06-13 22:30:26 +02001354#if defined(MAGICKCORE_OPENCL_SUPPORT)
Dusan Veljko94190b72015-11-09 14:45:26 +01001355 despeckle_image=AccelerateDespeckleImage(image,exception);
1356 if (despeckle_image != (Image *) NULL)
1357 return(despeckle_image);
dirk21dc0312016-06-13 22:30:26 +02001358#endif
cristy5473bf02012-01-16 19:15:08 +00001359 despeckle_image=CloneImage(image,0,0,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00001360 if (despeckle_image == (Image *) NULL)
1361 return((Image *) NULL);
cristya63e4a92011-09-09 00:47:59 +00001362 status=SetImageStorageClass(despeckle_image,DirectClass,exception);
1363 if (status == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001364 {
cristy3ed852e2009-09-05 21:47:34 +00001365 despeckle_image=DestroyImage(despeckle_image);
1366 return((Image *) NULL);
1367 }
1368 /*
cristy5473bf02012-01-16 19:15:08 +00001369 Allocate image buffer.
1370 */
1371 length=(size_t) ((image->columns+2)*(image->rows+2));
cristy7d4aa382013-06-30 01:59:39 +00001372 pixel_info=AcquireVirtualMemory(length,sizeof(*pixels));
1373 buffer_info=AcquireVirtualMemory(length,sizeof(*buffer));
1374 if ((pixel_info == (MemoryInfo *) NULL) ||
1375 (buffer_info == (MemoryInfo *) NULL))
cristy5473bf02012-01-16 19:15:08 +00001376 {
cristy7d4aa382013-06-30 01:59:39 +00001377 if (buffer_info != (MemoryInfo *) NULL)
1378 buffer_info=RelinquishVirtualMemory(buffer_info);
1379 if (pixel_info != (MemoryInfo *) NULL)
1380 pixel_info=RelinquishVirtualMemory(pixel_info);
cristy5473bf02012-01-16 19:15:08 +00001381 despeckle_image=DestroyImage(despeckle_image);
1382 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1383 }
cristy7d4aa382013-06-30 01:59:39 +00001384 pixels=(Quantum *) GetVirtualMemoryBlob(pixel_info);
1385 buffer=(Quantum *) GetVirtualMemoryBlob(buffer_info);
cristy5473bf02012-01-16 19:15:08 +00001386 /*
1387 Reduce speckle in the image.
cristy3ed852e2009-09-05 21:47:34 +00001388 */
1389 status=MagickTrue;
cristy46ff2672012-12-14 15:32:26 +00001390 image_view=AcquireVirtualCacheView(image,exception);
1391 despeckle_view=AcquireAuthenticCacheView(despeckle_image,exception);
cristy5473bf02012-01-16 19:15:08 +00001392 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy3ed852e2009-09-05 21:47:34 +00001393 {
cristy633067b2013-02-21 01:15:12 +00001394 PixelChannel
1395 channel;
1396
1397 PixelTrait
1398 despeckle_traits,
1399 traits;
1400
Cristyf2dc1dd2020-12-28 13:59:26 -05001401 ssize_t
cristy5473bf02012-01-16 19:15:08 +00001402 k,
cristyf0ae7762012-01-15 22:04:03 +00001403 x;
cristyc1488b52011-02-19 18:54:15 +00001404
cristy5473bf02012-01-16 19:15:08 +00001405 ssize_t
1406 j,
1407 y;
1408
cristy633067b2013-02-21 01:15:12 +00001409 if (status == MagickFalse)
1410 continue;
1411 channel=GetPixelChannelChannel(image,i);
1412 traits=GetPixelChannelTraits(image,channel);
1413 despeckle_traits=GetPixelChannelTraits(despeckle_image,channel);
cristy5473bf02012-01-16 19:15:08 +00001414 if ((traits == UndefinedPixelTrait) ||
1415 (despeckle_traits == UndefinedPixelTrait))
1416 continue;
1417 if ((despeckle_traits & CopyPixelTrait) != 0)
1418 continue;
Cristy81bfff22018-03-10 07:58:31 -05001419 (void) memset(pixels,0,length*sizeof(*pixels));
cristy5473bf02012-01-16 19:15:08 +00001420 j=(ssize_t) image->columns+2;
1421 for (y=0; y < (ssize_t) image->rows; y++)
cristyfc830f42012-01-15 02:45:06 +00001422 {
Cristyf2dc1dd2020-12-28 13:59:26 -05001423 const Quantum
dirk05d2ff72015-11-18 23:13:43 +01001424 *magick_restrict p;
cristyfc830f42012-01-15 02:45:06 +00001425
cristy5473bf02012-01-16 19:15:08 +00001426 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1427 if (p == (const Quantum *) NULL)
cristyf0ae7762012-01-15 22:04:03 +00001428 {
cristy5473bf02012-01-16 19:15:08 +00001429 status=MagickFalse;
1430 continue;
cristyf0ae7762012-01-15 22:04:03 +00001431 }
cristy5473bf02012-01-16 19:15:08 +00001432 j++;
1433 for (x=0; x < (ssize_t) image->columns; x++)
1434 {
1435 pixels[j++]=p[i];
1436 p+=GetPixelChannels(image);
cristyfc830f42012-01-15 02:45:06 +00001437 }
cristy5473bf02012-01-16 19:15:08 +00001438 j++;
cristy3ed852e2009-09-05 21:47:34 +00001439 }
Cristy81bfff22018-03-10 07:58:31 -05001440 (void) memset(buffer,0,length*sizeof(*buffer));
cristy5473bf02012-01-16 19:15:08 +00001441 for (k=0; k < 4; k++)
1442 {
cristy4ee2b0c2012-05-15 00:30:35 +00001443 Hull(image,X[k],Y[k],image->columns,image->rows,1,pixels,buffer);
1444 Hull(image,-X[k],-Y[k],image->columns,image->rows,1,pixels,buffer);
1445 Hull(image,-X[k],-Y[k],image->columns,image->rows,-1,pixels,buffer);
1446 Hull(image,X[k],Y[k],image->columns,image->rows,-1,pixels,buffer);
cristy5473bf02012-01-16 19:15:08 +00001447 }
1448 j=(ssize_t) image->columns+2;
1449 for (y=0; y < (ssize_t) image->rows; y++)
1450 {
1451 MagickBooleanType
1452 sync;
1453
Cristyf2dc1dd2020-12-28 13:59:26 -05001454 Quantum
dirk05d2ff72015-11-18 23:13:43 +01001455 *magick_restrict q;
cristy5473bf02012-01-16 19:15:08 +00001456
cristyf7ba7732013-01-22 22:16:21 +00001457 q=GetCacheViewAuthenticPixels(despeckle_view,0,y,despeckle_image->columns,
1458 1,exception);
cristy5473bf02012-01-16 19:15:08 +00001459 if (q == (Quantum *) NULL)
1460 {
1461 status=MagickFalse;
1462 continue;
1463 }
1464 j++;
1465 for (x=0; x < (ssize_t) image->columns; x++)
1466 {
1467 SetPixelChannel(despeckle_image,channel,pixels[j++],q);
1468 q+=GetPixelChannels(despeckle_image);
1469 }
1470 sync=SyncCacheViewAuthenticPixels(despeckle_view,exception);
1471 if (sync == MagickFalse)
1472 status=MagickFalse;
1473 j++;
1474 }
cristy3ed852e2009-09-05 21:47:34 +00001475 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1476 {
1477 MagickBooleanType
1478 proceed;
1479
cristy5473bf02012-01-16 19:15:08 +00001480 proceed=SetImageProgress(image,DespeckleImageTag,(MagickOffsetType) i,
1481 GetPixelChannels(image));
cristy3ed852e2009-09-05 21:47:34 +00001482 if (proceed == MagickFalse)
1483 status=MagickFalse;
1484 }
1485 }
1486 despeckle_view=DestroyCacheView(despeckle_view);
1487 image_view=DestroyCacheView(image_view);
cristy7d4aa382013-06-30 01:59:39 +00001488 buffer_info=RelinquishVirtualMemory(buffer_info);
1489 pixel_info=RelinquishVirtualMemory(pixel_info);
cristy3ed852e2009-09-05 21:47:34 +00001490 despeckle_image->type=image->type;
1491 if (status == MagickFalse)
1492 despeckle_image=DestroyImage(despeckle_image);
1493 return(despeckle_image);
1494}
1495
1496/*
1497%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1498% %
1499% %
1500% %
1501% E d g e I m a g e %
1502% %
1503% %
1504% %
1505%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1506%
1507% EdgeImage() finds edges in an image. Radius defines the radius of the
1508% convolution filter. Use a radius of 0 and EdgeImage() selects a suitable
1509% radius for you.
1510%
1511% The format of the EdgeImage method is:
1512%
1513% Image *EdgeImage(const Image *image,const double radius,
cristy9dc4c512013-03-24 01:38:00 +00001514% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001515%
1516% A description of each parameter follows:
1517%
1518% o image: the image.
1519%
1520% o radius: the radius of the pixel neighborhood.
1521%
1522% o exception: return any errors or warnings in this structure.
1523%
1524*/
1525MagickExport Image *EdgeImage(const Image *image,const double radius,
cristy9dc4c512013-03-24 01:38:00 +00001526 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001527{
cristy9ab5d042013-04-28 02:38:26 +00001528 Image
1529 *edge_image;
cristy3ed852e2009-09-05 21:47:34 +00001530
cristy41cbe682011-07-15 19:12:37 +00001531 KernelInfo
1532 *kernel_info;
cristy3ed852e2009-09-05 21:47:34 +00001533
Cristyf2dc1dd2020-12-28 13:59:26 -05001534 ssize_t
cristy9ab5d042013-04-28 02:38:26 +00001535 i;
1536
1537 size_t
1538 width;
cristy41cbe682011-07-15 19:12:37 +00001539
cristy3ed852e2009-09-05 21:47:34 +00001540 assert(image != (const Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +00001541 assert(image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00001542 if (image->debug != MagickFalse)
1543 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1544 assert(exception != (ExceptionInfo *) NULL);
cristye1c94d92015-06-28 12:16:33 +00001545 assert(exception->signature == MagickCoreSignature);
cristy9ab5d042013-04-28 02:38:26 +00001546 width=GetOptimalKernelWidth1D(radius,0.5);
cristy2c57b742014-10-31 00:40:34 +00001547 kernel_info=AcquireKernelInfo((const char *) NULL,exception);
cristy41cbe682011-07-15 19:12:37 +00001548 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001549 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
Cristy81bfff22018-03-10 07:58:31 -05001550 (void) memset(kernel_info,0,sizeof(*kernel_info));
cristy9ab5d042013-04-28 02:38:26 +00001551 kernel_info->width=width;
1552 kernel_info->height=width;
1553 kernel_info->x=(ssize_t) (kernel_info->width-1)/2;
1554 kernel_info->y=(ssize_t) (kernel_info->height-1)/2;
cristye1c94d92015-06-28 12:16:33 +00001555 kernel_info->signature=MagickCoreSignature;
cristy9ab5d042013-04-28 02:38:26 +00001556 kernel_info->values=(MagickRealType *) MagickAssumeAligned(
1557 AcquireAlignedMemory(kernel_info->width,kernel_info->height*
1558 sizeof(*kernel_info->values)));
1559 if (kernel_info->values == (MagickRealType *) NULL)
1560 {
1561 kernel_info=DestroyKernelInfo(kernel_info);
1562 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1563 }
1564 for (i=0; i < (ssize_t) (kernel_info->width*kernel_info->height); i++)
1565 kernel_info->values[i]=(-1.0);
1566 kernel_info->values[i/2]=(double) kernel_info->width*kernel_info->height-1.0;
dirk38aff572015-11-11 15:32:32 +01001567 edge_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00001568 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00001569 return(edge_image);
1570}
1571
1572/*
1573%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1574% %
1575% %
1576% %
1577% E m b o s s I m a g e %
1578% %
1579% %
1580% %
1581%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1582%
1583% EmbossImage() returns a grayscale image with a three-dimensional effect.
1584% We convolve the image with a Gaussian operator of the given radius and
1585% standard deviation (sigma). For reasonable results, radius should be
1586% larger than sigma. Use a radius of 0 and Emboss() selects a suitable
1587% radius for you.
1588%
1589% The format of the EmbossImage method is:
1590%
1591% Image *EmbossImage(const Image *image,const double radius,
1592% const double sigma,ExceptionInfo *exception)
1593%
1594% A description of each parameter follows:
1595%
1596% o image: the image.
1597%
1598% o radius: the radius of the pixel neighborhood.
1599%
1600% o sigma: the standard deviation of the Gaussian, in pixels.
1601%
1602% o exception: return any errors or warnings in this structure.
1603%
1604*/
1605MagickExport Image *EmbossImage(const Image *image,const double radius,
1606 const double sigma,ExceptionInfo *exception)
1607{
cristy0cfd5672013-03-26 14:34:25 +00001608 double
1609 gamma,
1610 normalize;
1611
cristy3ed852e2009-09-05 21:47:34 +00001612 Image
1613 *emboss_image;
1614
cristy41cbe682011-07-15 19:12:37 +00001615 KernelInfo
1616 *kernel_info;
1617
Cristyf2dc1dd2020-12-28 13:59:26 -05001618 ssize_t
cristy47e00502009-12-17 19:19:57 +00001619 i;
1620
cristybb503372010-05-27 20:51:26 +00001621 size_t
cristy3ed852e2009-09-05 21:47:34 +00001622 width;
1623
cristy117ff172010-08-15 21:35:32 +00001624 ssize_t
1625 j,
1626 k,
1627 u,
1628 v;
1629
cristy41cbe682011-07-15 19:12:37 +00001630 assert(image != (const Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +00001631 assert(image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00001632 if (image->debug != MagickFalse)
1633 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1634 assert(exception != (ExceptionInfo *) NULL);
cristye1c94d92015-06-28 12:16:33 +00001635 assert(exception->signature == MagickCoreSignature);
cristyfc0ae532011-12-06 15:14:45 +00001636 width=GetOptimalKernelWidth1D(radius,sigma);
cristy2c57b742014-10-31 00:40:34 +00001637 kernel_info=AcquireKernelInfo((const char *) NULL,exception);
cristy41cbe682011-07-15 19:12:37 +00001638 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001639 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy41cbe682011-07-15 19:12:37 +00001640 kernel_info->width=width;
1641 kernel_info->height=width;
cristy6b516212013-03-22 19:20:32 +00001642 kernel_info->x=(ssize_t) (width-1)/2;
1643 kernel_info->y=(ssize_t) (width-1)/2;
cristye42639a2012-08-23 01:53:24 +00001644 kernel_info->values=(MagickRealType *) MagickAssumeAligned(
1645 AcquireAlignedMemory(kernel_info->width,kernel_info->width*
1646 sizeof(*kernel_info->values)));
cristyd1e1c222012-08-13 01:13:28 +00001647 if (kernel_info->values == (MagickRealType *) NULL)
cristy41cbe682011-07-15 19:12:37 +00001648 {
1649 kernel_info=DestroyKernelInfo(kernel_info);
1650 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1651 }
cristy35faaa72013-03-08 12:33:42 +00001652 j=(ssize_t) (kernel_info->width-1)/2;
cristy47e00502009-12-17 19:19:57 +00001653 k=j;
1654 i=0;
1655 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00001656 {
cristy47e00502009-12-17 19:19:57 +00001657 for (u=(-j); u <= j; u++)
cristy3ed852e2009-09-05 21:47:34 +00001658 {
cristyd1e1c222012-08-13 01:13:28 +00001659 kernel_info->values[i]=(MagickRealType) (((u < 0) || (v < 0) ? -8.0 :
1660 8.0)*exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
cristy4205a3c2010-09-12 20:19:59 +00001661 (2.0*MagickPI*MagickSigma*MagickSigma));
cristy08ee72f2013-07-08 19:31:14 +00001662 if (u != k)
1663 kernel_info->values[i]=0.0;
cristy3ed852e2009-09-05 21:47:34 +00001664 i++;
1665 }
cristy47e00502009-12-17 19:19:57 +00001666 k--;
cristy3ed852e2009-09-05 21:47:34 +00001667 }
cristy0cfd5672013-03-26 14:34:25 +00001668 normalize=0.0;
1669 for (i=0; i < (ssize_t) (kernel_info->width*kernel_info->height); i++)
1670 normalize+=kernel_info->values[i];
1671 gamma=PerceptibleReciprocal(normalize);
1672 for (i=0; i < (ssize_t) (kernel_info->width*kernel_info->height); i++)
1673 kernel_info->values[i]*=gamma;
dirk38aff572015-11-11 15:32:32 +01001674 emboss_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00001675 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00001676 if (emboss_image != (Image *) NULL)
cristy6d8c3d72011-08-22 01:20:01 +00001677 (void) EqualizeImage(emboss_image,exception);
cristy3ed852e2009-09-05 21:47:34 +00001678 return(emboss_image);
1679}
1680
1681/*
1682%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1683% %
1684% %
1685% %
1686% G a u s s i a n B l u r I m a g e %
1687% %
1688% %
1689% %
1690%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1691%
1692% GaussianBlurImage() blurs an image. We convolve the image with a
1693% Gaussian operator of the given radius and standard deviation (sigma).
1694% For reasonable results, the radius should be larger than sigma. Use a
Cristy402bffe2020-11-25 01:37:04 +00001695% radius of 0 and GaussianBlurImage() selects a suitable radius for you.
cristy3ed852e2009-09-05 21:47:34 +00001696%
1697% The format of the GaussianBlurImage method is:
1698%
1699% Image *GaussianBlurImage(const Image *image,onst double radius,
cristyd89705a2012-01-20 02:52:24 +00001700% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001701%
1702% A description of each parameter follows:
1703%
1704% o image: the image.
1705%
cristy3ed852e2009-09-05 21:47:34 +00001706% o radius: the radius of the Gaussian, in pixels, not counting the center
1707% pixel.
1708%
1709% o sigma: the standard deviation of the Gaussian, in pixels.
1710%
1711% o exception: return any errors or warnings in this structure.
1712%
1713*/
cristy41cbe682011-07-15 19:12:37 +00001714MagickExport Image *GaussianBlurImage(const Image *image,const double radius,
cristyd89705a2012-01-20 02:52:24 +00001715 const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001716{
cristye6bdb5e2013-03-23 18:39:24 +00001717 char
cristy151b66d2015-04-15 10:50:31 +00001718 geometry[MagickPathExtent];
cristy3ed852e2009-09-05 21:47:34 +00001719
cristy41cbe682011-07-15 19:12:37 +00001720 KernelInfo
1721 *kernel_info;
1722
cristye6bdb5e2013-03-23 18:39:24 +00001723 Image
1724 *blur_image;
cristy117ff172010-08-15 21:35:32 +00001725
cristy3ed852e2009-09-05 21:47:34 +00001726 assert(image != (const Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +00001727 assert(image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00001728 if (image->debug != MagickFalse)
1729 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1730 assert(exception != (ExceptionInfo *) NULL);
cristye1c94d92015-06-28 12:16:33 +00001731 assert(exception->signature == MagickCoreSignature);
cristy151b66d2015-04-15 10:50:31 +00001732 (void) FormatLocaleString(geometry,MagickPathExtent,"gaussian:%.20gx%.20g",
cristye6bdb5e2013-03-23 18:39:24 +00001733 radius,sigma);
cristy2c57b742014-10-31 00:40:34 +00001734 kernel_info=AcquireKernelInfo(geometry,exception);
cristy41cbe682011-07-15 19:12:37 +00001735 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001736 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
dirk38aff572015-11-11 15:32:32 +01001737 blur_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00001738 kernel_info=DestroyKernelInfo(kernel_info);
cristy3ed852e2009-09-05 21:47:34 +00001739 return(blur_image);
1740}
1741
1742/*
1743%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1744% %
1745% %
1746% %
cristy3b207f82014-09-27 14:21:20 +00001747% K u w a h a r a I m a g e %
1748% %
1749% %
1750% %
1751%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1752%
cristya7c76222014-09-30 09:53:07 +00001753% KuwaharaImage() is an edge preserving noise reduction filter.
cristy3b207f82014-09-27 14:21:20 +00001754%
1755% The format of the KuwaharaImage method is:
1756%
cristydea96f32014-10-05 23:27:21 +00001757% Image *KuwaharaImage(const Image *image,const double radius,
cristy3b207f82014-09-27 14:21:20 +00001758% const double sigma,ExceptionInfo *exception)
1759%
1760% A description of each parameter follows:
1761%
1762% o image: the image.
1763%
cristydea96f32014-10-05 23:27:21 +00001764% o radius: the square window radius.
cristy3b207f82014-09-27 14:21:20 +00001765%
1766% o sigma: the standard deviation of the Gaussian, in pixels.
1767%
1768% o exception: return any errors or warnings in this structure.
1769%
1770*/
cristye67edc32014-10-02 13:39:48 +00001771
dirk05d2ff72015-11-18 23:13:43 +01001772static inline MagickRealType GetMeanLuma(const Image *magick_restrict image,
1773 const double *magick_restrict pixel)
cristye67edc32014-10-02 13:39:48 +00001774{
cristye67edc32014-10-02 13:39:48 +00001775 return(0.212656f*pixel[image->channel_map[RedPixelChannel].offset]+
1776 0.715158f*pixel[image->channel_map[GreenPixelChannel].offset]+
1777 0.072186f*pixel[image->channel_map[BluePixelChannel].offset]); /* Rec709 */
1778}
1779
cristydea96f32014-10-05 23:27:21 +00001780MagickExport Image *KuwaharaImage(const Image *image,const double radius,
cristy3b207f82014-09-27 14:21:20 +00001781 const double sigma,ExceptionInfo *exception)
1782{
cristy51b20df2014-09-29 18:52:28 +00001783#define KuwaharaImageTag "Kuwahara/Image"
cristy3b207f82014-09-27 14:21:20 +00001784
cristy51b20df2014-09-29 18:52:28 +00001785 CacheView
cristyee31ab82014-10-06 11:35:27 +00001786 *image_view,
cristy51b20df2014-09-29 18:52:28 +00001787 *kuwahara_view;
cristy3b207f82014-09-27 14:21:20 +00001788
1789 Image
cristy0562d772014-10-01 12:33:56 +00001790 *gaussian_image,
cristy3b207f82014-09-27 14:21:20 +00001791 *kuwahara_image;
1792
cristy51b20df2014-09-29 18:52:28 +00001793 MagickBooleanType
1794 status;
1795
1796 MagickOffsetType
1797 progress;
1798
cristya68a3bd2014-10-05 20:57:45 +00001799 size_t
cristydea96f32014-10-05 23:27:21 +00001800 width;
cristya68a3bd2014-10-05 20:57:45 +00001801
cristy51b20df2014-09-29 18:52:28 +00001802 ssize_t
1803 y;
1804
1805 /*
cristyb4a9d382014-10-04 14:06:13 +00001806 Initialize Kuwahara image attributes.
cristy51b20df2014-09-29 18:52:28 +00001807 */
1808 assert(image != (Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +00001809 assert(image->signature == MagickCoreSignature);
cristy3b207f82014-09-27 14:21:20 +00001810 if (image->debug != MagickFalse)
1811 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1812 assert(exception != (ExceptionInfo *) NULL);
cristye1c94d92015-06-28 12:16:33 +00001813 assert(exception->signature == MagickCoreSignature);
cristydea96f32014-10-05 23:27:21 +00001814 width=(size_t) radius+1;
1815 gaussian_image=BlurImage(image,radius,sigma,exception);
cristyc70ee662014-10-01 14:55:15 +00001816 if (gaussian_image == (Image *) NULL)
cristy0562d772014-10-01 12:33:56 +00001817 return((Image *) NULL);
Cristy590a11f2018-06-10 16:25:53 -04001818 kuwahara_image=CloneImage(image,0,0,MagickTrue,exception);
cristy51b20df2014-09-29 18:52:28 +00001819 if (kuwahara_image == (Image *) NULL)
cristy0562d772014-10-01 12:33:56 +00001820 {
1821 gaussian_image=DestroyImage(gaussian_image);
1822 return((Image *) NULL);
1823 }
cristy51b20df2014-09-29 18:52:28 +00001824 if (SetImageStorageClass(kuwahara_image,DirectClass,exception) == MagickFalse)
1825 {
cristy0562d772014-10-01 12:33:56 +00001826 gaussian_image=DestroyImage(gaussian_image);
cristy51b20df2014-09-29 18:52:28 +00001827 kuwahara_image=DestroyImage(kuwahara_image);
1828 return((Image *) NULL);
1829 }
1830 /*
cristyd1bc64d2014-09-30 09:52:12 +00001831 Edge preserving noise reduction filter.
cristy51b20df2014-09-29 18:52:28 +00001832 */
1833 status=MagickTrue;
1834 progress=0;
cristyee31ab82014-10-06 11:35:27 +00001835 image_view=AcquireVirtualCacheView(gaussian_image,exception);
cristy51b20df2014-09-29 18:52:28 +00001836 kuwahara_view=AcquireAuthenticCacheView(kuwahara_image,exception);
1837#if defined(MAGICKCORE_OPENMP_SUPPORT)
Cristy38b42e12018-03-18 10:13:01 -04001838 #pragma omp parallel for schedule(static) shared(progress,status) \
Cristydce076e2017-10-10 19:45:45 -04001839 magick_number_threads(image,kuwahara_image,gaussian_image->rows,1)
cristy51b20df2014-09-29 18:52:28 +00001840#endif
Cristyf90b5ce2016-07-02 17:29:32 -04001841 for (y=0; y < (ssize_t) gaussian_image->rows; y++)
cristy51b20df2014-09-29 18:52:28 +00001842 {
Cristyf2dc1dd2020-12-28 13:59:26 -05001843 Quantum
dirk05d2ff72015-11-18 23:13:43 +01001844 *magick_restrict q;
cristy51b20df2014-09-29 18:52:28 +00001845
Cristyf2dc1dd2020-12-28 13:59:26 -05001846 ssize_t
cristy51b20df2014-09-29 18:52:28 +00001847 x;
1848
1849 if (status == MagickFalse)
1850 continue;
1851 q=QueueCacheViewAuthenticPixels(kuwahara_view,0,y,kuwahara_image->columns,1,
1852 exception);
1853 if (q == (Quantum *) NULL)
1854 {
1855 status=MagickFalse;
1856 continue;
1857 }
Cristyf90b5ce2016-07-02 17:29:32 -04001858 for (x=0; x < (ssize_t) gaussian_image->columns; x++)
cristy51b20df2014-09-29 18:52:28 +00001859 {
cristybc8db772014-09-29 20:57:24 +00001860 const Quantum
dirk05d2ff72015-11-18 23:13:43 +01001861 *magick_restrict p;
cristybc8db772014-09-29 20:57:24 +00001862
cristyb4800612014-09-29 22:30:01 +00001863 double
cristybfcd1be2014-10-03 22:39:11 +00001864 min_variance;
cristy70965022014-09-29 23:26:43 +00001865
cristybfcd1be2014-10-03 22:39:11 +00001866 RectangleInfo
cristyee31ab82014-10-06 11:35:27 +00001867 quadrant,
1868 target;
cristyb4800612014-09-29 22:30:01 +00001869
Cristyf2dc1dd2020-12-28 13:59:26 -05001870 size_t
cristyee31ab82014-10-06 11:35:27 +00001871 i;
1872
cristyea715492014-10-03 16:27:53 +00001873 min_variance=MagickMaximumValue;
cristyee31ab82014-10-06 11:35:27 +00001874 SetGeometry(gaussian_image,&target);
1875 quadrant.width=width;
1876 quadrant.height=width;
cristyb4800612014-09-29 22:30:01 +00001877 for (i=0; i < 4; i++)
1878 {
cristye67edc32014-10-02 13:39:48 +00001879 const Quantum
dirk05d2ff72015-11-18 23:13:43 +01001880 *magick_restrict k;
cristye67edc32014-10-02 13:39:48 +00001881
cristyb4800612014-09-29 22:30:01 +00001882 double
cristyb4800612014-09-29 22:30:01 +00001883 mean[MaxPixelChannels],
cristy9e366d32014-09-30 18:45:56 +00001884 variance;
cristyb4800612014-09-29 22:30:01 +00001885
Cristyf2dc1dd2020-12-28 13:59:26 -05001886 ssize_t
cristyfa971252014-10-05 23:55:56 +00001887 n;
cristyb4800612014-09-29 22:30:01 +00001888
cristyee31ab82014-10-06 11:35:27 +00001889 ssize_t
1890 j;
1891
1892 quadrant.x=x;
1893 quadrant.y=y;
1894 switch (i)
1895 {
1896 case 0:
1897 {
1898 quadrant.x=x-(ssize_t) (width-1);
1899 quadrant.y=y-(ssize_t) (width-1);
1900 break;
1901 }
1902 case 1:
1903 {
1904 quadrant.y=y-(ssize_t) (width-1);
1905 break;
1906 }
1907 case 2:
1908 {
1909 quadrant.x=x-(ssize_t) (width-1);
1910 break;
1911 }
1912 case 3:
1913 default:
1914 break;
1915 }
1916 p=GetCacheViewVirtualPixels(image_view,quadrant.x,quadrant.y,
1917 quadrant.width,quadrant.height,exception);
1918 if (p == (const Quantum *) NULL)
1919 break;
1920 for (j=0; j < (ssize_t) GetPixelChannels(gaussian_image); j++)
cristyb4800612014-09-29 22:30:01 +00001921 mean[j]=0.0;
cristyee31ab82014-10-06 11:35:27 +00001922 k=p;
cristyfa971252014-10-05 23:55:56 +00001923 for (n=0; n < (ssize_t) (width*width); n++)
cristye67edc32014-10-02 13:39:48 +00001924 {
cristyee31ab82014-10-06 11:35:27 +00001925 for (j=0; j < (ssize_t) GetPixelChannels(gaussian_image); j++)
cristye67edc32014-10-02 13:39:48 +00001926 mean[j]+=(double) k[j];
Cristyf90b5ce2016-07-02 17:29:32 -04001927 k+=GetPixelChannels(gaussian_image);
cristye67edc32014-10-02 13:39:48 +00001928 }
cristyee31ab82014-10-06 11:35:27 +00001929 for (j=0; j < (ssize_t) GetPixelChannels(gaussian_image); j++)
cristydea96f32014-10-05 23:27:21 +00001930 mean[j]/=(double) (width*width);
cristyee31ab82014-10-06 11:35:27 +00001931 k=p;
cristye67edc32014-10-02 13:39:48 +00001932 variance=0.0;
cristyfa971252014-10-05 23:55:56 +00001933 for (n=0; n < (ssize_t) (width*width); n++)
cristyb4800612014-09-29 22:30:01 +00001934 {
cristy9e366d32014-09-30 18:45:56 +00001935 double
1936 luma;
1937
cristyee31ab82014-10-06 11:35:27 +00001938 luma=GetPixelLuma(gaussian_image,k);
cristye67edc32014-10-02 13:39:48 +00001939 variance+=(luma-GetMeanLuma(gaussian_image,mean))*
1940 (luma-GetMeanLuma(gaussian_image,mean));
Cristyf90b5ce2016-07-02 17:29:32 -04001941 k+=GetPixelChannels(gaussian_image);
cristy70965022014-09-29 23:26:43 +00001942 }
cristy9e366d32014-09-30 18:45:56 +00001943 if (variance < min_variance)
1944 {
1945 min_variance=variance;
cristyee31ab82014-10-06 11:35:27 +00001946 target=quadrant;
cristy9e366d32014-09-30 18:45:56 +00001947 }
cristyb4800612014-09-29 22:30:01 +00001948 }
cristyee31ab82014-10-06 11:35:27 +00001949 if (i < 4)
1950 {
1951 status=MagickFalse;
1952 break;
1953 }
1954 status=InterpolatePixelChannels(gaussian_image,image_view,kuwahara_image,
1955 UndefinedInterpolatePixel,(double) target.x+target.width/2.0,(double)
1956 target.y+target.height/2.0,q,exception);
Cristybb39c462018-01-07 18:57:25 -05001957 if (status == MagickFalse)
1958 break;
cristy51b20df2014-09-29 18:52:28 +00001959 q+=GetPixelChannels(kuwahara_image);
1960 }
1961 if (SyncCacheViewAuthenticPixels(kuwahara_view,exception) == MagickFalse)
1962 status=MagickFalse;
1963 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1964 {
1965 MagickBooleanType
1966 proceed;
1967
Cristyfc89eec2018-11-11 21:10:06 -05001968#if defined(MAGICKCORE_OPENMP_SUPPORT)
1969 #pragma omp atomic
1970#endif
1971 progress++;
1972 proceed=SetImageProgress(image,KuwaharaImageTag,progress,image->rows);
cristy51b20df2014-09-29 18:52:28 +00001973 if (proceed == MagickFalse)
1974 status=MagickFalse;
1975 }
1976 }
1977 kuwahara_view=DestroyCacheView(kuwahara_view);
cristyee31ab82014-10-06 11:35:27 +00001978 image_view=DestroyCacheView(image_view);
cristy0562d772014-10-01 12:33:56 +00001979 gaussian_image=DestroyImage(gaussian_image);
cristy51b20df2014-09-29 18:52:28 +00001980 if (status == MagickFalse)
1981 kuwahara_image=DestroyImage(kuwahara_image);
cristy3b207f82014-09-27 14:21:20 +00001982 return(kuwahara_image);
1983}
Cristyba4ad2d2016-06-07 09:15:26 -04001984
dirk3505d872015-11-01 23:30:09 +01001985/*
1986%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1987% %
1988% %
1989% %
1990% L o c a l C o n t r a s t I m a g e %
1991% %
1992% %
1993% %
1994%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1995%
1996% LocalContrastImage() attempts to increase the appearance of large-scale
dirkbe804602015-11-03 22:36:30 +01001997% light-dark transitions. Local contrast enhancement works similarly to
dirk3505d872015-11-01 23:30:09 +01001998% sharpening with an unsharp mask, however the mask is instead created using
1999% an image with a greater blur distance.
2000%
2001% The format of the LocalContrastImage method is:
2002%
2003% Image *LocalContrastImage(const Image *image, const double radius,
Cristyba4ad2d2016-06-07 09:15:26 -04002004% const double strength,ExceptionInfo *exception)
dirk3505d872015-11-01 23:30:09 +01002005%
2006% A description of each parameter follows:
2007%
2008% o image: the image.
2009%
dirk37a35932016-03-26 11:41:05 +01002010% o radius: the radius of the Gaussian blur, in percentage with 100%
2011% resulting in a blur radius of 20% of largest dimension.
dirk3505d872015-11-01 23:30:09 +01002012%
2013% o strength: the strength of the blur mask in percentage.
2014%
2015% o exception: return any errors or warnings in this structure.
2016%
2017*/
2018MagickExport Image *LocalContrastImage(const Image *image,const double radius,
2019 const double strength,ExceptionInfo *exception)
2020{
2021#define LocalContrastImageTag "LocalContrast/Image"
2022
2023 CacheView
2024 *image_view,
2025 *contrast_view;
2026
2027 float
2028 *interImage,
Cristy82864452020-06-07 08:20:36 -04002029 *scanline,
dirk3505d872015-11-01 23:30:09 +01002030 totalWeight;
2031
2032 Image
2033 *contrast_image;
2034
Cristy54c93932016-06-07 19:29:34 -04002035 MagickBooleanType
2036 status;
2037
dirk3505d872015-11-01 23:30:09 +01002038 MemoryInfo
Cristy82864452020-06-07 08:20:36 -04002039 *scanline_info,
dirk3505d872015-11-01 23:30:09 +01002040 *interImage_info;
2041
2042 ssize_t
dirk3505d872015-11-01 23:30:09 +01002043 scanLineSize,
dirk394f2112015-11-03 21:27:00 +01002044 width;
dirk3505d872015-11-01 23:30:09 +01002045
2046 /*
2047 Initialize contrast image attributes.
2048 */
2049 assert(image != (const Image *) NULL);
2050 assert(image->signature == MagickCoreSignature);
2051 if (image->debug != MagickFalse)
2052 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2053 assert(exception != (ExceptionInfo *) NULL);
2054 assert(exception->signature == MagickCoreSignature);
dirk21dc0312016-06-13 22:30:26 +02002055#if defined(MAGICKCORE_OPENCL_SUPPORT)
dirk3505d872015-11-01 23:30:09 +01002056 contrast_image=AccelerateLocalContrastImage(image,radius,strength,exception);
2057 if (contrast_image != (Image *) NULL)
2058 return(contrast_image);
dirk21dc0312016-06-13 22:30:26 +02002059#endif
dirk3505d872015-11-01 23:30:09 +01002060 contrast_image=CloneImage(image,0,0,MagickTrue,exception);
2061 if (contrast_image == (Image *) NULL)
2062 return((Image *) NULL);
2063 if (SetImageStorageClass(contrast_image,DirectClass,exception) == MagickFalse)
2064 {
2065 contrast_image=DestroyImage(contrast_image);
2066 return((Image *) NULL);
2067 }
2068 image_view=AcquireVirtualCacheView(image,exception);
2069 contrast_view=AcquireAuthenticCacheView(contrast_image,exception);
dirk3505d872015-11-01 23:30:09 +01002070 scanLineSize=(ssize_t) MagickMax(image->columns,image->rows);
dirk37a35932016-03-26 11:41:05 +01002071 width=(ssize_t) scanLineSize*0.002f*fabs(radius);
dirk394f2112015-11-03 21:27:00 +01002072 scanLineSize+=(2*width);
Cristy82864452020-06-07 08:20:36 -04002073 scanline_info=AcquireVirtualMemory((size_t) GetOpenMPMaximumThreads()*
2074 scanLineSize,sizeof(*scanline));
2075 if (scanline_info == (MemoryInfo *) NULL)
dirk3505d872015-11-01 23:30:09 +01002076 {
2077 contrast_view=DestroyCacheView(contrast_view);
2078 image_view=DestroyCacheView(image_view);
2079 contrast_image=DestroyImage(contrast_image);
2080 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2081 }
Cristy82864452020-06-07 08:20:36 -04002082 scanline=(float *) GetVirtualMemoryBlob(scanline_info);
Cristyba4ad2d2016-06-07 09:15:26 -04002083 /*
2084 Create intermediate buffer.
2085 */
dirkd6443992016-04-28 16:54:45 +02002086 interImage_info=AcquireVirtualMemory(image->rows*(image->columns+(2*width)),
dirk3505d872015-11-01 23:30:09 +01002087 sizeof(*interImage));
2088 if (interImage_info == (MemoryInfo *) NULL)
2089 {
Cristy82864452020-06-07 08:20:36 -04002090 scanline_info=RelinquishVirtualMemory(scanline_info);
dirk3505d872015-11-01 23:30:09 +01002091 contrast_view=DestroyCacheView(contrast_view);
2092 image_view=DestroyCacheView(image_view);
2093 contrast_image=DestroyImage(contrast_image);
2094 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2095 }
2096 interImage=(float *) GetVirtualMemoryBlob(interImage_info);
Cristy1a86bf62016-02-05 20:22:03 -05002097 totalWeight=(float) ((width+1)*(width+1));
Cristyba4ad2d2016-06-07 09:15:26 -04002098 /*
2099 Vertical pass.
2100 */
Cristy54c93932016-06-07 19:29:34 -04002101 status=MagickTrue;
dirk3505d872015-11-01 23:30:09 +01002102 {
2103 ssize_t
2104 x;
2105
2106#if defined(MAGICKCORE_OPENMP_SUPPORT)
Cristy38b42e12018-03-18 10:13:01 -04002107#pragma omp parallel for schedule(static) \
Cristy8255ca92017-10-09 08:37:35 -04002108 magick_number_threads(image,image,image->columns,1)
dirk3505d872015-11-01 23:30:09 +01002109#endif
2110 for (x=0; x < (ssize_t) image->columns; x++)
2111 {
Cristyba4ad2d2016-06-07 09:15:26 -04002112 const int
2113 id = GetOpenMPThreadId();
2114
dirk3505d872015-11-01 23:30:09 +01002115 const Quantum
dirk05d2ff72015-11-18 23:13:43 +01002116 *magick_restrict p;
dirk3505d872015-11-01 23:30:09 +01002117
2118 float
2119 *out,
2120 *pix,
2121 *pixels;
2122
Cristyf2dc1dd2020-12-28 13:59:26 -05002123 ssize_t
dirk3505d872015-11-01 23:30:09 +01002124 y;
2125
2126 ssize_t
2127 i;
2128
Cristy54c93932016-06-07 19:29:34 -04002129 if (status == MagickFalse)
2130 continue;
Cristy82864452020-06-07 08:20:36 -04002131 pixels=scanline;
Cristyba4ad2d2016-06-07 09:15:26 -04002132 pixels+=id*scanLineSize;
dirk3505d872015-11-01 23:30:09 +01002133 pix=pixels;
dirk394f2112015-11-03 21:27:00 +01002134 p=GetCacheViewVirtualPixels(image_view,x,-width,1,image->rows+(2*width),
2135 exception);
Cristy54c93932016-06-07 19:29:34 -04002136 if (p == (const Quantum *) NULL)
2137 {
2138 status=MagickFalse;
2139 continue;
2140 }
dirk394f2112015-11-03 21:27:00 +01002141 for (y=0; y < (ssize_t) image->rows+(2*width); y++)
dirk3505d872015-11-01 23:30:09 +01002142 {
2143 *pix++=(float)GetPixelLuma(image,p);
2144 p+=image->number_channels;
2145 }
dirk394f2112015-11-03 21:27:00 +01002146 out=interImage+x+width;
Cristyba4ad2d2016-06-07 09:15:26 -04002147 for (y=0; y < (ssize_t) image->rows; y++)
dirk3505d872015-11-01 23:30:09 +01002148 {
2149 float
2150 sum,
2151 weight;
2152
2153 weight=1.0f;
2154 sum=0;
2155 pix=pixels+y;
dirk394f2112015-11-03 21:27:00 +01002156 for (i=0; i < width; i++)
dirk3505d872015-11-01 23:30:09 +01002157 {
2158 sum+=weight*(*pix++);
2159 weight+=1.0f;
2160 }
dirk394f2112015-11-03 21:27:00 +01002161 for (i=width+1; i < (2*width); i++)
dirk3505d872015-11-01 23:30:09 +01002162 {
2163 sum+=weight*(*pix++);
2164 weight-=1.0f;
2165 }
2166 /* write to output */
2167 *out=sum/totalWeight;
2168 /* mirror into padding */
dirk394f2112015-11-03 21:27:00 +01002169 if (x <= width && x != 0)
dirk3505d872015-11-01 23:30:09 +01002170 *(out-(x*2))=*out;
Cristy3ea718e2015-12-17 13:34:39 -05002171 if ((x > (ssize_t) image->columns-width-2) &&
2172 (x != (ssize_t) image->columns-1))
dirk3505d872015-11-01 23:30:09 +01002173 *(out+((image->columns-x-1)*2))=*out;
dirk394f2112015-11-03 21:27:00 +01002174 out+=image->columns+(width*2);
dirk3505d872015-11-01 23:30:09 +01002175 }
2176 }
2177 }
Cristyba4ad2d2016-06-07 09:15:26 -04002178 /*
2179 Horizontal pass.
2180 */
dirk3505d872015-11-01 23:30:09 +01002181 {
2182 ssize_t
2183 y;
2184
2185#if defined(MAGICKCORE_OPENMP_SUPPORT)
Cristy38b42e12018-03-18 10:13:01 -04002186#pragma omp parallel for schedule(static) \
Cristy8255ca92017-10-09 08:37:35 -04002187 magick_number_threads(image,image,image->rows,1)
dirk3505d872015-11-01 23:30:09 +01002188#endif
2189 for (y=0; y < (ssize_t) image->rows; y++)
2190 {
Cristyba4ad2d2016-06-07 09:15:26 -04002191 const int
2192 id = GetOpenMPThreadId();
2193
dirk3505d872015-11-01 23:30:09 +01002194 const Quantum
dirk05d2ff72015-11-18 23:13:43 +01002195 *magick_restrict p;
dirk3505d872015-11-01 23:30:09 +01002196
2197 float
2198 *pix,
2199 *pixels;
2200
Cristyf2dc1dd2020-12-28 13:59:26 -05002201 Quantum
dirk05d2ff72015-11-18 23:13:43 +01002202 *magick_restrict q;
dirk3505d872015-11-01 23:30:09 +01002203
Cristyf2dc1dd2020-12-28 13:59:26 -05002204 ssize_t
dirk3505d872015-11-01 23:30:09 +01002205 x;
2206
2207 ssize_t
2208 i;
2209
Cristy54c93932016-06-07 19:29:34 -04002210 if (status == MagickFalse)
2211 continue;
Cristy82864452020-06-07 08:20:36 -04002212 pixels=scanline;
Cristyba4ad2d2016-06-07 09:15:26 -04002213 pixels+=id*scanLineSize;
Cristy54c93932016-06-07 19:29:34 -04002214 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
dirk3505d872015-11-01 23:30:09 +01002215 q=GetCacheViewAuthenticPixels(contrast_view,0,y,image->columns,1,
2216 exception);
Cristy54c93932016-06-07 19:29:34 -04002217 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
2218 {
2219 status=MagickFalse;
2220 continue;
2221 }
dirk394f2112015-11-03 21:27:00 +01002222 memcpy(pixels,interImage+(y*(image->columns+(2*width))),(image->columns+
2223 (2*width))*sizeof(float));
dirk3505d872015-11-01 23:30:09 +01002224 for (x=0; x < (ssize_t) image->columns; x++)
2225 {
2226 float
2227 mult,
2228 srcVal,
2229 sum,
2230 weight;
2231
Dirk Lemstra14ef67a2018-04-26 11:17:28 +00002232 PixelTrait
2233 traits;
2234
dirk3505d872015-11-01 23:30:09 +01002235 weight=1.0f;
2236 sum=0;
2237 pix=pixels+x;
dirk394f2112015-11-03 21:27:00 +01002238 for (i=0; i < width; i++)
dirk3505d872015-11-01 23:30:09 +01002239 {
2240 sum+=weight*(*pix++);
2241 weight+=1.0f;
2242 }
dirk394f2112015-11-03 21:27:00 +01002243 for (i=width+1; i < (2*width); i++)
dirk3505d872015-11-01 23:30:09 +01002244 {
2245 sum+=weight*(*pix++);
2246 weight-=1.0f;
2247 }
dirk3505d872015-11-01 23:30:09 +01002248 /* Apply and write */
2249 srcVal=(float) GetPixelLuma(image,p);
2250 mult=(srcVal-(sum/totalWeight))*(strength/100.0f);
2251 mult=(srcVal+mult)/srcVal;
Dirk Lemstra14ef67a2018-04-26 11:17:28 +00002252 traits=GetPixelChannelTraits(image,RedPixelChannel);
2253 if ((traits & UpdatePixelTrait) != 0)
Cristy82864452020-06-07 08:20:36 -04002254 SetPixelRed(contrast_image,ClampToQuantum((MagickRealType)
2255 GetPixelRed(image,p)*mult),q);
Dirk Lemstra14ef67a2018-04-26 11:17:28 +00002256 traits=GetPixelChannelTraits(image,GreenPixelChannel);
2257 if ((traits & UpdatePixelTrait) != 0)
Cristy82864452020-06-07 08:20:36 -04002258 SetPixelGreen(contrast_image,ClampToQuantum((MagickRealType)
2259 GetPixelGreen(image,p)*mult),q);
Dirk Lemstra14ef67a2018-04-26 11:17:28 +00002260 traits=GetPixelChannelTraits(image,BluePixelChannel);
2261 if ((traits & UpdatePixelTrait) != 0)
Cristy82864452020-06-07 08:20:36 -04002262 SetPixelBlue(contrast_image,ClampToQuantum((MagickRealType)
2263 GetPixelBlue(image,p)*mult),q);
dirk3505d872015-11-01 23:30:09 +01002264 p+=image->number_channels;
2265 q+=contrast_image->number_channels;
2266 }
Cristy54c93932016-06-07 19:29:34 -04002267 if (SyncCacheViewAuthenticPixels(contrast_view,exception) == MagickFalse)
2268 status=MagickFalse;
dirk3505d872015-11-01 23:30:09 +01002269 }
2270 }
Cristy82864452020-06-07 08:20:36 -04002271 scanline_info=RelinquishVirtualMemory(scanline_info);
dirk3505d872015-11-01 23:30:09 +01002272 interImage_info=RelinquishVirtualMemory(interImage_info);
2273 contrast_view=DestroyCacheView(contrast_view);
2274 image_view=DestroyCacheView(image_view);
Cristy54c93932016-06-07 19:29:34 -04002275 if (status == MagickFalse)
2276 contrast_image=DestroyImage(contrast_image);
dirk3505d872015-11-01 23:30:09 +01002277 return(contrast_image);
2278}
Cristy1a86bf62016-02-05 20:22:03 -05002279
cristy3b207f82014-09-27 14:21:20 +00002280/*
2281%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2282% %
2283% %
2284% %
cristy3ed852e2009-09-05 21:47:34 +00002285% M o t i o n B l u r I m a g e %
2286% %
2287% %
2288% %
2289%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2290%
2291% MotionBlurImage() simulates motion blur. We convolve the image with a
2292% Gaussian operator of the given radius and standard deviation (sigma).
2293% For reasonable results, radius should be larger than sigma. Use a
2294% radius of 0 and MotionBlurImage() selects a suitable radius for you.
2295% Angle gives the angle of the blurring motion.
2296%
2297% Andrew Protano contributed this effect.
2298%
2299% The format of the MotionBlurImage method is:
2300%
2301% Image *MotionBlurImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +00002302% const double sigma,const double angle,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002303%
2304% A description of each parameter follows:
2305%
2306% o image: the image.
2307%
cristy3ed852e2009-09-05 21:47:34 +00002308% o radius: the radius of the Gaussian, in pixels, not counting
2309% the center pixel.
2310%
2311% o sigma: the standard deviation of the Gaussian, in pixels.
2312%
cristycee97112010-05-28 00:44:52 +00002313% o angle: Apply the effect along this angle.
cristy3ed852e2009-09-05 21:47:34 +00002314%
2315% o exception: return any errors or warnings in this structure.
2316%
2317*/
2318
cristy0a887dc2012-08-15 22:58:36 +00002319static MagickRealType *GetMotionBlurKernel(const size_t width,
2320 const double sigma)
cristy3ed852e2009-09-05 21:47:34 +00002321{
cristy0a887dc2012-08-15 22:58:36 +00002322 MagickRealType
cristy47e00502009-12-17 19:19:57 +00002323 *kernel,
cristy3ed852e2009-09-05 21:47:34 +00002324 normalize;
2325
Cristyf2dc1dd2020-12-28 13:59:26 -05002326 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002327 i;
2328
2329 /*
cristy47e00502009-12-17 19:19:57 +00002330 Generate a 1-D convolution kernel.
cristy3ed852e2009-09-05 21:47:34 +00002331 */
2332 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
cristye42639a2012-08-23 01:53:24 +00002333 kernel=(MagickRealType *) MagickAssumeAligned(AcquireAlignedMemory((size_t)
2334 width,sizeof(*kernel)));
cristy0a887dc2012-08-15 22:58:36 +00002335 if (kernel == (MagickRealType *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002336 return(kernel);
cristy3ed852e2009-09-05 21:47:34 +00002337 normalize=0.0;
cristybb503372010-05-27 20:51:26 +00002338 for (i=0; i < (ssize_t) width; i++)
cristy47e00502009-12-17 19:19:57 +00002339 {
cristy0a887dc2012-08-15 22:58:36 +00002340 kernel[i]=(MagickRealType) (exp((-((double) i*i)/(double) (2.0*MagickSigma*
cristy4205a3c2010-09-12 20:19:59 +00002341 MagickSigma)))/(MagickSQ2PI*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00002342 normalize+=kernel[i];
cristy47e00502009-12-17 19:19:57 +00002343 }
cristybb503372010-05-27 20:51:26 +00002344 for (i=0; i < (ssize_t) width; i++)
cristy3ed852e2009-09-05 21:47:34 +00002345 kernel[i]/=normalize;
2346 return(kernel);
2347}
2348
cristya63e4a92011-09-09 00:47:59 +00002349MagickExport Image *MotionBlurImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +00002350 const double sigma,const double angle,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002351{
cristy590c8292013-02-21 01:37:36 +00002352#define BlurImageTag "Blur/Image"
2353
cristyc4c8d132010-01-07 01:58:38 +00002354 CacheView
2355 *blur_view,
cristy8b49f382012-01-17 03:11:03 +00002356 *image_view,
2357 *motion_view;
cristyc4c8d132010-01-07 01:58:38 +00002358
cristy3ed852e2009-09-05 21:47:34 +00002359 Image
2360 *blur_image;
2361
cristy3ed852e2009-09-05 21:47:34 +00002362 MagickBooleanType
2363 status;
2364
cristybb503372010-05-27 20:51:26 +00002365 MagickOffsetType
2366 progress;
2367
cristy0a887dc2012-08-15 22:58:36 +00002368 MagickRealType
2369 *kernel;
2370
cristy3ed852e2009-09-05 21:47:34 +00002371 OffsetInfo
2372 *offset;
2373
2374 PointInfo
2375 point;
2376
Cristyf2dc1dd2020-12-28 13:59:26 -05002377 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002378 i;
2379
cristybb503372010-05-27 20:51:26 +00002380 size_t
cristy3ed852e2009-09-05 21:47:34 +00002381 width;
2382
cristybb503372010-05-27 20:51:26 +00002383 ssize_t
2384 y;
2385
cristy3ed852e2009-09-05 21:47:34 +00002386 assert(image != (Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +00002387 assert(image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00002388 if (image->debug != MagickFalse)
2389 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2390 assert(exception != (ExceptionInfo *) NULL);
2391 width=GetOptimalKernelWidth1D(radius,sigma);
2392 kernel=GetMotionBlurKernel(width,sigma);
cristy0a887dc2012-08-15 22:58:36 +00002393 if (kernel == (MagickRealType *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002394 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2395 offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset));
2396 if (offset == (OffsetInfo *) NULL)
2397 {
cristy0a887dc2012-08-15 22:58:36 +00002398 kernel=(MagickRealType *) RelinquishAlignedMemory(kernel);
cristy3ed852e2009-09-05 21:47:34 +00002399 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2400 }
Dirk Lemstra590d5f32017-09-07 07:12:25 +02002401 point.x=(double) width*sin(DegreesToRadians(angle));
2402 point.y=(double) width*cos(DegreesToRadians(angle));
2403 for (i=0; i < (ssize_t) width; i++)
2404 {
Cristy20ab4502020-12-25 11:15:13 -05002405 offset[i].x=CastDoubleToLong(ceil((double) (i*point.y)/
Cristy705a60a2020-12-25 11:00:42 -05002406 hypot(point.x,point.y)-0.5));
Cristy20ab4502020-12-25 11:15:13 -05002407 offset[i].y=CastDoubleToLong(ceil((double) (i*point.x)/
Cristy705a60a2020-12-25 11:00:42 -05002408 hypot(point.x,point.y)-0.5));
Dirk Lemstra590d5f32017-09-07 07:12:25 +02002409 }
2410 /*
2411 Motion blur image.
2412 */
Dirk Lemstra5baa1f02017-09-06 21:42:18 +02002413#if defined(MAGICKCORE_OPENCL_SUPPORT)
2414 blur_image=AccelerateMotionBlurImage(image,kernel,width,offset,exception);
2415 if (blur_image != (Image *) NULL)
2416 {
2417 kernel=(MagickRealType *) RelinquishAlignedMemory(kernel);
2418 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2419 return(blur_image);
2420 }
2421#endif
Cristy590a11f2018-06-10 16:25:53 -04002422 blur_image=CloneImage(image,0,0,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00002423 if (blur_image == (Image *) NULL)
2424 {
cristy0a887dc2012-08-15 22:58:36 +00002425 kernel=(MagickRealType *) RelinquishAlignedMemory(kernel);
cristy3ed852e2009-09-05 21:47:34 +00002426 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2427 return((Image *) NULL);
2428 }
cristy574cc262011-08-05 01:23:58 +00002429 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00002430 {
cristy0a887dc2012-08-15 22:58:36 +00002431 kernel=(MagickRealType *) RelinquishAlignedMemory(kernel);
cristy3ed852e2009-09-05 21:47:34 +00002432 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
cristy3ed852e2009-09-05 21:47:34 +00002433 blur_image=DestroyImage(blur_image);
2434 return((Image *) NULL);
2435 }
cristy3ed852e2009-09-05 21:47:34 +00002436 status=MagickTrue;
2437 progress=0;
cristy46ff2672012-12-14 15:32:26 +00002438 image_view=AcquireVirtualCacheView(image,exception);
2439 motion_view=AcquireVirtualCacheView(image,exception);
2440 blur_view=AcquireAuthenticCacheView(blur_image,exception);
cristyb557a152011-02-22 12:14:30 +00002441#if defined(MAGICKCORE_OPENMP_SUPPORT)
Cristy38b42e12018-03-18 10:13:01 -04002442 #pragma omp parallel for schedule(static) shared(progress,status) \
Cristy8255ca92017-10-09 08:37:35 -04002443 magick_number_threads(image,blur_image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00002444#endif
cristybb503372010-05-27 20:51:26 +00002445 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002446 {
Cristyf2dc1dd2020-12-28 13:59:26 -05002447 const Quantum
dirk05d2ff72015-11-18 23:13:43 +01002448 *magick_restrict p;
cristyf7ef0252011-09-09 14:50:06 +00002449
Cristyf2dc1dd2020-12-28 13:59:26 -05002450 Quantum
dirk05d2ff72015-11-18 23:13:43 +01002451 *magick_restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002452
Cristyf2dc1dd2020-12-28 13:59:26 -05002453 ssize_t
cristy117ff172010-08-15 21:35:32 +00002454 x;
2455
cristy3ed852e2009-09-05 21:47:34 +00002456 if (status == MagickFalse)
2457 continue;
cristy8b49f382012-01-17 03:11:03 +00002458 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
2459 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +00002460 exception);
cristyf7ef0252011-09-09 14:50:06 +00002461 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00002462 {
2463 status=MagickFalse;
2464 continue;
2465 }
cristybb503372010-05-27 20:51:26 +00002466 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002467 {
Cristyf2dc1dd2020-12-28 13:59:26 -05002468 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002469 i;
2470
cristyf7ef0252011-09-09 14:50:06 +00002471 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2472 {
cristya19f1d72012-08-07 18:24:38 +00002473 double
cristyf7ef0252011-09-09 14:50:06 +00002474 alpha,
2475 gamma,
2476 pixel;
cristy3ed852e2009-09-05 21:47:34 +00002477
cristy633067b2013-02-21 01:15:12 +00002478 PixelChannel
2479 channel;
2480
2481 PixelTrait
2482 blur_traits,
2483 traits;
2484
Cristyf2dc1dd2020-12-28 13:59:26 -05002485 const Quantum
dirk05d2ff72015-11-18 23:13:43 +01002486 *magick_restrict r;
cristyf7ef0252011-09-09 14:50:06 +00002487
Cristyf2dc1dd2020-12-28 13:59:26 -05002488 MagickRealType
dirk05d2ff72015-11-18 23:13:43 +01002489 *magick_restrict k;
cristyf7ef0252011-09-09 14:50:06 +00002490
Cristyf2dc1dd2020-12-28 13:59:26 -05002491 ssize_t
cristyf7ef0252011-09-09 14:50:06 +00002492 j;
2493
cristy633067b2013-02-21 01:15:12 +00002494 channel=GetPixelChannelChannel(image,i);
2495 traits=GetPixelChannelTraits(image,channel);
2496 blur_traits=GetPixelChannelTraits(blur_image,channel);
cristyf7ef0252011-09-09 14:50:06 +00002497 if ((traits == UndefinedPixelTrait) ||
2498 (blur_traits == UndefinedPixelTrait))
2499 continue;
Cristydaf00192018-05-12 13:47:19 -04002500 if ((blur_traits & CopyPixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00002501 {
cristy0beccfa2011-09-25 20:47:53 +00002502 SetPixelChannel(blur_image,channel,p[i],q);
cristyf7ef0252011-09-09 14:50:06 +00002503 continue;
cristy3ed852e2009-09-05 21:47:34 +00002504 }
cristyf7ef0252011-09-09 14:50:06 +00002505 k=kernel;
cristyaa2c16c2012-03-25 22:21:35 +00002506 pixel=0.0;
cristyf7ef0252011-09-09 14:50:06 +00002507 if ((blur_traits & BlendPixelTrait) == 0)
2508 {
2509 for (j=0; j < (ssize_t) width; j++)
2510 {
cristy8b49f382012-01-17 03:11:03 +00002511 r=GetCacheViewVirtualPixels(motion_view,x+offset[j].x,y+
cristyf7ef0252011-09-09 14:50:06 +00002512 offset[j].y,1,1,exception);
2513 if (r == (const Quantum *) NULL)
2514 {
2515 status=MagickFalse;
2516 continue;
2517 }
2518 pixel+=(*k)*r[i];
2519 k++;
2520 }
cristy0beccfa2011-09-25 20:47:53 +00002521 SetPixelChannel(blur_image,channel,ClampToQuantum(pixel),q);
cristyf7ef0252011-09-09 14:50:06 +00002522 continue;
2523 }
2524 alpha=0.0;
2525 gamma=0.0;
2526 for (j=0; j < (ssize_t) width; j++)
2527 {
cristy8b49f382012-01-17 03:11:03 +00002528 r=GetCacheViewVirtualPixels(motion_view,x+offset[j].x,y+offset[j].y,1,
cristyf7ef0252011-09-09 14:50:06 +00002529 1,exception);
2530 if (r == (const Quantum *) NULL)
2531 {
2532 status=MagickFalse;
2533 continue;
2534 }
cristya19f1d72012-08-07 18:24:38 +00002535 alpha=(double) (QuantumScale*GetPixelAlpha(image,r));
cristyf7ef0252011-09-09 14:50:06 +00002536 pixel+=(*k)*alpha*r[i];
2537 gamma+=(*k)*alpha;
2538 k++;
cristy3ed852e2009-09-05 21:47:34 +00002539 }
cristy3e3ec3a2012-11-03 23:11:06 +00002540 gamma=PerceptibleReciprocal(gamma);
cristy0beccfa2011-09-25 20:47:53 +00002541 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristyf7ef0252011-09-09 14:50:06 +00002542 }
2543 p+=GetPixelChannels(image);
cristyed231572011-07-14 02:18:59 +00002544 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00002545 }
2546 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
2547 status=MagickFalse;
2548 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2549 {
2550 MagickBooleanType
2551 proceed;
2552
Cristyfc89eec2018-11-11 21:10:06 -05002553#if defined(MAGICKCORE_OPENMP_SUPPORT)
2554 #pragma omp atomic
2555#endif
2556 progress++;
2557 proceed=SetImageProgress(image,BlurImageTag,progress,image->rows);
cristy3ed852e2009-09-05 21:47:34 +00002558 if (proceed == MagickFalse)
2559 status=MagickFalse;
2560 }
2561 }
2562 blur_view=DestroyCacheView(blur_view);
cristy8b49f382012-01-17 03:11:03 +00002563 motion_view=DestroyCacheView(motion_view);
cristy3ed852e2009-09-05 21:47:34 +00002564 image_view=DestroyCacheView(image_view);
cristy0a887dc2012-08-15 22:58:36 +00002565 kernel=(MagickRealType *) RelinquishAlignedMemory(kernel);
cristy3ed852e2009-09-05 21:47:34 +00002566 offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2567 if (status == MagickFalse)
2568 blur_image=DestroyImage(blur_image);
2569 return(blur_image);
2570}
2571
2572/*
2573%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2574% %
2575% %
2576% %
2577% P r e v i e w I m a g e %
2578% %
2579% %
2580% %
2581%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2582%
2583% PreviewImage() tiles 9 thumbnails of the specified image with an image
2584% processing operation applied with varying parameters. This may be helpful
2585% pin-pointing an appropriate parameter for a particular image processing
2586% operation.
2587%
2588% The format of the PreviewImages method is:
2589%
2590% Image *PreviewImages(const Image *image,const PreviewType preview,
2591% ExceptionInfo *exception)
2592%
2593% A description of each parameter follows:
2594%
2595% o image: the image.
2596%
2597% o preview: the image processing operation.
2598%
2599% o exception: return any errors or warnings in this structure.
2600%
2601*/
2602MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
2603 ExceptionInfo *exception)
2604{
2605#define NumberTiles 9
2606#define PreviewImageTag "Preview/Image"
2607#define DefaultPreviewGeometry "204x204+10+10"
2608
2609 char
cristy151b66d2015-04-15 10:50:31 +00002610 factor[MagickPathExtent],
2611 label[MagickPathExtent];
cristy3ed852e2009-09-05 21:47:34 +00002612
2613 double
2614 degrees,
2615 gamma,
2616 percentage,
2617 radius,
2618 sigma,
2619 threshold;
2620
2621 Image
2622 *images,
2623 *montage_image,
2624 *preview_image,
2625 *thumbnail;
2626
2627 ImageInfo
2628 *preview_info;
2629
cristy3ed852e2009-09-05 21:47:34 +00002630 MagickBooleanType
2631 proceed;
2632
2633 MontageInfo
2634 *montage_info;
2635
2636 QuantizeInfo
2637 quantize_info;
2638
2639 RectangleInfo
2640 geometry;
2641
Cristyf2dc1dd2020-12-28 13:59:26 -05002642 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002643 i,
2644 x;
2645
cristybb503372010-05-27 20:51:26 +00002646 size_t
cristy3ed852e2009-09-05 21:47:34 +00002647 colors;
2648
cristy117ff172010-08-15 21:35:32 +00002649 ssize_t
2650 y;
2651
cristy3ed852e2009-09-05 21:47:34 +00002652 /*
2653 Open output image file.
2654 */
2655 assert(image != (Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +00002656 assert(image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00002657 if (image->debug != MagickFalse)
2658 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2659 colors=2;
2660 degrees=0.0;
2661 gamma=(-0.2f);
2662 preview_info=AcquireImageInfo();
2663 SetGeometry(image,&geometry);
2664 (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
2665 &geometry.width,&geometry.height);
2666 images=NewImageList();
2667 percentage=12.5;
2668 GetQuantizeInfo(&quantize_info);
2669 radius=0.0;
2670 sigma=1.0;
2671 threshold=0.0;
2672 x=0;
2673 y=0;
2674 for (i=0; i < NumberTiles; i++)
2675 {
2676 thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
2677 if (thumbnail == (Image *) NULL)
2678 break;
2679 (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
2680 (void *) NULL);
cristyd15e6592011-10-15 00:13:06 +00002681 (void) SetImageProperty(thumbnail,"label",DefaultTileLabel,exception);
cristy3ed852e2009-09-05 21:47:34 +00002682 if (i == (NumberTiles/2))
2683 {
cristy9950d572011-10-01 18:22:35 +00002684 (void) QueryColorCompliance("#dfdfdf",AllCompliance,
Cristy18b27502017-02-16 07:29:19 -05002685 &thumbnail->matte_color,exception);
cristy3ed852e2009-09-05 21:47:34 +00002686 AppendImageToList(&images,thumbnail);
2687 continue;
2688 }
2689 switch (preview)
2690 {
2691 case RotatePreview:
2692 {
2693 degrees+=45.0;
2694 preview_image=RotateImage(thumbnail,degrees,exception);
cristy151b66d2015-04-15 10:50:31 +00002695 (void) FormatLocaleString(label,MagickPathExtent,"rotate %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002696 break;
2697 }
2698 case ShearPreview:
2699 {
2700 degrees+=5.0;
2701 preview_image=ShearImage(thumbnail,degrees,degrees,exception);
cristy151b66d2015-04-15 10:50:31 +00002702 (void) FormatLocaleString(label,MagickPathExtent,"shear %gx%g",degrees,
cristyd9fdd232013-01-19 00:14:59 +00002703 2.0*degrees);
cristy3ed852e2009-09-05 21:47:34 +00002704 break;
2705 }
2706 case RollPreview:
2707 {
cristybb503372010-05-27 20:51:26 +00002708 x=(ssize_t) ((i+1)*thumbnail->columns)/NumberTiles;
2709 y=(ssize_t) ((i+1)*thumbnail->rows)/NumberTiles;
cristy3ed852e2009-09-05 21:47:34 +00002710 preview_image=RollImage(thumbnail,x,y,exception);
cristy151b66d2015-04-15 10:50:31 +00002711 (void) FormatLocaleString(label,MagickPathExtent,"roll %+.20gx%+.20g",
cristye8c25f92010-06-03 00:53:06 +00002712 (double) x,(double) y);
cristy3ed852e2009-09-05 21:47:34 +00002713 break;
2714 }
2715 case HuePreview:
2716 {
2717 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2718 if (preview_image == (Image *) NULL)
2719 break;
cristy151b66d2015-04-15 10:50:31 +00002720 (void) FormatLocaleString(factor,MagickPathExtent,"100,100,%g",2.0*
cristyd9fdd232013-01-19 00:14:59 +00002721 percentage);
cristy33bd5152011-08-24 01:42:24 +00002722 (void) ModulateImage(preview_image,factor,exception);
cristy151b66d2015-04-15 10:50:31 +00002723 (void) FormatLocaleString(label,MagickPathExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002724 break;
2725 }
2726 case SaturationPreview:
2727 {
2728 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2729 if (preview_image == (Image *) NULL)
2730 break;
Cristyb6617952017-07-19 18:01:49 -04002731 (void) FormatLocaleString(factor,MagickPathExtent,"100,%g",2.0*
2732 percentage);
cristy33bd5152011-08-24 01:42:24 +00002733 (void) ModulateImage(preview_image,factor,exception);
cristy151b66d2015-04-15 10:50:31 +00002734 (void) FormatLocaleString(label,MagickPathExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002735 break;
2736 }
2737 case BrightnessPreview:
2738 {
2739 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2740 if (preview_image == (Image *) NULL)
2741 break;
cristy151b66d2015-04-15 10:50:31 +00002742 (void) FormatLocaleString(factor,MagickPathExtent,"%g",2.0*percentage);
cristy33bd5152011-08-24 01:42:24 +00002743 (void) ModulateImage(preview_image,factor,exception);
cristy151b66d2015-04-15 10:50:31 +00002744 (void) FormatLocaleString(label,MagickPathExtent,"modulate %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002745 break;
2746 }
2747 case GammaPreview:
2748 default:
2749 {
2750 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2751 if (preview_image == (Image *) NULL)
2752 break;
2753 gamma+=0.4f;
cristyb3e7c6c2011-07-24 01:43:55 +00002754 (void) GammaImage(preview_image,gamma,exception);
cristy151b66d2015-04-15 10:50:31 +00002755 (void) FormatLocaleString(label,MagickPathExtent,"gamma %g",gamma);
cristy3ed852e2009-09-05 21:47:34 +00002756 break;
2757 }
2758 case SpiffPreview:
2759 {
2760 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2761 if (preview_image != (Image *) NULL)
2762 for (x=0; x < i; x++)
cristye23ec9d2011-08-16 18:15:40 +00002763 (void) ContrastImage(preview_image,MagickTrue,exception);
cristy151b66d2015-04-15 10:50:31 +00002764 (void) FormatLocaleString(label,MagickPathExtent,"contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002765 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002766 break;
2767 }
2768 case DullPreview:
2769 {
2770 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2771 if (preview_image == (Image *) NULL)
2772 break;
2773 for (x=0; x < i; x++)
cristye23ec9d2011-08-16 18:15:40 +00002774 (void) ContrastImage(preview_image,MagickFalse,exception);
cristy151b66d2015-04-15 10:50:31 +00002775 (void) FormatLocaleString(label,MagickPathExtent,"+contrast (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002776 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002777 break;
2778 }
2779 case GrayscalePreview:
2780 {
2781 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2782 if (preview_image == (Image *) NULL)
2783 break;
2784 colors<<=1;
2785 quantize_info.number_colors=colors;
Cristybeb4c2b2017-12-26 19:43:17 -05002786 quantize_info.colorspace=GRAYColorspace;
cristy018f07f2011-09-04 21:15:19 +00002787 (void) QuantizeImage(&quantize_info,preview_image,exception);
cristy151b66d2015-04-15 10:50:31 +00002788 (void) FormatLocaleString(label,MagickPathExtent,
cristye8c25f92010-06-03 00:53:06 +00002789 "-colorspace gray -colors %.20g",(double) colors);
cristy3ed852e2009-09-05 21:47:34 +00002790 break;
2791 }
2792 case QuantizePreview:
2793 {
2794 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2795 if (preview_image == (Image *) NULL)
2796 break;
2797 colors<<=1;
2798 quantize_info.number_colors=colors;
cristy018f07f2011-09-04 21:15:19 +00002799 (void) QuantizeImage(&quantize_info,preview_image,exception);
Cristyb6617952017-07-19 18:01:49 -04002800 (void) FormatLocaleString(label,MagickPathExtent,"colors %.20g",
2801 (double) colors);
cristy3ed852e2009-09-05 21:47:34 +00002802 break;
2803 }
2804 case DespecklePreview:
2805 {
2806 for (x=0; x < (i-1); x++)
2807 {
2808 preview_image=DespeckleImage(thumbnail,exception);
2809 if (preview_image == (Image *) NULL)
2810 break;
2811 thumbnail=DestroyImage(thumbnail);
2812 thumbnail=preview_image;
2813 }
2814 preview_image=DespeckleImage(thumbnail,exception);
2815 if (preview_image == (Image *) NULL)
2816 break;
cristy151b66d2015-04-15 10:50:31 +00002817 (void) FormatLocaleString(label,MagickPathExtent,"despeckle (%.20g)",
cristye8c25f92010-06-03 00:53:06 +00002818 (double) i+1);
cristy3ed852e2009-09-05 21:47:34 +00002819 break;
2820 }
2821 case ReduceNoisePreview:
2822 {
Cristyb6617952017-07-19 18:01:49 -04002823 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t)
2824 radius,(size_t) radius,exception);
cristy151b66d2015-04-15 10:50:31 +00002825 (void) FormatLocaleString(label,MagickPathExtent,"noise %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002826 break;
2827 }
2828 case AddNoisePreview:
2829 {
2830 switch ((int) i)
2831 {
2832 case 0:
2833 {
cristy151b66d2015-04-15 10:50:31 +00002834 (void) CopyMagickString(factor,"uniform",MagickPathExtent);
cristy3ed852e2009-09-05 21:47:34 +00002835 break;
2836 }
2837 case 1:
2838 {
cristy151b66d2015-04-15 10:50:31 +00002839 (void) CopyMagickString(factor,"gaussian",MagickPathExtent);
cristy3ed852e2009-09-05 21:47:34 +00002840 break;
2841 }
2842 case 2:
2843 {
cristy151b66d2015-04-15 10:50:31 +00002844 (void) CopyMagickString(factor,"multiplicative",MagickPathExtent);
cristy3ed852e2009-09-05 21:47:34 +00002845 break;
2846 }
2847 case 3:
2848 {
cristy151b66d2015-04-15 10:50:31 +00002849 (void) CopyMagickString(factor,"impulse",MagickPathExtent);
cristy3ed852e2009-09-05 21:47:34 +00002850 break;
2851 }
cristyf2a82ee2014-05-26 17:49:54 +00002852 case 5:
cristy3ed852e2009-09-05 21:47:34 +00002853 {
cristy151b66d2015-04-15 10:50:31 +00002854 (void) CopyMagickString(factor,"laplacian",MagickPathExtent);
cristy3ed852e2009-09-05 21:47:34 +00002855 break;
2856 }
cristyf2a82ee2014-05-26 17:49:54 +00002857 case 6:
cristy3ed852e2009-09-05 21:47:34 +00002858 {
cristy151b66d2015-04-15 10:50:31 +00002859 (void) CopyMagickString(factor,"Poisson",MagickPathExtent);
cristy3ed852e2009-09-05 21:47:34 +00002860 break;
2861 }
2862 default:
2863 {
cristy151b66d2015-04-15 10:50:31 +00002864 (void) CopyMagickString(thumbnail->magick,"NULL",MagickPathExtent);
cristy3ed852e2009-09-05 21:47:34 +00002865 break;
2866 }
2867 }
cristyd76c51e2011-03-26 00:21:26 +00002868 preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) i,
2869 (size_t) i,exception);
cristy151b66d2015-04-15 10:50:31 +00002870 (void) FormatLocaleString(label,MagickPathExtent,"+noise %s",factor);
cristy3ed852e2009-09-05 21:47:34 +00002871 break;
2872 }
2873 case SharpenPreview:
2874 {
cristyaa2c16c2012-03-25 22:21:35 +00002875 preview_image=SharpenImage(thumbnail,radius,sigma,exception);
Cristyb6617952017-07-19 18:01:49 -04002876 (void) FormatLocaleString(label,MagickPathExtent,"sharpen %gx%g",
2877 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002878 break;
2879 }
2880 case BlurPreview:
2881 {
cristyaa2c16c2012-03-25 22:21:35 +00002882 preview_image=BlurImage(thumbnail,radius,sigma,exception);
cristy151b66d2015-04-15 10:50:31 +00002883 (void) FormatLocaleString(label,MagickPathExtent,"blur %gx%g",radius,
cristy3ed852e2009-09-05 21:47:34 +00002884 sigma);
2885 break;
2886 }
2887 case ThresholdPreview:
2888 {
2889 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2890 if (preview_image == (Image *) NULL)
2891 break;
cristya19f1d72012-08-07 18:24:38 +00002892 (void) BilevelImage(thumbnail,(double) (percentage*((double)
cristye941a752011-10-15 01:52:48 +00002893 QuantumRange+1.0))/100.0,exception);
Cristyb6617952017-07-19 18:01:49 -04002894 (void) FormatLocaleString(label,MagickPathExtent,"threshold %g",
2895 (double) (percentage*((double) QuantumRange+1.0))/100.0);
cristy3ed852e2009-09-05 21:47:34 +00002896 break;
2897 }
2898 case EdgeDetectPreview:
2899 {
cristy9dc4c512013-03-24 01:38:00 +00002900 preview_image=EdgeImage(thumbnail,radius,exception);
cristy151b66d2015-04-15 10:50:31 +00002901 (void) FormatLocaleString(label,MagickPathExtent,"edge %g",radius);
cristy3ed852e2009-09-05 21:47:34 +00002902 break;
2903 }
2904 case SpreadPreview:
2905 {
Cristye3319c12015-08-24 07:11:48 -04002906 preview_image=SpreadImage(thumbnail,image->interpolate,radius,
2907 exception);
Cristycc332e32015-08-09 12:15:50 -04002908 (void) FormatLocaleString(label,MagickPathExtent,"spread %g",
2909 radius+0.5);
cristy3ed852e2009-09-05 21:47:34 +00002910 break;
2911 }
2912 case SolarizePreview:
2913 {
2914 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2915 if (preview_image == (Image *) NULL)
2916 break;
cristyd9fdd232013-01-19 00:14:59 +00002917 (void) SolarizeImage(preview_image,(double) QuantumRange*percentage/
2918 100.0,exception);
cristy151b66d2015-04-15 10:50:31 +00002919 (void) FormatLocaleString(label,MagickPathExtent,"solarize %g",
cristy3ed852e2009-09-05 21:47:34 +00002920 (QuantumRange*percentage)/100.0);
2921 break;
2922 }
2923 case ShadePreview:
2924 {
2925 degrees+=10.0;
2926 preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
2927 exception);
cristy151b66d2015-04-15 10:50:31 +00002928 (void) FormatLocaleString(label,MagickPathExtent,"shade %gx%g",degrees,
cristyd9fdd232013-01-19 00:14:59 +00002929 degrees);
cristy3ed852e2009-09-05 21:47:34 +00002930 break;
2931 }
2932 case RaisePreview:
2933 {
Cristy08baf0c2019-04-06 12:24:51 -04002934 RectangleInfo
2935 raise;
2936
cristy3ed852e2009-09-05 21:47:34 +00002937 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2938 if (preview_image == (Image *) NULL)
2939 break;
Cristy08baf0c2019-04-06 12:24:51 -04002940 raise.width=(size_t) (2*i+2);
2941 raise.height=(size_t) (2*i+2);
2942 raise.x=(i-1)/2;
2943 raise.y=(i-1)/2;
2944 (void) RaiseImage(preview_image,&raise,MagickTrue,exception);
cristy151b66d2015-04-15 10:50:31 +00002945 (void) FormatLocaleString(label,MagickPathExtent,
Cristy08baf0c2019-04-06 12:24:51 -04002946 "raise %.20gx%.20g%+.20g%+.20g",(double) raise.width,(double)
2947 raise.height,(double) raise.x,(double) raise.y);
cristy3ed852e2009-09-05 21:47:34 +00002948 break;
2949 }
2950 case SegmentPreview:
2951 {
2952 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2953 if (preview_image == (Image *) NULL)
2954 break;
2955 threshold+=0.4f;
cristyc511e882012-04-16 21:11:14 +00002956 (void) SegmentImage(preview_image,sRGBColorspace,MagickFalse,threshold,
cristy018f07f2011-09-04 21:15:19 +00002957 threshold,exception);
cristy151b66d2015-04-15 10:50:31 +00002958 (void) FormatLocaleString(label,MagickPathExtent,"segment %gx%g",
cristy3ed852e2009-09-05 21:47:34 +00002959 threshold,threshold);
2960 break;
2961 }
2962 case SwirlPreview:
2963 {
cristy76f512e2011-09-12 01:26:56 +00002964 preview_image=SwirlImage(thumbnail,degrees,image->interpolate,
2965 exception);
cristy151b66d2015-04-15 10:50:31 +00002966 (void) FormatLocaleString(label,MagickPathExtent,"swirl %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002967 degrees+=45.0;
2968 break;
2969 }
2970 case ImplodePreview:
2971 {
2972 degrees+=0.1f;
cristy76f512e2011-09-12 01:26:56 +00002973 preview_image=ImplodeImage(thumbnail,degrees,image->interpolate,
2974 exception);
cristy151b66d2015-04-15 10:50:31 +00002975 (void) FormatLocaleString(label,MagickPathExtent,"implode %g",degrees);
cristy3ed852e2009-09-05 21:47:34 +00002976 break;
2977 }
2978 case WavePreview:
2979 {
2980 degrees+=5.0f;
cristy5c4e2582011-09-11 19:21:03 +00002981 preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,
2982 image->interpolate,exception);
Cristyb6617952017-07-19 18:01:49 -04002983 (void) FormatLocaleString(label,MagickPathExtent,"wave %gx%g",0.5*
2984 degrees,2.0*degrees);
cristy3ed852e2009-09-05 21:47:34 +00002985 break;
2986 }
2987 case OilPaintPreview:
2988 {
cristy14973ba2011-08-27 23:48:07 +00002989 preview_image=OilPaintImage(thumbnail,(double) radius,(double) sigma,
2990 exception);
Cristyb6617952017-07-19 18:01:49 -04002991 (void) FormatLocaleString(label,MagickPathExtent,"charcoal %gx%g",
2992 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00002993 break;
2994 }
2995 case CharcoalDrawingPreview:
2996 {
2997 preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
cristyaa2c16c2012-03-25 22:21:35 +00002998 exception);
Cristyb6617952017-07-19 18:01:49 -04002999 (void) FormatLocaleString(label,MagickPathExtent,"charcoal %gx%g",
3000 radius,sigma);
cristy3ed852e2009-09-05 21:47:34 +00003001 break;
3002 }
3003 case JPEGPreview:
3004 {
3005 char
cristy151b66d2015-04-15 10:50:31 +00003006 filename[MagickPathExtent];
cristy3ed852e2009-09-05 21:47:34 +00003007
3008 int
3009 file;
3010
3011 MagickBooleanType
3012 status;
3013
3014 preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3015 if (preview_image == (Image *) NULL)
3016 break;
cristybb503372010-05-27 20:51:26 +00003017 preview_info->quality=(size_t) percentage;
cristy151b66d2015-04-15 10:50:31 +00003018 (void) FormatLocaleString(factor,MagickPathExtent,"%.20g",(double)
cristye8c25f92010-06-03 00:53:06 +00003019 preview_info->quality);
cristy3ed852e2009-09-05 21:47:34 +00003020 file=AcquireUniqueFileResource(filename);
3021 if (file != -1)
3022 file=close(file)-1;
cristy151b66d2015-04-15 10:50:31 +00003023 (void) FormatLocaleString(preview_image->filename,MagickPathExtent,
cristy3ed852e2009-09-05 21:47:34 +00003024 "jpeg:%s",filename);
cristy6f9e0d32011-08-28 16:32:09 +00003025 status=WriteImage(preview_info,preview_image,exception);
cristy3ed852e2009-09-05 21:47:34 +00003026 if (status != MagickFalse)
3027 {
3028 Image
3029 *quality_image;
3030
3031 (void) CopyMagickString(preview_info->filename,
cristy151b66d2015-04-15 10:50:31 +00003032 preview_image->filename,MagickPathExtent);
cristy3ed852e2009-09-05 21:47:34 +00003033 quality_image=ReadImage(preview_info,exception);
3034 if (quality_image != (Image *) NULL)
3035 {
3036 preview_image=DestroyImage(preview_image);
3037 preview_image=quality_image;
3038 }
3039 }
3040 (void) RelinquishUniqueFileResource(preview_image->filename);
3041 if ((GetBlobSize(preview_image)/1024) >= 1024)
cristy151b66d2015-04-15 10:50:31 +00003042 (void) FormatLocaleString(label,MagickPathExtent,"quality %s\n%gmb ",
cristy3ed852e2009-09-05 21:47:34 +00003043 factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
3044 1024.0/1024.0);
3045 else
3046 if (GetBlobSize(preview_image) >= 1024)
cristy151b66d2015-04-15 10:50:31 +00003047 (void) FormatLocaleString(label,MagickPathExtent,
cristye7f51092010-01-17 00:39:37 +00003048 "quality %s\n%gkb ",factor,(double) ((MagickOffsetType)
cristy8cd5b312010-01-07 01:10:24 +00003049 GetBlobSize(preview_image))/1024.0);
cristy3ed852e2009-09-05 21:47:34 +00003050 else
Cristyb6617952017-07-19 18:01:49 -04003051 (void) FormatLocaleString(label,MagickPathExtent,
3052 "quality %s\n%.20gb ",factor,(double) ((MagickOffsetType)
3053 GetBlobSize(thumbnail)));
cristy3ed852e2009-09-05 21:47:34 +00003054 break;
3055 }
3056 }
3057 thumbnail=DestroyImage(thumbnail);
3058 percentage+=12.5;
3059 radius+=0.5;
3060 sigma+=0.25;
3061 if (preview_image == (Image *) NULL)
3062 break;
Cristy73da9ae2020-03-14 17:56:39 -04003063 preview_image->alpha_trait=UndefinedPixelTrait;
cristy3ed852e2009-09-05 21:47:34 +00003064 (void) DeleteImageProperty(preview_image,"label");
cristyd15e6592011-10-15 00:13:06 +00003065 (void) SetImageProperty(preview_image,"label",label,exception);
cristy3ed852e2009-09-05 21:47:34 +00003066 AppendImageToList(&images,preview_image);
cristybb503372010-05-27 20:51:26 +00003067 proceed=SetImageProgress(image,PreviewImageTag,(MagickOffsetType) i,
3068 NumberTiles);
cristy3ed852e2009-09-05 21:47:34 +00003069 if (proceed == MagickFalse)
3070 break;
3071 }
3072 if (images == (Image *) NULL)
3073 {
3074 preview_info=DestroyImageInfo(preview_info);
3075 return((Image *) NULL);
3076 }
3077 /*
3078 Create the montage.
3079 */
3080 montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
Cristyb6617952017-07-19 18:01:49 -04003081 (void) CopyMagickString(montage_info->filename,image->filename,
3082 MagickPathExtent);
cristy3ed852e2009-09-05 21:47:34 +00003083 montage_info->shadow=MagickTrue;
3084 (void) CloneString(&montage_info->tile,"3x3");
3085 (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
3086 (void) CloneString(&montage_info->frame,DefaultTileFrame);
3087 montage_image=MontageImages(images,montage_info,exception);
3088 montage_info=DestroyMontageInfo(montage_info);
3089 images=DestroyImageList(images);
3090 if (montage_image == (Image *) NULL)
3091 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3092 if (montage_image->montage != (char *) NULL)
3093 {
3094 /*
3095 Free image directory.
3096 */
3097 montage_image->montage=(char *) RelinquishMagickMemory(
3098 montage_image->montage);
3099 if (image->directory != (char *) NULL)
3100 montage_image->directory=(char *) RelinquishMagickMemory(
3101 montage_image->directory);
3102 }
3103 preview_info=DestroyImageInfo(preview_info);
3104 return(montage_image);
3105}
3106
3107/*
3108%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3109% %
3110% %
3111% %
dirk6d612cf2014-03-13 21:17:23 +00003112% R o t a t i o n a l B l u r I m a g e %
cristy3ed852e2009-09-05 21:47:34 +00003113% %
3114% %
3115% %
3116%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3117%
dirk6d612cf2014-03-13 21:17:23 +00003118% RotationalBlurImage() applies a radial blur to the image.
cristy3ed852e2009-09-05 21:47:34 +00003119%
3120% Andrew Protano contributed this effect.
3121%
dirk6d612cf2014-03-13 21:17:23 +00003122% The format of the RotationalBlurImage method is:
cristy3ed852e2009-09-05 21:47:34 +00003123%
dirk6d612cf2014-03-13 21:17:23 +00003124% Image *RotationalBlurImage(const Image *image,const double angle,
cristyaa2c16c2012-03-25 22:21:35 +00003125% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003126%
3127% A description of each parameter follows:
3128%
3129% o image: the image.
3130%
cristy3ed852e2009-09-05 21:47:34 +00003131% o angle: the angle of the radial blur.
3132%
cristy6435bd92011-09-10 02:10:07 +00003133% o blur: the blur.
3134%
cristy3ed852e2009-09-05 21:47:34 +00003135% o exception: return any errors or warnings in this structure.
3136%
3137*/
dirk6d612cf2014-03-13 21:17:23 +00003138MagickExport Image *RotationalBlurImage(const Image *image,const double angle,
cristyaa2c16c2012-03-25 22:21:35 +00003139 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003140{
cristyc4c8d132010-01-07 01:58:38 +00003141 CacheView
3142 *blur_view,
cristy8b49f382012-01-17 03:11:03 +00003143 *image_view,
3144 *radial_view;
cristyc4c8d132010-01-07 01:58:38 +00003145
cristyadc349b2014-12-06 21:40:39 +00003146 double
3147 blur_radius,
3148 *cos_theta,
3149 offset,
3150 *sin_theta,
3151 theta;
3152
cristy3ed852e2009-09-05 21:47:34 +00003153 Image
3154 *blur_image;
3155
cristy3ed852e2009-09-05 21:47:34 +00003156 MagickBooleanType
3157 status;
3158
cristybb503372010-05-27 20:51:26 +00003159 MagickOffsetType
3160 progress;
3161
cristy3ed852e2009-09-05 21:47:34 +00003162 PointInfo
3163 blur_center;
3164
Cristyf2dc1dd2020-12-28 13:59:26 -05003165 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003166 i;
3167
cristybb503372010-05-27 20:51:26 +00003168 size_t
cristy3ed852e2009-09-05 21:47:34 +00003169 n;
3170
cristybb503372010-05-27 20:51:26 +00003171 ssize_t
3172 y;
3173
cristy3ed852e2009-09-05 21:47:34 +00003174 /*
3175 Allocate blur image.
3176 */
3177 assert(image != (Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +00003178 assert(image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00003179 if (image->debug != MagickFalse)
3180 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3181 assert(exception != (ExceptionInfo *) NULL);
cristye1c94d92015-06-28 12:16:33 +00003182 assert(exception->signature == MagickCoreSignature);
dirk21dc0312016-06-13 22:30:26 +02003183#if defined(MAGICKCORE_OPENCL_SUPPORT)
dirk06c4f032016-02-11 23:00:56 +01003184 blur_image=AccelerateRotationalBlurImage(image,angle,exception);
Dusan Veljko94190b72015-11-09 14:45:26 +01003185 if (blur_image != (Image *) NULL)
3186 return(blur_image);
dirk21dc0312016-06-13 22:30:26 +02003187#endif
Cristy590a11f2018-06-10 16:25:53 -04003188 blur_image=CloneImage(image,0,0,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00003189 if (blur_image == (Image *) NULL)
3190 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003191 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003192 {
cristy3ed852e2009-09-05 21:47:34 +00003193 blur_image=DestroyImage(blur_image);
3194 return((Image *) NULL);
3195 }
cristy6b516212013-03-22 19:20:32 +00003196 blur_center.x=(double) (image->columns-1)/2.0;
3197 blur_center.y=(double) (image->rows-1)/2.0;
cristy3ed852e2009-09-05 21:47:34 +00003198 blur_radius=hypot(blur_center.x,blur_center.y);
cristy117ff172010-08-15 21:35:32 +00003199 n=(size_t) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+2UL);
cristya19f1d72012-08-07 18:24:38 +00003200 theta=DegreesToRadians(angle)/(double) (n-1);
Elliott Hughes5d41fba2021-04-12 16:36:42 -07003201 cos_theta=(double *) AcquireQuantumMemory((size_t) n,sizeof(*cos_theta));
3202 sin_theta=(double *) AcquireQuantumMemory((size_t) n,sizeof(*sin_theta));
3203 if ((cos_theta == (double *) NULL) || (sin_theta == (double *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003204 {
Cristya9239a92017-09-30 11:09:55 -04003205 if (cos_theta != (double *) NULL)
3206 cos_theta=(double *) RelinquishMagickMemory(cos_theta);
3207 if (sin_theta != (double *) NULL)
3208 sin_theta=(double *) RelinquishMagickMemory(sin_theta);
cristy3ed852e2009-09-05 21:47:34 +00003209 blur_image=DestroyImage(blur_image);
3210 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3211 }
cristya19f1d72012-08-07 18:24:38 +00003212 offset=theta*(double) (n-1)/2.0;
cristybb503372010-05-27 20:51:26 +00003213 for (i=0; i < (ssize_t) n; i++)
cristy3ed852e2009-09-05 21:47:34 +00003214 {
3215 cos_theta[i]=cos((double) (theta*i-offset));
3216 sin_theta[i]=sin((double) (theta*i-offset));
3217 }
3218 /*
3219 Radial blur image.
3220 */
3221 status=MagickTrue;
3222 progress=0;
cristy46ff2672012-12-14 15:32:26 +00003223 image_view=AcquireVirtualCacheView(image,exception);
3224 radial_view=AcquireVirtualCacheView(image,exception);
3225 blur_view=AcquireAuthenticCacheView(blur_image,exception);
cristyb5d5f722009-11-04 03:03:49 +00003226#if defined(MAGICKCORE_OPENMP_SUPPORT)
Cristy38b42e12018-03-18 10:13:01 -04003227 #pragma omp parallel for schedule(static) shared(progress,status) \
Cristy8255ca92017-10-09 08:37:35 -04003228 magick_number_threads(image,blur_image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00003229#endif
cristy1e7aa312011-09-10 20:01:36 +00003230 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003231 {
Cristyf2dc1dd2020-12-28 13:59:26 -05003232 const Quantum
dirk05d2ff72015-11-18 23:13:43 +01003233 *magick_restrict p;
cristy1e7aa312011-09-10 20:01:36 +00003234
Cristyf2dc1dd2020-12-28 13:59:26 -05003235 Quantum
dirk05d2ff72015-11-18 23:13:43 +01003236 *magick_restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003237
Cristyf2dc1dd2020-12-28 13:59:26 -05003238 ssize_t
cristy117ff172010-08-15 21:35:32 +00003239 x;
3240
cristy3ed852e2009-09-05 21:47:34 +00003241 if (status == MagickFalse)
3242 continue;
cristy8b49f382012-01-17 03:11:03 +00003243 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
3244 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +00003245 exception);
cristy1e7aa312011-09-10 20:01:36 +00003246 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003247 {
3248 status=MagickFalse;
3249 continue;
3250 }
cristy1e7aa312011-09-10 20:01:36 +00003251 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003252 {
cristya19f1d72012-08-07 18:24:38 +00003253 double
cristy3ed852e2009-09-05 21:47:34 +00003254 radius;
3255
cristy3ed852e2009-09-05 21:47:34 +00003256 PointInfo
3257 center;
3258
Cristyf2dc1dd2020-12-28 13:59:26 -05003259 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003260 i;
3261
cristybb503372010-05-27 20:51:26 +00003262 size_t
cristy3ed852e2009-09-05 21:47:34 +00003263 step;
3264
3265 center.x=(double) x-blur_center.x;
3266 center.y=(double) y-blur_center.y;
3267 radius=hypot((double) center.x,center.y);
3268 if (radius == 0)
3269 step=1;
3270 else
3271 {
cristybb503372010-05-27 20:51:26 +00003272 step=(size_t) (blur_radius/radius);
cristy3ed852e2009-09-05 21:47:34 +00003273 if (step == 0)
3274 step=1;
3275 else
3276 if (step >= n)
3277 step=n-1;
3278 }
cristy1e7aa312011-09-10 20:01:36 +00003279 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3280 {
cristya19f1d72012-08-07 18:24:38 +00003281 double
cristy1e7aa312011-09-10 20:01:36 +00003282 gamma,
3283 pixel;
cristy3ed852e2009-09-05 21:47:34 +00003284
cristy633067b2013-02-21 01:15:12 +00003285 PixelChannel
3286 channel;
3287
3288 PixelTrait
3289 blur_traits,
3290 traits;
3291
Cristyf2dc1dd2020-12-28 13:59:26 -05003292 const Quantum
dirk05d2ff72015-11-18 23:13:43 +01003293 *magick_restrict r;
cristy1e7aa312011-09-10 20:01:36 +00003294
Cristyf2dc1dd2020-12-28 13:59:26 -05003295 ssize_t
cristy1e7aa312011-09-10 20:01:36 +00003296 j;
3297
cristy633067b2013-02-21 01:15:12 +00003298 channel=GetPixelChannelChannel(image,i);
3299 traits=GetPixelChannelTraits(image,channel);
3300 blur_traits=GetPixelChannelTraits(blur_image,channel);
cristy1e7aa312011-09-10 20:01:36 +00003301 if ((traits == UndefinedPixelTrait) ||
3302 (blur_traits == UndefinedPixelTrait))
3303 continue;
Cristydaf00192018-05-12 13:47:19 -04003304 if ((blur_traits & CopyPixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003305 {
cristy0beccfa2011-09-25 20:47:53 +00003306 SetPixelChannel(blur_image,channel,p[i],q);
cristy1e7aa312011-09-10 20:01:36 +00003307 continue;
cristy3ed852e2009-09-05 21:47:34 +00003308 }
cristy1e7aa312011-09-10 20:01:36 +00003309 gamma=0.0;
cristyaa2c16c2012-03-25 22:21:35 +00003310 pixel=0.0;
Cristy0934d9f2016-07-30 08:15:02 -04003311 if ((GetPixelChannelTraits(image,AlphaPixelChannel) == UndefinedPixelTrait) ||
Cristyb62fabf2016-07-15 21:38:51 -04003312 (channel == AlphaPixelChannel))
cristy1e7aa312011-09-10 20:01:36 +00003313 {
3314 for (j=0; j < (ssize_t) n; j+=(ssize_t) step)
3315 {
cristy8b49f382012-01-17 03:11:03 +00003316 r=GetCacheViewVirtualPixels(radial_view, (ssize_t) (blur_center.x+
cristy1e7aa312011-09-10 20:01:36 +00003317 center.x*cos_theta[j]-center.y*sin_theta[j]+0.5),(ssize_t)
3318 (blur_center.y+center.x*sin_theta[j]+center.y*cos_theta[j]+0.5),
3319 1,1,exception);
3320 if (r == (const Quantum *) NULL)
3321 {
3322 status=MagickFalse;
3323 continue;
3324 }
3325 pixel+=r[i];
3326 gamma++;
3327 }
cristy3e3ec3a2012-11-03 23:11:06 +00003328 gamma=PerceptibleReciprocal(gamma);
cristy0beccfa2011-09-25 20:47:53 +00003329 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy1e7aa312011-09-10 20:01:36 +00003330 continue;
3331 }
3332 for (j=0; j < (ssize_t) n; j+=(ssize_t) step)
3333 {
dirk27b8b332016-07-11 20:53:14 +02003334 double
3335 alpha;
3336
cristy8b49f382012-01-17 03:11:03 +00003337 r=GetCacheViewVirtualPixels(radial_view, (ssize_t) (blur_center.x+
cristy1e7aa312011-09-10 20:01:36 +00003338 center.x*cos_theta[j]-center.y*sin_theta[j]+0.5),(ssize_t)
3339 (blur_center.y+center.x*sin_theta[j]+center.y*cos_theta[j]+0.5),
3340 1,1,exception);
3341 if (r == (const Quantum *) NULL)
3342 {
3343 status=MagickFalse;
3344 continue;
3345 }
dirk27b8b332016-07-11 20:53:14 +02003346 alpha=(double) QuantumScale*GetPixelAlpha(image,r);
3347 pixel+=alpha*r[i];
3348 gamma+=alpha;
cristy3ed852e2009-09-05 21:47:34 +00003349 }
cristy3e3ec3a2012-11-03 23:11:06 +00003350 gamma=PerceptibleReciprocal(gamma);
cristy0beccfa2011-09-25 20:47:53 +00003351 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy1e7aa312011-09-10 20:01:36 +00003352 }
3353 p+=GetPixelChannels(image);
cristyed231572011-07-14 02:18:59 +00003354 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003355 }
3356 if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
3357 status=MagickFalse;
3358 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3359 {
3360 MagickBooleanType
3361 proceed;
3362
Cristyfc89eec2018-11-11 21:10:06 -05003363#if defined(MAGICKCORE_OPENMP_SUPPORT)
3364 #pragma omp atomic
3365#endif
3366 progress++;
3367 proceed=SetImageProgress(image,BlurImageTag,progress,image->rows);
cristy3ed852e2009-09-05 21:47:34 +00003368 if (proceed == MagickFalse)
3369 status=MagickFalse;
3370 }
3371 }
3372 blur_view=DestroyCacheView(blur_view);
cristy8b49f382012-01-17 03:11:03 +00003373 radial_view=DestroyCacheView(radial_view);
cristy3ed852e2009-09-05 21:47:34 +00003374 image_view=DestroyCacheView(image_view);
cristya19f1d72012-08-07 18:24:38 +00003375 cos_theta=(double *) RelinquishMagickMemory(cos_theta);
3376 sin_theta=(double *) RelinquishMagickMemory(sin_theta);
cristy3ed852e2009-09-05 21:47:34 +00003377 if (status == MagickFalse)
3378 blur_image=DestroyImage(blur_image);
3379 return(blur_image);
3380}
3381
3382/*
3383%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3384% %
3385% %
3386% %
cristy3ed852e2009-09-05 21:47:34 +00003387% S e l e c t i v e B l u r I m a g e %
3388% %
3389% %
3390% %
3391%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3392%
3393% SelectiveBlurImage() selectively blur pixels within a contrast threshold.
3394% It is similar to the unsharpen mask that sharpens everything with contrast
3395% above a certain threshold.
3396%
3397% The format of the SelectiveBlurImage method is:
3398%
3399% Image *SelectiveBlurImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +00003400% const double sigma,const double threshold,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003401%
3402% A description of each parameter follows:
3403%
3404% o image: the image.
3405%
cristy3ed852e2009-09-05 21:47:34 +00003406% o radius: the radius of the Gaussian, in pixels, not counting the center
3407% pixel.
3408%
3409% o sigma: the standard deviation of the Gaussian, in pixels.
3410%
3411% o threshold: only pixels within this contrast threshold are included
3412% in the blur operation.
3413%
3414% o exception: return any errors or warnings in this structure.
3415%
3416*/
cristy4282c702011-11-21 00:01:06 +00003417MagickExport Image *SelectiveBlurImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +00003418 const double sigma,const double threshold,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003419{
3420#define SelectiveBlurImageTag "SelectiveBlur/Image"
3421
cristy47e00502009-12-17 19:19:57 +00003422 CacheView
3423 *blur_view,
cristy9c7cf372012-08-08 22:58:27 +00003424 *image_view,
3425 *luminance_view;
cristy47e00502009-12-17 19:19:57 +00003426
cristy3ed852e2009-09-05 21:47:34 +00003427 Image
cristy9c7cf372012-08-08 22:58:27 +00003428 *blur_image,
3429 *luminance_image;
cristy3ed852e2009-09-05 21:47:34 +00003430
cristy3ed852e2009-09-05 21:47:34 +00003431 MagickBooleanType
3432 status;
3433
cristybb503372010-05-27 20:51:26 +00003434 MagickOffsetType
3435 progress;
3436
cristy0a887dc2012-08-15 22:58:36 +00003437 MagickRealType
3438 *kernel;
3439
Cristyf2dc1dd2020-12-28 13:59:26 -05003440 ssize_t
cristy47e00502009-12-17 19:19:57 +00003441 i;
cristy3ed852e2009-09-05 21:47:34 +00003442
cristybb503372010-05-27 20:51:26 +00003443 size_t
cristy3ed852e2009-09-05 21:47:34 +00003444 width;
3445
cristybb503372010-05-27 20:51:26 +00003446 ssize_t
cristyc8523c12011-09-13 00:02:53 +00003447 center,
cristybb503372010-05-27 20:51:26 +00003448 j,
3449 u,
3450 v,
3451 y;
3452
cristy3ed852e2009-09-05 21:47:34 +00003453 /*
3454 Initialize blur image attributes.
3455 */
3456 assert(image != (Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +00003457 assert(image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00003458 if (image->debug != MagickFalse)
3459 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3460 assert(exception != (ExceptionInfo *) NULL);
cristye1c94d92015-06-28 12:16:33 +00003461 assert(exception->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00003462 width=GetOptimalKernelWidth1D(radius,sigma);
cristye42639a2012-08-23 01:53:24 +00003463 kernel=(MagickRealType *) MagickAssumeAligned(AcquireAlignedMemory((size_t)
3464 width,width*sizeof(*kernel)));
cristy0a887dc2012-08-15 22:58:36 +00003465 if (kernel == (MagickRealType *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003466 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
cristy6b516212013-03-22 19:20:32 +00003467 j=(ssize_t) (width-1)/2;
cristy3ed852e2009-09-05 21:47:34 +00003468 i=0;
cristy47e00502009-12-17 19:19:57 +00003469 for (v=(-j); v <= j; v++)
cristy3ed852e2009-09-05 21:47:34 +00003470 {
cristy47e00502009-12-17 19:19:57 +00003471 for (u=(-j); u <= j; u++)
cristy0a887dc2012-08-15 22:58:36 +00003472 kernel[i++]=(MagickRealType) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
cristy4205a3c2010-09-12 20:19:59 +00003473 MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
cristy3ed852e2009-09-05 21:47:34 +00003474 }
3475 if (image->debug != MagickFalse)
3476 {
3477 char
cristy151b66d2015-04-15 10:50:31 +00003478 format[MagickPathExtent],
cristy3ed852e2009-09-05 21:47:34 +00003479 *message;
3480
Cristyf2dc1dd2020-12-28 13:59:26 -05003481 const MagickRealType
cristy117ff172010-08-15 21:35:32 +00003482 *k;
3483
cristybb503372010-05-27 20:51:26 +00003484 ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003485 u,
3486 v;
3487
cristy3ed852e2009-09-05 21:47:34 +00003488 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye8c25f92010-06-03 00:53:06 +00003489 " SelectiveBlurImage with %.20gx%.20g kernel:",(double) width,(double)
3490 width);
cristy3ed852e2009-09-05 21:47:34 +00003491 message=AcquireString("");
3492 k=kernel;
cristybb503372010-05-27 20:51:26 +00003493 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003494 {
3495 *message='\0';
cristy151b66d2015-04-15 10:50:31 +00003496 (void) FormatLocaleString(format,MagickPathExtent,"%.20g: ",(double) v);
cristy3ed852e2009-09-05 21:47:34 +00003497 (void) ConcatenateString(&message,format);
cristybb503372010-05-27 20:51:26 +00003498 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003499 {
Cristyabcaa092018-06-10 16:10:26 -04003500 (void) FormatLocaleString(format,MagickPathExtent,"%+f ",(double)
3501 *k++);
cristy3ed852e2009-09-05 21:47:34 +00003502 (void) ConcatenateString(&message,format);
3503 }
3504 (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
3505 }
3506 message=DestroyString(message);
3507 }
Cristy590a11f2018-06-10 16:25:53 -04003508 blur_image=CloneImage(image,0,0,MagickTrue,exception);
cristy3ed852e2009-09-05 21:47:34 +00003509 if (blur_image == (Image *) NULL)
3510 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00003511 if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003512 {
cristy3ed852e2009-09-05 21:47:34 +00003513 blur_image=DestroyImage(blur_image);
cristy0a887dc2012-08-15 22:58:36 +00003514 kernel=(MagickRealType *) RelinquishAlignedMemory(kernel);
cristy9c7cf372012-08-08 22:58:27 +00003515 return((Image *) NULL);
3516 }
3517 luminance_image=CloneImage(image,0,0,MagickTrue,exception);
3518 if (luminance_image == (Image *) NULL)
3519 {
3520 blur_image=DestroyImage(blur_image);
cristy0a887dc2012-08-15 22:58:36 +00003521 kernel=(MagickRealType *) RelinquishAlignedMemory(kernel);
cristy9c7cf372012-08-08 22:58:27 +00003522 return((Image *) NULL);
3523 }
Cristybeb4c2b2017-12-26 19:43:17 -05003524 status=TransformImageColorspace(luminance_image,GRAYColorspace,exception);
cristy9c7cf372012-08-08 22:58:27 +00003525 if (status == MagickFalse)
3526 {
3527 luminance_image=DestroyImage(luminance_image);
3528 blur_image=DestroyImage(blur_image);
cristy0a887dc2012-08-15 22:58:36 +00003529 kernel=(MagickRealType *) RelinquishAlignedMemory(kernel);
cristy3ed852e2009-09-05 21:47:34 +00003530 return((Image *) NULL);
3531 }
3532 /*
3533 Threshold blur image.
3534 */
3535 status=MagickTrue;
3536 progress=0;
cristy6b516212013-03-22 19:20:32 +00003537 center=(ssize_t) (GetPixelChannels(image)*(image->columns+width)*
3538 ((width-1)/2L)+GetPixelChannels(image)*((width-1)/2L));
cristy46ff2672012-12-14 15:32:26 +00003539 image_view=AcquireVirtualCacheView(image,exception);
3540 luminance_view=AcquireVirtualCacheView(luminance_image,exception);
3541 blur_view=AcquireAuthenticCacheView(blur_image,exception);
cristy5a691dc2012-08-23 22:56:49 +00003542#if defined(MAGICKCORE_OPENMP_SUPPORT)
Cristy38b42e12018-03-18 10:13:01 -04003543 #pragma omp parallel for schedule(static) shared(progress,status) \
Cristy8255ca92017-10-09 08:37:35 -04003544 magick_number_threads(image,blur_image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00003545#endif
cristybb503372010-05-27 20:51:26 +00003546 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003547 {
cristy4c08aed2011-07-01 19:47:50 +00003548 double
3549 contrast;
3550
cristy3ed852e2009-09-05 21:47:34 +00003551 MagickBooleanType
3552 sync;
3553
Cristyf2dc1dd2020-12-28 13:59:26 -05003554 const Quantum
dirk05d2ff72015-11-18 23:13:43 +01003555 *magick_restrict l,
3556 *magick_restrict p;
cristy3ed852e2009-09-05 21:47:34 +00003557
Cristyf2dc1dd2020-12-28 13:59:26 -05003558 Quantum
dirk05d2ff72015-11-18 23:13:43 +01003559 *magick_restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003560
Cristyf2dc1dd2020-12-28 13:59:26 -05003561 ssize_t
cristy117ff172010-08-15 21:35:32 +00003562 x;
3563
cristy3ed852e2009-09-05 21:47:34 +00003564 if (status == MagickFalse)
3565 continue;
cristy6b516212013-03-22 19:20:32 +00003566 p=GetCacheViewVirtualPixels(image_view,-((ssize_t) (width-1)/2L),y-(ssize_t)
3567 ((width-1)/2L),image->columns+width,width,exception);
3568 l=GetCacheViewVirtualPixels(luminance_view,-((ssize_t) (width-1)/2L),y-
3569 (ssize_t) ((width-1)/2L),luminance_image->columns+width,width,exception);
cristy8b49f382012-01-17 03:11:03 +00003570 q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
cristy3ed852e2009-09-05 21:47:34 +00003571 exception);
Cristy50830912017-08-27 13:39:13 -04003572 if ((p == (const Quantum *) NULL) || (l == (const Quantum *) NULL) ||
3573 (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003574 {
3575 status=MagickFalse;
3576 continue;
3577 }
cristybb503372010-05-27 20:51:26 +00003578 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003579 {
cristy9c7cf372012-08-08 22:58:27 +00003580 double
3581 intensity;
3582
Cristyf2dc1dd2020-12-28 13:59:26 -05003583 ssize_t
cristy1e7aa312011-09-10 20:01:36 +00003584 i;
cristy3ed852e2009-09-05 21:47:34 +00003585
cristyf13c5942012-08-08 23:50:11 +00003586 intensity=GetPixelIntensity(image,p+center);
cristy1e7aa312011-09-10 20:01:36 +00003587 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3588 {
cristya19f1d72012-08-07 18:24:38 +00003589 double
cristy1e7aa312011-09-10 20:01:36 +00003590 alpha,
3591 gamma,
cristy1e7aa312011-09-10 20:01:36 +00003592 pixel;
cristy117ff172010-08-15 21:35:32 +00003593
cristy633067b2013-02-21 01:15:12 +00003594 PixelChannel
3595 channel;
3596
3597 PixelTrait
3598 blur_traits,
3599 traits;
3600
Cristyf2dc1dd2020-12-28 13:59:26 -05003601 const MagickRealType
dirk05d2ff72015-11-18 23:13:43 +01003602 *magick_restrict k;
cristy1e7aa312011-09-10 20:01:36 +00003603
Cristyf2dc1dd2020-12-28 13:59:26 -05003604 const Quantum
dirk05d2ff72015-11-18 23:13:43 +01003605 *magick_restrict luminance_pixels,
3606 *magick_restrict pixels;
cristy1e7aa312011-09-10 20:01:36 +00003607
Cristyf2dc1dd2020-12-28 13:59:26 -05003608 ssize_t
cristy1e7aa312011-09-10 20:01:36 +00003609 u;
3610
3611 ssize_t
3612 v;
3613
cristy633067b2013-02-21 01:15:12 +00003614 channel=GetPixelChannelChannel(image,i);
3615 traits=GetPixelChannelTraits(image,channel);
3616 blur_traits=GetPixelChannelTraits(blur_image,channel);
cristy1e7aa312011-09-10 20:01:36 +00003617 if ((traits == UndefinedPixelTrait) ||
3618 (blur_traits == UndefinedPixelTrait))
3619 continue;
Cristydaf00192018-05-12 13:47:19 -04003620 if ((blur_traits & CopyPixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003621 {
cristy0beccfa2011-09-25 20:47:53 +00003622 SetPixelChannel(blur_image,channel,p[center+i],q);
cristy1e7aa312011-09-10 20:01:36 +00003623 continue;
cristy3ed852e2009-09-05 21:47:34 +00003624 }
cristy1e7aa312011-09-10 20:01:36 +00003625 k=kernel;
cristyaa2c16c2012-03-25 22:21:35 +00003626 pixel=0.0;
cristy1e7aa312011-09-10 20:01:36 +00003627 pixels=p;
cristy9c7cf372012-08-08 22:58:27 +00003628 luminance_pixels=l;
cristy1e7aa312011-09-10 20:01:36 +00003629 gamma=0.0;
3630 if ((blur_traits & BlendPixelTrait) == 0)
cristy3ed852e2009-09-05 21:47:34 +00003631 {
cristy1e7aa312011-09-10 20:01:36 +00003632 for (v=0; v < (ssize_t) width; v++)
cristy3ed852e2009-09-05 21:47:34 +00003633 {
cristy1e7aa312011-09-10 20:01:36 +00003634 for (u=0; u < (ssize_t) width; u++)
cristy3ed852e2009-09-05 21:47:34 +00003635 {
cristyf13c5942012-08-08 23:50:11 +00003636 contrast=GetPixelIntensity(luminance_image,luminance_pixels)-
3637 intensity;
cristy1e7aa312011-09-10 20:01:36 +00003638 if (fabs(contrast) < threshold)
3639 {
3640 pixel+=(*k)*pixels[i];
3641 gamma+=(*k);
3642 }
3643 k++;
3644 pixels+=GetPixelChannels(image);
cristy9c7cf372012-08-08 22:58:27 +00003645 luminance_pixels+=GetPixelChannels(luminance_image);
cristy3ed852e2009-09-05 21:47:34 +00003646 }
cristyadc349b2014-12-06 21:40:39 +00003647 pixels+=GetPixelChannels(image)*image->columns;
3648 luminance_pixels+=GetPixelChannels(luminance_image)*
3649 luminance_image->columns;
cristy3ed852e2009-09-05 21:47:34 +00003650 }
cristy1e7aa312011-09-10 20:01:36 +00003651 if (fabs((double) gamma) < MagickEpsilon)
3652 {
cristy0beccfa2011-09-25 20:47:53 +00003653 SetPixelChannel(blur_image,channel,p[center+i],q);
cristy1e7aa312011-09-10 20:01:36 +00003654 continue;
3655 }
cristy3e3ec3a2012-11-03 23:11:06 +00003656 gamma=PerceptibleReciprocal(gamma);
cristy0beccfa2011-09-25 20:47:53 +00003657 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy1e7aa312011-09-10 20:01:36 +00003658 continue;
3659 }
3660 for (v=0; v < (ssize_t) width; v++)
3661 {
3662 for (u=0; u < (ssize_t) width; u++)
3663 {
3664 contrast=GetPixelIntensity(image,pixels)-intensity;
3665 if (fabs(contrast) < threshold)
3666 {
cristy60dc90c2013-02-09 18:33:21 +00003667 alpha=(double) (QuantumScale*GetPixelAlpha(image,pixels));
cristy1e7aa312011-09-10 20:01:36 +00003668 pixel+=(*k)*alpha*pixels[i];
3669 gamma+=(*k)*alpha;
3670 }
3671 k++;
3672 pixels+=GetPixelChannels(image);
cristy9c7cf372012-08-08 22:58:27 +00003673 luminance_pixels+=GetPixelChannels(luminance_image);
cristy1e7aa312011-09-10 20:01:36 +00003674 }
cristyadc349b2014-12-06 21:40:39 +00003675 pixels+=GetPixelChannels(image)*image->columns;
3676 luminance_pixels+=GetPixelChannels(luminance_image)*
3677 luminance_image->columns;
cristy3ed852e2009-09-05 21:47:34 +00003678 }
cristy1e7aa312011-09-10 20:01:36 +00003679 if (fabs((double) gamma) < MagickEpsilon)
3680 {
cristy0beccfa2011-09-25 20:47:53 +00003681 SetPixelChannel(blur_image,channel,p[center+i],q);
cristy1e7aa312011-09-10 20:01:36 +00003682 continue;
3683 }
cristy3e3ec3a2012-11-03 23:11:06 +00003684 gamma=PerceptibleReciprocal(gamma);
cristy0beccfa2011-09-25 20:47:53 +00003685 SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
cristy1e7aa312011-09-10 20:01:36 +00003686 }
cristyed231572011-07-14 02:18:59 +00003687 p+=GetPixelChannels(image);
cristy9c7cf372012-08-08 22:58:27 +00003688 l+=GetPixelChannels(luminance_image);
cristyed231572011-07-14 02:18:59 +00003689 q+=GetPixelChannels(blur_image);
cristy3ed852e2009-09-05 21:47:34 +00003690 }
3691 sync=SyncCacheViewAuthenticPixels(blur_view,exception);
3692 if (sync == MagickFalse)
3693 status=MagickFalse;
3694 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3695 {
3696 MagickBooleanType
3697 proceed;
3698
Cristyfc89eec2018-11-11 21:10:06 -05003699#if defined(MAGICKCORE_OPENMP_SUPPORT)
3700 #pragma omp atomic
3701#endif
3702 progress++;
3703 proceed=SetImageProgress(image,SelectiveBlurImageTag,progress,
cristy3ed852e2009-09-05 21:47:34 +00003704 image->rows);
3705 if (proceed == MagickFalse)
3706 status=MagickFalse;
3707 }
3708 }
3709 blur_image->type=image->type;
3710 blur_view=DestroyCacheView(blur_view);
Dirk Lemstra9d3c0552019-10-04 23:11:07 +02003711 luminance_view=DestroyCacheView(luminance_view);
cristy3ed852e2009-09-05 21:47:34 +00003712 image_view=DestroyCacheView(image_view);
cristy9c7cf372012-08-08 22:58:27 +00003713 luminance_image=DestroyImage(luminance_image);
cristy0a887dc2012-08-15 22:58:36 +00003714 kernel=(MagickRealType *) RelinquishAlignedMemory(kernel);
cristy3ed852e2009-09-05 21:47:34 +00003715 if (status == MagickFalse)
3716 blur_image=DestroyImage(blur_image);
3717 return(blur_image);
3718}
3719
3720/*
3721%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3722% %
3723% %
3724% %
3725% S h a d e I m a g e %
3726% %
3727% %
3728% %
3729%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3730%
3731% ShadeImage() shines a distant light on an image to create a
3732% three-dimensional effect. You control the positioning of the light with
3733% azimuth and elevation; azimuth is measured in degrees off the x axis
3734% and elevation is measured in pixels above the Z axis.
3735%
3736% The format of the ShadeImage method is:
3737%
3738% Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3739% const double azimuth,const double elevation,ExceptionInfo *exception)
3740%
3741% A description of each parameter follows:
3742%
3743% o image: the image.
3744%
3745% o gray: A value other than zero shades the intensity of each pixel.
3746%
3747% o azimuth, elevation: Define the light source direction.
3748%
3749% o exception: return any errors or warnings in this structure.
3750%
3751*/
3752MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3753 const double azimuth,const double elevation,ExceptionInfo *exception)
3754{
Cristyefd0e512019-01-12 10:36:50 -05003755#define GetShadeIntensity(image,pixel) \
3756 ClampPixel(GetPixelIntensity((image),(pixel)))
cristy3ed852e2009-09-05 21:47:34 +00003757#define ShadeImageTag "Shade/Image"
3758
cristyc4c8d132010-01-07 01:58:38 +00003759 CacheView
3760 *image_view,
3761 *shade_view;
3762
cristy3ed852e2009-09-05 21:47:34 +00003763 Image
cristy7d9d8ca2012-12-20 17:11:08 +00003764 *linear_image,
cristy3ed852e2009-09-05 21:47:34 +00003765 *shade_image;
3766
cristy3ed852e2009-09-05 21:47:34 +00003767 MagickBooleanType
3768 status;
3769
cristybb503372010-05-27 20:51:26 +00003770 MagickOffsetType
3771 progress;
3772
cristy3ed852e2009-09-05 21:47:34 +00003773 PrimaryInfo
3774 light;
3775
cristybb503372010-05-27 20:51:26 +00003776 ssize_t
3777 y;
3778
cristy3ed852e2009-09-05 21:47:34 +00003779 /*
3780 Initialize shaded image attributes.
3781 */
3782 assert(image != (const Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +00003783 assert(image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00003784 if (image->debug != MagickFalse)
3785 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3786 assert(exception != (ExceptionInfo *) NULL);
cristye1c94d92015-06-28 12:16:33 +00003787 assert(exception->signature == MagickCoreSignature);
cristy7d9d8ca2012-12-20 17:11:08 +00003788 linear_image=CloneImage(image,0,0,MagickTrue,exception);
Cristy590a11f2018-06-10 16:25:53 -04003789 shade_image=CloneImage(image,0,0,MagickTrue,exception);
cristy7d9d8ca2012-12-20 17:11:08 +00003790 if ((linear_image == (Image *) NULL) || (shade_image == (Image *) NULL))
3791 {
3792 if (linear_image != (Image *) NULL)
3793 linear_image=DestroyImage(linear_image);
3794 if (shade_image != (Image *) NULL)
3795 shade_image=DestroyImage(shade_image);
3796 return((Image *) NULL);
3797 }
cristy574cc262011-08-05 01:23:58 +00003798 if (SetImageStorageClass(shade_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00003799 {
cristy7d9d8ca2012-12-20 17:11:08 +00003800 linear_image=DestroyImage(linear_image);
cristy3ed852e2009-09-05 21:47:34 +00003801 shade_image=DestroyImage(shade_image);
3802 return((Image *) NULL);
3803 }
3804 /*
3805 Compute the light vector.
3806 */
3807 light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
3808 cos(DegreesToRadians(elevation));
3809 light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
3810 cos(DegreesToRadians(elevation));
3811 light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
3812 /*
3813 Shade image.
3814 */
3815 status=MagickTrue;
3816 progress=0;
cristy7d9d8ca2012-12-20 17:11:08 +00003817 image_view=AcquireVirtualCacheView(linear_image,exception);
cristy46ff2672012-12-14 15:32:26 +00003818 shade_view=AcquireAuthenticCacheView(shade_image,exception);
cristyb5d5f722009-11-04 03:03:49 +00003819#if defined(MAGICKCORE_OPENMP_SUPPORT)
Cristy38b42e12018-03-18 10:13:01 -04003820 #pragma omp parallel for schedule(static) shared(progress,status) \
Cristy8255ca92017-10-09 08:37:35 -04003821 magick_number_threads(linear_image,shade_image,linear_image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00003822#endif
cristy7d9d8ca2012-12-20 17:11:08 +00003823 for (y=0; y < (ssize_t) linear_image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003824 {
cristya19f1d72012-08-07 18:24:38 +00003825 double
cristy3ed852e2009-09-05 21:47:34 +00003826 distance,
3827 normal_distance,
3828 shade;
3829
3830 PrimaryInfo
3831 normal;
3832
Cristyf2dc1dd2020-12-28 13:59:26 -05003833 const Quantum
dirk05d2ff72015-11-18 23:13:43 +01003834 *magick_restrict center,
3835 *magick_restrict p,
3836 *magick_restrict post,
3837 *magick_restrict pre;
cristy3ed852e2009-09-05 21:47:34 +00003838
Cristyf2dc1dd2020-12-28 13:59:26 -05003839 Quantum
dirk05d2ff72015-11-18 23:13:43 +01003840 *magick_restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003841
Cristyf2dc1dd2020-12-28 13:59:26 -05003842 ssize_t
cristy117ff172010-08-15 21:35:32 +00003843 x;
3844
cristy3ed852e2009-09-05 21:47:34 +00003845 if (status == MagickFalse)
3846 continue;
cristy7d9d8ca2012-12-20 17:11:08 +00003847 p=GetCacheViewVirtualPixels(image_view,-1,y-1,linear_image->columns+2,3,
3848 exception);
cristy3ed852e2009-09-05 21:47:34 +00003849 q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
3850 exception);
cristy4c08aed2011-07-01 19:47:50 +00003851 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00003852 {
3853 status=MagickFalse;
3854 continue;
3855 }
3856 /*
3857 Shade this row of pixels.
3858 */
3859 normal.z=2.0*(double) QuantumRange; /* constant Z of surface normal */
cristy7d9d8ca2012-12-20 17:11:08 +00003860 for (x=0; x < (ssize_t) linear_image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003861 {
Cristyf2dc1dd2020-12-28 13:59:26 -05003862 ssize_t
cristy1e7aa312011-09-10 20:01:36 +00003863 i;
3864
cristy3ed852e2009-09-05 21:47:34 +00003865 /*
3866 Determine the surface normal and compute shading.
3867 */
Cristy5e6f3782015-09-08 18:51:56 -04003868 pre=p+GetPixelChannels(linear_image);
3869 center=pre+(linear_image->columns+2)*GetPixelChannels(linear_image);
3870 post=center+(linear_image->columns+2)*GetPixelChannels(linear_image);
cristy7d9d8ca2012-12-20 17:11:08 +00003871 normal.x=(double) (
Cristyefd0e512019-01-12 10:36:50 -05003872 GetShadeIntensity(linear_image,pre-GetPixelChannels(linear_image))+
3873 GetShadeIntensity(linear_image,center-GetPixelChannels(linear_image))+
3874 GetShadeIntensity(linear_image,post-GetPixelChannels(linear_image))-
3875 GetShadeIntensity(linear_image,pre+GetPixelChannels(linear_image))-
3876 GetShadeIntensity(linear_image,center+GetPixelChannels(linear_image))-
3877 GetShadeIntensity(linear_image,post+GetPixelChannels(linear_image)));
cristy7d9d8ca2012-12-20 17:11:08 +00003878 normal.y=(double) (
Cristyefd0e512019-01-12 10:36:50 -05003879 GetShadeIntensity(linear_image,post-GetPixelChannels(linear_image))+
3880 GetShadeIntensity(linear_image,post)+
3881 GetShadeIntensity(linear_image,post+GetPixelChannels(linear_image))-
3882 GetShadeIntensity(linear_image,pre-GetPixelChannels(linear_image))-
3883 GetShadeIntensity(linear_image,pre)-
3884 GetShadeIntensity(linear_image,pre+GetPixelChannels(linear_image)));
Cristy54c93932016-06-07 19:29:34 -04003885 if ((fabs(normal.x) <= MagickEpsilon) &&
Cristy5e6f3782015-09-08 18:51:56 -04003886 (fabs(normal.y) <= MagickEpsilon))
cristy3ed852e2009-09-05 21:47:34 +00003887 shade=light.z;
3888 else
3889 {
3890 shade=0.0;
3891 distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
3892 if (distance > MagickEpsilon)
3893 {
cristy7d9d8ca2012-12-20 17:11:08 +00003894 normal_distance=normal.x*normal.x+normal.y*normal.y+
3895 normal.z*normal.z;
cristy3ed852e2009-09-05 21:47:34 +00003896 if (normal_distance > (MagickEpsilon*MagickEpsilon))
3897 shade=distance/sqrt((double) normal_distance);
3898 }
3899 }
cristy7d9d8ca2012-12-20 17:11:08 +00003900 for (i=0; i < (ssize_t) GetPixelChannels(linear_image); i++)
cristy1e7aa312011-09-10 20:01:36 +00003901 {
cristy633067b2013-02-21 01:15:12 +00003902 PixelChannel
3903 channel;
3904
3905 PixelTrait
3906 shade_traits,
3907 traits;
3908
3909 channel=GetPixelChannelChannel(linear_image,i);
3910 traits=GetPixelChannelTraits(linear_image,channel);
3911 shade_traits=GetPixelChannelTraits(shade_image,channel);
cristy1e7aa312011-09-10 20:01:36 +00003912 if ((traits == UndefinedPixelTrait) ||
3913 (shade_traits == UndefinedPixelTrait))
3914 continue;
Cristydaf00192018-05-12 13:47:19 -04003915 if ((shade_traits & CopyPixelTrait) != 0)
cristy1e7aa312011-09-10 20:01:36 +00003916 {
cristy0beccfa2011-09-25 20:47:53 +00003917 SetPixelChannel(shade_image,channel,center[i],q);
cristy1e7aa312011-09-10 20:01:36 +00003918 continue;
3919 }
cristy28350032014-11-16 01:19:51 +00003920 if ((traits & UpdatePixelTrait) == 0)
3921 {
3922 SetPixelChannel(shade_image,channel,center[i],q);
3923 continue;
3924 }
cristy1e7aa312011-09-10 20:01:36 +00003925 if (gray != MagickFalse)
3926 {
cristy0beccfa2011-09-25 20:47:53 +00003927 SetPixelChannel(shade_image,channel,ClampToQuantum(shade),q);
cristy1e7aa312011-09-10 20:01:36 +00003928 continue;
3929 }
cristy0beccfa2011-09-25 20:47:53 +00003930 SetPixelChannel(shade_image,channel,ClampToQuantum(QuantumScale*shade*
3931 center[i]),q);
cristy1e7aa312011-09-10 20:01:36 +00003932 }
Cristy5e6f3782015-09-08 18:51:56 -04003933 p+=GetPixelChannels(linear_image);
cristyed231572011-07-14 02:18:59 +00003934 q+=GetPixelChannels(shade_image);
cristy3ed852e2009-09-05 21:47:34 +00003935 }
3936 if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
3937 status=MagickFalse;
3938 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3939 {
3940 MagickBooleanType
3941 proceed;
3942
Cristyfc89eec2018-11-11 21:10:06 -05003943#if defined(MAGICKCORE_OPENMP_SUPPORT)
3944 #pragma omp atomic
3945#endif
3946 progress++;
3947 proceed=SetImageProgress(image,ShadeImageTag,progress,image->rows);
cristy3ed852e2009-09-05 21:47:34 +00003948 if (proceed == MagickFalse)
3949 status=MagickFalse;
3950 }
3951 }
3952 shade_view=DestroyCacheView(shade_view);
3953 image_view=DestroyCacheView(image_view);
cristy7d9d8ca2012-12-20 17:11:08 +00003954 linear_image=DestroyImage(linear_image);
cristy3ed852e2009-09-05 21:47:34 +00003955 if (status == MagickFalse)
3956 shade_image=DestroyImage(shade_image);
3957 return(shade_image);
3958}
3959
3960/*
3961%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3962% %
3963% %
3964% %
3965% S h a r p e n I m a g e %
3966% %
3967% %
3968% %
3969%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3970%
3971% SharpenImage() sharpens the image. We convolve the image with a Gaussian
3972% operator of the given radius and standard deviation (sigma). For
3973% reasonable results, radius should be larger than sigma. Use a radius of 0
3974% and SharpenImage() selects a suitable radius for you.
3975%
3976% Using a separable kernel would be faster, but the negative weights cancel
3977% out on the corners of the kernel producing often undesirable ringing in the
3978% filtered result; this can be avoided by using a 2D gaussian shaped image
3979% sharpening kernel instead.
3980%
3981% The format of the SharpenImage method is:
3982%
3983% Image *SharpenImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +00003984% const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003985%
3986% A description of each parameter follows:
3987%
3988% o image: the image.
3989%
cristy3ed852e2009-09-05 21:47:34 +00003990% o radius: the radius of the Gaussian, in pixels, not counting the center
3991% pixel.
3992%
3993% o sigma: the standard deviation of the Laplacian, in pixels.
3994%
3995% o exception: return any errors or warnings in this structure.
3996%
3997*/
cristy3ed852e2009-09-05 21:47:34 +00003998MagickExport Image *SharpenImage(const Image *image,const double radius,
cristyaa2c16c2012-03-25 22:21:35 +00003999 const double sigma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00004000{
cristy09dbb822013-03-24 23:25:21 +00004001 double
cristy0cfd5672013-03-26 14:34:25 +00004002 gamma,
cristy09dbb822013-03-24 23:25:21 +00004003 normalize;
4004
4005 Image
4006 *sharp_image;
cristy3ed852e2009-09-05 21:47:34 +00004007
cristy41cbe682011-07-15 19:12:37 +00004008 KernelInfo
4009 *kernel_info;
4010
Cristyf2dc1dd2020-12-28 13:59:26 -05004011 ssize_t
cristy09dbb822013-03-24 23:25:21 +00004012 i;
4013
4014 size_t
4015 width;
4016
4017 ssize_t
4018 j,
4019 u,
4020 v;
cristy117ff172010-08-15 21:35:32 +00004021
cristy3ed852e2009-09-05 21:47:34 +00004022 assert(image != (const Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +00004023 assert(image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00004024 if (image->debug != MagickFalse)
4025 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4026 assert(exception != (ExceptionInfo *) NULL);
cristye1c94d92015-06-28 12:16:33 +00004027 assert(exception->signature == MagickCoreSignature);
cristy09dbb822013-03-24 23:25:21 +00004028 width=GetOptimalKernelWidth2D(radius,sigma);
cristy2c57b742014-10-31 00:40:34 +00004029 kernel_info=AcquireKernelInfo((const char *) NULL,exception);
cristy41cbe682011-07-15 19:12:37 +00004030 if (kernel_info == (KernelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00004031 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
Cristy81bfff22018-03-10 07:58:31 -05004032 (void) memset(kernel_info,0,sizeof(*kernel_info));
cristy09dbb822013-03-24 23:25:21 +00004033 kernel_info->width=width;
4034 kernel_info->height=width;
cristyf1307112013-03-25 22:16:57 +00004035 kernel_info->x=(ssize_t) (width-1)/2;
4036 kernel_info->y=(ssize_t) (width-1)/2;
cristye1c94d92015-06-28 12:16:33 +00004037 kernel_info->signature=MagickCoreSignature;
cristy09dbb822013-03-24 23:25:21 +00004038 kernel_info->values=(MagickRealType *) MagickAssumeAligned(
cristy39a5afb2013-03-26 19:09:41 +00004039 AcquireAlignedMemory(kernel_info->width,kernel_info->height*
cristy09dbb822013-03-24 23:25:21 +00004040 sizeof(*kernel_info->values)));
4041 if (kernel_info->values == (MagickRealType *) NULL)
4042 {
4043 kernel_info=DestroyKernelInfo(kernel_info);
4044 ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
4045 }
4046 normalize=0.0;
4047 j=(ssize_t) (kernel_info->width-1)/2;
4048 i=0;
4049 for (v=(-j); v <= j; v++)
4050 {
4051 for (u=(-j); u <= j; u++)
4052 {
4053 kernel_info->values[i]=(MagickRealType) (-exp(-((double) u*u+v*v)/(2.0*
4054 MagickSigma*MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
4055 normalize+=kernel_info->values[i];
4056 i++;
4057 }
4058 }
4059 kernel_info->values[i/2]=(double) ((-2.0)*normalize);
cristy78bfb0f2013-03-26 13:18:48 +00004060 normalize=0.0;
cristy0cfd5672013-03-26 14:34:25 +00004061 for (i=0; i < (ssize_t) (kernel_info->width*kernel_info->height); i++)
cristy78bfb0f2013-03-26 13:18:48 +00004062 normalize+=kernel_info->values[i];
cristy0cfd5672013-03-26 14:34:25 +00004063 gamma=PerceptibleReciprocal(normalize);
4064 for (i=0; i < (ssize_t) (kernel_info->width*kernel_info->height); i++)
4065 kernel_info->values[i]*=gamma;
dirk38aff572015-11-11 15:32:32 +01004066 sharp_image=ConvolveImage(image,kernel_info,exception);
cristy41cbe682011-07-15 19:12:37 +00004067 kernel_info=DestroyKernelInfo(kernel_info);
cristy9dc4c512013-03-24 01:38:00 +00004068 return(sharp_image);
cristy3ed852e2009-09-05 21:47:34 +00004069}
4070
4071/*
4072%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4073% %
4074% %
4075% %
4076% S p r e a d I m a g e %
4077% %
4078% %
4079% %
4080%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4081%
4082% SpreadImage() is a special effects method that randomly displaces each
anthony0edcd8b2015-08-24 10:11:27 +10004083% pixel in a square area defined by the radius parameter.
cristy3ed852e2009-09-05 21:47:34 +00004084%
4085% The format of the SpreadImage method is:
4086%
Cristye3319c12015-08-24 07:11:48 -04004087% Image *SpreadImage(const Image *image,
4088% const PixelInterpolateMethod method,const double radius,
Cristycc332e32015-08-09 12:15:50 -04004089% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00004090%
4091% A description of each parameter follows:
4092%
4093% o image: the image.
4094%
Cristye3319c12015-08-24 07:11:48 -04004095% o method: intepolation method.
4096%
cristy5c4e2582011-09-11 19:21:03 +00004097% o radius: choose a random pixel in a neighborhood of this extent.
4098%
cristy3ed852e2009-09-05 21:47:34 +00004099% o exception: return any errors or warnings in this structure.
4100%
4101*/
Cristye3319c12015-08-24 07:11:48 -04004102MagickExport Image *SpreadImage(const Image *image,
Cristy1c03a242015-08-11 06:22:22 -04004103 const PixelInterpolateMethod method,const double radius,
4104 ExceptionInfo *exception)
4105{
4106#define SpreadImageTag "Spread/Image"
4107
4108 CacheView
4109 *image_view,
4110 *spread_view;
4111
4112 Image
4113 *spread_image;
4114
4115 MagickBooleanType
4116 status;
4117
4118 MagickOffsetType
4119 progress;
4120
4121 RandomInfo
dirk05d2ff72015-11-18 23:13:43 +01004122 **magick_restrict random_info;
Cristy1c03a242015-08-11 06:22:22 -04004123
4124 size_t
4125 width;
4126
4127 ssize_t
4128 y;
4129
4130#if defined(MAGICKCORE_OPENMP_SUPPORT)
4131 unsigned long
4132 key;
4133#endif
4134
4135 /*
4136 Initialize spread image attributes.
4137 */
4138 assert(image != (Image *) NULL);
4139 assert(image->signature == MagickCoreSignature);
4140 if (image->debug != MagickFalse)
4141 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4142 assert(exception != (ExceptionInfo *) NULL);
4143 assert(exception->signature == MagickCoreSignature);
Cristy590a11f2018-06-10 16:25:53 -04004144 spread_image=CloneImage(image,0,0,MagickTrue,exception);
Cristy1c03a242015-08-11 06:22:22 -04004145 if (spread_image == (Image *) NULL)
4146 return((Image *) NULL);
4147 if (SetImageStorageClass(spread_image,DirectClass,exception) == MagickFalse)
4148 {
4149 spread_image=DestroyImage(spread_image);
4150 return((Image *) NULL);
4151 }
4152 /*
4153 Spread image.
4154 */
4155 status=MagickTrue;
4156 progress=0;
4157 width=GetOptimalKernelWidth1D(radius,0.5);
4158 random_info=AcquireRandomInfoThreadSet();
4159 image_view=AcquireVirtualCacheView(image,exception);
4160 spread_view=AcquireAuthenticCacheView(spread_image,exception);
4161#if defined(MAGICKCORE_OPENMP_SUPPORT)
4162 key=GetRandomSecretKey(random_info[0]);
Cristy38b42e12018-03-18 10:13:01 -04004163 #pragma omp parallel for schedule(static) shared(progress,status) \
Cristy8255ca92017-10-09 08:37:35 -04004164 magick_number_threads(image,spread_image,image->rows,key == ~0UL)
Cristy1c03a242015-08-11 06:22:22 -04004165#endif
4166 for (y=0; y < (ssize_t) image->rows; y++)
4167 {
4168 const int
4169 id = GetOpenMPThreadId();
4170
Cristyf2dc1dd2020-12-28 13:59:26 -05004171 Quantum
dirk05d2ff72015-11-18 23:13:43 +01004172 *magick_restrict q;
Cristy1c03a242015-08-11 06:22:22 -04004173
Cristyf2dc1dd2020-12-28 13:59:26 -05004174 ssize_t
Cristy1c03a242015-08-11 06:22:22 -04004175 x;
4176
4177 if (status == MagickFalse)
4178 continue;
4179 q=QueueCacheViewAuthenticPixels(spread_view,0,y,spread_image->columns,1,
4180 exception);
4181 if (q == (Quantum *) NULL)
4182 {
4183 status=MagickFalse;
4184 continue;
4185 }
4186 for (x=0; x < (ssize_t) image->columns; x++)
4187 {
4188 PointInfo
4189 point;
4190
4191 point.x=GetPseudoRandomValue(random_info[id]);
4192 point.y=GetPseudoRandomValue(random_info[id]);
4193 status=InterpolatePixelChannels(image,image_view,spread_image,method,
4194 (double) x+width*(point.x-0.5),(double) y+width*(point.y-0.5),q,
4195 exception);
Cristybb39c462018-01-07 18:57:25 -05004196 if (status == MagickFalse)
4197 break;
Cristy1c03a242015-08-11 06:22:22 -04004198 q+=GetPixelChannels(spread_image);
4199 }
4200 if (SyncCacheViewAuthenticPixels(spread_view,exception) == MagickFalse)
4201 status=MagickFalse;
4202 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4203 {
4204 MagickBooleanType
4205 proceed;
4206
Cristyfc89eec2018-11-11 21:10:06 -05004207#if defined(MAGICKCORE_OPENMP_SUPPORT)
4208 #pragma omp atomic
4209#endif
4210 progress++;
4211 proceed=SetImageProgress(image,SpreadImageTag,progress,image->rows);
Cristy1c03a242015-08-11 06:22:22 -04004212 if (proceed == MagickFalse)
4213 status=MagickFalse;
4214 }
4215 }
4216 spread_view=DestroyCacheView(spread_view);
4217 image_view=DestroyCacheView(image_view);
4218 random_info=DestroyRandomInfoThreadSet(random_info);
4219 if (status == MagickFalse)
4220 spread_image=DestroyImage(spread_image);
4221 return(spread_image);
4222}
cristy3ed852e2009-09-05 21:47:34 +00004223
4224/*
4225%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4226% %
4227% %
4228% %
4229% U n s h a r p M a s k I m a g e %
4230% %
4231% %
4232% %
4233%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4234%
4235% UnsharpMaskImage() sharpens one or more image channels. We convolve the
4236% image with a Gaussian operator of the given radius and standard deviation
4237% (sigma). For reasonable results, radius should be larger than sigma. Use a
4238% radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
4239%
4240% The format of the UnsharpMaskImage method is:
4241%
4242% Image *UnsharpMaskImage(const Image *image,const double radius,
cristy3afd4012013-03-25 11:30:44 +00004243% const double sigma,const double amount,const double threshold,
4244% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00004245%
4246% A description of each parameter follows:
4247%
4248% o image: the image.
4249%
cristy3ed852e2009-09-05 21:47:34 +00004250% o radius: the radius of the Gaussian, in pixels, not counting the center
4251% pixel.
4252%
4253% o sigma: the standard deviation of the Gaussian, in pixels.
4254%
cristy2fd9be62013-03-23 20:44:18 +00004255% o gain: the percentage of the difference between the original and the
cristy3ed852e2009-09-05 21:47:34 +00004256% blur image that is added back into the original.
4257%
cristy3afd4012013-03-25 11:30:44 +00004258% o threshold: the threshold in pixels needed to apply the diffence gain.
4259%
cristy3ed852e2009-09-05 21:47:34 +00004260% o exception: return any errors or warnings in this structure.
4261%
4262*/
cristy31bb6272011-11-20 23:57:25 +00004263MagickExport Image *UnsharpMaskImage(const Image *image,const double radius,
cristy3afd4012013-03-25 11:30:44 +00004264 const double sigma,const double gain,const double threshold,
4265 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00004266{
cristy3afd4012013-03-25 11:30:44 +00004267#define SharpenImageTag "Sharpen/Image"
cristy3ed852e2009-09-05 21:47:34 +00004268
cristy3afd4012013-03-25 11:30:44 +00004269 CacheView
4270 *image_view,
4271 *unsharp_view;
cristyc4c8d132010-01-07 01:58:38 +00004272
cristy3ed852e2009-09-05 21:47:34 +00004273 Image
4274 *unsharp_image;
4275
cristy3afd4012013-03-25 11:30:44 +00004276 MagickBooleanType
4277 status;
4278
4279 MagickOffsetType
4280 progress;
4281
4282 double
4283 quantum_threshold;
4284
4285 ssize_t
4286 y;
4287
cristy3ed852e2009-09-05 21:47:34 +00004288 assert(image != (const Image *) NULL);
cristye1c94d92015-06-28 12:16:33 +00004289 assert(image->signature == MagickCoreSignature);
cristy3ed852e2009-09-05 21:47:34 +00004290 if (image->debug != MagickFalse)
4291 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4292 assert(exception != (ExceptionInfo *) NULL);
Dirk Lemstra46190de2019-12-03 22:36:47 +01004293/* This kernel appears to be broken.
dirk21dc0312016-06-13 22:30:26 +02004294#if defined(MAGICKCORE_OPENCL_SUPPORT)
dirk06c4f032016-02-11 23:00:56 +01004295 unsharp_image=AccelerateUnsharpMaskImage(image,radius,sigma,gain,threshold,
4296 exception);
Dusan Veljko94190b72015-11-09 14:45:26 +01004297 if (unsharp_image != (Image *) NULL)
4298 return(unsharp_image);
dirk21dc0312016-06-13 22:30:26 +02004299#endif
Dirk Lemstra46190de2019-12-03 22:36:47 +01004300*/
cristy3afd4012013-03-25 11:30:44 +00004301 unsharp_image=BlurImage(image,radius,sigma,exception);
4302 if (unsharp_image == (Image *) NULL)
4303 return((Image *) NULL);
4304 quantum_threshold=(double) QuantumRange*threshold;
4305 /*
4306 Unsharp-mask image.
4307 */
4308 status=MagickTrue;
4309 progress=0;
4310 image_view=AcquireVirtualCacheView(image,exception);
4311 unsharp_view=AcquireAuthenticCacheView(unsharp_image,exception);
4312#if defined(MAGICKCORE_OPENMP_SUPPORT)
Cristy38b42e12018-03-18 10:13:01 -04004313 #pragma omp parallel for schedule(static) shared(progress,status) \
Cristy8255ca92017-10-09 08:37:35 -04004314 magick_number_threads(image,unsharp_image,image->rows,1)
cristy3afd4012013-03-25 11:30:44 +00004315#endif
4316 for (y=0; y < (ssize_t) image->rows; y++)
4317 {
Cristyf2dc1dd2020-12-28 13:59:26 -05004318 const Quantum
dirk05d2ff72015-11-18 23:13:43 +01004319 *magick_restrict p;
cristy3afd4012013-03-25 11:30:44 +00004320
Cristyf2dc1dd2020-12-28 13:59:26 -05004321 Quantum
dirk05d2ff72015-11-18 23:13:43 +01004322 *magick_restrict q;
cristy3afd4012013-03-25 11:30:44 +00004323
Cristyf2dc1dd2020-12-28 13:59:26 -05004324 ssize_t
cristy3afd4012013-03-25 11:30:44 +00004325 x;
4326
4327 if (status == MagickFalse)
4328 continue;
4329 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
4330 q=QueueCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
4331 exception);
4332 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
4333 {
4334 status=MagickFalse;
4335 continue;
4336 }
4337 for (x=0; x < (ssize_t) image->columns; x++)
4338 {
Cristyf2dc1dd2020-12-28 13:59:26 -05004339 ssize_t
cristy3afd4012013-03-25 11:30:44 +00004340 i;
4341
4342 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4343 {
4344 double
4345 pixel;
4346
4347 PixelChannel
4348 channel;
4349
4350 PixelTrait
4351 traits,
4352 unsharp_traits;
4353
4354 channel=GetPixelChannelChannel(image,i);
4355 traits=GetPixelChannelTraits(image,channel);
4356 unsharp_traits=GetPixelChannelTraits(unsharp_image,channel);
4357 if ((traits == UndefinedPixelTrait) ||
4358 (unsharp_traits == UndefinedPixelTrait))
4359 continue;
Cristydaf00192018-05-12 13:47:19 -04004360 if ((unsharp_traits & CopyPixelTrait) != 0)
cristy3afd4012013-03-25 11:30:44 +00004361 {
4362 SetPixelChannel(unsharp_image,channel,p[i],q);
4363 continue;
4364 }
4365 pixel=p[i]-(double) GetPixelChannel(unsharp_image,channel,q);
4366 if (fabs(2.0*pixel) < quantum_threshold)
4367 pixel=(double) p[i];
4368 else
4369 pixel=(double) p[i]+gain*pixel;
4370 SetPixelChannel(unsharp_image,channel,ClampToQuantum(pixel),q);
4371 }
4372 p+=GetPixelChannels(image);
4373 q+=GetPixelChannels(unsharp_image);
4374 }
4375 if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
4376 status=MagickFalse;
4377 if (image->progress_monitor != (MagickProgressMonitor) NULL)
4378 {
4379 MagickBooleanType
4380 proceed;
4381
Cristyfc89eec2018-11-11 21:10:06 -05004382#if defined(MAGICKCORE_OPENMP_SUPPORT)
4383 #pragma omp atomic
4384#endif
4385 progress++;
4386 proceed=SetImageProgress(image,SharpenImageTag,progress,image->rows);
cristy3afd4012013-03-25 11:30:44 +00004387 if (proceed == MagickFalse)
4388 status=MagickFalse;
4389 }
4390 }
4391 unsharp_image->type=image->type;
4392 unsharp_view=DestroyCacheView(unsharp_view);
4393 image_view=DestroyCacheView(image_view);
4394 if (status == MagickFalse)
4395 unsharp_image=DestroyImage(unsharp_image);
cristy3ed852e2009-09-05 21:47:34 +00004396 return(unsharp_image);
4397}