Coverage Report

Created: 2025-11-14 07:32

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/imagemagick/MagickCore/effect.c
Line
Count
Source
1
/*
2
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3
%                                                                             %
4
%                                                                             %
5
%                                                                             %
6
%                   EEEEE  FFFFF  FFFFF  EEEEE  CCCC  TTTTT                   %
7
%                   E      F      F      E     C        T                     %
8
%                   EEE    FFF    FFF    EEE   C        T                     %
9
%                   E      F      F      E     C        T                     %
10
%                   EEEEE  F      F      EEEEE  CCCC    T                     %
11
%                                                                             %
12
%                                                                             %
13
%                       MagickCore Image Effects Methods                      %
14
%                                                                             %
15
%                               Software Design                               %
16
%                                    Cristy                                   %
17
%                                 October 1996                                %
18
%                                                                             %
19
%                                                                             %
20
%  Copyright @ 1999 ImageMagick Studio LLC, a non-profit organization         %
21
%  dedicated to making software imaging solutions freely available.           %
22
%                                                                             %
23
%  You may not use this file except in compliance with the License.  You may  %
24
%  obtain a copy of the License at                                            %
25
%                                                                             %
26
%    https://imagemagick.org/script/license.php                               %
27
%                                                                             %
28
%  Unless required by applicable law or agreed to in writing, software        %
29
%  distributed under the License is distributed on an "AS IS" BASIS,          %
30
%  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   %
31
%  See the License for the specific language governing permissions and        %
32
%  limitations under the License.                                             %
33
%                                                                             %
34
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35
%
36
%
37
%
38
*/
39

40
/*
41
  Include declarations.
42
*/
43
#include "MagickCore/studio.h"
44
#include "MagickCore/accelerate-private.h"
45
#include "MagickCore/blob.h"
46
#include "MagickCore/cache-view.h"
47
#include "MagickCore/color.h"
48
#include "MagickCore/color-private.h"
49
#include "MagickCore/colorspace.h"
50
#include "MagickCore/constitute.h"
51
#include "MagickCore/decorate.h"
52
#include "MagickCore/distort.h"
53
#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"
60
#include "MagickCore/gem-private.h"
61
#include "MagickCore/geometry.h"
62
#include "MagickCore/image-private.h"
63
#include "MagickCore/list.h"
64
#include "MagickCore/log.h"
65
#include "MagickCore/matrix.h"
66
#include "MagickCore/memory_.h"
67
#include "MagickCore/memory-private.h"
68
#include "MagickCore/monitor.h"
69
#include "MagickCore/monitor-private.h"
70
#include "MagickCore/montage.h"
71
#include "MagickCore/morphology.h"
72
#include "MagickCore/morphology-private.h"
73
#include "MagickCore/paint.h"
74
#include "MagickCore/pixel-accessor.h"
75
#include "MagickCore/property.h"
76
#include "MagickCore/quantize.h"
77
#include "MagickCore/quantum.h"
78
#include "MagickCore/quantum-private.h"
79
#include "MagickCore/random_.h"
80
#include "MagickCore/random-private.h"
81
#include "MagickCore/resample.h"
82
#include "MagickCore/resample-private.h"
83
#include "MagickCore/resize.h"
84
#include "MagickCore/resource_.h"
85
#include "MagickCore/segment.h"
86
#include "MagickCore/shear.h"
87
#include "MagickCore/signature-private.h"
88
#include "MagickCore/statistic.h"
89
#include "MagickCore/string_.h"
90
#include "MagickCore/thread-private.h"
91
#include "MagickCore/transform.h"
92
#include "MagickCore/threshold.h"
93
#include "MagickCore/utility-private.h"
94

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,
115
%        const double sigma,ExceptionInfo *exception)
116
%
117
%  A description of each parameter follows:
118
%
119
%    o image: the image.
120
%
121
%    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
*/
129
MagickExport Image *AdaptiveBlurImage(const Image *image,const double radius,
130
  const double sigma,ExceptionInfo *exception)
131
0
{
132
0
#define AdaptiveBlurImageTag  "Convolve/Image"
133
0
#define MagickSigma  (fabs(sigma) < MagickEpsilon ? MagickEpsilon : sigma)
134
135
0
  CacheView
136
0
    *blur_view,
137
0
    *edge_view,
138
0
    *image_view;
139
140
0
  double
141
0
    normalize,
142
0
    **kernel;
143
144
0
  Image
145
0
    *blur_image,
146
0
    *edge_image,
147
0
    *gaussian_image;
148
149
0
  MagickBooleanType
150
0
    status;
151
152
0
  MagickOffsetType
153
0
    progress;
154
155
0
  size_t
156
0
    width;
157
158
0
  ssize_t
159
0
    w,
160
0
    y;
161
162
0
  assert(image != (const Image *) NULL);
163
0
  assert(image->signature == MagickCoreSignature);
164
0
  assert(exception != (ExceptionInfo *) NULL);
165
0
  assert(exception->signature == MagickCoreSignature);
166
0
  if (IsEventLogging() != MagickFalse)
167
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
168
0
  blur_image=CloneImage(image,0,0,MagickTrue,exception);
169
0
  if (blur_image == (Image *) NULL)
170
0
    return((Image *) NULL);
171
0
  if (fabs(sigma) < MagickEpsilon)
172
0
    return(blur_image);
173
0
  if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
174
0
    {
175
0
      blur_image=DestroyImage(blur_image);
176
0
      return((Image *) NULL);
177
0
    }
178
  /*
179
    Edge detect the image brightness channel, level, blur, and level again.
180
  */
181
0
  edge_image=EdgeImage(image,radius,exception);
182
0
  if (edge_image == (Image *) NULL)
183
0
    {
184
0
      blur_image=DestroyImage(blur_image);
185
0
      return((Image *) NULL);
186
0
    }
187
0
  (void) AutoLevelImage(edge_image,exception);
188
0
  gaussian_image=BlurImage(edge_image,radius,sigma,exception);
189
0
  if (gaussian_image != (Image *) NULL)
190
0
    {
191
0
      edge_image=DestroyImage(edge_image);
192
0
      edge_image=gaussian_image;
193
0
    }
194
0
  (void) AutoLevelImage(edge_image,exception);
195
  /*
196
    Create a set of kernels from maximum (radius,sigma) to minimum.
197
  */
198
0
  width=GetOptimalKernelWidth2D(radius,sigma);
199
0
  kernel=(double **) MagickAssumeAligned(AcquireAlignedMemory((size_t) width,
200
0
    sizeof(*kernel)));
201
0
  if (kernel == (double **) NULL)
202
0
    {
203
0
      edge_image=DestroyImage(edge_image);
204
0
      blur_image=DestroyImage(blur_image);
205
0
      ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
206
0
    }
207
0
  (void) memset(kernel,0,(size_t) width*sizeof(*kernel));
208
0
  for (w=0; w < (ssize_t) width; w+=2)
209
0
  {
210
0
    ssize_t
211
0
      j,
212
0
      k,
213
0
      u,
214
0
      v;
215
216
0
    kernel[w]=(double *) MagickAssumeAligned(AcquireAlignedMemory(
217
0
      (width-(size_t) w),(width-(size_t) w)*sizeof(**kernel)));
218
0
    if (kernel[w] == (double *) NULL)
219
0
      break;
220
0
    normalize=0.0;
221
0
    j=((ssize_t) width-w-1)/2;
222
0
    k=0;
223
0
    for (v=(-j); v <= j; v++)
224
0
    {
225
0
      for (u=(-j); u <= j; u++)
226
0
      {
227
0
        kernel[w][k]=(double) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
228
0
          MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
229
0
        normalize+=kernel[w][k];
230
0
        k++;
231
0
      }
232
0
    }
233
0
    kernel[w][(k-1)/2]+=(double) (1.0-normalize);
234
0
    if (sigma < MagickEpsilon)
235
0
      kernel[w][(k-1)/2]=1.0;
236
0
  }
237
0
  if (w < (ssize_t) width)
238
0
    {
239
0
      for (w-=2; w >= 0; w-=2)
240
0
        kernel[w]=(double *) RelinquishAlignedMemory(kernel[w]);
241
0
      kernel=(double **) RelinquishAlignedMemory(kernel);
242
0
      edge_image=DestroyImage(edge_image);
243
0
      blur_image=DestroyImage(blur_image);
244
0
      ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
245
0
    }
246
  /*
247
    Adaptively blur image.
248
  */
249
0
  status=MagickTrue;
250
0
  progress=0;
251
0
  image_view=AcquireVirtualCacheView(image,exception);
252
0
  edge_view=AcquireVirtualCacheView(edge_image,exception);
253
0
  blur_view=AcquireAuthenticCacheView(blur_image,exception);
254
#if defined(MAGICKCORE_OPENMP_SUPPORT)
255
  #pragma omp parallel for schedule(static) shared(progress,status) \
256
    magick_number_threads(image,blur_image,blur_image->rows,1)
257
#endif
258
0
  for (y=0; y < (ssize_t) blur_image->rows; y++)
259
0
  {
260
0
    const Quantum
261
0
      *magick_restrict r;
262
263
0
    Quantum
264
0
      *magick_restrict q;
265
266
0
    ssize_t
267
0
      x;
268
269
0
    if (status == MagickFalse)
270
0
      continue;
271
0
    r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
272
0
    q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
273
0
      exception);
274
0
    if ((r == (const Quantum *) NULL) || (q == (Quantum *) NULL))
275
0
      {
276
0
        status=MagickFalse;
277
0
        continue;
278
0
      }
279
0
    for (x=0; x < (ssize_t) blur_image->columns; x++)
280
0
    {
281
0
      const Quantum
282
0
        *magick_restrict p;
283
284
0
      ssize_t
285
0
        i;
286
287
0
      ssize_t
288
0
        center,
289
0
        j;
290
291
0
      j=CastDoubleToSsizeT(ceil((double) width*(1.0-QuantumScale*
292
0
        GetPixelIntensity(edge_image,r))-0.5));
293
0
      if (j < 0)
294
0
        j=0;
295
0
      else
296
0
        if (j > (ssize_t) width)
297
0
          j=(ssize_t) width;
298
0
      if ((j & 0x01) != 0)
299
0
        j--;
300
0
      p=GetCacheViewVirtualPixels(image_view,x-((ssize_t) width-j)/2L,y-
301
0
        ((ssize_t) width-j)/2L,width-(size_t) j,width-(size_t) j,exception);
302
0
      if (p == (const Quantum *) NULL)
303
0
        break;
304
0
      center=(ssize_t) (GetPixelChannels(image)*(width-(size_t) j)*
305
0
        ((width-(size_t) j)/2L)+GetPixelChannels(image)*((width-(size_t) j)/2));
306
0
      for (i=0; i < (ssize_t) GetPixelChannels(blur_image); i++)
307
0
      {
308
0
        const double
309
0
          *magick_restrict k;
310
311
0
        const Quantum
312
0
          *magick_restrict pixels;
313
314
0
        double
315
0
          alpha,
316
0
          gamma,
317
0
          pixel;
318
319
0
        PixelChannel
320
0
          channel;
321
322
0
        PixelTrait
323
0
          blur_traits,
324
0
          traits;
325
326
0
        ssize_t
327
0
          u,
328
0
          v;
329
330
0
        channel=GetPixelChannelChannel(image,i);
331
0
        traits=GetPixelChannelTraits(image,channel);
332
0
        blur_traits=GetPixelChannelTraits(blur_image,channel);
333
0
        if ((traits == UndefinedPixelTrait) ||
334
0
            (blur_traits == UndefinedPixelTrait))
335
0
          continue;
336
0
        if ((blur_traits & CopyPixelTrait) != 0)
337
0
          {
338
0
            SetPixelChannel(blur_image,channel,p[center+i],q);
339
0
            continue;
340
0
          }
341
0
        k=kernel[j];
342
0
        pixels=p;
343
0
        pixel=0.0;
344
0
        gamma=0.0;
345
0
        if ((blur_traits & BlendPixelTrait) == 0)
346
0
          {
347
            /*
348
              No alpha blending.
349
            */
350
0
            for (v=0; v < ((ssize_t) width-j); v++)
351
0
            {
352
0
              for (u=0; u < ((ssize_t) width-j); u++)
353
0
              {
354
0
                pixel+=(*k)*(double) pixels[i];
355
0
                gamma+=(*k);
356
0
                k++;
357
0
                pixels+=(ptrdiff_t) GetPixelChannels(image);
358
0
              }
359
0
            }
360
0
            gamma=MagickSafeReciprocal(gamma);
361
0
            SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
362
0
            continue;
363
0
          }
364
        /*
365
          Alpha blending.
366
        */
367
0
        for (v=0; v < ((ssize_t) width-j); v++)
368
0
        {
369
0
          for (u=0; u < ((ssize_t) width-j); u++)
370
0
          {
371
0
            alpha=(double) (QuantumScale*(double) GetPixelAlpha(image,pixels));
372
0
            pixel+=(*k)*alpha*(double) pixels[i];
373
0
            gamma+=(*k)*alpha;
374
0
            k++;
375
0
            pixels+=(ptrdiff_t) GetPixelChannels(image);
376
0
          }
377
0
        }
378
0
        gamma=MagickSafeReciprocal(gamma);
379
0
        SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
380
0
      }
381
0
      q+=(ptrdiff_t) GetPixelChannels(blur_image);
382
0
      r+=(ptrdiff_t) GetPixelChannels(edge_image);
383
0
    }
384
0
    if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
385
0
      status=MagickFalse;
386
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
387
0
      {
388
0
        MagickBooleanType
389
0
          proceed;
390
391
#if defined(MAGICKCORE_OPENMP_SUPPORT)
392
        #pragma omp atomic
393
#endif
394
0
        progress++;
395
0
        proceed=SetImageProgress(image,AdaptiveBlurImageTag,progress,
396
0
          image->rows);
397
0
        if (proceed == MagickFalse)
398
0
          status=MagickFalse;
399
0
      }
400
0
  }
401
0
  blur_image->type=image->type;
402
0
  blur_view=DestroyCacheView(blur_view);
403
0
  edge_view=DestroyCacheView(edge_view);
404
0
  image_view=DestroyCacheView(image_view);
405
0
  edge_image=DestroyImage(edge_image);
406
0
  for (w=0; w < (ssize_t) width; w+=2)
407
0
    kernel[w]=(double *) RelinquishAlignedMemory(kernel[w]);
408
0
  kernel=(double **) RelinquishAlignedMemory(kernel);
409
0
  if (status == MagickFalse)
410
0
    blur_image=DestroyImage(blur_image);
411
0
  return(blur_image);
412
0
}
413

414
/*
415
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
416
%                                                                             %
417
%                                                                             %
418
%                                                                             %
419
%     A d a p t i v e S h a r p e n I m a g e                                 %
420
%                                                                             %
421
%                                                                             %
422
%                                                                             %
423
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
424
%
425
%  AdaptiveSharpenImage() adaptively sharpens the image by sharpening more
426
%  intensely near image edges and less intensely far from edges. We sharpen the
427
%  image with a Gaussian operator of the given radius and standard deviation
428
%  (sigma).  For reasonable results, radius should be larger than sigma.  Use a
429
%  radius of 0 and AdaptiveSharpenImage() selects a suitable radius for you.
430
%
431
%  The format of the AdaptiveSharpenImage method is:
432
%
433
%      Image *AdaptiveSharpenImage(const Image *image,const double radius,
434
%        const double sigma,ExceptionInfo *exception)
435
%
436
%  A description of each parameter follows:
437
%
438
%    o image: the image.
439
%
440
%    o radius: the radius of the Gaussian, in pixels, not counting the center
441
%      pixel.
442
%
443
%    o sigma: the standard deviation of the Laplacian, in pixels.
444
%
445
%    o exception: return any errors or warnings in this structure.
446
%
447
*/
448
MagickExport Image *AdaptiveSharpenImage(const Image *image,const double radius,
449
  const double sigma,ExceptionInfo *exception)
450
0
{
451
0
#define AdaptiveSharpenImageTag  "Convolve/Image"
452
0
#define MagickSigma  (fabs(sigma) < MagickEpsilon ? MagickEpsilon : sigma)
453
454
0
  CacheView
455
0
    *sharp_view,
456
0
    *edge_view,
457
0
    *image_view;
458
459
0
  double
460
0
    normalize,
461
0
    **kernel;
462
463
0
  Image
464
0
    *sharp_image,
465
0
    *edge_image,
466
0
    *gaussian_image;
467
468
0
  MagickBooleanType
469
0
    status;
470
471
0
  MagickOffsetType
472
0
    progress;
473
474
0
  size_t
475
0
    width;
476
477
0
  ssize_t
478
0
    w,
479
0
    y;
480
481
0
  assert(image != (const Image *) NULL);
482
0
  assert(image->signature == MagickCoreSignature);
483
0
  assert(exception != (ExceptionInfo *) NULL);
484
0
  assert(exception->signature == MagickCoreSignature);
485
0
  if (IsEventLogging() != MagickFalse)
486
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
487
0
  sharp_image=CloneImage(image,0,0,MagickTrue,exception);
488
0
  if (sharp_image == (Image *) NULL)
489
0
    return((Image *) NULL);
490
0
  if (fabs(sigma) < MagickEpsilon)
491
0
    return(sharp_image);
492
0
  if (SetImageStorageClass(sharp_image,DirectClass,exception) == MagickFalse)
493
0
    {
494
0
      sharp_image=DestroyImage(sharp_image);
495
0
      return((Image *) NULL);
496
0
    }
497
  /*
498
    Edge detect the image brightness channel, level, sharp, and level again.
499
  */
500
0
  edge_image=EdgeImage(image,radius,exception);
501
0
  if (edge_image == (Image *) NULL)
502
0
    {
503
0
      sharp_image=DestroyImage(sharp_image);
504
0
      return((Image *) NULL);
505
0
    }
506
0
  (void) AutoLevelImage(edge_image,exception);
507
0
  gaussian_image=BlurImage(edge_image,radius,sigma,exception);
508
0
  if (gaussian_image != (Image *) NULL)
509
0
    {
510
0
      edge_image=DestroyImage(edge_image);
511
0
      edge_image=gaussian_image;
512
0
    }
513
0
  (void) AutoLevelImage(edge_image,exception);
514
  /*
515
    Create a set of kernels from maximum (radius,sigma) to minimum.
516
  */
517
0
  width=GetOptimalKernelWidth2D(radius,sigma);
518
0
  kernel=(double **) MagickAssumeAligned(AcquireAlignedMemory((size_t)
519
0
    width,sizeof(*kernel)));
520
0
  if (kernel == (double **) NULL)
521
0
    {
522
0
      edge_image=DestroyImage(edge_image);
523
0
      sharp_image=DestroyImage(sharp_image);
524
0
      ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
525
0
    }
526
0
  (void) memset(kernel,0,(size_t) width*sizeof(*kernel));
527
0
  for (w=0; w < (ssize_t) width; w+=2)
528
0
  {
529
0
    ssize_t
530
0
      j,
531
0
      k,
532
0
      u,
533
0
      v;
534
535
0
    kernel[w]=(double *) MagickAssumeAligned(AcquireAlignedMemory((size_t)
536
0
      (width-(size_t) w),(width-(size_t) w)*sizeof(**kernel)));
537
0
    if (kernel[w] == (double *) NULL)
538
0
      break;
539
0
    normalize=0.0;
540
0
    j=((ssize_t) width-w-1)/2;
541
0
    k=0;
542
0
    for (v=(-j); v <= j; v++)
543
0
    {
544
0
      for (u=(-j); u <= j; u++)
545
0
      {
546
0
        kernel[w][k]=(double) (-exp(-((double) u*u+v*v)/(2.0*MagickSigma*
547
0
          MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
548
0
        normalize+=kernel[w][k];
549
0
        k++;
550
0
      }
551
0
    }
552
0
    kernel[w][(k-1)/2]=(double) ((-2.0)*normalize);
553
0
    if (sigma < MagickEpsilon)
554
0
      kernel[w][(k-1)/2]=1.0;
555
0
  }
556
0
  if (w < (ssize_t) width)
557
0
    {
558
0
      for (w-=2; w >= 0; w-=2)
559
0
        kernel[w]=(double *) RelinquishAlignedMemory(kernel[w]);
560
0
      kernel=(double **) RelinquishAlignedMemory(kernel);
561
0
      edge_image=DestroyImage(edge_image);
562
0
      sharp_image=DestroyImage(sharp_image);
563
0
      ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
564
0
    }
565
  /*
566
    Adaptively sharpen image.
567
  */
568
0
  status=MagickTrue;
569
0
  progress=0;
570
0
  image_view=AcquireVirtualCacheView(image,exception);
571
0
  edge_view=AcquireVirtualCacheView(edge_image,exception);
572
0
  sharp_view=AcquireAuthenticCacheView(sharp_image,exception);
573
#if defined(MAGICKCORE_OPENMP_SUPPORT)
574
  #pragma omp parallel for schedule(static) shared(progress,status) \
575
    magick_number_threads(image,sharp_image,sharp_image->rows,1)
576
#endif
577
0
  for (y=0; y < (ssize_t) sharp_image->rows; y++)
578
0
  {
579
0
    const Quantum
580
0
      *magick_restrict r;
581
582
0
    Quantum
583
0
      *magick_restrict q;
584
585
0
    ssize_t
586
0
      x;
587
588
0
    if (status == MagickFalse)
589
0
      continue;
590
0
    r=GetCacheViewVirtualPixels(edge_view,0,y,edge_image->columns,1,exception);
591
0
    q=QueueCacheViewAuthenticPixels(sharp_view,0,y,sharp_image->columns,1,
592
0
      exception);
593
0
    if ((r == (const Quantum *) NULL) || (q == (Quantum *) NULL))
594
0
      {
595
0
        status=MagickFalse;
596
0
        continue;
597
0
      }
598
0
    for (x=0; x < (ssize_t) sharp_image->columns; x++)
599
0
    {
600
0
      const Quantum
601
0
        *magick_restrict p;
602
603
0
      ssize_t
604
0
        i;
605
606
0
      ssize_t
607
0
        center,
608
0
        j;
609
610
0
      j=CastDoubleToSsizeT(ceil((double) width*(1.0-QuantumScale*
611
0
        GetPixelIntensity(edge_image,r))-0.5));
612
0
      if (j < 0)
613
0
        j=0;
614
0
      else
615
0
        if (j > (ssize_t) width)
616
0
          j=(ssize_t) width;
617
0
      if ((j & 0x01) != 0)
618
0
        j--;
619
0
      p=GetCacheViewVirtualPixels(image_view,x-(((ssize_t) width-j)/2L),y-
620
0
        (((ssize_t) width-j)/2L),width-(size_t) j,width-(size_t) j,exception);
621
0
      if (p == (const Quantum *) NULL)
622
0
        break;
623
0
      center=(ssize_t) (GetPixelChannels(image)*(width-(size_t) j)*
624
0
       ((width-(size_t) j)/2L)+GetPixelChannels(image)*((width-(size_t) j)/2));
625
0
      for (i=0; i < (ssize_t) GetPixelChannels(sharp_image); i++)
626
0
      {
627
0
        const double
628
0
          *magick_restrict k;
629
630
0
        const Quantum
631
0
          *magick_restrict pixels;
632
633
0
        double
634
0
          alpha,
635
0
          gamma,
636
0
          pixel;
637
638
0
        PixelChannel
639
0
          channel;
640
641
0
        PixelTrait
642
0
          sharp_traits,
643
0
          traits;
644
645
0
        ssize_t
646
0
          u,
647
0
          v;
648
649
0
        channel=GetPixelChannelChannel(image,i);
650
0
        traits=GetPixelChannelTraits(image,channel);
651
0
        sharp_traits=GetPixelChannelTraits(sharp_image,channel);
652
0
        if ((traits == UndefinedPixelTrait) ||
653
0
            (sharp_traits == UndefinedPixelTrait))
654
0
          continue;
655
0
        if ((sharp_traits & CopyPixelTrait) != 0)
656
0
          {
657
0
            SetPixelChannel(sharp_image,channel,p[center+i],q);
658
0
            continue;
659
0
          }
660
0
        k=kernel[j];
661
0
        pixels=p;
662
0
        pixel=0.0;
663
0
        gamma=0.0;
664
0
        if ((sharp_traits & BlendPixelTrait) == 0)
665
0
          {
666
            /*
667
              No alpha blending.
668
            */
669
0
            for (v=0; v < ((ssize_t) width-j); v++)
670
0
            {
671
0
              for (u=0; u < ((ssize_t) width-j); u++)
672
0
              {
673
0
                pixel+=(*k)*(double) pixels[i];
674
0
                gamma+=(*k);
675
0
                k++;
676
0
                pixels+=(ptrdiff_t) GetPixelChannels(image);
677
0
              }
678
0
            }
679
0
            gamma=MagickSafeReciprocal(gamma);
680
0
            SetPixelChannel(sharp_image,channel,ClampToQuantum(gamma*pixel),q);
681
0
            continue;
682
0
          }
683
        /*
684
          Alpha blending.
685
        */
686
0
        for (v=0; v < ((ssize_t) width-j); v++)
687
0
        {
688
0
          for (u=0; u < ((ssize_t) width-j); u++)
689
0
          {
690
0
            alpha=(double) (QuantumScale*(double) GetPixelAlpha(image,pixels));
691
0
            pixel+=(*k)*alpha*(double) pixels[i];
692
0
            gamma+=(*k)*alpha;
693
0
            k++;
694
0
            pixels+=(ptrdiff_t) GetPixelChannels(image);
695
0
          }
696
0
        }
697
0
        gamma=MagickSafeReciprocal(gamma);
698
0
        SetPixelChannel(sharp_image,channel,ClampToQuantum(gamma*pixel),q);
699
0
      }
700
0
      q+=(ptrdiff_t) GetPixelChannels(sharp_image);
701
0
      r+=(ptrdiff_t) GetPixelChannels(edge_image);
702
0
    }
703
0
    if (SyncCacheViewAuthenticPixels(sharp_view,exception) == MagickFalse)
704
0
      status=MagickFalse;
705
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
706
0
      {
707
0
        MagickBooleanType
708
0
          proceed;
709
710
#if defined(MAGICKCORE_OPENMP_SUPPORT)
711
        #pragma omp atomic
712
#endif
713
0
        progress++;
714
0
        proceed=SetImageProgress(image,AdaptiveSharpenImageTag,progress,
715
0
          image->rows);
716
0
        if (proceed == MagickFalse)
717
0
          status=MagickFalse;
718
0
      }
719
0
  }
720
0
  sharp_image->type=image->type;
721
0
  sharp_view=DestroyCacheView(sharp_view);
722
0
  edge_view=DestroyCacheView(edge_view);
723
0
  image_view=DestroyCacheView(image_view);
724
0
  edge_image=DestroyImage(edge_image);
725
0
  for (w=0; w < (ssize_t) width; w+=2)
726
0
    kernel[w]=(double *) RelinquishAlignedMemory(kernel[w]);
727
0
  kernel=(double **) RelinquishAlignedMemory(kernel);
728
0
  if (status == MagickFalse)
729
0
    sharp_image=DestroyImage(sharp_image);
730
0
  return(sharp_image);
731
0
}
732

733
/*
734
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
735
%                                                                             %
736
%                                                                             %
737
%                                                                             %
738
%     B l u r I m a g e                                                       %
739
%                                                                             %
740
%                                                                             %
741
%                                                                             %
742
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
743
%
744
%  BlurImage() blurs an image.  We convolve the image with a Gaussian operator
745
%  of the given radius and standard deviation (sigma).  For reasonable results,
746
%  the radius should be larger than sigma.  Use a radius of 0 and BlurImage()
747
%  selects a suitable radius for you.
748
%
749
%  The format of the BlurImage method is:
750
%
751
%      Image *BlurImage(const Image *image,const double radius,
752
%        const double sigma,ExceptionInfo *exception)
753
%
754
%  A description of each parameter follows:
755
%
756
%    o image: the image.
757
%
758
%    o radius: the radius of the Gaussian, in pixels, not counting the center
759
%      pixel.
760
%
761
%    o sigma: the standard deviation of the Gaussian, in pixels.
762
%
763
%    o exception: return any errors or warnings in this structure.
764
%
765
*/
766
MagickExport Image *BlurImage(const Image *image,const double radius,
767
  const double sigma,ExceptionInfo *exception)
768
0
{
769
0
  char
770
0
    geometry[MagickPathExtent];
771
772
0
  KernelInfo
773
0
    *kernel_info;
774
775
0
  Image
776
0
    *blur_image;
777
778
0
  assert(image != (const Image *) NULL);
779
0
  assert(image->signature == MagickCoreSignature);
780
0
  assert(exception != (ExceptionInfo *) NULL);
781
0
  assert(exception->signature == MagickCoreSignature);
782
0
  if (IsEventLogging() != MagickFalse)
783
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
784
#if defined(MAGICKCORE_OPENCL_SUPPORT)
785
  blur_image=AccelerateBlurImage(image,radius,sigma,exception);
786
  if (blur_image != (Image *) NULL)
787
    return(blur_image);
788
#endif
789
0
  (void) FormatLocaleString(geometry,MagickPathExtent,
790
0
    "blur:%.20gx%.20g;blur:%.20gx%.20g+90",radius,sigma,radius,sigma);
791
0
  kernel_info=AcquireKernelInfo(geometry,exception);
792
0
  if (kernel_info == (KernelInfo *) NULL)
793
0
    ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
794
0
  blur_image=ConvolveImage(image,kernel_info,exception);
795
0
  kernel_info=DestroyKernelInfo(kernel_info);
796
0
  return(blur_image);
797
0
}
798

799
/*
800
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
801
%                                                                             %
802
%                                                                             %
803
%                                                                             %
804
%     B i l a t e r a l B l u r I m a g e                                     %
805
%                                                                             %
806
%                                                                             %
807
%                                                                             %
808
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
809
%
810
%  BilateralBlurImage() is a non-linear, edge-preserving, and noise-reducing
811
%  smoothing filter for images.  It replaces the intensity of each pixel with
812
%  a weighted average of intensity values from nearby pixels.  This weight is
813
%  based on a Gaussian distribution.  The weights depend not only on Euclidean
814
%  distance of pixels, but also on the radiometric differences (e.g., range
815
%  differences, such as color intensity, depth distance, etc.). This preserves
816
%  sharp edges.
817
%
818
%  The format of the BilateralBlurImage method is:
819
%
820
%      Image *BilateralBlurImage(const Image *image,const size_t width,
821
%        const size_t height,const double intensity_sigma,
822
%        const double spatial_sigma,ExceptionInfo *exception)
823
%
824
%  A description of each parameter follows:
825
%
826
%    o image: the image.
827
%
828
%    o width: the width of the neighborhood in pixels.
829
%
830
%    o height: the height of the neighborhood in pixels.
831
%
832
%    o intensity_sigma: sigma in the intensity space. A larger value means
833
%      that farther colors within the pixel neighborhood (see spatial_sigma)
834
%      will be mixed together, resulting in larger areas of semi-equal color.
835
%
836
%    o spatial_sigma: sigma in the coordinate space. A larger value means that
837
%      farther pixels influence each other as long as their colors are close
838
%      enough (see intensity_sigma ). When the neighborhood diameter is greater
839
%      than zero, it specifies the neighborhood size regardless of
840
%      spatial_sigma. Otherwise, the neighborhood diameter is proportional to
841
%      spatial_sigma.
842
%
843
%    o exception: return any errors or warnings in this structure.
844
%
845
*/
846
847
static inline double BlurDistance(const ssize_t x,const ssize_t y,
848
  const ssize_t u,const ssize_t v)
849
0
{
850
0
  return(sqrt(((double) x-u)*((double) x-u)+((double) y-v)*((double) y-v)));
851
0
}
852
853
static inline double BlurGaussian(const double x,const double sigma)
854
0
{
855
0
  return(exp(-((double) x*x)*MagickSafeReciprocal(2.0*sigma*sigma))*
856
0
    MagickSafeReciprocal(Magick2PI*sigma*sigma));
857
0
}
858
859
static double **DestroyBilateralTLS(const size_t number_threads,
860
  double **weights)
861
0
{
862
0
  ssize_t
863
0
    i;
864
865
0
  assert(weights != (double **) NULL);
866
0
  for (i=0; i <= (ssize_t) number_threads; i++)
867
0
    if (weights[i] != (double *) NULL)
868
0
      weights[i]=(double *) RelinquishMagickMemory(weights[i]);
869
0
  weights=(double **) RelinquishMagickMemory(weights);
870
0
  return(weights);
871
0
}
872
873
static double **AcquireBilateralTLS(const size_t number_threads,
874
  const size_t width,const size_t height)
875
0
{
876
0
  double
877
0
    **weights;
878
879
0
  ssize_t
880
0
    i;
881
882
0
  weights=(double **) AcquireQuantumMemory(number_threads+1,sizeof(*weights));
883
0
  if (weights == (double **) NULL)
884
0
    return((double **) NULL);
885
0
  (void) memset(weights,0,number_threads*sizeof(*weights));
886
0
  for (i=0; i <= (ssize_t) number_threads; i++)
887
0
  {
888
0
    weights[i]=(double *) AcquireQuantumMemory(width,height*sizeof(**weights));
889
0
    if (weights[i] == (double *) NULL)
890
0
      return(DestroyBilateralTLS(number_threads,weights));
891
0
  }
892
0
  return(weights);
893
0
}
894
895
MagickExport Image *BilateralBlurImage(const Image *image,const size_t width,
896
  const size_t height,const double intensity_sigma,const double spatial_sigma,
897
  ExceptionInfo *exception)
898
0
{
899
0
#define MaxIntensity  (255)
900
0
#define BilateralBlurImageTag  "Blur/Image"
901
902
0
  CacheView
903
0
    *blur_view,
904
0
    *image_view;
905
906
0
  double
907
0
    intensity_gaussian[2*(MaxIntensity+1)],
908
0
    *spatial_gaussian,
909
0
    **weights;
910
911
0
  Image
912
0
    *blur_image;
913
914
0
  MagickBooleanType
915
0
    status;
916
917
0
  MagickOffsetType
918
0
    progress;
919
920
0
  OffsetInfo
921
0
    mid;
922
923
0
  size_t
924
0
    number_threads;
925
926
0
  ssize_t
927
0
    w,
928
0
    y;
929
930
0
  assert(image != (const Image *) NULL);
931
0
  assert(image->signature == MagickCoreSignature);
932
0
  assert(exception != (ExceptionInfo *) NULL);
933
0
  assert(exception->signature == MagickCoreSignature);
934
0
  if (IsEventLogging() != MagickFalse)
935
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
936
0
  blur_image=CloneImage(image,0,0,MagickTrue,exception);
937
0
  if (blur_image == (Image *) NULL)
938
0
    return((Image *) NULL);
939
0
  if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
940
0
    {
941
0
      blur_image=DestroyImage(blur_image);
942
0
      return((Image *) NULL);
943
0
    }
944
0
  number_threads=(size_t) GetMagickResourceLimit(ThreadResource);
945
0
  weights=AcquireBilateralTLS(number_threads,MagickMax(width,1),
946
0
    MagickMax(height,1));
947
0
  if (weights == (double **) NULL)
948
0
    {
949
0
      blur_image=DestroyImage(blur_image);
950
0
      ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
951
0
    }
952
0
  for (w=(-MaxIntensity); w < MaxIntensity; w++)
953
0
    intensity_gaussian[w+MaxIntensity]=BlurGaussian((double) w,intensity_sigma);
954
0
  spatial_gaussian=weights[number_threads];
955
0
  {
956
0
    ssize_t
957
0
      n,
958
0
      v;
959
960
0
    n=0;
961
0
    mid.x=(ssize_t) (MagickMax(width,1)/2L);
962
0
    mid.y=(ssize_t) (MagickMax(height,1)/2L);
963
0
    for (v=0; v < (ssize_t) MagickMax(height,1); v++)
964
0
    {
965
0
      ssize_t
966
0
        u;
967
968
0
      for (u=0; u < (ssize_t) MagickMax(width,1); u++)
969
0
        spatial_gaussian[n++]=BlurGaussian(BlurDistance(0,0,u-mid.x,v-mid.y),
970
0
          spatial_sigma);
971
0
    }
972
0
  }
973
  /*
974
    Bilateral blur image.
975
  */
976
0
  status=MagickTrue;
977
0
  progress=0;
978
0
  image_view=AcquireVirtualCacheView(image,exception);
979
0
  blur_view=AcquireAuthenticCacheView(blur_image,exception);
980
#if defined(MAGICKCORE_OPENMP_SUPPORT)
981
  #pragma omp parallel for schedule(static) shared(progress,status) \
982
    magick_number_threads(image,blur_image,blur_image->rows,1)
983
#endif
984
0
  for (y=0; y < (ssize_t) blur_image->rows; y++)
985
0
  {
986
0
    const int
987
0
      id = GetOpenMPThreadId();
988
989
0
    Quantum
990
0
      *magick_restrict q;
991
992
0
    ssize_t
993
0
      x;
994
995
0
    if (status == MagickFalse)
996
0
      continue;
997
0
    q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
998
0
      exception);
999
0
    if (q == (Quantum *) NULL)
1000
0
      {
1001
0
        status=MagickFalse;
1002
0
        continue;
1003
0
      }
1004
0
    for (x=0; x < (ssize_t) blur_image->columns; x++)
1005
0
    {
1006
0
      const Quantum
1007
0
        *magick_restrict p,
1008
0
        *magick_restrict r;
1009
1010
0
      double
1011
0
        gamma,
1012
0
        pixel;
1013
1014
0
      ssize_t
1015
0
        i,
1016
0
        n,
1017
0
        u,
1018
0
        v;
1019
1020
      /*
1021
        Tonal weighting preserves edges while smoothing in the flat regions.
1022
      */
1023
0
      p=GetCacheViewVirtualPixels(image_view,x-mid.x,y-mid.y,MagickMax(width,1),
1024
0
        MagickMax(height,1),exception);
1025
0
      if (p == (const Quantum *) NULL)
1026
0
        break;
1027
0
      p+=(ptrdiff_t) (GetPixelChannels(image)*MagickMax(width,1)*(size_t) mid.y+
1028
0
        GetPixelChannels(image)*(size_t) mid.x);
1029
0
      n=0;
1030
0
      for (v=0; v < (ssize_t) MagickMax(height,1); v++)
1031
0
      {
1032
0
        for (u=0; u < (ssize_t) MagickMax(width,1); u++)
1033
0
        {
1034
0
          double
1035
0
            intensity;
1036
1037
0
          r=p+(ssize_t) (GetPixelChannels(image)*MagickMax(width,1)*
1038
0
            (size_t) (mid.y-v)+GetPixelChannels(image)*(size_t) (mid.x-u));
1039
0
          intensity=ScaleQuantumToChar((const Quantum) GetPixelIntensity(image,r))-
1040
0
            (double) ScaleQuantumToChar((const Quantum) GetPixelIntensity(image,p));
1041
0
          if ((intensity >= -MaxIntensity) && (intensity <= MaxIntensity))
1042
0
            weights[id][n]=intensity_gaussian[(ssize_t) intensity+MaxIntensity]*
1043
0
              spatial_gaussian[n];
1044
0
          else
1045
0
            weights[id][n]=BlurGaussian(intensity,intensity_sigma)*
1046
0
              BlurGaussian(BlurDistance(x,y,x+u-mid.x,y+v-mid.y),spatial_sigma);
1047
0
          n++;
1048
0
        }
1049
0
      }
1050
0
      for (i=0; i < (ssize_t) GetPixelChannels(blur_image); i++)
1051
0
      {
1052
0
        PixelChannel
1053
0
          channel;
1054
1055
0
        PixelTrait
1056
0
          blur_traits,
1057
0
          traits;
1058
1059
0
        channel=GetPixelChannelChannel(image,i);
1060
0
        traits=GetPixelChannelTraits(image,channel);
1061
0
        blur_traits=GetPixelChannelTraits(blur_image,channel);
1062
0
        if ((traits == UndefinedPixelTrait) ||
1063
0
            (blur_traits == UndefinedPixelTrait))
1064
0
          continue;
1065
0
        if ((blur_traits & CopyPixelTrait) != 0)
1066
0
          {
1067
0
            SetPixelChannel(blur_image,channel,p[i],q);
1068
0
            continue;
1069
0
          }
1070
0
        pixel=0.0;
1071
0
        gamma=0.0;
1072
0
        n=0;
1073
0
        if ((blur_traits & BlendPixelTrait) == 0)
1074
0
          {
1075
            /*
1076
              No alpha blending.
1077
            */
1078
0
            for (v=0; v < (ssize_t) MagickMax(height,1); v++)
1079
0
            {
1080
0
              for (u=0; u < (ssize_t) MagickMax(width,1); u++)
1081
0
              {
1082
0
                r=p+GetPixelChannels(image)*MagickMax(width,1)*(size_t)
1083
0
                  (mid.y-v)+GetPixelChannels(image)*(size_t) (mid.x-u);
1084
0
                pixel+=weights[id][n]*(double) r[i];
1085
0
                gamma+=weights[id][n];
1086
0
                n++;
1087
0
              }
1088
0
            }
1089
0
            SetPixelChannel(blur_image,channel,ClampToQuantum(
1090
0
              MagickSafeReciprocal(gamma)*pixel),q);
1091
0
            continue;
1092
0
          }
1093
        /*
1094
          Alpha blending.
1095
        */
1096
0
        for (v=0; v < (ssize_t) MagickMax(height,1); v++)
1097
0
        {
1098
0
          for (u=0; u < (ssize_t) MagickMax(width,1); u++)
1099
0
          {
1100
0
            double
1101
0
              alpha,
1102
0
              beta;
1103
1104
0
            r=p+GetPixelChannels(image)*MagickMax(width,1)*(size_t) (mid.y-v)+
1105
0
              GetPixelChannels(image)*(size_t) (mid.x-u);
1106
0
            alpha=(double) (QuantumScale*(double) GetPixelAlpha(image,p));
1107
0
            beta=(double) (QuantumScale*(double) GetPixelAlpha(image,r));
1108
0
            pixel+=weights[id][n]*(double) r[i];
1109
0
            gamma+=weights[id][n]*alpha*beta;
1110
0
            n++;
1111
0
          }
1112
0
        }
1113
0
        SetPixelChannel(blur_image,channel,ClampToQuantum(
1114
0
          MagickSafeReciprocal(gamma)*pixel),q);
1115
0
      }
1116
0
      q+=(ptrdiff_t) GetPixelChannels(blur_image);
1117
0
    }
1118
0
    if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
1119
0
      status=MagickFalse;
1120
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
1121
0
      {
1122
0
        MagickBooleanType
1123
0
          proceed;
1124
1125
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1126
        #pragma omp atomic
1127
#endif
1128
0
        progress++;
1129
0
        proceed=SetImageProgress(image,BilateralBlurImageTag,progress,
1130
0
          image->rows);
1131
0
        if (proceed == MagickFalse)
1132
0
          status=MagickFalse;
1133
0
      }
1134
0
  }
1135
0
  blur_image->type=image->type;
1136
0
  blur_view=DestroyCacheView(blur_view);
1137
0
  image_view=DestroyCacheView(image_view);
1138
0
  weights=DestroyBilateralTLS(number_threads,weights);
1139
0
  if (status == MagickFalse)
1140
0
    blur_image=DestroyImage(blur_image);
1141
0
  return(blur_image);
1142
0
}
1143

1144
/*
1145
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1146
%                                                                             %
1147
%                                                                             %
1148
%                                                                             %
1149
%     C o n v o l v e I m a g e                                               %
1150
%                                                                             %
1151
%                                                                             %
1152
%                                                                             %
1153
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1154
%
1155
%  ConvolveImage() applies a custom convolution kernel to the image.
1156
%
1157
%  The format of the ConvolveImage method is:
1158
%
1159
%      Image *ConvolveImage(const Image *image,const KernelInfo *kernel,
1160
%        ExceptionInfo *exception)
1161
%
1162
%  A description of each parameter follows:
1163
%
1164
%    o image: the image.
1165
%
1166
%    o kernel: the filtering kernel.
1167
%
1168
%    o exception: return any errors or warnings in this structure.
1169
%
1170
*/
1171
MagickExport Image *ConvolveImage(const Image *image,
1172
  const KernelInfo *kernel_info,ExceptionInfo *exception)
1173
0
{
1174
0
  Image
1175
0
    *convolve_image;
1176
1177
0
  convolve_image=MorphologyImage(image,ConvolveMorphology,1,kernel_info,
1178
0
    exception);
1179
0
  return(convolve_image);
1180
0
}
1181

1182
/*
1183
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1184
%                                                                             %
1185
%                                                                             %
1186
%                                                                             %
1187
%     D e s p e c k l e I m a g e                                             %
1188
%                                                                             %
1189
%                                                                             %
1190
%                                                                             %
1191
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1192
%
1193
%  DespeckleImage() reduces the speckle noise in an image while preserving the
1194
%  edges of the original image.  A speckle removing filter uses a complementary
1195
%  hulling technique (raising pixels that are darker than their surrounding
1196
%  neighbors, then complementarily lowering pixels that are brighter than their
1197
%  surrounding neighbors) to reduce the speckle index of that image (reference
1198
%  Crimmins speckle removal).
1199
%
1200
%  The format of the DespeckleImage method is:
1201
%
1202
%      Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1203
%
1204
%  A description of each parameter follows:
1205
%
1206
%    o image: the image.
1207
%
1208
%    o exception: return any errors or warnings in this structure.
1209
%
1210
*/
1211
1212
static void Hull(const Image *image,const ssize_t x_offset,
1213
  const ssize_t y_offset,const size_t columns,const size_t rows,
1214
  const int polarity,Quantum *magick_restrict f,Quantum *magick_restrict g)
1215
0
{
1216
0
  Quantum
1217
0
    *p,
1218
0
    *q,
1219
0
    *r,
1220
0
    *s;
1221
1222
0
  ssize_t
1223
0
    y;
1224
1225
0
  assert(image != (const Image *) NULL);
1226
0
  assert(image->signature == MagickCoreSignature);
1227
0
  assert(f != (Quantum *) NULL);
1228
0
  assert(g != (Quantum *) NULL);
1229
0
  assert(columns <= (size_t) (MAGICK_SSIZE_MAX-2));
1230
0
  if (IsEventLogging() != MagickFalse)
1231
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1232
0
  p=f+(ptrdiff_t) (columns+2);
1233
0
  q=g+(ptrdiff_t) (columns+2);
1234
0
  r=p+(ptrdiff_t) (y_offset*((ssize_t) columns+2)+x_offset);
1235
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1236
  #pragma omp parallel for schedule(static) \
1237
    magick_number_threads(image,image,rows,2)
1238
#endif
1239
0
  for (y=0; y < (ssize_t) rows; y++)
1240
0
  {
1241
0
    MagickRealType
1242
0
      v;
1243
1244
0
    ssize_t
1245
0
      i,
1246
0
      x;
1247
1248
0
    i=(2*y+1)+y*(ssize_t) columns;
1249
0
    if (polarity > 0)
1250
0
      for (x=0; x < (ssize_t) columns; x++)
1251
0
      {
1252
0
        v=(MagickRealType) p[i];
1253
0
        if ((MagickRealType) r[i] >= (v+(double) ScaleCharToQuantum(2)))
1254
0
          v+=(double) ScaleCharToQuantum(1);
1255
0
        q[i]=(Quantum) v;
1256
0
        i++;
1257
0
      }
1258
0
    else
1259
0
      for (x=0; x < (ssize_t) columns; x++)
1260
0
      {
1261
0
        v=(MagickRealType) p[i];
1262
0
        if ((MagickRealType) r[i] <= (v-(double) ScaleCharToQuantum(2)))
1263
0
          v-=(double) ScaleCharToQuantum(1);
1264
0
        q[i]=(Quantum) v;
1265
0
        i++;
1266
0
      }
1267
0
  }
1268
0
  p=f+(ptrdiff_t) (columns+2);
1269
0
  q=g+(ptrdiff_t) (columns+2);
1270
0
  r=q+(ptrdiff_t) (y_offset*((ssize_t) columns+2)+x_offset);
1271
0
  s=q-(ptrdiff_t) (y_offset*((ssize_t) columns+2)+x_offset);
1272
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1273
  #pragma omp parallel for schedule(static) \
1274
    magick_number_threads(image,image,rows,2)
1275
#endif
1276
0
  for (y=0; y < (ssize_t) rows; y++)
1277
0
  {
1278
0
    ssize_t
1279
0
      i,
1280
0
      x;
1281
1282
0
    MagickRealType
1283
0
      v;
1284
1285
0
    i=(2*y+1)+y*(ssize_t) columns;
1286
0
    if (polarity > 0)
1287
0
      for (x=0; x < (ssize_t) columns; x++)
1288
0
      {
1289
0
        v=(MagickRealType) q[i];
1290
0
        if (((MagickRealType) s[i] >= (v+(double) ScaleCharToQuantum(2))) &&
1291
0
            ((MagickRealType) r[i] > v))
1292
0
          v+=(double) ScaleCharToQuantum(1);
1293
0
        p[i]=(Quantum) v;
1294
0
        i++;
1295
0
      }
1296
0
    else
1297
0
      for (x=0; x < (ssize_t) columns; x++)
1298
0
      {
1299
0
        v=(MagickRealType) q[i];
1300
0
        if (((MagickRealType) s[i] <= (v-(double) ScaleCharToQuantum(2))) &&
1301
0
            ((MagickRealType) r[i] < v))
1302
0
          v-=(double) ScaleCharToQuantum(1);
1303
0
        p[i]=(Quantum) v;
1304
0
        i++;
1305
0
      }
1306
0
  }
1307
0
}
1308
1309
MagickExport Image *DespeckleImage(const Image *image,ExceptionInfo *exception)
1310
0
{
1311
0
#define DespeckleImageTag  "Despeckle/Image"
1312
1313
0
  CacheView
1314
0
    *despeckle_view,
1315
0
    *image_view;
1316
1317
0
  Image
1318
0
    *despeckle_image;
1319
1320
0
  MagickBooleanType
1321
0
    status;
1322
1323
0
  MemoryInfo
1324
0
    *buffer_info,
1325
0
    *pixel_info;
1326
1327
0
  Quantum
1328
0
    *magick_restrict buffer,
1329
0
    *magick_restrict pixels;
1330
1331
0
  size_t
1332
0
    length;
1333
1334
0
  ssize_t
1335
0
    i;
1336
1337
0
  static const ssize_t
1338
0
    X[4] = {0, 1, 1,-1},
1339
0
    Y[4] = {1, 0, 1, 1};
1340
1341
  /*
1342
    Allocate despeckled image.
1343
  */
1344
0
  assert(image != (const Image *) NULL);
1345
0
  assert(image->signature == MagickCoreSignature);
1346
0
  assert(exception != (ExceptionInfo *) NULL);
1347
0
  assert(exception->signature == MagickCoreSignature);
1348
0
  if (IsEventLogging() != MagickFalse)
1349
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1350
#if defined(MAGICKCORE_OPENCL_SUPPORT)
1351
  despeckle_image=AccelerateDespeckleImage(image,exception);
1352
  if (despeckle_image != (Image *) NULL)
1353
    return(despeckle_image);
1354
#endif
1355
0
  despeckle_image=CloneImage(image,0,0,MagickTrue,exception);
1356
0
  if (despeckle_image == (Image *) NULL)
1357
0
    return((Image *) NULL);
1358
0
  status=SetImageStorageClass(despeckle_image,DirectClass,exception);
1359
0
  if (status == MagickFalse)
1360
0
    {
1361
0
      despeckle_image=DestroyImage(despeckle_image);
1362
0
      return((Image *) NULL);
1363
0
    }
1364
  /*
1365
    Allocate image buffer.
1366
  */
1367
0
  length=(size_t) ((image->columns+2)*(image->rows+2));
1368
0
  pixel_info=AcquireVirtualMemory(length,sizeof(*pixels));
1369
0
  buffer_info=AcquireVirtualMemory(length,sizeof(*buffer));
1370
0
  if ((pixel_info == (MemoryInfo *) NULL) ||
1371
0
      (buffer_info == (MemoryInfo *) NULL))
1372
0
    {
1373
0
      if (buffer_info != (MemoryInfo *) NULL)
1374
0
        buffer_info=RelinquishVirtualMemory(buffer_info);
1375
0
      if (pixel_info != (MemoryInfo *) NULL)
1376
0
        pixel_info=RelinquishVirtualMemory(pixel_info);
1377
0
      despeckle_image=DestroyImage(despeckle_image);
1378
0
      ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1379
0
    }
1380
0
  pixels=(Quantum *) GetVirtualMemoryBlob(pixel_info);
1381
0
  buffer=(Quantum *) GetVirtualMemoryBlob(buffer_info);
1382
  /*
1383
    Reduce speckle in the image.
1384
  */
1385
0
  status=MagickTrue;
1386
0
  image_view=AcquireVirtualCacheView(image,exception);
1387
0
  despeckle_view=AcquireAuthenticCacheView(despeckle_image,exception);
1388
0
  for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1389
0
  {
1390
0
    PixelChannel
1391
0
       channel;
1392
1393
0
    PixelTrait
1394
0
      despeckle_traits,
1395
0
      traits;
1396
1397
0
    ssize_t
1398
0
      k,
1399
0
      x;
1400
1401
0
    ssize_t
1402
0
      j,
1403
0
      y;
1404
1405
0
    if (status == MagickFalse)
1406
0
      continue;
1407
0
    channel=GetPixelChannelChannel(image,i);
1408
0
    traits=GetPixelChannelTraits(image,channel);
1409
0
    despeckle_traits=GetPixelChannelTraits(despeckle_image,channel);
1410
0
    if ((traits == UndefinedPixelTrait) ||
1411
0
        (despeckle_traits == UndefinedPixelTrait))
1412
0
      continue;
1413
0
    if ((despeckle_traits & CopyPixelTrait) != 0)
1414
0
      continue;
1415
0
    (void) memset(pixels,0,length*sizeof(*pixels));
1416
0
    j=(ssize_t) image->columns+2;
1417
0
    for (y=0; y < (ssize_t) image->rows; y++)
1418
0
    {
1419
0
      const Quantum
1420
0
        *magick_restrict p;
1421
1422
0
      p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1423
0
      if (p == (const Quantum *) NULL)
1424
0
        {
1425
0
          status=MagickFalse;
1426
0
          continue;
1427
0
        }
1428
0
      j++;
1429
0
      for (x=0; x < (ssize_t) image->columns; x++)
1430
0
      {
1431
0
        pixels[j++]=p[i];
1432
0
        p+=(ptrdiff_t) GetPixelChannels(image);
1433
0
      }
1434
0
      j++;
1435
0
    }
1436
0
    (void) memset(buffer,0,length*sizeof(*buffer));
1437
0
    for (k=0; k < 4; k++)
1438
0
    {
1439
0
      Hull(image,X[k],Y[k],image->columns,image->rows,1,pixels,buffer);
1440
0
      Hull(image,-X[k],-Y[k],image->columns,image->rows,1,pixels,buffer);
1441
0
      Hull(image,-X[k],-Y[k],image->columns,image->rows,-1,pixels,buffer);
1442
0
      Hull(image,X[k],Y[k],image->columns,image->rows,-1,pixels,buffer);
1443
0
    }
1444
0
    j=(ssize_t) image->columns+2;
1445
0
    for (y=0; y < (ssize_t) image->rows; y++)
1446
0
    {
1447
0
      MagickBooleanType
1448
0
        sync;
1449
1450
0
      Quantum
1451
0
        *magick_restrict q;
1452
1453
0
      q=GetCacheViewAuthenticPixels(despeckle_view,0,y,despeckle_image->columns,
1454
0
        1,exception);
1455
0
      if (q == (Quantum *) NULL)
1456
0
        {
1457
0
          status=MagickFalse;
1458
0
          continue;
1459
0
        }
1460
0
      j++;
1461
0
      for (x=0; x < (ssize_t) image->columns; x++)
1462
0
      {
1463
0
        SetPixelChannel(despeckle_image,channel,pixels[j++],q);
1464
0
        q+=(ptrdiff_t) GetPixelChannels(despeckle_image);
1465
0
      }
1466
0
      sync=SyncCacheViewAuthenticPixels(despeckle_view,exception);
1467
0
      if (sync == MagickFalse)
1468
0
        status=MagickFalse;
1469
0
      j++;
1470
0
    }
1471
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
1472
0
      {
1473
0
        MagickBooleanType
1474
0
          proceed;
1475
1476
0
        proceed=SetImageProgress(image,DespeckleImageTag,(MagickOffsetType) i,
1477
0
          GetPixelChannels(image));
1478
0
        if (proceed == MagickFalse)
1479
0
          status=MagickFalse;
1480
0
      }
1481
0
  }
1482
0
  despeckle_view=DestroyCacheView(despeckle_view);
1483
0
  image_view=DestroyCacheView(image_view);
1484
0
  buffer_info=RelinquishVirtualMemory(buffer_info);
1485
0
  pixel_info=RelinquishVirtualMemory(pixel_info);
1486
0
  despeckle_image->type=image->type;
1487
0
  if (status == MagickFalse)
1488
0
    despeckle_image=DestroyImage(despeckle_image);
1489
0
  return(despeckle_image);
1490
0
}
1491

1492
/*
1493
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1494
%                                                                             %
1495
%                                                                             %
1496
%                                                                             %
1497
%     E d g e I m a g e                                                       %
1498
%                                                                             %
1499
%                                                                             %
1500
%                                                                             %
1501
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1502
%
1503
%  EdgeImage() finds edges in an image.  Radius defines the radius of the
1504
%  convolution filter.  Use a radius of 0 and EdgeImage() selects a suitable
1505
%  radius for you.
1506
%
1507
%  The format of the EdgeImage method is:
1508
%
1509
%      Image *EdgeImage(const Image *image,const double radius,
1510
%        ExceptionInfo *exception)
1511
%
1512
%  A description of each parameter follows:
1513
%
1514
%    o image: the image.
1515
%
1516
%    o radius: the radius of the pixel neighborhood.
1517
%
1518
%    o exception: return any errors or warnings in this structure.
1519
%
1520
*/
1521
MagickExport Image *EdgeImage(const Image *image,const double radius,
1522
  ExceptionInfo *exception)
1523
0
{
1524
0
  Image
1525
0
    *edge_image;
1526
1527
0
  KernelInfo
1528
0
    *kernel_info;
1529
1530
0
  ssize_t
1531
0
    i;
1532
1533
0
  size_t
1534
0
    width;
1535
1536
0
  assert(image != (const Image *) NULL);
1537
0
  assert(image->signature == MagickCoreSignature);
1538
0
  assert(exception != (ExceptionInfo *) NULL);
1539
0
  assert(exception->signature == MagickCoreSignature);
1540
0
  if (IsEventLogging() != MagickFalse)
1541
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1542
0
  width=GetOptimalKernelWidth1D(radius,0.5);
1543
0
  kernel_info=AcquireKernelInfo((const char *) NULL,exception);
1544
0
  if (kernel_info == (KernelInfo *) NULL)
1545
0
    ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1546
0
  (void) memset(kernel_info,0,sizeof(*kernel_info));
1547
0
  kernel_info->width=width;
1548
0
  kernel_info->height=width;
1549
0
  kernel_info->x=(ssize_t) (kernel_info->width-1)/2;
1550
0
  kernel_info->y=(ssize_t) (kernel_info->height-1)/2;
1551
0
  kernel_info->signature=MagickCoreSignature;
1552
0
  kernel_info->values=(MagickRealType *) MagickAssumeAligned(
1553
0
    AcquireAlignedMemory(kernel_info->width,kernel_info->height*
1554
0
    sizeof(*kernel_info->values)));
1555
0
  if (kernel_info->values == (MagickRealType *) NULL)
1556
0
    {
1557
0
      kernel_info=DestroyKernelInfo(kernel_info);
1558
0
      ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1559
0
    }
1560
0
  for (i=0; i < (ssize_t) (kernel_info->width*kernel_info->height); i++)
1561
0
    kernel_info->values[i]=(-1.0);
1562
0
  kernel_info->values[i/2]=(double) kernel_info->width*kernel_info->height-1.0;
1563
0
  edge_image=ConvolveImage(image,kernel_info,exception);
1564
0
  kernel_info=DestroyKernelInfo(kernel_info);
1565
0
  return(edge_image);
1566
0
}
1567

1568
/*
1569
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1570
%                                                                             %
1571
%                                                                             %
1572
%                                                                             %
1573
%     E m b o s s I m a g e                                                   %
1574
%                                                                             %
1575
%                                                                             %
1576
%                                                                             %
1577
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1578
%
1579
%  EmbossImage() returns a grayscale image with a three-dimensional effect.
1580
%  We convolve the image with a Gaussian operator of the given radius and
1581
%  standard deviation (sigma).  For reasonable results, radius should be
1582
%  larger than sigma.  Use a radius of 0 and Emboss() selects a suitable
1583
%  radius for you.
1584
%
1585
%  The format of the EmbossImage method is:
1586
%
1587
%      Image *EmbossImage(const Image *image,const double radius,
1588
%        const double sigma,ExceptionInfo *exception)
1589
%
1590
%  A description of each parameter follows:
1591
%
1592
%    o image: the image.
1593
%
1594
%    o radius: the radius of the pixel neighborhood.
1595
%
1596
%    o sigma: the standard deviation of the Gaussian, in pixels.
1597
%
1598
%    o exception: return any errors or warnings in this structure.
1599
%
1600
*/
1601
MagickExport Image *EmbossImage(const Image *image,const double radius,
1602
  const double sigma,ExceptionInfo *exception)
1603
0
{
1604
0
  double
1605
0
    gamma,
1606
0
    normalize;
1607
1608
0
  Image
1609
0
    *emboss_image;
1610
1611
0
  KernelInfo
1612
0
    *kernel_info;
1613
1614
0
  ssize_t
1615
0
    i;
1616
1617
0
  size_t
1618
0
    width;
1619
1620
0
  ssize_t
1621
0
    j,
1622
0
    k,
1623
0
    u,
1624
0
    v;
1625
1626
0
  assert(image != (const Image *) NULL);
1627
0
  assert(image->signature == MagickCoreSignature);
1628
0
  assert(exception != (ExceptionInfo *) NULL);
1629
0
  assert(exception->signature == MagickCoreSignature);
1630
0
  if (IsEventLogging() != MagickFalse)
1631
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1632
0
  width=GetOptimalKernelWidth1D(radius,sigma);
1633
0
  kernel_info=AcquireKernelInfo((const char *) NULL,exception);
1634
0
  if (kernel_info == (KernelInfo *) NULL)
1635
0
    ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1636
0
  kernel_info->width=width;
1637
0
  kernel_info->height=width;
1638
0
  kernel_info->x=(ssize_t) (width-1)/2;
1639
0
  kernel_info->y=(ssize_t) (width-1)/2;
1640
0
  kernel_info->values=(MagickRealType *) MagickAssumeAligned(
1641
0
    AcquireAlignedMemory(kernel_info->width,kernel_info->width*
1642
0
    sizeof(*kernel_info->values)));
1643
0
  if (kernel_info->values == (MagickRealType *) NULL)
1644
0
    {
1645
0
      kernel_info=DestroyKernelInfo(kernel_info);
1646
0
      ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1647
0
    }
1648
0
  j=(ssize_t) (kernel_info->width-1)/2;
1649
0
  k=j;
1650
0
  i=0;
1651
0
  for (v=(-j); v <= j; v++)
1652
0
  {
1653
0
    for (u=(-j); u <= j; u++)
1654
0
    {
1655
0
      kernel_info->values[i]=(MagickRealType) (((u < 0) || (v < 0) ? -8.0 :
1656
0
        8.0)*exp(-((double) u*u+v*v)/(2.0*MagickSigma*MagickSigma))/
1657
0
        (2.0*MagickPI*MagickSigma*MagickSigma));
1658
0
      if (u != k)
1659
0
        kernel_info->values[i]=0.0;
1660
0
      i++;
1661
0
    }
1662
0
    k--;
1663
0
  }
1664
0
  normalize=0.0;
1665
0
  for (i=0; i < (ssize_t) (kernel_info->width*kernel_info->height); i++)
1666
0
    normalize+=kernel_info->values[i];
1667
0
  gamma=MagickSafeReciprocal(normalize);
1668
0
  for (i=0; i < (ssize_t) (kernel_info->width*kernel_info->height); i++)
1669
0
    kernel_info->values[i]*=gamma;
1670
0
  emboss_image=ConvolveImage(image,kernel_info,exception);
1671
0
  kernel_info=DestroyKernelInfo(kernel_info);
1672
0
  if (emboss_image != (Image *) NULL)
1673
0
    (void) EqualizeImage(emboss_image,exception);
1674
0
  return(emboss_image);
1675
0
}
1676

1677
/*
1678
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1679
%                                                                             %
1680
%                                                                             %
1681
%                                                                             %
1682
%     G a u s s i a n B l u r I m a g e                                       %
1683
%                                                                             %
1684
%                                                                             %
1685
%                                                                             %
1686
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1687
%
1688
%  GaussianBlurImage() blurs an image.  We convolve the image with a
1689
%  Gaussian operator of the given radius and standard deviation (sigma).
1690
%  For reasonable results, the radius should be larger than sigma.  Use a
1691
%  radius of 0 and GaussianBlurImage() selects a suitable radius for you.
1692
%
1693
%  The format of the GaussianBlurImage method is:
1694
%
1695
%      Image *GaussianBlurImage(const Image *image,const double radius,
1696
%        const double sigma,ExceptionInfo *exception)
1697
%
1698
%  A description of each parameter follows:
1699
%
1700
%    o image: the image.
1701
%
1702
%    o radius: the radius of the Gaussian, in pixels, not counting the center
1703
%      pixel.
1704
%
1705
%    o sigma: the standard deviation of the Gaussian, in pixels.
1706
%
1707
%    o exception: return any errors or warnings in this structure.
1708
%
1709
*/
1710
MagickExport Image *GaussianBlurImage(const Image *image,const double radius,
1711
  const double sigma,ExceptionInfo *exception)
1712
0
{
1713
0
  char
1714
0
    geometry[MagickPathExtent];
1715
1716
0
  KernelInfo
1717
0
    *kernel_info;
1718
1719
0
  Image
1720
0
    *blur_image;
1721
1722
0
  assert(image != (const Image *) NULL);
1723
0
  assert(image->signature == MagickCoreSignature);
1724
0
  assert(exception != (ExceptionInfo *) NULL);
1725
0
  assert(exception->signature == MagickCoreSignature);
1726
0
  if (IsEventLogging() != MagickFalse)
1727
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1728
0
  (void) FormatLocaleString(geometry,MagickPathExtent,"gaussian:%.20gx%.20g",
1729
0
    radius,sigma);
1730
0
  kernel_info=AcquireKernelInfo(geometry,exception);
1731
0
  if (kernel_info == (KernelInfo *) NULL)
1732
0
    ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
1733
0
  blur_image=ConvolveImage(image,kernel_info,exception);
1734
0
  kernel_info=DestroyKernelInfo(kernel_info);
1735
0
  return(blur_image);
1736
0
}
1737

1738
/*
1739
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1740
%                                                                             %
1741
%                                                                             %
1742
%                                                                             %
1743
%     K u w a h a r a I m a g e                                               %
1744
%                                                                             %
1745
%                                                                             %
1746
%                                                                             %
1747
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1748
%
1749
%  KuwaharaImage() is an edge preserving noise reduction filter.
1750
%
1751
%  The format of the KuwaharaImage method is:
1752
%
1753
%      Image *KuwaharaImage(const Image *image,const double radius,
1754
%        const double sigma,ExceptionInfo *exception)
1755
%
1756
%  A description of each parameter follows:
1757
%
1758
%    o image: the image.
1759
%
1760
%    o radius: the square window radius.
1761
%
1762
%    o sigma: the standard deviation of the Gaussian, in pixels.
1763
%
1764
%    o exception: return any errors or warnings in this structure.
1765
%
1766
*/
1767
1768
static inline MagickRealType GetMeanLuma(const Image *magick_restrict image,
1769
  const double *magick_restrict pixel)
1770
0
{
1771
0
  return(0.212656*pixel[image->channel_map[RedPixelChannel].offset]+
1772
0
    0.715158*pixel[image->channel_map[GreenPixelChannel].offset]+
1773
0
    0.072186*pixel[image->channel_map[BluePixelChannel].offset]);  /* Rec709 */
1774
0
}
1775
1776
MagickExport Image *KuwaharaImage(const Image *image,const double radius,
1777
  const double sigma,ExceptionInfo *exception)
1778
0
{
1779
0
#define KuwaharaImageTag  "Kuwahara/Image"
1780
1781
0
  CacheView
1782
0
    *image_view,
1783
0
    *kuwahara_view;
1784
1785
0
  Image
1786
0
    *gaussian_image,
1787
0
    *kuwahara_image;
1788
1789
0
  MagickBooleanType
1790
0
    status;
1791
1792
0
  MagickOffsetType
1793
0
    progress;
1794
1795
0
  size_t
1796
0
    width;
1797
1798
0
  ssize_t
1799
0
    y;
1800
1801
  /*
1802
    Initialize Kuwahara image attributes.
1803
  */
1804
0
  assert(image != (Image *) NULL);
1805
0
  assert(image->signature == MagickCoreSignature);
1806
0
  assert(exception != (ExceptionInfo *) NULL);
1807
0
  assert(exception->signature == MagickCoreSignature);
1808
0
  if (IsEventLogging() != MagickFalse)
1809
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1810
0
  width=(size_t) radius+1;
1811
0
  gaussian_image=BlurImage(image,radius,sigma,exception);
1812
0
  if (gaussian_image == (Image *) NULL)
1813
0
    return((Image *) NULL);
1814
0
  kuwahara_image=CloneImage(image,0,0,MagickTrue,exception);
1815
0
  if (kuwahara_image == (Image *) NULL)
1816
0
    {
1817
0
      gaussian_image=DestroyImage(gaussian_image);
1818
0
      return((Image *) NULL);
1819
0
    }
1820
0
  if (SetImageStorageClass(kuwahara_image,DirectClass,exception) == MagickFalse)
1821
0
    {
1822
0
      gaussian_image=DestroyImage(gaussian_image);
1823
0
      kuwahara_image=DestroyImage(kuwahara_image);
1824
0
      return((Image *) NULL);
1825
0
    }
1826
  /*
1827
    Edge preserving noise reduction filter.
1828
  */
1829
0
  status=MagickTrue;
1830
0
  progress=0;
1831
0
  image_view=AcquireVirtualCacheView(gaussian_image,exception);
1832
0
  kuwahara_view=AcquireAuthenticCacheView(kuwahara_image,exception);
1833
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1834
  #pragma omp parallel for schedule(static) shared(progress,status) \
1835
    magick_number_threads(image,kuwahara_image,gaussian_image->rows,1)
1836
#endif
1837
0
  for (y=0; y < (ssize_t) gaussian_image->rows; y++)
1838
0
  {
1839
0
    Quantum
1840
0
      *magick_restrict q;
1841
1842
0
    ssize_t
1843
0
      x;
1844
1845
0
    if (status == MagickFalse)
1846
0
      continue;
1847
0
    q=QueueCacheViewAuthenticPixels(kuwahara_view,0,y,kuwahara_image->columns,1,
1848
0
      exception);
1849
0
    if (q == (Quantum *) NULL)
1850
0
      {
1851
0
        status=MagickFalse;
1852
0
        continue;
1853
0
      }
1854
0
    for (x=0; x < (ssize_t) gaussian_image->columns; x++)
1855
0
    {
1856
0
      const Quantum
1857
0
        *magick_restrict p;
1858
1859
0
      double
1860
0
        min_variance;
1861
1862
0
      RectangleInfo
1863
0
        quadrant,
1864
0
        target;
1865
1866
0
      size_t
1867
0
        i;
1868
1869
0
      min_variance=MagickMaximumValue;
1870
0
      SetGeometry(gaussian_image,&target);
1871
0
      quadrant.width=width;
1872
0
      quadrant.height=width;
1873
0
      for (i=0; i < 4; i++)
1874
0
      {
1875
0
        const Quantum
1876
0
          *magick_restrict k;
1877
1878
0
        double
1879
0
          mean[MaxPixelChannels],
1880
0
          variance;
1881
1882
0
        ssize_t
1883
0
          n;
1884
1885
0
        ssize_t
1886
0
          j;
1887
1888
0
        quadrant.x=x;
1889
0
        quadrant.y=y;
1890
0
        switch (i)
1891
0
        {
1892
0
          case 0:
1893
0
          {
1894
0
            quadrant.x=x-(ssize_t) (width-1);
1895
0
            quadrant.y=y-(ssize_t) (width-1);
1896
0
            break;
1897
0
          }
1898
0
          case 1:
1899
0
          {
1900
0
            quadrant.y=y-(ssize_t) (width-1);
1901
0
            break;
1902
0
          }
1903
0
          case 2:
1904
0
          {
1905
0
            quadrant.x=x-(ssize_t) (width-1);
1906
0
            break;
1907
0
          }
1908
0
          case 3:
1909
0
          default:
1910
0
            break;
1911
0
        }
1912
0
        p=GetCacheViewVirtualPixels(image_view,quadrant.x,quadrant.y,
1913
0
          quadrant.width,quadrant.height,exception);
1914
0
        if (p == (const Quantum *) NULL)
1915
0
          break;
1916
0
        for (j=0; j < (ssize_t) GetPixelChannels(gaussian_image); j++)
1917
0
          mean[j]=0.0;
1918
0
        k=p;
1919
0
        for (n=0; n < (ssize_t) (width*width); n++)
1920
0
        {
1921
0
          for (j=0; j < (ssize_t) GetPixelChannels(gaussian_image); j++)
1922
0
            mean[j]+=(double) k[j];
1923
0
          k+=(ptrdiff_t) GetPixelChannels(gaussian_image);
1924
0
        }
1925
0
        for (j=0; j < (ssize_t) GetPixelChannels(gaussian_image); j++)
1926
0
          mean[j]/=(double) (width*width);
1927
0
        k=p;
1928
0
        variance=0.0;
1929
0
        for (n=0; n < (ssize_t) (width*width); n++)
1930
0
        {
1931
0
          double
1932
0
            luma;
1933
1934
0
          luma=GetPixelLuma(gaussian_image,k);
1935
0
          variance+=(luma-GetMeanLuma(gaussian_image,mean))*
1936
0
            (luma-GetMeanLuma(gaussian_image,mean));
1937
0
          k+=(ptrdiff_t) GetPixelChannels(gaussian_image);
1938
0
        }
1939
0
        if (variance < min_variance)
1940
0
          {
1941
0
            min_variance=variance;
1942
0
            target=quadrant;
1943
0
          }
1944
0
      }
1945
0
      if (i < 4)
1946
0
        {
1947
0
          status=MagickFalse;
1948
0
          break;
1949
0
        }
1950
0
      status=InterpolatePixelChannels(gaussian_image,image_view,kuwahara_image,
1951
0
        UndefinedInterpolatePixel,(double) target.x+target.width/2.0,(double)
1952
0
        target.y+target.height/2.0,q,exception);
1953
0
      if (status == MagickFalse)
1954
0
        break;
1955
0
      q+=(ptrdiff_t) GetPixelChannels(kuwahara_image);
1956
0
    }
1957
0
    if (SyncCacheViewAuthenticPixels(kuwahara_view,exception) == MagickFalse)
1958
0
      status=MagickFalse;
1959
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
1960
0
      {
1961
0
        MagickBooleanType
1962
0
          proceed;
1963
1964
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1965
        #pragma omp atomic
1966
#endif
1967
0
        progress++;
1968
0
        proceed=SetImageProgress(image,KuwaharaImageTag,progress,image->rows);
1969
0
        if (proceed == MagickFalse)
1970
0
          status=MagickFalse;
1971
0
      }
1972
0
  }
1973
0
  kuwahara_view=DestroyCacheView(kuwahara_view);
1974
0
  image_view=DestroyCacheView(image_view);
1975
0
  gaussian_image=DestroyImage(gaussian_image);
1976
0
  if (status == MagickFalse)
1977
0
    kuwahara_image=DestroyImage(kuwahara_image);
1978
0
  return(kuwahara_image);
1979
0
}
1980

1981
/*
1982
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1983
%                                                                             %
1984
%                                                                             %
1985
%                                                                             %
1986
%     L o c a l C o n t r a s t I m a g e                                     %
1987
%                                                                             %
1988
%                                                                             %
1989
%                                                                             %
1990
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1991
%
1992
%  LocalContrastImage() attempts to increase the appearance of large-scale
1993
%  light-dark transitions. Local contrast enhancement works similarly to
1994
%  sharpening with an unsharp mask, however the mask is instead created using
1995
%  an image with a greater blur distance.
1996
%
1997
%  The format of the LocalContrastImage method is:
1998
%
1999
%      Image *LocalContrastImage(const Image *image, const double radius,
2000
%        const double strength,ExceptionInfo *exception)
2001
%
2002
%  A description of each parameter follows:
2003
%
2004
%    o image: the image.
2005
%
2006
%    o radius: the radius of the Gaussian blur, in percentage with 100%
2007
%      resulting in a blur radius of 20% of largest dimension.
2008
%
2009
%    o strength: the strength of the blur mask in percentage.
2010
%
2011
%    o exception: return any errors or warnings in this structure.
2012
%
2013
*/
2014
MagickExport Image *LocalContrastImage(const Image *image,const double radius,
2015
  const double strength,ExceptionInfo *exception)
2016
0
{
2017
0
#define LocalContrastImageTag  "LocalContrast/Image"
2018
2019
0
  CacheView
2020
0
    *image_view,
2021
0
    *contrast_view;
2022
2023
0
  double
2024
0
    totalWeight;
2025
2026
0
  float
2027
0
    *interImage,
2028
0
    *scanline;
2029
2030
0
  Image
2031
0
    *contrast_image;
2032
2033
0
  MagickBooleanType
2034
0
    status;
2035
2036
0
  MemoryInfo
2037
0
    *scanline_info,
2038
0
    *interImage_info;
2039
2040
0
  ssize_t
2041
0
    scanLineSize,
2042
0
    width;
2043
2044
  /*
2045
    Initialize contrast image attributes.
2046
  */
2047
0
  assert(image != (const Image *) NULL);
2048
0
  assert(image->signature == MagickCoreSignature);
2049
0
  assert(exception != (ExceptionInfo *) NULL);
2050
0
  assert(exception->signature == MagickCoreSignature);
2051
0
  if (IsEventLogging() != MagickFalse)
2052
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2053
#if defined(MAGICKCORE_OPENCL_SUPPORT)
2054
  contrast_image=AccelerateLocalContrastImage(image,radius,strength,exception);
2055
  if (contrast_image != (Image *) NULL)
2056
    return(contrast_image);
2057
#endif
2058
0
  contrast_image=CloneImage(image,0,0,MagickTrue,exception);
2059
0
  if (contrast_image == (Image *) NULL)
2060
0
    return((Image *) NULL);
2061
0
  if (SetImageStorageClass(contrast_image,DirectClass,exception) == MagickFalse)
2062
0
    {
2063
0
      contrast_image=DestroyImage(contrast_image);
2064
0
      return((Image *) NULL);
2065
0
    }
2066
0
  image_view=AcquireVirtualCacheView(image,exception);
2067
0
  contrast_view=AcquireAuthenticCacheView(contrast_image,exception);
2068
0
  scanLineSize=(ssize_t) MagickMax(image->columns,image->rows);
2069
0
  width=(ssize_t) (scanLineSize*0.002*fabs(radius));
2070
0
  scanLineSize+=(2*width);
2071
0
  scanline_info=AcquireVirtualMemory(GetOpenMPMaximumThreads()*
2072
0
    (size_t) scanLineSize,sizeof(*scanline));
2073
0
  if (scanline_info == (MemoryInfo *) NULL)
2074
0
    {
2075
0
      contrast_view=DestroyCacheView(contrast_view);
2076
0
      image_view=DestroyCacheView(image_view);
2077
0
      contrast_image=DestroyImage(contrast_image);
2078
0
      ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2079
0
    }
2080
0
  scanline=(float *) GetVirtualMemoryBlob(scanline_info);
2081
  /*
2082
    Create intermediate buffer.
2083
  */
2084
0
  interImage_info=AcquireVirtualMemory(image->rows*(image->columns+(size_t)
2085
0
    (2*width)),sizeof(*interImage));
2086
0
  if (interImage_info == (MemoryInfo *) NULL)
2087
0
    {
2088
0
      scanline_info=RelinquishVirtualMemory(scanline_info);
2089
0
      contrast_view=DestroyCacheView(contrast_view);
2090
0
      image_view=DestroyCacheView(image_view);
2091
0
      contrast_image=DestroyImage(contrast_image);
2092
0
      ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2093
0
    }
2094
0
  interImage=(float *) GetVirtualMemoryBlob(interImage_info);
2095
0
  totalWeight=(float) ((width+1)*(width+1));
2096
  /*
2097
    Vertical pass.
2098
  */
2099
0
  status=MagickTrue;
2100
0
  {
2101
0
    ssize_t
2102
0
      x;
2103
2104
#if defined(MAGICKCORE_OPENMP_SUPPORT)
2105
#pragma omp parallel for schedule(static) \
2106
    magick_number_threads(image,image,image->columns,1)
2107
#endif
2108
0
    for (x=0; x < (ssize_t) image->columns; x++)
2109
0
    {
2110
0
      const int
2111
0
        id = GetOpenMPThreadId();
2112
2113
0
      const Quantum
2114
0
        *magick_restrict p;
2115
2116
0
      float
2117
0
        *out,
2118
0
        *pix,
2119
0
        *pixels;
2120
2121
0
      ssize_t
2122
0
        y;
2123
2124
0
      ssize_t
2125
0
        i;
2126
2127
0
      if (status == MagickFalse)
2128
0
        continue;
2129
0
      pixels=scanline;
2130
0
      pixels+=id*scanLineSize;
2131
0
      pix=pixels;
2132
0
      p=GetCacheViewVirtualPixels(image_view,x,-(ssize_t) width,1,
2133
0
        image->rows+(size_t) (2*width),exception);
2134
0
      if (p == (const Quantum *) NULL)
2135
0
        {
2136
0
          status=MagickFalse;
2137
0
          continue;
2138
0
        }
2139
0
      for (y=0; y < (ssize_t) image->rows+(2*width); y++)
2140
0
      {
2141
0
        *pix++=(float)GetPixelLuma(image,p);
2142
0
        p+=(ptrdiff_t) image->number_channels;
2143
0
      }
2144
0
      out=interImage+x+width;
2145
0
      for (y=0; y < (ssize_t) image->rows; y++)
2146
0
      {
2147
0
        double
2148
0
          sum,
2149
0
          weight;
2150
2151
0
        weight=1.0;
2152
0
        sum=0;
2153
0
        pix=pixels+y;
2154
0
        for (i=0; i < width; i++)
2155
0
        {
2156
0
          sum+=weight*((double) *pix++);
2157
0
          weight+=1.0;
2158
0
        }
2159
0
        for (i=width+1; i < (2*width); i++)
2160
0
        {
2161
0
          sum+=weight*((double) *pix++);
2162
0
          weight-=1.0;
2163
0
        }
2164
        /* write to output */
2165
0
        *out=(float) (sum/totalWeight);
2166
        /* mirror into padding */
2167
0
        if ((x <= width) && (x != 0))
2168
0
          *(out-(x*2))=*out;
2169
0
        if ((x > (ssize_t) image->columns-width-2) &&
2170
0
            (x != (ssize_t) image->columns-1))
2171
0
          *(out+((image->columns-(size_t) x-1)*2))=*out;
2172
0
        out+=image->columns+(size_t) (width*2);
2173
0
      }
2174
0
    }
2175
0
  }
2176
  /*
2177
    Horizontal pass.
2178
  */
2179
0
  {
2180
0
    ssize_t
2181
0
      y;
2182
2183
#if defined(MAGICKCORE_OPENMP_SUPPORT)
2184
#pragma omp parallel for schedule(static) \
2185
    magick_number_threads(image,image,image->rows,1)
2186
#endif
2187
0
    for (y=0; y < (ssize_t) image->rows; y++)
2188
0
    {
2189
0
      const int
2190
0
        id = GetOpenMPThreadId();
2191
2192
0
      const Quantum
2193
0
        *magick_restrict p;
2194
2195
0
      float
2196
0
        *pix,
2197
0
        *pixels;
2198
2199
0
      Quantum
2200
0
        *magick_restrict q;
2201
2202
0
      ssize_t
2203
0
        i,
2204
0
        x;
2205
2206
0
      if (status == MagickFalse)
2207
0
        continue;
2208
0
      pixels=scanline;
2209
0
      pixels+=id*scanLineSize;
2210
0
      p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
2211
0
      q=GetCacheViewAuthenticPixels(contrast_view,0,y,image->columns,1,
2212
0
        exception);
2213
0
      if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
2214
0
        {
2215
0
          status=MagickFalse;
2216
0
          continue;
2217
0
        }
2218
0
      memcpy(pixels,interImage+((size_t) y*(image->columns+(size_t) (2*width))),
2219
0
        (image->columns+(size_t) (2*width))*sizeof(float));
2220
0
      for (x=0; x < (ssize_t) image->columns; x++)
2221
0
      {
2222
0
        double
2223
0
          mult,
2224
0
          srcVal,
2225
0
          sum,
2226
0
          weight;
2227
2228
0
        PixelTrait
2229
0
          traits;
2230
2231
0
        weight=1.0;
2232
0
        sum=0;
2233
0
        pix=pixels+x;
2234
0
        for (i=0; i < width; i++)
2235
0
        {
2236
0
          sum+=weight*((double) *pix++);
2237
0
          weight+=1.0;
2238
0
        }
2239
0
        for (i=width+1; i < (2*width); i++)
2240
0
        {
2241
0
          sum+=weight*((double) *pix++);
2242
0
          weight-=1.0;
2243
0
        }
2244
        /*
2245
          Apply and write.
2246
        */
2247
0
        srcVal=(float) GetPixelLuma(image,p);
2248
0
        mult=(srcVal-(sum/totalWeight))*(strength/100.0);
2249
0
        mult=(srcVal+mult)/srcVal;
2250
0
        traits=GetPixelChannelTraits(image,RedPixelChannel);
2251
0
        if ((traits & UpdatePixelTrait) != 0)
2252
0
          SetPixelRed(contrast_image,ClampToQuantum((MagickRealType)
2253
0
            GetPixelRed(image,p)*mult),q);
2254
0
        traits=GetPixelChannelTraits(image,GreenPixelChannel);
2255
0
        if ((traits & UpdatePixelTrait) != 0)
2256
0
          SetPixelGreen(contrast_image,ClampToQuantum((MagickRealType)
2257
0
            GetPixelGreen(image,p)*mult),q);
2258
0
        traits=GetPixelChannelTraits(image,BluePixelChannel);
2259
0
        if ((traits & UpdatePixelTrait) != 0)
2260
0
          SetPixelBlue(contrast_image,ClampToQuantum((MagickRealType)
2261
0
            GetPixelBlue(image,p)*mult),q);
2262
0
        p+=(ptrdiff_t) image->number_channels;
2263
0
        q+=(ptrdiff_t) contrast_image->number_channels;
2264
0
      }
2265
0
      if (SyncCacheViewAuthenticPixels(contrast_view,exception) == MagickFalse)
2266
0
        status=MagickFalse;
2267
0
    }
2268
0
  }
2269
0
  scanline_info=RelinquishVirtualMemory(scanline_info);
2270
0
  interImage_info=RelinquishVirtualMemory(interImage_info);
2271
0
  contrast_view=DestroyCacheView(contrast_view);
2272
0
  image_view=DestroyCacheView(image_view);
2273
0
  if (status == MagickFalse)
2274
0
    contrast_image=DestroyImage(contrast_image);
2275
0
  return(contrast_image);
2276
0
}
2277

2278
/*
2279
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2280
%                                                                             %
2281
%                                                                             %
2282
%                                                                             %
2283
%     M o t i o n B l u r I m a g e                                           %
2284
%                                                                             %
2285
%                                                                             %
2286
%                                                                             %
2287
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2288
%
2289
%  MotionBlurImage() simulates motion blur.  We convolve the image with a
2290
%  Gaussian operator of the given radius and standard deviation (sigma).
2291
%  For reasonable results, radius should be larger than sigma.  Use a
2292
%  radius of 0 and MotionBlurImage() selects a suitable radius for you.
2293
%  Angle gives the angle of the blurring motion.
2294
%
2295
%  Andrew Protano contributed this effect.
2296
%
2297
%  The format of the MotionBlurImage method is:
2298
%
2299
%    Image *MotionBlurImage(const Image *image,const double radius,
2300
%      const double sigma,const double angle,ExceptionInfo *exception)
2301
%
2302
%  A description of each parameter follows:
2303
%
2304
%    o image: the image.
2305
%
2306
%    o radius: the radius of the Gaussian, in pixels, not counting
2307
%      the center pixel.
2308
%
2309
%    o sigma: the standard deviation of the Gaussian, in pixels.
2310
%
2311
%    o angle: Apply the effect along this angle.
2312
%
2313
%    o exception: return any errors or warnings in this structure.
2314
%
2315
*/
2316
2317
static MagickRealType *GetMotionBlurKernel(const size_t width,
2318
  const double sigma)
2319
0
{
2320
0
  MagickRealType
2321
0
    *kernel,
2322
0
    normalize;
2323
2324
0
  ssize_t
2325
0
    i;
2326
2327
  /*
2328
   Generate a 1-D convolution kernel.
2329
  */
2330
0
  if (IsEventLogging() != MagickFalse)
2331
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"...");
2332
0
  kernel=(MagickRealType *) MagickAssumeAligned(AcquireAlignedMemory((size_t)
2333
0
    width,sizeof(*kernel)));
2334
0
  if (kernel == (MagickRealType *) NULL)
2335
0
    return(kernel);
2336
0
  normalize=0.0;
2337
0
  for (i=0; i < (ssize_t) width; i++)
2338
0
  {
2339
0
    kernel[i]=(MagickRealType) (exp((-((double) i*i)/(double) (2.0*MagickSigma*
2340
0
      MagickSigma)))/(MagickSQ2PI*MagickSigma));
2341
0
    normalize+=kernel[i];
2342
0
  }
2343
0
  for (i=0; i < (ssize_t) width; i++)
2344
0
    kernel[i]/=normalize;
2345
0
  return(kernel);
2346
0
}
2347
2348
MagickExport Image *MotionBlurImage(const Image *image,const double radius,
2349
  const double sigma,const double angle,ExceptionInfo *exception)
2350
0
{
2351
0
#define BlurImageTag  "Blur/Image"
2352
2353
0
  CacheView
2354
0
    *blur_view,
2355
0
    *image_view,
2356
0
    *motion_view;
2357
2358
0
  Image
2359
0
    *blur_image;
2360
2361
0
  MagickBooleanType
2362
0
    status;
2363
2364
0
  MagickOffsetType
2365
0
    progress;
2366
2367
0
  MagickRealType
2368
0
    *kernel;
2369
2370
0
  OffsetInfo
2371
0
    *offset;
2372
2373
0
  PointInfo
2374
0
    point;
2375
2376
0
  size_t
2377
0
    width;
2378
2379
0
  ssize_t
2380
0
    w,
2381
0
    y;
2382
2383
0
  assert(image != (Image *) NULL);
2384
0
  assert(image->signature == MagickCoreSignature);
2385
0
  assert(exception != (ExceptionInfo *) NULL);
2386
0
  assert(exception->signature == MagickCoreSignature);
2387
0
  if (IsEventLogging() != MagickFalse)
2388
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2389
0
  width=GetOptimalKernelWidth1D(radius,sigma);
2390
0
  kernel=GetMotionBlurKernel(width,sigma);
2391
0
  if (kernel == (MagickRealType *) NULL)
2392
0
    ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2393
0
  offset=(OffsetInfo *) AcquireQuantumMemory(width,sizeof(*offset));
2394
0
  if (offset == (OffsetInfo *) NULL)
2395
0
    {
2396
0
      kernel=(MagickRealType *) RelinquishAlignedMemory(kernel);
2397
0
      ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
2398
0
    }
2399
0
  point.x=(double) width*sin(DegreesToRadians(angle));
2400
0
  point.y=(double) width*cos(DegreesToRadians(angle));
2401
0
  for (w=0; w < (ssize_t) width; w++)
2402
0
  {
2403
0
    offset[w].x=CastDoubleToSsizeT(ceil((double) (w*point.y)/
2404
0
      hypot(point.x,point.y)-0.5));
2405
0
    offset[w].y=CastDoubleToSsizeT(ceil((double) (w*point.x)/
2406
0
      hypot(point.x,point.y)-0.5));
2407
0
  }
2408
  /*
2409
    Motion blur image.
2410
  */
2411
#if defined(MAGICKCORE_OPENCL_SUPPORT)
2412
  blur_image=AccelerateMotionBlurImage(image,kernel,width,offset,exception);
2413
  if (blur_image != (Image *) NULL)
2414
    {
2415
      kernel=(MagickRealType *) RelinquishAlignedMemory(kernel);
2416
      offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2417
      return(blur_image);
2418
    }
2419
#endif
2420
0
  blur_image=CloneImage(image,0,0,MagickTrue,exception);
2421
0
  if (blur_image == (Image *) NULL)
2422
0
    {
2423
0
      kernel=(MagickRealType *) RelinquishAlignedMemory(kernel);
2424
0
      offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2425
0
      return((Image *) NULL);
2426
0
    }
2427
0
  if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
2428
0
    {
2429
0
      kernel=(MagickRealType *) RelinquishAlignedMemory(kernel);
2430
0
      offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2431
0
      blur_image=DestroyImage(blur_image);
2432
0
      return((Image *) NULL);
2433
0
    }
2434
0
  status=MagickTrue;
2435
0
  progress=0;
2436
0
  image_view=AcquireVirtualCacheView(image,exception);
2437
0
  motion_view=AcquireVirtualCacheView(image,exception);
2438
0
  blur_view=AcquireAuthenticCacheView(blur_image,exception);
2439
#if defined(MAGICKCORE_OPENMP_SUPPORT)
2440
  #pragma omp parallel for schedule(static) shared(progress,status) \
2441
    magick_number_threads(image,blur_image,image->rows,1)
2442
#endif
2443
0
  for (y=0; y < (ssize_t) image->rows; y++)
2444
0
  {
2445
0
    const Quantum
2446
0
      *magick_restrict p;
2447
2448
0
    Quantum
2449
0
      *magick_restrict q;
2450
2451
0
    ssize_t
2452
0
      x;
2453
2454
0
    if (status == MagickFalse)
2455
0
      continue;
2456
0
    p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
2457
0
    q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
2458
0
      exception);
2459
0
    if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
2460
0
      {
2461
0
        status=MagickFalse;
2462
0
        continue;
2463
0
      }
2464
0
    for (x=0; x < (ssize_t) image->columns; x++)
2465
0
    {
2466
0
      ssize_t
2467
0
        i;
2468
2469
0
      for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2470
0
      {
2471
0
        double
2472
0
          alpha = 0.0,
2473
0
          gamma = 0.0,
2474
0
          pixel;
2475
2476
0
        PixelChannel
2477
0
          channel;
2478
2479
0
        PixelTrait
2480
0
          blur_traits,
2481
0
          traits;
2482
2483
0
        const Quantum
2484
0
          *magick_restrict r;
2485
2486
0
        MagickRealType
2487
0
          *magick_restrict k;
2488
2489
0
        ssize_t
2490
0
          j;
2491
2492
0
        channel=GetPixelChannelChannel(image,i);
2493
0
        traits=GetPixelChannelTraits(image,channel);
2494
0
        blur_traits=GetPixelChannelTraits(blur_image,channel);
2495
0
        if ((traits == UndefinedPixelTrait) ||
2496
0
            (blur_traits == UndefinedPixelTrait))
2497
0
          continue;
2498
0
        if ((blur_traits & CopyPixelTrait) != 0)
2499
0
          {
2500
0
            SetPixelChannel(blur_image,channel,p[i],q);
2501
0
            continue;
2502
0
          }
2503
0
        k=kernel;
2504
0
        pixel=0.0;
2505
0
        if ((blur_traits & BlendPixelTrait) == 0)
2506
0
          {
2507
0
            for (j=0; j < (ssize_t) width; j++)
2508
0
            {
2509
0
              r=GetCacheViewVirtualPixels(motion_view,x+offset[j].x,y+
2510
0
                offset[j].y,1,1,exception);
2511
0
              if (r == (const Quantum *) NULL)
2512
0
                {
2513
0
                  status=MagickFalse;
2514
0
                  continue;
2515
0
                }
2516
0
              pixel+=(*k)*(double) r[i];
2517
0
              k++;
2518
0
            }
2519
0
            SetPixelChannel(blur_image,channel,ClampToQuantum(pixel),q);
2520
0
            continue;
2521
0
          }
2522
0
        for (j=0; j < (ssize_t) width; j++)
2523
0
        {
2524
0
          r=GetCacheViewVirtualPixels(motion_view,x+offset[j].x,y+offset[j].y,1,
2525
0
            1,exception);
2526
0
          if (r == (const Quantum *) NULL)
2527
0
            {
2528
0
              status=MagickFalse;
2529
0
              continue;
2530
0
            }
2531
0
          alpha=QuantumScale*(double) GetPixelAlpha(image,r);
2532
0
          pixel+=(*k)*alpha*(double) r[i];
2533
0
          gamma+=(*k)*alpha;
2534
0
          k++;
2535
0
        }
2536
0
        gamma=MagickSafeReciprocal(gamma);
2537
0
        SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
2538
0
      }
2539
0
      p+=(ptrdiff_t) GetPixelChannels(image);
2540
0
      q+=(ptrdiff_t) GetPixelChannels(blur_image);
2541
0
    }
2542
0
    if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
2543
0
      status=MagickFalse;
2544
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
2545
0
      {
2546
0
        MagickBooleanType
2547
0
          proceed;
2548
2549
#if defined(MAGICKCORE_OPENMP_SUPPORT)
2550
        #pragma omp atomic
2551
#endif
2552
0
        progress++;
2553
0
        proceed=SetImageProgress(image,BlurImageTag,progress,image->rows);
2554
0
        if (proceed == MagickFalse)
2555
0
          status=MagickFalse;
2556
0
      }
2557
0
  }
2558
0
  blur_view=DestroyCacheView(blur_view);
2559
0
  motion_view=DestroyCacheView(motion_view);
2560
0
  image_view=DestroyCacheView(image_view);
2561
0
  kernel=(MagickRealType *) RelinquishAlignedMemory(kernel);
2562
0
  offset=(OffsetInfo *) RelinquishMagickMemory(offset);
2563
0
  if (status == MagickFalse)
2564
0
    blur_image=DestroyImage(blur_image);
2565
0
  return(blur_image);
2566
0
}
2567

2568
/*
2569
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2570
%                                                                             %
2571
%                                                                             %
2572
%                                                                             %
2573
%     P r e v i e w I m a g e                                                 %
2574
%                                                                             %
2575
%                                                                             %
2576
%                                                                             %
2577
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2578
%
2579
%  PreviewImage() tiles 9 thumbnails of the specified image with an image
2580
%  processing operation applied with varying parameters.  This may be helpful
2581
%  pin-pointing an appropriate parameter for a particular image processing
2582
%  operation.
2583
%
2584
%  The format of the PreviewImages method is:
2585
%
2586
%      Image *PreviewImages(const Image *image,const PreviewType preview,
2587
%        ExceptionInfo *exception)
2588
%
2589
%  A description of each parameter follows:
2590
%
2591
%    o image: the image.
2592
%
2593
%    o preview: the image processing operation.
2594
%
2595
%    o exception: return any errors or warnings in this structure.
2596
%
2597
*/
2598
MagickExport Image *PreviewImage(const Image *image,const PreviewType preview,
2599
  ExceptionInfo *exception)
2600
0
{
2601
0
#define NumberTiles  9
2602
0
#define PreviewImageTag  "Preview/Image"
2603
0
#define DefaultPreviewGeometry  "204x204+10+10"
2604
2605
0
  char
2606
0
    factor[MagickPathExtent],
2607
0
    label[MagickPathExtent];
2608
2609
0
  double
2610
0
    degrees,
2611
0
    gamma,
2612
0
    percentage,
2613
0
    radius,
2614
0
    sigma,
2615
0
    threshold;
2616
2617
0
  Image
2618
0
    *images,
2619
0
    *montage_image,
2620
0
    *preview_image,
2621
0
    *thumbnail;
2622
2623
0
  ImageInfo
2624
0
    *preview_info;
2625
2626
0
  MagickBooleanType
2627
0
    proceed;
2628
2629
0
  MontageInfo
2630
0
    *montage_info;
2631
2632
0
  QuantizeInfo
2633
0
    quantize_info;
2634
2635
0
  RectangleInfo
2636
0
    geometry;
2637
2638
0
  size_t
2639
0
    colors;
2640
2641
0
  ssize_t
2642
0
    i,
2643
0
    x = 0,
2644
0
    y = 0;
2645
2646
  /*
2647
    Open output image file.
2648
  */
2649
0
  assert(image != (Image *) NULL);
2650
0
  assert(image->signature == MagickCoreSignature);
2651
0
  if (IsEventLogging() != MagickFalse)
2652
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2653
0
  colors=2;
2654
0
  degrees=0.0;
2655
0
  gamma=(-0.2f);
2656
0
  preview_info=AcquireImageInfo();
2657
0
  SetGeometry(image,&geometry);
2658
0
  (void) ParseMetaGeometry(DefaultPreviewGeometry,&geometry.x,&geometry.y,
2659
0
    &geometry.width,&geometry.height);
2660
0
  images=NewImageList();
2661
0
  percentage=12.5;
2662
0
  GetQuantizeInfo(&quantize_info);
2663
0
  radius=0.0;
2664
0
  sigma=1.0;
2665
0
  threshold=0.0;
2666
0
  for (i=0; i < NumberTiles; i++)
2667
0
  {
2668
0
    thumbnail=ThumbnailImage(image,geometry.width,geometry.height,exception);
2669
0
    if (thumbnail == (Image *) NULL)
2670
0
      break;
2671
0
    (void) SetImageProgressMonitor(thumbnail,(MagickProgressMonitor) NULL,
2672
0
      (void *) NULL);
2673
0
    (void) SetImageProperty(thumbnail,"label",DefaultTileLabel,exception);
2674
0
    if (i == (NumberTiles/2))
2675
0
      {
2676
0
        (void) QueryColorCompliance("#dfdfdf",AllCompliance,
2677
0
          &thumbnail->matte_color,exception);
2678
0
        AppendImageToList(&images,thumbnail);
2679
0
        continue;
2680
0
      }
2681
0
    switch (preview)
2682
0
    {
2683
0
      case RotatePreview:
2684
0
      {
2685
0
        degrees+=45.0;
2686
0
        preview_image=RotateImage(thumbnail,degrees,exception);
2687
0
        (void) FormatLocaleString(label,MagickPathExtent,"rotate %g",degrees);
2688
0
        break;
2689
0
      }
2690
0
      case ShearPreview:
2691
0
      {
2692
0
        degrees+=5.0;
2693
0
        preview_image=ShearImage(thumbnail,degrees,degrees,exception);
2694
0
        (void) FormatLocaleString(label,MagickPathExtent,"shear %gx%g",degrees,
2695
0
          2.0*degrees);
2696
0
        break;
2697
0
      }
2698
0
      case RollPreview:
2699
0
      {
2700
0
        x=((i+1)*(ssize_t) thumbnail->columns)/NumberTiles;
2701
0
        y=((i+1)*(ssize_t) thumbnail->rows)/NumberTiles;
2702
0
        preview_image=RollImage(thumbnail,x,y,exception);
2703
0
        (void) FormatLocaleString(label,MagickPathExtent,"roll %+.20gx%+.20g",
2704
0
          (double) x,(double) y);
2705
0
        break;
2706
0
      }
2707
0
      case HuePreview:
2708
0
      {
2709
0
        preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2710
0
        if (preview_image == (Image *) NULL)
2711
0
          break;
2712
0
        (void) FormatLocaleString(factor,MagickPathExtent,"100,100,%g",2.0*
2713
0
          percentage);
2714
0
        (void) ModulateImage(preview_image,factor,exception);
2715
0
        (void) FormatLocaleString(label,MagickPathExtent,"modulate %s",factor);
2716
0
        break;
2717
0
      }
2718
0
      case SaturationPreview:
2719
0
      {
2720
0
        preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2721
0
        if (preview_image == (Image *) NULL)
2722
0
          break;
2723
0
        (void) FormatLocaleString(factor,MagickPathExtent,"100,%g",2.0*
2724
0
          percentage);
2725
0
        (void) ModulateImage(preview_image,factor,exception);
2726
0
        (void) FormatLocaleString(label,MagickPathExtent,"modulate %s",factor);
2727
0
        break;
2728
0
      }
2729
0
      case BrightnessPreview:
2730
0
      {
2731
0
        preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2732
0
        if (preview_image == (Image *) NULL)
2733
0
          break;
2734
0
        (void) FormatLocaleString(factor,MagickPathExtent,"%g",2.0*percentage);
2735
0
        (void) ModulateImage(preview_image,factor,exception);
2736
0
        (void) FormatLocaleString(label,MagickPathExtent,"modulate %s",factor);
2737
0
        break;
2738
0
      }
2739
0
      case GammaPreview:
2740
0
      default:
2741
0
      {
2742
0
        preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2743
0
        if (preview_image == (Image *) NULL)
2744
0
          break;
2745
0
        gamma+=0.4;
2746
0
        (void) GammaImage(preview_image,gamma,exception);
2747
0
        (void) FormatLocaleString(label,MagickPathExtent,"gamma %g",gamma);
2748
0
        break;
2749
0
      }
2750
0
      case SpiffPreview:
2751
0
      {
2752
0
        preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2753
0
        if (preview_image != (Image *) NULL)
2754
0
          for (x=0; x < i; x++)
2755
0
            (void) ContrastImage(preview_image,MagickTrue,exception);
2756
0
        (void) FormatLocaleString(label,MagickPathExtent,"contrast (%.20g)",
2757
0
          (double) i+1);
2758
0
        break;
2759
0
      }
2760
0
      case DullPreview:
2761
0
      {
2762
0
        preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2763
0
        if (preview_image == (Image *) NULL)
2764
0
          break;
2765
0
        for (x=0; x < i; x++)
2766
0
          (void) ContrastImage(preview_image,MagickFalse,exception);
2767
0
        (void) FormatLocaleString(label,MagickPathExtent,"+contrast (%.20g)",
2768
0
          (double) i+1);
2769
0
        break;
2770
0
      }
2771
0
      case GrayscalePreview:
2772
0
      {
2773
0
        preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2774
0
        if (preview_image == (Image *) NULL)
2775
0
          break;
2776
0
        colors<<=1;
2777
0
        quantize_info.number_colors=colors;
2778
0
        quantize_info.colorspace=GRAYColorspace;
2779
0
        (void) QuantizeImage(&quantize_info,preview_image,exception);
2780
0
        (void) FormatLocaleString(label,MagickPathExtent,
2781
0
          "-colorspace gray -colors %.20g",(double) colors);
2782
0
        break;
2783
0
      }
2784
0
      case QuantizePreview:
2785
0
      {
2786
0
        preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2787
0
        if (preview_image == (Image *) NULL)
2788
0
          break;
2789
0
        colors<<=1;
2790
0
        quantize_info.number_colors=colors;
2791
0
        (void) QuantizeImage(&quantize_info,preview_image,exception);
2792
0
        (void) FormatLocaleString(label,MagickPathExtent,"colors %.20g",
2793
0
          (double) colors);
2794
0
        break;
2795
0
      }
2796
0
      case DespecklePreview:
2797
0
      {
2798
0
        for (x=0; x < (i-1); x++)
2799
0
        {
2800
0
          preview_image=DespeckleImage(thumbnail,exception);
2801
0
          if (preview_image == (Image *) NULL)
2802
0
            break;
2803
0
          thumbnail=DestroyImage(thumbnail);
2804
0
          thumbnail=preview_image;
2805
0
        }
2806
0
        preview_image=DespeckleImage(thumbnail,exception);
2807
0
        if (preview_image == (Image *) NULL)
2808
0
          break;
2809
0
        (void) FormatLocaleString(label,MagickPathExtent,"despeckle (%.20g)",
2810
0
          (double) i+1);
2811
0
        break;
2812
0
      }
2813
0
      case ReduceNoisePreview:
2814
0
      {
2815
0
        preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t)
2816
0
          radius,(size_t) radius,exception);
2817
0
        (void) FormatLocaleString(label,MagickPathExtent,"noise %g",radius);
2818
0
        break;
2819
0
      }
2820
0
      case AddNoisePreview:
2821
0
      {
2822
0
        switch ((int) i)
2823
0
        {
2824
0
          case 0:
2825
0
          {
2826
0
            (void) CopyMagickString(factor,"uniform",MagickPathExtent);
2827
0
            break;
2828
0
          }
2829
0
          case 1:
2830
0
          {
2831
0
            (void) CopyMagickString(factor,"gaussian",MagickPathExtent);
2832
0
            break;
2833
0
          }
2834
0
          case 2:
2835
0
          {
2836
0
            (void) CopyMagickString(factor,"multiplicative",MagickPathExtent);
2837
0
            break;
2838
0
          }
2839
0
          case 3:
2840
0
          {
2841
0
            (void) CopyMagickString(factor,"impulse",MagickPathExtent);
2842
0
            break;
2843
0
          }
2844
0
          case 5:
2845
0
          {
2846
0
            (void) CopyMagickString(factor,"laplacian",MagickPathExtent);
2847
0
            break;
2848
0
          }
2849
0
          case 6:
2850
0
          {
2851
0
            (void) CopyMagickString(factor,"Poisson",MagickPathExtent);
2852
0
            break;
2853
0
          }
2854
0
          default:
2855
0
          {
2856
0
            (void) CopyMagickString(thumbnail->magick,"NULL",MagickPathExtent);
2857
0
            break;
2858
0
          }
2859
0
        }
2860
0
        preview_image=StatisticImage(thumbnail,NonpeakStatistic,(size_t) i,
2861
0
          (size_t) i,exception);
2862
0
        (void) FormatLocaleString(label,MagickPathExtent,"+noise %s",factor);
2863
0
        break;
2864
0
      }
2865
0
      case SharpenPreview:
2866
0
      {
2867
0
        preview_image=SharpenImage(thumbnail,radius,sigma,exception);
2868
0
        (void) FormatLocaleString(label,MagickPathExtent,"sharpen %gx%g",
2869
0
          radius,sigma);
2870
0
        break;
2871
0
      }
2872
0
      case BlurPreview:
2873
0
      {
2874
0
        preview_image=BlurImage(thumbnail,radius,sigma,exception);
2875
0
        (void) FormatLocaleString(label,MagickPathExtent,"blur %gx%g",radius,
2876
0
          sigma);
2877
0
        break;
2878
0
      }
2879
0
      case ThresholdPreview:
2880
0
      {
2881
0
        preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2882
0
        if (preview_image == (Image *) NULL)
2883
0
          break;
2884
0
        (void) BilevelImage(thumbnail,(double) (percentage*((double)
2885
0
          QuantumRange+1.0))/100.0,exception);
2886
0
        (void) FormatLocaleString(label,MagickPathExtent,"threshold %g",
2887
0
          (double) (percentage*((double) QuantumRange+1.0))/100.0);
2888
0
        break;
2889
0
      }
2890
0
      case EdgeDetectPreview:
2891
0
      {
2892
0
        preview_image=EdgeImage(thumbnail,radius,exception);
2893
0
        (void) FormatLocaleString(label,MagickPathExtent,"edge %g",radius);
2894
0
        break;
2895
0
      }
2896
0
      case SpreadPreview:
2897
0
      {
2898
0
        preview_image=SpreadImage(thumbnail,image->interpolate,radius,
2899
0
          exception);
2900
0
        (void) FormatLocaleString(label,MagickPathExtent,"spread %g",
2901
0
          radius+0.5);
2902
0
        break;
2903
0
      }
2904
0
      case SolarizePreview:
2905
0
      {
2906
0
        preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2907
0
        if (preview_image == (Image *) NULL)
2908
0
          break;
2909
0
        (void) SolarizeImage(preview_image,(double) QuantumRange*percentage/
2910
0
          100.0,exception);
2911
0
        (void) FormatLocaleString(label,MagickPathExtent,"solarize %g",
2912
0
          ((double) QuantumRange*percentage)/100.0);
2913
0
        break;
2914
0
      }
2915
0
      case ShadePreview:
2916
0
      {
2917
0
        degrees+=10.0;
2918
0
        preview_image=ShadeImage(thumbnail,MagickTrue,degrees,degrees,
2919
0
          exception);
2920
0
        (void) FormatLocaleString(label,MagickPathExtent,"shade %gx%g",degrees,
2921
0
          degrees);
2922
0
        break;
2923
0
      }
2924
0
      case RaisePreview:
2925
0
      {
2926
0
        RectangleInfo
2927
0
          raise;
2928
2929
0
        preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2930
0
        if (preview_image == (Image *) NULL)
2931
0
          break;
2932
0
        raise.width=(size_t) (2*i+2);
2933
0
        raise.height=(size_t) (2*i+2);
2934
0
        raise.x=(i-1)/2;
2935
0
        raise.y=(i-1)/2;
2936
0
        (void) RaiseImage(preview_image,&raise,MagickTrue,exception);
2937
0
        (void) FormatLocaleString(label,MagickPathExtent,
2938
0
          "raise %.20gx%.20g%+.20g%+.20g",(double) raise.width,(double)
2939
0
          raise.height,(double) raise.x,(double) raise.y);
2940
0
        break;
2941
0
      }
2942
0
      case SegmentPreview:
2943
0
      {
2944
0
        preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
2945
0
        if (preview_image == (Image *) NULL)
2946
0
          break;
2947
0
        threshold+=0.4;
2948
0
        (void) SegmentImage(preview_image,sRGBColorspace,MagickFalse,threshold,
2949
0
          threshold,exception);
2950
0
        (void) FormatLocaleString(label,MagickPathExtent,"segment %gx%g",
2951
0
          threshold,threshold);
2952
0
        break;
2953
0
      }
2954
0
      case SwirlPreview:
2955
0
      {
2956
0
        preview_image=SwirlImage(thumbnail,degrees,image->interpolate,
2957
0
          exception);
2958
0
        (void) FormatLocaleString(label,MagickPathExtent,"swirl %g",degrees);
2959
0
        degrees+=45.0;
2960
0
        break;
2961
0
      }
2962
0
      case ImplodePreview:
2963
0
      {
2964
0
        degrees+=0.1;
2965
0
        preview_image=ImplodeImage(thumbnail,degrees,image->interpolate,
2966
0
          exception);
2967
0
        (void) FormatLocaleString(label,MagickPathExtent,"implode %g",degrees);
2968
0
        break;
2969
0
      }
2970
0
      case WavePreview:
2971
0
      {
2972
0
        degrees+=5.0;
2973
0
        preview_image=WaveImage(thumbnail,0.5*degrees,2.0*degrees,
2974
0
          image->interpolate,exception);
2975
0
        (void) FormatLocaleString(label,MagickPathExtent,"wave %gx%g",0.5*
2976
0
          degrees,2.0*degrees);
2977
0
        break;
2978
0
      }
2979
0
      case OilPaintPreview:
2980
0
      {
2981
0
        preview_image=OilPaintImage(thumbnail,(double) radius,(double) sigma,
2982
0
          exception);
2983
0
        (void) FormatLocaleString(label,MagickPathExtent,"charcoal %gx%g",
2984
0
          radius,sigma);
2985
0
        break;
2986
0
      }
2987
0
      case CharcoalDrawingPreview:
2988
0
      {
2989
0
        preview_image=CharcoalImage(thumbnail,(double) radius,(double) sigma,
2990
0
          exception);
2991
0
        (void) FormatLocaleString(label,MagickPathExtent,"charcoal %gx%g",
2992
0
          radius,sigma);
2993
0
        break;
2994
0
      }
2995
0
      case JPEGPreview:
2996
0
      {
2997
0
        char
2998
0
          filename[MagickPathExtent];
2999
3000
0
        int
3001
0
          file;
3002
3003
0
        MagickBooleanType
3004
0
          status;
3005
3006
0
        preview_image=CloneImage(thumbnail,0,0,MagickTrue,exception);
3007
0
        if (preview_image == (Image *) NULL)
3008
0
          break;
3009
0
        preview_info->quality=(size_t) percentage;
3010
0
        (void) FormatLocaleString(factor,MagickPathExtent,"%.20g",(double)
3011
0
          preview_info->quality);
3012
0
        file=AcquireUniqueFileResource(filename);
3013
0
        if (file != -1)
3014
0
          file=close_utf8(file)-1;
3015
0
        (void) FormatLocaleString(preview_image->filename,MagickPathExtent,
3016
0
          "jpeg:%s",filename);
3017
0
        status=WriteImage(preview_info,preview_image,exception);
3018
0
        if (status != MagickFalse)
3019
0
          {
3020
0
            Image
3021
0
              *quality_image;
3022
3023
0
            (void) CopyMagickString(preview_info->filename,
3024
0
              preview_image->filename,MagickPathExtent);
3025
0
            quality_image=ReadImage(preview_info,exception);
3026
0
            if (quality_image != (Image *) NULL)
3027
0
              {
3028
0
                preview_image=DestroyImage(preview_image);
3029
0
                preview_image=quality_image;
3030
0
              }
3031
0
          }
3032
0
        (void) RelinquishUniqueFileResource(preview_image->filename);
3033
0
        if ((GetBlobSize(preview_image)/1024) >= 1024)
3034
0
          (void) FormatLocaleString(label,MagickPathExtent,"quality %s\n%gmb ",
3035
0
            factor,(double) ((MagickOffsetType) GetBlobSize(preview_image))/
3036
0
            1024.0/1024.0);
3037
0
        else
3038
0
          if (GetBlobSize(preview_image) >= 1024)
3039
0
            (void) FormatLocaleString(label,MagickPathExtent,
3040
0
              "quality %s\n%gkb ",factor,(double) ((MagickOffsetType)
3041
0
              GetBlobSize(preview_image))/1024.0);
3042
0
          else
3043
0
            (void) FormatLocaleString(label,MagickPathExtent,
3044
0
              "quality %s\n%.20gb ",factor,(double) ((MagickOffsetType)
3045
0
              GetBlobSize(thumbnail)));
3046
0
        break;
3047
0
      }
3048
0
    }
3049
0
    thumbnail=DestroyImage(thumbnail);
3050
0
    percentage+=12.5;
3051
0
    radius+=0.5;
3052
0
    sigma+=0.25;
3053
0
    if (preview_image == (Image *) NULL)
3054
0
      break;
3055
0
    preview_image->alpha_trait=UndefinedPixelTrait;
3056
0
    (void) DeleteImageProperty(preview_image,"label");
3057
0
    (void) SetImageProperty(preview_image,"label",label,exception);
3058
0
    AppendImageToList(&images,preview_image);
3059
0
    proceed=SetImageProgress(image,PreviewImageTag,(MagickOffsetType) i,
3060
0
      NumberTiles);
3061
0
    if (proceed == MagickFalse)
3062
0
      break;
3063
0
  }
3064
0
  if (images == (Image *) NULL)
3065
0
    {
3066
0
      preview_info=DestroyImageInfo(preview_info);
3067
0
      return((Image *) NULL);
3068
0
    }
3069
  /*
3070
    Create the montage.
3071
  */
3072
0
  montage_info=CloneMontageInfo(preview_info,(MontageInfo *) NULL);
3073
0
  (void) CopyMagickString(montage_info->filename,image->filename,
3074
0
    MagickPathExtent);
3075
0
  montage_info->shadow=MagickTrue;
3076
0
  (void) CloneString(&montage_info->tile,"3x3");
3077
0
  (void) CloneString(&montage_info->geometry,DefaultPreviewGeometry);
3078
0
  (void) CloneString(&montage_info->frame,DefaultTileFrame);
3079
0
  montage_image=MontageImages(images,montage_info,exception);
3080
0
  montage_info=DestroyMontageInfo(montage_info);
3081
0
  images=DestroyImageList(images);
3082
0
  if (montage_image == (Image *) NULL)
3083
0
    ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3084
0
  if (montage_image->montage != (char *) NULL)
3085
0
    {
3086
      /*
3087
        Free image directory.
3088
      */
3089
0
      montage_image->montage=(char *) RelinquishMagickMemory(
3090
0
        montage_image->montage);
3091
0
      if (image->directory != (char *) NULL)
3092
0
        montage_image->directory=(char *) RelinquishMagickMemory(
3093
0
          montage_image->directory);
3094
0
    }
3095
0
  preview_info=DestroyImageInfo(preview_info);
3096
0
  return(montage_image);
3097
0
}
3098

3099
/*
3100
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3101
%                                                                             %
3102
%                                                                             %
3103
%                                                                             %
3104
%     R o t a t i o n a l B l u r I m a g e                                   %
3105
%                                                                             %
3106
%                                                                             %
3107
%                                                                             %
3108
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3109
%
3110
%  RotationalBlurImage() applies a radial blur to the image.
3111
%
3112
%  Andrew Protano contributed this effect.
3113
%
3114
%  The format of the RotationalBlurImage method is:
3115
%
3116
%    Image *RotationalBlurImage(const Image *image,const double angle,
3117
%      ExceptionInfo *exception)
3118
%
3119
%  A description of each parameter follows:
3120
%
3121
%    o image: the image.
3122
%
3123
%    o angle: the angle of the radial blur.
3124
%
3125
%    o blur: the blur.
3126
%
3127
%    o exception: return any errors or warnings in this structure.
3128
%
3129
*/
3130
MagickExport Image *RotationalBlurImage(const Image *image,const double angle,
3131
  ExceptionInfo *exception)
3132
0
{
3133
0
  CacheView
3134
0
    *blur_view,
3135
0
    *image_view,
3136
0
    *radial_view;
3137
3138
0
  double
3139
0
    blur_radius,
3140
0
    *cos_theta,
3141
0
    offset,
3142
0
    *sin_theta,
3143
0
    theta;
3144
3145
0
  Image
3146
0
    *blur_image;
3147
3148
0
  MagickBooleanType
3149
0
    status;
3150
3151
0
  MagickOffsetType
3152
0
    progress;
3153
3154
0
  PointInfo
3155
0
    blur_center;
3156
3157
0
  size_t
3158
0
    n;
3159
3160
0
  ssize_t
3161
0
    w,
3162
0
    y;
3163
3164
  /*
3165
    Allocate blur image.
3166
  */
3167
0
  assert(image != (Image *) NULL);
3168
0
  assert(image->signature == MagickCoreSignature);
3169
0
  assert(exception != (ExceptionInfo *) NULL);
3170
0
  assert(exception->signature == MagickCoreSignature);
3171
0
  if (IsEventLogging() != MagickFalse)
3172
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3173
#if defined(MAGICKCORE_OPENCL_SUPPORT)
3174
  blur_image=AccelerateRotationalBlurImage(image,angle,exception);
3175
  if (blur_image != (Image *) NULL)
3176
    return(blur_image);
3177
#endif
3178
0
  blur_image=CloneImage(image,0,0,MagickTrue,exception);
3179
0
  if (blur_image == (Image *) NULL)
3180
0
    return((Image *) NULL);
3181
0
  if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
3182
0
    {
3183
0
      blur_image=DestroyImage(blur_image);
3184
0
      return((Image *) NULL);
3185
0
    }
3186
0
  blur_center.x=(double) (image->columns-1)/2.0;
3187
0
  blur_center.y=(double) (image->rows-1)/2.0;
3188
0
  blur_radius=hypot(blur_center.x,blur_center.y);
3189
0
  n=(size_t) fabs(4.0*DegreesToRadians(angle)*sqrt((double) blur_radius)+2UL);
3190
0
  theta=DegreesToRadians(angle)/(double) (n-1);
3191
0
  cos_theta=(double *) AcquireQuantumMemory((size_t) n,sizeof(*cos_theta));
3192
0
  sin_theta=(double *) AcquireQuantumMemory((size_t) n,sizeof(*sin_theta));
3193
0
  if ((cos_theta == (double *) NULL) || (sin_theta == (double *) NULL))
3194
0
    {
3195
0
      if (cos_theta != (double *) NULL)
3196
0
        cos_theta=(double *) RelinquishMagickMemory(cos_theta);
3197
0
      if (sin_theta != (double *) NULL)
3198
0
        sin_theta=(double *) RelinquishMagickMemory(sin_theta);
3199
0
      blur_image=DestroyImage(blur_image);
3200
0
      ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3201
0
    }
3202
0
  offset=theta*(double) (n-1)/2.0;
3203
0
  for (w=0; w < (ssize_t) n; w++)
3204
0
  {
3205
0
    cos_theta[w]=cos((double) (theta*w-offset));
3206
0
    sin_theta[w]=sin((double) (theta*w-offset));
3207
0
  }
3208
  /*
3209
    Radial blur image.
3210
  */
3211
0
  status=MagickTrue;
3212
0
  progress=0;
3213
0
  image_view=AcquireVirtualCacheView(image,exception);
3214
0
  radial_view=AcquireVirtualCacheView(image,exception);
3215
0
  blur_view=AcquireAuthenticCacheView(blur_image,exception);
3216
#if defined(MAGICKCORE_OPENMP_SUPPORT)
3217
  #pragma omp parallel for schedule(static) shared(progress,status) \
3218
    magick_number_threads(image,blur_image,image->rows,1)
3219
#endif
3220
0
  for (y=0; y < (ssize_t) image->rows; y++)
3221
0
  {
3222
0
    const Quantum
3223
0
      *magick_restrict p;
3224
3225
0
    Quantum
3226
0
      *magick_restrict q;
3227
3228
0
    ssize_t
3229
0
      x;
3230
3231
0
    if (status == MagickFalse)
3232
0
      continue;
3233
0
    p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
3234
0
    q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3235
0
      exception);
3236
0
    if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
3237
0
      {
3238
0
        status=MagickFalse;
3239
0
        continue;
3240
0
      }
3241
0
    for (x=0; x < (ssize_t) image->columns; x++)
3242
0
    {
3243
0
      double
3244
0
        radius;
3245
3246
0
      PointInfo
3247
0
        center;
3248
3249
0
      ssize_t
3250
0
        i;
3251
3252
0
      size_t
3253
0
        step;
3254
3255
0
      center.x=(double) x-blur_center.x;
3256
0
      center.y=(double) y-blur_center.y;
3257
0
      radius=hypot((double) center.x,center.y);
3258
0
      if (radius == 0)
3259
0
        step=1;
3260
0
      else
3261
0
        {
3262
0
          step=(size_t) (blur_radius/radius);
3263
0
          if (step == 0)
3264
0
            step=1;
3265
0
          else
3266
0
            if (step >= n)
3267
0
              step=n-1;
3268
0
        }
3269
0
      for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3270
0
      {
3271
0
        double
3272
0
          gamma,
3273
0
          pixel;
3274
3275
0
        PixelChannel
3276
0
          channel;
3277
3278
0
        PixelTrait
3279
0
          blur_traits,
3280
0
          traits;
3281
3282
0
        const Quantum
3283
0
          *magick_restrict r;
3284
3285
0
        ssize_t
3286
0
          j;
3287
3288
0
        channel=GetPixelChannelChannel(image,i);
3289
0
        traits=GetPixelChannelTraits(image,channel);
3290
0
        blur_traits=GetPixelChannelTraits(blur_image,channel);
3291
0
        if ((traits == UndefinedPixelTrait) ||
3292
0
            (blur_traits == UndefinedPixelTrait))
3293
0
          continue;
3294
0
        if ((blur_traits & CopyPixelTrait) != 0)
3295
0
          {
3296
0
            SetPixelChannel(blur_image,channel,p[i],q);
3297
0
            continue;
3298
0
          }
3299
0
        gamma=0.0;
3300
0
        pixel=0.0;
3301
0
        if ((GetPixelChannelTraits(image,AlphaPixelChannel) == UndefinedPixelTrait) ||
3302
0
            (channel == AlphaPixelChannel))
3303
0
          {
3304
0
            for (j=0; j < (ssize_t) n; j+=(ssize_t) step)
3305
0
            {
3306
0
              r=GetCacheViewVirtualPixels(radial_view, (ssize_t) (blur_center.x+
3307
0
                center.x*cos_theta[j]-center.y*sin_theta[j]+0.5),(ssize_t)
3308
0
                (blur_center.y+center.x*sin_theta[j]+center.y*cos_theta[j]+0.5),
3309
0
                1,1,exception);
3310
0
              if (r == (const Quantum *) NULL)
3311
0
                {
3312
0
                  status=MagickFalse;
3313
0
                  continue;
3314
0
                }
3315
0
              pixel+=(double) r[i];
3316
0
              gamma++;
3317
0
            }
3318
0
            gamma=MagickSafeReciprocal(gamma);
3319
0
            SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
3320
0
            continue;
3321
0
          }
3322
0
        for (j=0; j < (ssize_t) n; j+=(ssize_t) step)
3323
0
        {
3324
0
          double
3325
0
            alpha;
3326
3327
0
          r=GetCacheViewVirtualPixels(radial_view, (ssize_t) (blur_center.x+
3328
0
            center.x*cos_theta[j]-center.y*sin_theta[j]+0.5),(ssize_t)
3329
0
            (blur_center.y+center.x*sin_theta[j]+center.y*cos_theta[j]+0.5),
3330
0
            1,1,exception);
3331
0
          if (r == (const Quantum *) NULL)
3332
0
            {
3333
0
              status=MagickFalse;
3334
0
              continue;
3335
0
            }
3336
0
          alpha=QuantumScale*(double) GetPixelAlpha(image,r);
3337
0
          pixel+=alpha*(double) r[i];
3338
0
          gamma+=alpha;
3339
0
        }
3340
0
        gamma=MagickSafeReciprocal(gamma);
3341
0
        SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
3342
0
      }
3343
0
      p+=(ptrdiff_t) GetPixelChannels(image);
3344
0
      q+=(ptrdiff_t) GetPixelChannels(blur_image);
3345
0
    }
3346
0
    if (SyncCacheViewAuthenticPixels(blur_view,exception) == MagickFalse)
3347
0
      status=MagickFalse;
3348
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
3349
0
      {
3350
0
        MagickBooleanType
3351
0
          proceed;
3352
3353
#if defined(MAGICKCORE_OPENMP_SUPPORT)
3354
        #pragma omp atomic
3355
#endif
3356
0
        progress++;
3357
0
        proceed=SetImageProgress(image,BlurImageTag,progress,image->rows);
3358
0
        if (proceed == MagickFalse)
3359
0
          status=MagickFalse;
3360
0
      }
3361
0
  }
3362
0
  blur_view=DestroyCacheView(blur_view);
3363
0
  radial_view=DestroyCacheView(radial_view);
3364
0
  image_view=DestroyCacheView(image_view);
3365
0
  cos_theta=(double *) RelinquishMagickMemory(cos_theta);
3366
0
  sin_theta=(double *) RelinquishMagickMemory(sin_theta);
3367
0
  if (status == MagickFalse)
3368
0
    blur_image=DestroyImage(blur_image);
3369
0
  return(blur_image);
3370
0
}
3371

3372
/*
3373
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3374
%                                                                             %
3375
%                                                                             %
3376
%                                                                             %
3377
%     S e l e c t i v e B l u r I m a g e                                     %
3378
%                                                                             %
3379
%                                                                             %
3380
%                                                                             %
3381
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3382
%
3383
%  SelectiveBlurImage() selectively blur pixels within a contrast threshold.
3384
%  It is similar to the unsharpen mask that sharpens everything with contrast
3385
%  above a certain threshold.
3386
%
3387
%  The format of the SelectiveBlurImage method is:
3388
%
3389
%      Image *SelectiveBlurImage(const Image *image,const double radius,
3390
%        const double sigma,const double threshold,ExceptionInfo *exception)
3391
%
3392
%  A description of each parameter follows:
3393
%
3394
%    o image: the image.
3395
%
3396
%    o radius: the radius of the Gaussian, in pixels, not counting the center
3397
%      pixel.
3398
%
3399
%    o sigma: the standard deviation of the Gaussian, in pixels.
3400
%
3401
%    o threshold: only pixels within this contrast threshold are included
3402
%      in the blur operation.
3403
%
3404
%    o exception: return any errors or warnings in this structure.
3405
%
3406
*/
3407
MagickExport Image *SelectiveBlurImage(const Image *image,const double radius,
3408
  const double sigma,const double threshold,ExceptionInfo *exception)
3409
0
{
3410
0
#define SelectiveBlurImageTag  "SelectiveBlur/Image"
3411
3412
0
  CacheView
3413
0
    *blur_view,
3414
0
    *image_view,
3415
0
    *luminance_view;
3416
3417
0
  Image
3418
0
    *blur_image,
3419
0
    *luminance_image;
3420
3421
0
  MagickBooleanType
3422
0
    status;
3423
3424
0
  MagickOffsetType
3425
0
    progress;
3426
3427
0
  MagickRealType
3428
0
    *kernel;
3429
3430
0
  size_t
3431
0
    width;
3432
3433
0
  ssize_t
3434
0
    center,
3435
0
    y;
3436
3437
  /*
3438
    Initialize blur image attributes.
3439
  */
3440
0
  assert(image != (Image *) NULL);
3441
0
  assert(image->signature == MagickCoreSignature);
3442
0
  assert(exception != (ExceptionInfo *) NULL);
3443
0
  assert(exception->signature == MagickCoreSignature);
3444
0
  if (IsEventLogging() != MagickFalse)
3445
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3446
0
  width=GetOptimalKernelWidth1D(radius,sigma);
3447
0
  kernel=(MagickRealType *) MagickAssumeAligned(AcquireAlignedMemory((size_t)
3448
0
    width,width*sizeof(*kernel)));
3449
0
  if (kernel == (MagickRealType *) NULL)
3450
0
    ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
3451
0
  {
3452
0
    ssize_t
3453
0
      i,
3454
0
      j,
3455
0
      v;
3456
3457
0
    j=(ssize_t) (width-1)/2;
3458
0
    i=0;
3459
0
    for (v=(-j); v <= j; v++)
3460
0
    {
3461
0
      ssize_t
3462
0
        u;
3463
3464
0
      for (u=(-j); u <= j; u++)
3465
0
        kernel[i++]=(MagickRealType) (exp(-((double) u*u+v*v)/(2.0*MagickSigma*
3466
0
          MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
3467
0
    }
3468
0
  }
3469
0
  if (image->debug != MagickFalse)
3470
0
    {
3471
0
      char
3472
0
        format[MagickPathExtent],
3473
0
        *message;
3474
3475
0
      const MagickRealType
3476
0
        *k;
3477
3478
0
      ssize_t
3479
0
        u,
3480
0
        v;
3481
3482
0
      (void) LogMagickEvent(TransformEvent,GetMagickModule(),
3483
0
        "  SelectiveBlurImage with %.20gx%.20g kernel:",(double) width,(double)
3484
0
        width);
3485
0
      message=AcquireString("");
3486
0
      k=kernel;
3487
0
      for (v=0; v < (ssize_t) width; v++)
3488
0
      {
3489
0
        *message='\0';
3490
0
        (void) FormatLocaleString(format,MagickPathExtent,"%.20g: ",(double) v);
3491
0
        (void) ConcatenateString(&message,format);
3492
0
        for (u=0; u < (ssize_t) width; u++)
3493
0
        {
3494
0
          (void) FormatLocaleString(format,MagickPathExtent,"%+f ",(double)
3495
0
            *k++);
3496
0
          (void) ConcatenateString(&message,format);
3497
0
        }
3498
0
        (void) LogMagickEvent(TransformEvent,GetMagickModule(),"%s",message);
3499
0
      }
3500
0
      message=DestroyString(message);
3501
0
    }
3502
0
  blur_image=CloneImage(image,0,0,MagickTrue,exception);
3503
0
  if (blur_image == (Image *) NULL)
3504
0
    return((Image *) NULL);
3505
0
  if (SetImageStorageClass(blur_image,DirectClass,exception) == MagickFalse)
3506
0
    {
3507
0
      blur_image=DestroyImage(blur_image);
3508
0
      kernel=(MagickRealType *) RelinquishAlignedMemory(kernel);
3509
0
      return((Image *) NULL);
3510
0
    }
3511
0
  luminance_image=CloneImage(image,0,0,MagickTrue,exception);
3512
0
  if (luminance_image == (Image *) NULL)
3513
0
    {
3514
0
      blur_image=DestroyImage(blur_image);
3515
0
      kernel=(MagickRealType *) RelinquishAlignedMemory(kernel);
3516
0
      return((Image *) NULL);
3517
0
    }
3518
0
  status=TransformImageColorspace(luminance_image,GRAYColorspace,exception);
3519
0
  if (status == MagickFalse)
3520
0
    {
3521
0
      luminance_image=DestroyImage(luminance_image);
3522
0
      blur_image=DestroyImage(blur_image);
3523
0
      kernel=(MagickRealType *) RelinquishAlignedMemory(kernel);
3524
0
      return((Image *) NULL);
3525
0
    }
3526
  /*
3527
    Threshold blur image.
3528
  */
3529
0
  status=MagickTrue;
3530
0
  progress=0;
3531
0
  center=(ssize_t) (GetPixelChannels(image)*(image->columns+width)*
3532
0
    ((width-1)/2L)+GetPixelChannels(image)*((width-1)/2L));
3533
0
  image_view=AcquireVirtualCacheView(image,exception);
3534
0
  luminance_view=AcquireVirtualCacheView(luminance_image,exception);
3535
0
  blur_view=AcquireAuthenticCacheView(blur_image,exception);
3536
#if defined(MAGICKCORE_OPENMP_SUPPORT)
3537
  #pragma omp parallel for schedule(static) shared(progress,status) \
3538
    magick_number_threads(image,blur_image,image->rows,1)
3539
#endif
3540
0
  for (y=0; y < (ssize_t) image->rows; y++)
3541
0
  {
3542
0
    double
3543
0
      contrast;
3544
3545
0
    MagickBooleanType
3546
0
      sync;
3547
3548
0
    const Quantum
3549
0
      *magick_restrict l,
3550
0
      *magick_restrict p;
3551
3552
0
    Quantum
3553
0
      *magick_restrict q;
3554
3555
0
    ssize_t
3556
0
      x;
3557
3558
0
    if (status == MagickFalse)
3559
0
      continue;
3560
0
    p=GetCacheViewVirtualPixels(image_view,-((ssize_t) (width-1)/2L),y-(ssize_t)
3561
0
      ((width-1)/2L),image->columns+width,width,exception);
3562
0
    l=GetCacheViewVirtualPixels(luminance_view,-((ssize_t) (width-1)/2L),y-
3563
0
      (ssize_t) ((width-1)/2L),luminance_image->columns+width,width,exception);
3564
0
    q=QueueCacheViewAuthenticPixels(blur_view,0,y,blur_image->columns,1,
3565
0
      exception);
3566
0
    if ((p == (const Quantum *) NULL) || (l == (const Quantum *) NULL) ||
3567
0
        (q == (Quantum *) NULL))
3568
0
      {
3569
0
        status=MagickFalse;
3570
0
        continue;
3571
0
      }
3572
0
    for (x=0; x < (ssize_t) image->columns; x++)
3573
0
    {
3574
0
      double
3575
0
        intensity;
3576
3577
0
      ssize_t
3578
0
        i;
3579
3580
0
      intensity=GetPixelIntensity(image,p+center);
3581
0
      for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3582
0
      {
3583
0
        double
3584
0
          alpha,
3585
0
          gamma,
3586
0
          pixel;
3587
3588
0
        PixelChannel
3589
0
          channel;
3590
3591
0
        PixelTrait
3592
0
          blur_traits,
3593
0
          traits;
3594
3595
0
        const MagickRealType
3596
0
          *magick_restrict k;
3597
3598
0
        const Quantum
3599
0
          *magick_restrict luminance_pixels,
3600
0
          *magick_restrict pixels;
3601
3602
0
        ssize_t
3603
0
          u;
3604
3605
0
        ssize_t
3606
0
          v;
3607
3608
0
        channel=GetPixelChannelChannel(image,i);
3609
0
        traits=GetPixelChannelTraits(image,channel);
3610
0
        blur_traits=GetPixelChannelTraits(blur_image,channel);
3611
0
        if ((traits == UndefinedPixelTrait) ||
3612
0
            (blur_traits == UndefinedPixelTrait))
3613
0
          continue;
3614
0
        if ((blur_traits & CopyPixelTrait) != 0)
3615
0
          {
3616
0
            SetPixelChannel(blur_image,channel,p[center+i],q);
3617
0
            continue;
3618
0
          }
3619
0
        k=kernel;
3620
0
        pixel=0.0;
3621
0
        pixels=p;
3622
0
        luminance_pixels=l;
3623
0
        gamma=0.0;
3624
0
        if ((blur_traits & BlendPixelTrait) == 0)
3625
0
          {
3626
0
            for (v=0; v < (ssize_t) width; v++)
3627
0
            {
3628
0
              for (u=0; u < (ssize_t) width; u++)
3629
0
              {
3630
0
                contrast=GetPixelIntensity(luminance_image,luminance_pixels)-
3631
0
                  intensity;
3632
0
                if (fabs(contrast) < threshold)
3633
0
                  {
3634
0
                    pixel+=(*k)*(double) pixels[i];
3635
0
                    gamma+=(*k);
3636
0
                  }
3637
0
                k++;
3638
0
                pixels+=(ptrdiff_t) GetPixelChannels(image);
3639
0
                luminance_pixels+=(ptrdiff_t) GetPixelChannels(luminance_image);
3640
0
              }
3641
0
              pixels+=(ptrdiff_t) GetPixelChannels(image)*image->columns;
3642
0
              luminance_pixels+=(ptrdiff_t) GetPixelChannels(luminance_image)*
3643
0
                luminance_image->columns;
3644
0
            }
3645
0
            if (fabs((double) gamma) < MagickEpsilon)
3646
0
              {
3647
0
                SetPixelChannel(blur_image,channel,p[center+i],q);
3648
0
                continue;
3649
0
              }
3650
0
            gamma=MagickSafeReciprocal(gamma);
3651
0
            SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
3652
0
            continue;
3653
0
          }
3654
0
        for (v=0; v < (ssize_t) width; v++)
3655
0
        {
3656
0
          for (u=0; u < (ssize_t) width; u++)
3657
0
          {
3658
0
            contrast=GetPixelIntensity(image,pixels)-intensity;
3659
0
            if (fabs(contrast) < threshold)
3660
0
              {
3661
0
                alpha=QuantumScale*(double) GetPixelAlpha(image,pixels);
3662
0
                pixel+=(*k)*alpha*(double) pixels[i];
3663
0
                gamma+=(*k)*alpha;
3664
0
              }
3665
0
            k++;
3666
0
            pixels+=(ptrdiff_t) GetPixelChannels(image);
3667
0
            luminance_pixels+=(ptrdiff_t) GetPixelChannels(luminance_image);
3668
0
          }
3669
0
          pixels+=(ptrdiff_t) GetPixelChannels(image)*image->columns;
3670
0
          luminance_pixels+=(ptrdiff_t) GetPixelChannels(luminance_image)*
3671
0
            luminance_image->columns;
3672
0
        }
3673
0
        if (fabs((double) gamma) < MagickEpsilon)
3674
0
          {
3675
0
            SetPixelChannel(blur_image,channel,p[center+i],q);
3676
0
            continue;
3677
0
          }
3678
0
        gamma=MagickSafeReciprocal(gamma);
3679
0
        SetPixelChannel(blur_image,channel,ClampToQuantum(gamma*pixel),q);
3680
0
      }
3681
0
      p+=(ptrdiff_t) GetPixelChannels(image);
3682
0
      l+=(ptrdiff_t) GetPixelChannels(luminance_image);
3683
0
      q+=(ptrdiff_t) GetPixelChannels(blur_image);
3684
0
    }
3685
0
    sync=SyncCacheViewAuthenticPixels(blur_view,exception);
3686
0
    if (sync == MagickFalse)
3687
0
      status=MagickFalse;
3688
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
3689
0
      {
3690
0
        MagickBooleanType
3691
0
          proceed;
3692
3693
#if defined(MAGICKCORE_OPENMP_SUPPORT)
3694
        #pragma omp atomic
3695
#endif
3696
0
        progress++;
3697
0
        proceed=SetImageProgress(image,SelectiveBlurImageTag,progress,
3698
0
          image->rows);
3699
0
        if (proceed == MagickFalse)
3700
0
          status=MagickFalse;
3701
0
      }
3702
0
  }
3703
0
  blur_image->type=image->type;
3704
0
  blur_view=DestroyCacheView(blur_view);
3705
0
  luminance_view=DestroyCacheView(luminance_view);
3706
0
  image_view=DestroyCacheView(image_view);
3707
0
  luminance_image=DestroyImage(luminance_image);
3708
0
  kernel=(MagickRealType *) RelinquishAlignedMemory(kernel);
3709
0
  if (status == MagickFalse)
3710
0
    blur_image=DestroyImage(blur_image);
3711
0
  return(blur_image);
3712
0
}
3713

3714
/*
3715
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3716
%                                                                             %
3717
%                                                                             %
3718
%                                                                             %
3719
%     S h a d e I m a g e                                                     %
3720
%                                                                             %
3721
%                                                                             %
3722
%                                                                             %
3723
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3724
%
3725
%  ShadeImage() shines a distant light on an image to create a
3726
%  three-dimensional effect. You control the positioning of the light with
3727
%  azimuth and elevation; azimuth is measured in degrees off the x axis
3728
%  and elevation is measured in pixels above the Z axis.
3729
%
3730
%  The format of the ShadeImage method is:
3731
%
3732
%      Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3733
%        const double azimuth,const double elevation,ExceptionInfo *exception)
3734
%
3735
%  A description of each parameter follows:
3736
%
3737
%    o image: the image.
3738
%
3739
%    o gray: A value other than zero shades the intensity of each pixel.
3740
%
3741
%    o azimuth, elevation:  Define the light source direction.
3742
%
3743
%    o exception: return any errors or warnings in this structure.
3744
%
3745
*/
3746
MagickExport Image *ShadeImage(const Image *image,const MagickBooleanType gray,
3747
  const double azimuth,const double elevation,ExceptionInfo *exception)
3748
0
{
3749
0
#define GetShadeIntensity(image,pixel) \
3750
0
  ClampPixel(GetPixelIntensity((image),(pixel)))
3751
0
#define ShadeImageTag  "Shade/Image"
3752
3753
0
  CacheView
3754
0
    *image_view,
3755
0
    *shade_view;
3756
3757
0
  Image
3758
0
    *linear_image,
3759
0
    *shade_image;
3760
3761
0
  MagickBooleanType
3762
0
    status;
3763
3764
0
  MagickOffsetType
3765
0
    progress;
3766
3767
0
  PrimaryInfo
3768
0
    light;
3769
3770
0
  ssize_t
3771
0
    y;
3772
3773
  /*
3774
    Initialize shaded image attributes.
3775
  */
3776
0
  assert(image != (const Image *) NULL);
3777
0
  assert(image->signature == MagickCoreSignature);
3778
0
  assert(exception != (ExceptionInfo *) NULL);
3779
0
  assert(exception->signature == MagickCoreSignature);
3780
0
  if (IsEventLogging() != MagickFalse)
3781
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3782
0
  linear_image=CloneImage(image,0,0,MagickTrue,exception);
3783
0
  shade_image=CloneImage(image,0,0,MagickTrue,exception);
3784
0
  if ((linear_image == (Image *) NULL) || (shade_image == (Image *) NULL))
3785
0
    {
3786
0
      if (linear_image != (Image *) NULL)
3787
0
        linear_image=DestroyImage(linear_image);
3788
0
      if (shade_image != (Image *) NULL)
3789
0
        shade_image=DestroyImage(shade_image);
3790
0
      return((Image *) NULL);
3791
0
    }
3792
0
  if (SetImageStorageClass(shade_image,DirectClass,exception) == MagickFalse)
3793
0
    {
3794
0
      linear_image=DestroyImage(linear_image);
3795
0
      shade_image=DestroyImage(shade_image);
3796
0
      return((Image *) NULL);
3797
0
    }
3798
  /*
3799
    Compute the light vector.
3800
  */
3801
0
  light.x=(double) QuantumRange*cos(DegreesToRadians(azimuth))*
3802
0
    cos(DegreesToRadians(elevation));
3803
0
  light.y=(double) QuantumRange*sin(DegreesToRadians(azimuth))*
3804
0
    cos(DegreesToRadians(elevation));
3805
0
  light.z=(double) QuantumRange*sin(DegreesToRadians(elevation));
3806
  /*
3807
    Shade image.
3808
  */
3809
0
  status=MagickTrue;
3810
0
  progress=0;
3811
0
  image_view=AcquireVirtualCacheView(linear_image,exception);
3812
0
  shade_view=AcquireAuthenticCacheView(shade_image,exception);
3813
#if defined(MAGICKCORE_OPENMP_SUPPORT)
3814
  #pragma omp parallel for schedule(static) shared(progress,status) \
3815
    magick_number_threads(linear_image,shade_image,linear_image->rows,1)
3816
#endif
3817
0
  for (y=0; y < (ssize_t) linear_image->rows; y++)
3818
0
  {
3819
0
    double
3820
0
      distance,
3821
0
      normal_distance,
3822
0
      shade;
3823
3824
0
    PrimaryInfo
3825
0
      normal;
3826
3827
0
    const Quantum
3828
0
      *magick_restrict center,
3829
0
      *magick_restrict p,
3830
0
      *magick_restrict post,
3831
0
      *magick_restrict pre;
3832
3833
0
    Quantum
3834
0
      *magick_restrict q;
3835
3836
0
    ssize_t
3837
0
      x;
3838
3839
0
    if (status == MagickFalse)
3840
0
      continue;
3841
0
    p=GetCacheViewVirtualPixels(image_view,-1,y-1,linear_image->columns+2,3,
3842
0
      exception);
3843
0
    q=QueueCacheViewAuthenticPixels(shade_view,0,y,shade_image->columns,1,
3844
0
      exception);
3845
0
    if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
3846
0
      {
3847
0
        status=MagickFalse;
3848
0
        continue;
3849
0
      }
3850
    /*
3851
      Shade this row of pixels.
3852
    */
3853
0
    normal.z=2.0*(double) QuantumRange;  /* constant Z of surface normal */
3854
0
    for (x=0; x < (ssize_t) linear_image->columns; x++)
3855
0
    {
3856
0
      ssize_t
3857
0
        i;
3858
3859
      /*
3860
        Determine the surface normal and compute shading.
3861
      */
3862
0
      pre=p+GetPixelChannels(linear_image);
3863
0
      center=pre+(linear_image->columns+2)*GetPixelChannels(linear_image);
3864
0
      post=center+(linear_image->columns+2)*GetPixelChannels(linear_image);
3865
0
      normal.x=(double) (
3866
0
        GetShadeIntensity(linear_image,pre-GetPixelChannels(linear_image))+
3867
0
        GetShadeIntensity(linear_image,center-GetPixelChannels(linear_image))+
3868
0
        GetShadeIntensity(linear_image,post-GetPixelChannels(linear_image))-
3869
0
        GetShadeIntensity(linear_image,pre+GetPixelChannels(linear_image))-
3870
0
        GetShadeIntensity(linear_image,center+GetPixelChannels(linear_image))-
3871
0
        GetShadeIntensity(linear_image,post+GetPixelChannels(linear_image)));
3872
0
      normal.y=(double) (
3873
0
        GetShadeIntensity(linear_image,post-GetPixelChannels(linear_image))+
3874
0
        GetShadeIntensity(linear_image,post)+
3875
0
        GetShadeIntensity(linear_image,post+GetPixelChannels(linear_image))-
3876
0
        GetShadeIntensity(linear_image,pre-GetPixelChannels(linear_image))-
3877
0
        GetShadeIntensity(linear_image,pre)-
3878
0
        GetShadeIntensity(linear_image,pre+GetPixelChannels(linear_image)));
3879
0
      if ((fabs(normal.x) <= MagickEpsilon) &&
3880
0
          (fabs(normal.y) <= MagickEpsilon))
3881
0
        shade=light.z;
3882
0
      else
3883
0
        {
3884
0
          shade=0.0;
3885
0
          distance=normal.x*light.x+normal.y*light.y+normal.z*light.z;
3886
0
          if (distance > MagickEpsilon)
3887
0
            {
3888
0
              normal_distance=normal.x*normal.x+normal.y*normal.y+
3889
0
                normal.z*normal.z;
3890
0
              if (normal_distance > (MagickEpsilon*MagickEpsilon))
3891
0
                shade=distance/sqrt((double) normal_distance);
3892
0
            }
3893
0
        }
3894
0
      for (i=0; i < (ssize_t) GetPixelChannels(linear_image); i++)
3895
0
      {
3896
0
        PixelChannel
3897
0
          channel;
3898
3899
0
        PixelTrait
3900
0
          shade_traits,
3901
0
          traits;
3902
3903
0
        channel=GetPixelChannelChannel(linear_image,i);
3904
0
        traits=GetPixelChannelTraits(linear_image,channel);
3905
0
        shade_traits=GetPixelChannelTraits(shade_image,channel);
3906
0
        if ((traits == UndefinedPixelTrait) ||
3907
0
            (shade_traits == UndefinedPixelTrait))
3908
0
          continue;
3909
0
        if ((shade_traits & CopyPixelTrait) != 0)
3910
0
          {
3911
0
            SetPixelChannel(shade_image,channel,center[i],q);
3912
0
            continue;
3913
0
          }
3914
0
        if ((traits & UpdatePixelTrait) == 0)
3915
0
          {
3916
0
            SetPixelChannel(shade_image,channel,center[i],q);
3917
0
            continue;
3918
0
          }
3919
0
        if (gray != MagickFalse)
3920
0
          {
3921
0
            SetPixelChannel(shade_image,channel,ClampToQuantum(shade),q);
3922
0
            continue;
3923
0
          }
3924
0
        SetPixelChannel(shade_image,channel,ClampToQuantum(QuantumScale*
3925
0
          shade*(double) center[i]),q);
3926
0
      }
3927
0
      p+=(ptrdiff_t) GetPixelChannels(linear_image);
3928
0
      q+=(ptrdiff_t) GetPixelChannels(shade_image);
3929
0
    }
3930
0
    if (SyncCacheViewAuthenticPixels(shade_view,exception) == MagickFalse)
3931
0
      status=MagickFalse;
3932
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
3933
0
      {
3934
0
        MagickBooleanType
3935
0
          proceed;
3936
3937
#if defined(MAGICKCORE_OPENMP_SUPPORT)
3938
        #pragma omp atomic
3939
#endif
3940
0
        progress++;
3941
0
        proceed=SetImageProgress(image,ShadeImageTag,progress,image->rows);
3942
0
        if (proceed == MagickFalse)
3943
0
          status=MagickFalse;
3944
0
      }
3945
0
  }
3946
0
  shade_view=DestroyCacheView(shade_view);
3947
0
  image_view=DestroyCacheView(image_view);
3948
0
  linear_image=DestroyImage(linear_image);
3949
0
  if (status == MagickFalse)
3950
0
    shade_image=DestroyImage(shade_image);
3951
0
  return(shade_image);
3952
0
}
3953

3954
/*
3955
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3956
%                                                                             %
3957
%                                                                             %
3958
%                                                                             %
3959
%     S h a r p e n I m a g e                                                 %
3960
%                                                                             %
3961
%                                                                             %
3962
%                                                                             %
3963
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3964
%
3965
%  SharpenImage() sharpens the image.  We convolve the image with a Gaussian
3966
%  operator of the given radius and standard deviation (sigma).  For
3967
%  reasonable results, radius should be larger than sigma.  Use a radius of 0
3968
%  and SharpenImage() selects a suitable radius for you.
3969
%
3970
%  Using a separable kernel would be faster, but the negative weights cancel
3971
%  out on the corners of the kernel producing often undesirable ringing in the
3972
%  filtered result; this can be avoided by using a 2D gaussian shaped image
3973
%  sharpening kernel instead.
3974
%
3975
%  The format of the SharpenImage method is:
3976
%
3977
%    Image *SharpenImage(const Image *image,const double radius,
3978
%      const double sigma,ExceptionInfo *exception)
3979
%
3980
%  A description of each parameter follows:
3981
%
3982
%    o image: the image.
3983
%
3984
%    o radius: the radius of the Gaussian, in pixels, not counting the center
3985
%      pixel.
3986
%
3987
%    o sigma: the standard deviation of the Laplacian, in pixels.
3988
%
3989
%    o exception: return any errors or warnings in this structure.
3990
%
3991
*/
3992
MagickExport Image *SharpenImage(const Image *image,const double radius,
3993
  const double sigma,ExceptionInfo *exception)
3994
0
{
3995
0
  double
3996
0
    gamma,
3997
0
    normalize;
3998
3999
0
  Image
4000
0
    *sharp_image;
4001
4002
0
  KernelInfo
4003
0
    *kernel_info;
4004
4005
0
  ssize_t
4006
0
    i;
4007
4008
0
  size_t
4009
0
    width;
4010
4011
0
  ssize_t
4012
0
    j,
4013
0
    u,
4014
0
    v;
4015
4016
0
  assert(image != (const Image *) NULL);
4017
0
  assert(image->signature == MagickCoreSignature);
4018
0
  assert(exception != (ExceptionInfo *) NULL);
4019
0
  assert(exception->signature == MagickCoreSignature);
4020
0
  if (IsEventLogging() != MagickFalse)
4021
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4022
0
  width=GetOptimalKernelWidth2D(radius,sigma);
4023
0
  kernel_info=AcquireKernelInfo((const char *) NULL,exception);
4024
0
  if (kernel_info == (KernelInfo *) NULL)
4025
0
    ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
4026
0
  (void) memset(kernel_info,0,sizeof(*kernel_info));
4027
0
  kernel_info->width=width;
4028
0
  kernel_info->height=width;
4029
0
  kernel_info->x=(ssize_t) (width-1)/2;
4030
0
  kernel_info->y=(ssize_t) (width-1)/2;
4031
0
  kernel_info->signature=MagickCoreSignature;
4032
0
  kernel_info->values=(MagickRealType *) MagickAssumeAligned(
4033
0
    AcquireAlignedMemory(kernel_info->width,kernel_info->height*
4034
0
    sizeof(*kernel_info->values)));
4035
0
  if (kernel_info->values == (MagickRealType *) NULL)
4036
0
    {
4037
0
      kernel_info=DestroyKernelInfo(kernel_info);
4038
0
      ThrowImageException(ResourceLimitError,"MemoryAllocationFailed");
4039
0
    }
4040
0
  normalize=0.0;
4041
0
  j=(ssize_t) (kernel_info->width-1)/2;
4042
0
  i=0;
4043
0
  for (v=(-j); v <= j; v++)
4044
0
  {
4045
0
    for (u=(-j); u <= j; u++)
4046
0
    {
4047
0
      kernel_info->values[i]=(MagickRealType) (-exp(-((double) u*u+v*v)/(2.0*
4048
0
        MagickSigma*MagickSigma))/(2.0*MagickPI*MagickSigma*MagickSigma));
4049
0
      normalize+=kernel_info->values[i];
4050
0
      i++;
4051
0
    }
4052
0
  }
4053
0
  kernel_info->values[i/2]=(double) ((-2.0)*normalize);
4054
0
  normalize=0.0;
4055
0
  for (i=0; i < (ssize_t) (kernel_info->width*kernel_info->height); i++)
4056
0
    normalize+=kernel_info->values[i];
4057
0
  gamma=MagickSafeReciprocal(normalize);
4058
0
  for (i=0; i < (ssize_t) (kernel_info->width*kernel_info->height); i++)
4059
0
    kernel_info->values[i]*=gamma;
4060
0
  sharp_image=ConvolveImage(image,kernel_info,exception);
4061
0
  kernel_info=DestroyKernelInfo(kernel_info);
4062
0
  return(sharp_image);
4063
0
}
4064

4065
/*
4066
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4067
%                                                                             %
4068
%                                                                             %
4069
%                                                                             %
4070
%     S p r e a d I m a g e                                                   %
4071
%                                                                             %
4072
%                                                                             %
4073
%                                                                             %
4074
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4075
%
4076
%  SpreadImage() is a special effects method that randomly displaces each
4077
%  pixel in a square area defined by the radius parameter.
4078
%
4079
%  The format of the SpreadImage method is:
4080
%
4081
%      Image *SpreadImage(const Image *image,
4082
%        const PixelInterpolateMethod method,const double radius,
4083
%        ExceptionInfo *exception)
4084
%
4085
%  A description of each parameter follows:
4086
%
4087
%    o image: the image.
4088
%
4089
%    o method:  interpolation method.
4090
%
4091
%    o radius:  choose a random pixel in a neighborhood of this extent.
4092
%
4093
%    o exception: return any errors or warnings in this structure.
4094
%
4095
*/
4096
MagickExport Image *SpreadImage(const Image *image,
4097
  const PixelInterpolateMethod method,const double radius,
4098
  ExceptionInfo *exception)
4099
0
{
4100
0
#define SpreadImageTag  "Spread/Image"
4101
4102
0
  CacheView
4103
0
    *image_view,
4104
0
    *spread_view;
4105
4106
0
  Image
4107
0
    *spread_image;
4108
4109
0
  MagickBooleanType
4110
0
    status;
4111
4112
0
  MagickOffsetType
4113
0
    progress;
4114
4115
0
  RandomInfo
4116
0
    **magick_restrict random_info;
4117
4118
0
  size_t
4119
0
    width;
4120
4121
0
  ssize_t
4122
0
    y;
4123
4124
#if defined(MAGICKCORE_OPENMP_SUPPORT)
4125
  unsigned long
4126
    key;
4127
#endif
4128
4129
  /*
4130
    Initialize spread image attributes.
4131
  */
4132
0
  assert(image != (Image *) NULL);
4133
0
  assert(image->signature == MagickCoreSignature);
4134
0
  assert(exception != (ExceptionInfo *) NULL);
4135
0
  assert(exception->signature == MagickCoreSignature);
4136
0
  if (IsEventLogging() != MagickFalse)
4137
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4138
0
  spread_image=CloneImage(image,0,0,MagickTrue,exception);
4139
0
  if (spread_image == (Image *) NULL)
4140
0
    return((Image *) NULL);
4141
0
  if (SetImageStorageClass(spread_image,DirectClass,exception) == MagickFalse)
4142
0
    {
4143
0
      spread_image=DestroyImage(spread_image);
4144
0
      return((Image *) NULL);
4145
0
    }
4146
  /*
4147
    Spread image.
4148
  */
4149
0
  status=MagickTrue;
4150
0
  progress=0;
4151
0
  width=GetOptimalKernelWidth1D(radius,0.5);
4152
0
  random_info=AcquireRandomInfoTLS();
4153
0
  image_view=AcquireVirtualCacheView(image,exception);
4154
0
  spread_view=AcquireAuthenticCacheView(spread_image,exception);
4155
#if defined(MAGICKCORE_OPENMP_SUPPORT)
4156
  key=GetRandomSecretKey(random_info[0]);
4157
  #pragma omp parallel for schedule(static) shared(progress,status) \
4158
    magick_number_threads(image,spread_image,image->rows,key == ~0UL)
4159
#endif
4160
0
  for (y=0; y < (ssize_t) image->rows; y++)
4161
0
  {
4162
0
    const int
4163
0
      id = GetOpenMPThreadId();
4164
4165
0
    Quantum
4166
0
      *magick_restrict q;
4167
4168
0
    ssize_t
4169
0
      x;
4170
4171
0
    if (status == MagickFalse)
4172
0
      continue;
4173
0
    q=QueueCacheViewAuthenticPixels(spread_view,0,y,spread_image->columns,1,
4174
0
      exception);
4175
0
    if (q == (Quantum *) NULL)
4176
0
      {
4177
0
        status=MagickFalse;
4178
0
        continue;
4179
0
      }
4180
0
    for (x=0; x < (ssize_t) image->columns; x++)
4181
0
    {
4182
0
      PointInfo
4183
0
        point;
4184
4185
0
      point.x=GetPseudoRandomValue(random_info[id]);
4186
0
      point.y=GetPseudoRandomValue(random_info[id]);
4187
0
      status=InterpolatePixelChannels(image,image_view,spread_image,method,
4188
0
        (double) x+width*(point.x-0.5),(double) y+width*(point.y-0.5),q,
4189
0
        exception);
4190
0
      if (status == MagickFalse)
4191
0
        break;
4192
0
      q+=(ptrdiff_t) GetPixelChannels(spread_image);
4193
0
    }
4194
0
    if (SyncCacheViewAuthenticPixels(spread_view,exception) == MagickFalse)
4195
0
      status=MagickFalse;
4196
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
4197
0
      {
4198
0
        MagickBooleanType
4199
0
          proceed;
4200
4201
#if defined(MAGICKCORE_OPENMP_SUPPORT)
4202
        #pragma omp atomic
4203
#endif
4204
0
        progress++;
4205
0
        proceed=SetImageProgress(image,SpreadImageTag,progress,image->rows);
4206
0
        if (proceed == MagickFalse)
4207
0
          status=MagickFalse;
4208
0
      }
4209
0
  }
4210
0
  spread_view=DestroyCacheView(spread_view);
4211
0
  image_view=DestroyCacheView(image_view);
4212
0
  random_info=DestroyRandomInfoTLS(random_info);
4213
0
  if (status == MagickFalse)
4214
0
    spread_image=DestroyImage(spread_image);
4215
0
  return(spread_image);
4216
0
}
4217

4218
/*
4219
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4220
%                                                                             %
4221
%                                                                             %
4222
%                                                                             %
4223
%     U n s h a r p M a s k I m a g e                                         %
4224
%                                                                             %
4225
%                                                                             %
4226
%                                                                             %
4227
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4228
%
4229
%  UnsharpMaskImage() sharpens one or more image channels.  We convolve the
4230
%  image with a Gaussian operator of the given radius and standard deviation
4231
%  (sigma).  For reasonable results, radius should be larger than sigma.  Use a
4232
%  radius of 0 and UnsharpMaskImage() selects a suitable radius for you.
4233
%
4234
%  The format of the UnsharpMaskImage method is:
4235
%
4236
%    Image *UnsharpMaskImage(const Image *image,const double radius,
4237
%      const double sigma,const double amount,const double threshold,
4238
%      ExceptionInfo *exception)
4239
%
4240
%  A description of each parameter follows:
4241
%
4242
%    o image: the image.
4243
%
4244
%    o radius: the radius of the Gaussian, in pixels, not counting the center
4245
%      pixel.
4246
%
4247
%    o sigma: the standard deviation of the Gaussian, in pixels.
4248
%
4249
%    o gain: the percentage of the difference between the original and the
4250
%      blur image that is added back into the original.
4251
%
4252
%    o threshold: the threshold in pixels needed to apply the difference gain.
4253
%
4254
%    o exception: return any errors or warnings in this structure.
4255
%
4256
*/
4257
MagickExport Image *UnsharpMaskImage(const Image *image,const double radius,
4258
  const double sigma,const double gain,const double threshold,
4259
  ExceptionInfo *exception)
4260
0
{
4261
0
#define SharpenImageTag  "Sharpen/Image"
4262
4263
0
  CacheView
4264
0
    *image_view,
4265
0
    *unsharp_view;
4266
4267
0
  Image
4268
0
    *unsharp_image;
4269
4270
0
  MagickBooleanType
4271
0
    status;
4272
4273
0
  MagickOffsetType
4274
0
    progress;
4275
4276
0
  double
4277
0
    quantum_threshold;
4278
4279
0
  ssize_t
4280
0
    y;
4281
4282
0
  assert(image != (const Image *) NULL);
4283
0
  assert(image->signature == MagickCoreSignature);
4284
0
  assert(exception != (ExceptionInfo *) NULL);
4285
0
  assert(exception->signature == MagickCoreSignature);
4286
0
  if (IsEventLogging() != MagickFalse)
4287
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4288
/* This kernel appears to be broken.
4289
#if defined(MAGICKCORE_OPENCL_SUPPORT)
4290
  unsharp_image=AccelerateUnsharpMaskImage(image,radius,sigma,gain,threshold,
4291
    exception);
4292
  if (unsharp_image != (Image *) NULL)
4293
    return(unsharp_image);
4294
#endif
4295
*/
4296
0
  unsharp_image=BlurImage(image,radius,sigma,exception);
4297
0
  if (unsharp_image == (Image *) NULL)
4298
0
    return((Image *) NULL);
4299
0
  quantum_threshold=(double) QuantumRange*threshold;
4300
  /*
4301
    Unsharp-mask image.
4302
  */
4303
0
  status=MagickTrue;
4304
0
  progress=0;
4305
0
  image_view=AcquireVirtualCacheView(image,exception);
4306
0
  unsharp_view=AcquireAuthenticCacheView(unsharp_image,exception);
4307
#if defined(MAGICKCORE_OPENMP_SUPPORT)
4308
  #pragma omp parallel for schedule(static) shared(progress,status) \
4309
    magick_number_threads(image,unsharp_image,image->rows,1)
4310
#endif
4311
0
  for (y=0; y < (ssize_t) image->rows; y++)
4312
0
  {
4313
0
    const Quantum
4314
0
      *magick_restrict p;
4315
4316
0
    Quantum
4317
0
      *magick_restrict q;
4318
4319
0
    ssize_t
4320
0
      x;
4321
4322
0
    if (status == MagickFalse)
4323
0
      continue;
4324
0
    p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
4325
0
    q=GetCacheViewAuthenticPixels(unsharp_view,0,y,unsharp_image->columns,1,
4326
0
      exception);
4327
0
    if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
4328
0
      {
4329
0
        status=MagickFalse;
4330
0
        continue;
4331
0
      }
4332
0
    for (x=0; x < (ssize_t) image->columns; x++)
4333
0
    {
4334
0
      ssize_t
4335
0
        i;
4336
4337
0
      for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4338
0
      {
4339
0
        double
4340
0
          pixel;
4341
4342
0
        PixelChannel
4343
0
          channel;
4344
4345
0
        PixelTrait
4346
0
          traits,
4347
0
          unsharp_traits;
4348
4349
0
        channel=GetPixelChannelChannel(image,i);
4350
0
        traits=GetPixelChannelTraits(image,channel);
4351
0
        unsharp_traits=GetPixelChannelTraits(unsharp_image,channel);
4352
0
        if ((traits == UndefinedPixelTrait) ||
4353
0
            (unsharp_traits == UndefinedPixelTrait))
4354
0
          continue;
4355
0
        if ((unsharp_traits & CopyPixelTrait) != 0)
4356
0
          {
4357
0
            SetPixelChannel(unsharp_image,channel,p[i],q);
4358
0
            continue;
4359
0
          }
4360
0
        pixel=(double) p[i]-(double) GetPixelChannel(unsharp_image,channel,q);
4361
0
        if (fabs(2.0*pixel) < quantum_threshold)
4362
0
          pixel=(double) p[i];
4363
0
        else
4364
0
          pixel=(double) p[i]+gain*pixel;
4365
0
        SetPixelChannel(unsharp_image,channel,ClampToQuantum(pixel),q);
4366
0
      }
4367
0
      p+=(ptrdiff_t) GetPixelChannels(image);
4368
0
      q+=(ptrdiff_t) GetPixelChannels(unsharp_image);
4369
0
    }
4370
0
    if (SyncCacheViewAuthenticPixels(unsharp_view,exception) == MagickFalse)
4371
0
      status=MagickFalse;
4372
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
4373
0
      {
4374
0
        MagickBooleanType
4375
0
          proceed;
4376
4377
#if defined(MAGICKCORE_OPENMP_SUPPORT)
4378
        #pragma omp atomic
4379
#endif
4380
0
        progress++;
4381
0
        proceed=SetImageProgress(image,SharpenImageTag,progress,image->rows);
4382
0
        if (proceed == MagickFalse)
4383
0
          status=MagickFalse;
4384
0
      }
4385
0
  }
4386
0
  unsharp_image->type=image->type;
4387
0
  unsharp_view=DestroyCacheView(unsharp_view);
4388
0
  image_view=DestroyCacheView(image_view);
4389
0
  if (status == MagickFalse)
4390
0
    unsharp_image=DestroyImage(unsharp_image);
4391
0
  return(unsharp_image);
4392
0
}