Coverage Report

Created: 2025-08-12 07:37

/src/imagemagick/MagickCore/enhance.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3
%                                                                             %
4
%                                                                             %
5
%                                                                             %
6
%              EEEEE  N   N  H   H   AAA   N   N   CCCC  EEEEE                %
7
%              E      NN  N  H   H  A   A  NN  N  C      E                    %
8
%              EEE    N N N  HHHHH  AAAAA  N N N  C      EEE                  %
9
%              E      N  NN  H   H  A   A  N  NN  C      E                    %
10
%              EEEEE  N   N  H   H  A   A  N   N   CCCC  EEEEE                %
11
%                                                                             %
12
%                                                                             %
13
%                    MagickCore Image Enhancement Methods                     %
14
%                                                                             %
15
%                              Software Design                                %
16
%                                   Cristy                                    %
17
%                                 July 1992                                   %
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/artifact.h"
46
#include "MagickCore/attribute.h"
47
#include "MagickCore/cache.h"
48
#include "MagickCore/cache-private.h"
49
#include "MagickCore/cache-view.h"
50
#include "MagickCore/channel.h"
51
#include "MagickCore/color.h"
52
#include "MagickCore/color-private.h"
53
#include "MagickCore/colorspace.h"
54
#include "MagickCore/colorspace-private.h"
55
#include "MagickCore/composite-private.h"
56
#include "MagickCore/enhance.h"
57
#include "MagickCore/exception.h"
58
#include "MagickCore/exception-private.h"
59
#include "MagickCore/fx.h"
60
#include "MagickCore/gem.h"
61
#include "MagickCore/gem-private.h"
62
#include "MagickCore/geometry.h"
63
#include "MagickCore/histogram.h"
64
#include "MagickCore/image.h"
65
#include "MagickCore/image-private.h"
66
#include "MagickCore/memory_.h"
67
#include "MagickCore/monitor.h"
68
#include "MagickCore/monitor-private.h"
69
#include "MagickCore/option.h"
70
#include "MagickCore/pixel.h"
71
#include "MagickCore/pixel-accessor.h"
72
#include "MagickCore/property.h"
73
#include "MagickCore/quantum.h"
74
#include "MagickCore/quantum-private.h"
75
#include "MagickCore/resample.h"
76
#include "MagickCore/resample-private.h"
77
#include "MagickCore/resource_.h"
78
#include "MagickCore/statistic.h"
79
#include "MagickCore/string_.h"
80
#include "MagickCore/string-private.h"
81
#include "MagickCore/thread-private.h"
82
#include "MagickCore/threshold.h"
83
#include "MagickCore/token.h"
84
#include "MagickCore/xml-tree.h"
85
#include "MagickCore/xml-tree-private.h"
86

87
/*
88
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
89
%                                                                             %
90
%                                                                             %
91
%                                                                             %
92
%     A u t o G a m m a I m a g e                                             %
93
%                                                                             %
94
%                                                                             %
95
%                                                                             %
96
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
97
%
98
%  AutoGammaImage() extract the 'mean' from the image and adjust the image
99
%  to try make set its gamma appropriately.
100
%
101
%  The format of the AutoGammaImage method is:
102
%
103
%      MagickBooleanType AutoGammaImage(Image *image,ExceptionInfo *exception)
104
%
105
%  A description of each parameter follows:
106
%
107
%    o image: The image to auto-level
108
%
109
%    o exception: return any errors or warnings in this structure.
110
%
111
*/
112
MagickExport MagickBooleanType AutoGammaImage(Image *image,
113
  ExceptionInfo *exception)
114
0
{
115
0
  double
116
0
    gamma,
117
0
    log_mean,
118
0
    mean,
119
0
    sans;
120
121
0
  MagickStatusType
122
0
    status;
123
124
0
  ssize_t
125
0
    i;
126
127
0
  log_mean=log(0.5);
128
0
  if (image->channel_mask == AllChannels)
129
0
    {
130
      /*
131
        Apply gamma correction equally across all given channels.
132
      */
133
0
      (void) GetImageMean(image,&mean,&sans,exception);
134
0
      gamma=log(mean*QuantumScale)/log_mean;
135
0
      return(LevelImage(image,0.0,(double) QuantumRange,gamma,exception));
136
0
    }
137
  /*
138
    Auto-gamma each channel separately.
139
  */
140
0
  status=MagickTrue;
141
0
  for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
142
0
  {
143
0
    ChannelType
144
0
      channel_mask;
145
146
0
    PixelChannel channel = GetPixelChannelChannel(image,i);
147
0
    PixelTrait traits = GetPixelChannelTraits(image,channel);
148
0
    if ((traits & UpdatePixelTrait) == 0)
149
0
      continue;
150
0
    channel_mask=SetImageChannelMask(image,(ChannelType) (1UL << i));
151
0
    status=GetImageMean(image,&mean,&sans,exception);
152
0
    gamma=log(mean*QuantumScale)/log_mean;
153
0
    status&=(MagickStatusType) LevelImage(image,0.0,(double) QuantumRange,gamma,
154
0
      exception);
155
0
    (void) SetImageChannelMask(image,channel_mask);
156
0
    if (status == MagickFalse)
157
0
      break;
158
0
  }
159
0
  return(status != 0 ? MagickTrue : MagickFalse);
160
0
}
161

162
/*
163
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
164
%                                                                             %
165
%                                                                             %
166
%                                                                             %
167
%     A u t o L e v e l I m a g e                                             %
168
%                                                                             %
169
%                                                                             %
170
%                                                                             %
171
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
172
%
173
%  AutoLevelImage() adjusts the levels of a particular image channel by
174
%  scaling the minimum and maximum values to the full quantum range.
175
%
176
%  The format of the LevelImage method is:
177
%
178
%      MagickBooleanType AutoLevelImage(Image *image,ExceptionInfo *exception)
179
%
180
%  A description of each parameter follows:
181
%
182
%    o image: The image to auto-level
183
%
184
%    o exception: return any errors or warnings in this structure.
185
%
186
*/
187
MagickExport MagickBooleanType AutoLevelImage(Image *image,
188
  ExceptionInfo *exception)
189
0
{
190
0
  return(MinMaxStretchImage(image,0.0,0.0,1.0,exception));
191
0
}
192

193
/*
194
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
195
%                                                                             %
196
%                                                                             %
197
%                                                                             %
198
%     B r i g h t n e s s C o n t r a s t I m a g e                           %
199
%                                                                             %
200
%                                                                             %
201
%                                                                             %
202
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
203
%
204
%  BrightnessContrastImage() changes the brightness and/or contrast of an
205
%  image.  It converts the brightness and contrast parameters into slope and
206
%  intercept and calls a polynomial function to apply to the image.
207
%
208
%  The format of the BrightnessContrastImage method is:
209
%
210
%      MagickBooleanType BrightnessContrastImage(Image *image,
211
%        const double brightness,const double contrast,ExceptionInfo *exception)
212
%
213
%  A description of each parameter follows:
214
%
215
%    o image: the image.
216
%
217
%    o brightness: the brightness percent (-100 .. 100).
218
%
219
%    o contrast: the contrast percent (-100 .. 100).
220
%
221
%    o exception: return any errors or warnings in this structure.
222
%
223
*/
224
MagickExport MagickBooleanType BrightnessContrastImage(Image *image,
225
  const double brightness,const double contrast,ExceptionInfo *exception)
226
0
{
227
0
#define BrightnessContrastImageTag  "BrightnessContrast/Image"
228
229
0
  double
230
0
    coefficients[2],
231
0
    intercept,
232
0
    slope;
233
234
0
  MagickBooleanType
235
0
    status;
236
237
  /*
238
    Compute slope and intercept.
239
  */
240
0
  assert(image != (Image *) NULL);
241
0
  assert(image->signature == MagickCoreSignature);
242
0
  if (IsEventLogging() != MagickFalse)
243
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
244
0
  slope=100.0*MagickSafeReciprocal(100.0-contrast);
245
0
  if (contrast < 0.0)
246
0
    slope=0.01*contrast+1.0;
247
0
  intercept=(0.01*brightness-0.5)*slope+0.5;
248
0
  coefficients[0]=slope;
249
0
  coefficients[1]=intercept;
250
0
  status=FunctionImage(image,PolynomialFunction,2,coefficients,exception);
251
0
  return(status);
252
0
}
253

254
/*
255
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
256
%                                                                             %
257
%                                                                             %
258
%                                                                             %
259
%     C L A H E I m a g e                                                     %
260
%                                                                             %
261
%                                                                             %
262
%                                                                             %
263
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
264
%
265
%  CLAHEImage() is a variant of adaptive histogram equalization in which the
266
%  contrast amplification is limited, so as to reduce this problem of noise
267
%  amplification.
268
%
269
%  Adapted from implementation by Karel Zuiderveld, karel@cv.ruu.nl in
270
%  "Graphics Gems IV", Academic Press, 1994.
271
%
272
%  The format of the CLAHEImage method is:
273
%
274
%      MagickBooleanType CLAHEImage(Image *image,const size_t width,
275
%        const size_t height,const size_t number_bins,const double clip_limit,
276
%        ExceptionInfo *exception)
277
%
278
%  A description of each parameter follows:
279
%
280
%    o image: the image.
281
%
282
%    o width: the width of the tile divisions to use in horizontal direction.
283
%
284
%    o height: the height of the tile divisions to use in vertical direction.
285
%
286
%    o number_bins: number of bins for histogram ("dynamic range").
287
%
288
%    o clip_limit: contrast limit for localised changes in contrast. A limit
289
%      less than 1 results in standard non-contrast limited AHE.
290
%
291
%    o exception: return any errors or warnings in this structure.
292
%
293
*/
294
295
typedef struct _RangeInfo
296
{
297
  unsigned short
298
    min,
299
    max;
300
} RangeInfo;
301
302
static void ClipCLAHEHistogram(const double clip_limit,const size_t number_bins,
303
  size_t *histogram)
304
0
{
305
0
#define NumberCLAHEGrays  (65536)
306
307
0
  ssize_t
308
0
    cumulative_excess,
309
0
    excess,
310
0
    i,
311
0
    previous_excess,
312
0
    step;
313
314
  /*
315
    Compute total number of excess pixels.
316
  */
317
0
  if (number_bins == 0)
318
0
    return;
319
0
  cumulative_excess=0;
320
0
  for (i=0; i < (ssize_t) number_bins; i++)
321
0
  {
322
0
    excess=(ssize_t) histogram[i]-(ssize_t) clip_limit;
323
0
    if (excess > 0)
324
0
      cumulative_excess+=excess;
325
0
  }
326
  /*
327
    Clip histogram and redistribute excess pixels across all bins.
328
  */
329
0
  step=cumulative_excess/(ssize_t) number_bins;
330
0
  excess=(ssize_t) (clip_limit-step);
331
0
  for (i=0; i < (ssize_t) number_bins; i++)
332
0
  {
333
0
    if ((double) histogram[i] > clip_limit)
334
0
      histogram[i]=(size_t) clip_limit;
335
0
    else
336
0
      if ((ssize_t) histogram[i] > excess)
337
0
        {
338
0
          cumulative_excess-=(ssize_t) histogram[i]-excess;
339
0
          histogram[i]=(size_t) clip_limit;
340
0
        }
341
0
      else
342
0
        {
343
0
          cumulative_excess-=step;
344
0
          histogram[i]+=(size_t) step;
345
0
        }
346
0
  }
347
  /*
348
    Redistribute remaining excess.
349
  */
350
0
  do
351
0
  {
352
0
    size_t
353
0
      *p;
354
355
0
    size_t
356
0
      *q;
357
358
0
    previous_excess=cumulative_excess;
359
0
    p=histogram;
360
0
    q=histogram+number_bins;
361
0
    while ((cumulative_excess != 0) && (p < q))
362
0
    {
363
0
      step=(ssize_t) number_bins/cumulative_excess;
364
0
      if (step < 1)
365
0
        step=1;
366
0
      for (p=histogram; (p < q) && (cumulative_excess != 0); p+=(ptrdiff_t) step)
367
0
        if ((double) *p < clip_limit)
368
0
          {
369
0
            (*p)++;
370
0
            cumulative_excess--;
371
0
          }
372
0
      p++;
373
0
    }
374
0
  } while ((cumulative_excess != 0) && (cumulative_excess < previous_excess));
375
0
}
376
377
static void GenerateCLAHEHistogram(const RectangleInfo *clahe_info,
378
  const RectangleInfo *tile_info,const size_t number_bins,
379
  const unsigned short *lut,const unsigned short *pixels,size_t *histogram)
380
0
{
381
0
  const unsigned short
382
0
    *p;
383
384
0
  ssize_t
385
0
    i;
386
387
  /*
388
    Classify the pixels into a gray histogram.
389
  */
390
0
  for (i=0; i < (ssize_t) number_bins; i++)
391
0
    histogram[i]=0L;
392
0
  p=pixels;
393
0
  for (i=0; i < (ssize_t) tile_info->height; i++)
394
0
  {
395
0
    const unsigned short
396
0
      *q;
397
398
0
    q=p+tile_info->width;
399
0
    while (p < q)
400
0
      histogram[lut[*p++]]++;
401
0
    q+=(ptrdiff_t) clahe_info->width;
402
0
    p=q-tile_info->width;
403
0
  }
404
0
}
405
406
static void InterpolateCLAHE(const RectangleInfo *clahe_info,const size_t *Q12,
407
  const size_t *Q22,const size_t *Q11,const size_t *Q21,
408
  const RectangleInfo *tile,const unsigned short *lut,unsigned short *pixels)
409
0
{
410
0
  ssize_t
411
0
    y;
412
413
0
  unsigned short
414
0
    intensity;
415
416
  /*
417
    Bilinear interpolate four tiles to eliminate boundary artifacts.
418
  */
419
0
  for (y=(ssize_t) tile->height; y > 0; y--)
420
0
  {
421
0
    ssize_t
422
0
      x;
423
424
0
    for (x=(ssize_t) tile->width; x > 0; x--)
425
0
    {
426
0
      intensity=lut[*pixels];
427
0
      *pixels++=(unsigned short) (MagickSafeReciprocal((double) tile->width*
428
0
        tile->height)*(y*((double) x*Q12[intensity]+((double) tile->width-x)*
429
0
        Q22[intensity])+((double) tile->height-y)*((double) x*Q11[intensity]+
430
0
        ((double) tile->width-x)*Q21[intensity])));
431
0
    }
432
0
    pixels+=(clahe_info->width-tile->width);
433
0
  }
434
0
}
435
436
static void GenerateCLAHELut(const RangeInfo *range_info,
437
  const size_t number_bins,unsigned short *lut)
438
0
{
439
0
  ssize_t
440
0
    i;
441
442
0
  unsigned short
443
0
    delta;
444
445
  /*
446
    Scale input image [intensity min,max] to [0,number_bins-1].
447
  */
448
0
  delta=(unsigned short) ((range_info->max-range_info->min)/number_bins+1);
449
0
  for (i=(ssize_t) range_info->min; i <= (ssize_t) range_info->max; i++)
450
0
    lut[i]=(unsigned short) ((i-range_info->min)/delta);
451
0
}
452
453
static void MapCLAHEHistogram(const RangeInfo *range_info,
454
  const size_t number_bins,const size_t number_pixels,size_t *histogram)
455
0
{
456
0
  double
457
0
    scale,
458
0
    sum;
459
460
0
  ssize_t
461
0
    i;
462
463
  /*
464
    Rescale histogram to range [min-intensity .. max-intensity].
465
  */
466
0
  scale=(double) (range_info->max-range_info->min)/number_pixels;
467
0
  sum=0.0;
468
0
  for (i=0; i < (ssize_t) number_bins; i++)
469
0
  {
470
0
    sum+=histogram[i];
471
0
    histogram[i]=(size_t) (range_info->min+scale*sum);
472
0
    if (histogram[i] > range_info->max)
473
0
      histogram[i]=range_info->max;
474
0
  }
475
0
}
476
477
static MagickBooleanType CLAHE(const RectangleInfo *clahe_info,
478
  const RectangleInfo *tile_info,const RangeInfo *range_info,
479
  const size_t number_bins,const double clip_limit,unsigned short *pixels)
480
0
{
481
0
  MemoryInfo
482
0
    *tile_cache;
483
484
0
  unsigned short
485
0
    *p;
486
487
0
  size_t
488
0
    limit,
489
0
    *tiles;
490
491
0
  ssize_t
492
0
    y;
493
494
0
  unsigned short
495
0
    *lut;
496
497
  /*
498
    Contrast limited adapted histogram equalization.
499
  */
500
0
  if (clip_limit == 1.0)
501
0
    return(MagickTrue);
502
0
  tile_cache=AcquireVirtualMemory((size_t) clahe_info->x*number_bins,
503
0
    (size_t) clahe_info->y*sizeof(*tiles));
504
0
  if (tile_cache == (MemoryInfo *) NULL)
505
0
    return(MagickFalse);
506
0
  lut=(unsigned short *) AcquireQuantumMemory(NumberCLAHEGrays,sizeof(*lut));
507
0
  if (lut == (unsigned short *) NULL)
508
0
    {
509
0
      tile_cache=RelinquishVirtualMemory(tile_cache);
510
0
      return(MagickFalse);
511
0
    }
512
0
  tiles=(size_t *) GetVirtualMemoryBlob(tile_cache);
513
0
  limit=(size_t) (clip_limit*(tile_info->width*tile_info->height)/number_bins);
514
0
  if (limit < 1UL)
515
0
    limit=1UL;
516
  /*
517
    Generate greylevel mappings for each tile.
518
  */
519
0
  GenerateCLAHELut(range_info,number_bins,lut);
520
0
  p=pixels;
521
0
  for (y=0; y < (ssize_t) clahe_info->y; y++)
522
0
  {
523
0
    ssize_t
524
0
      x;
525
526
0
    for (x=0; x < (ssize_t) clahe_info->x; x++)
527
0
    {
528
0
      size_t
529
0
        *histogram;
530
531
0
      histogram=tiles+((ssize_t) number_bins*(y*clahe_info->x+x));
532
0
      GenerateCLAHEHistogram(clahe_info,tile_info,number_bins,lut,p,histogram);
533
0
      ClipCLAHEHistogram((double) limit,number_bins,histogram);
534
0
      MapCLAHEHistogram(range_info,number_bins,tile_info->width*
535
0
        tile_info->height,histogram);
536
0
      p+=(ptrdiff_t) tile_info->width;
537
0
    }
538
0
    p+=(ptrdiff_t) clahe_info->width*(tile_info->height-1);
539
0
  }
540
  /*
541
    Interpolate greylevel mappings to get CLAHE image.
542
  */
543
0
  p=pixels;
544
0
  for (y=0; y <= (ssize_t) clahe_info->y; y++)
545
0
  {
546
0
    OffsetInfo
547
0
      offset;
548
549
0
    RectangleInfo
550
0
      tile;
551
552
0
    ssize_t
553
0
      x;
554
555
0
    tile.height=tile_info->height;
556
0
    tile.y=y-1;
557
0
    offset.y=tile.y+1;
558
0
    if (y == 0)
559
0
      {
560
        /*
561
          Top row.
562
        */
563
0
        tile.height=tile_info->height >> 1;
564
0
        tile.y=0;
565
0
        offset.y=0;
566
0
      }
567
0
    else
568
0
      if (y == (ssize_t) clahe_info->y)
569
0
        {
570
          /*
571
            Bottom row.
572
          */
573
0
          tile.height=(tile_info->height+1) >> 1;
574
0
          tile.y=clahe_info->y-1;
575
0
          offset.y=tile.y;
576
0
        }
577
0
    for (x=0; x <= (ssize_t) clahe_info->x; x++)
578
0
    {
579
0
      tile.width=tile_info->width;
580
0
      tile.x=x-1;
581
0
      offset.x=tile.x+1;
582
0
      if (x == 0)
583
0
        {
584
          /*
585
            Left column.
586
          */
587
0
          tile.width=tile_info->width >> 1;
588
0
          tile.x=0;
589
0
          offset.x=0;
590
0
        }
591
0
      else
592
0
        if (x == (ssize_t) clahe_info->x)
593
0
          {
594
            /*
595
              Right column.
596
            */
597
0
            tile.width=(tile_info->width+1) >> 1;
598
0
            tile.x=clahe_info->x-1;
599
0
            offset.x=tile.x;
600
0
          }
601
0
      InterpolateCLAHE(clahe_info,
602
0
        tiles+((ssize_t) number_bins*(tile.y*clahe_info->x+tile.x)),   /* Q12 */
603
0
        tiles+((ssize_t) number_bins*(tile.y*clahe_info->x+offset.x)), /* Q22 */
604
0
        tiles+((ssize_t) number_bins*(offset.y*clahe_info->x+tile.x)), /* Q11 */
605
0
        tiles+((ssize_t) number_bins*(offset.y*clahe_info->x+offset.x)), /* Q21 */
606
0
        &tile,lut,p);
607
0
      p+=(ptrdiff_t) tile.width;
608
0
    }
609
0
    p+=(ptrdiff_t) clahe_info->width*(tile.height-1);
610
0
  }
611
0
  lut=(unsigned short *) RelinquishMagickMemory(lut);
612
0
  tile_cache=RelinquishVirtualMemory(tile_cache);
613
0
  return(MagickTrue);
614
0
}
615
616
MagickExport MagickBooleanType CLAHEImage(Image *image,const size_t width,
617
  const size_t height,const size_t number_bins,const double clip_limit,
618
  ExceptionInfo *exception)
619
0
{
620
0
#define CLAHEImageTag  "CLAHE/Image"
621
622
0
  CacheView
623
0
    *image_view;
624
625
0
  ColorspaceType
626
0
    colorspace;
627
628
0
  MagickBooleanType
629
0
    status;
630
631
0
  MagickOffsetType
632
0
    progress;
633
634
0
  MemoryInfo
635
0
    *pixel_cache;
636
637
0
  RangeInfo
638
0
    range_info;
639
640
0
  RectangleInfo
641
0
    clahe_info,
642
0
    tile_info;
643
644
0
  size_t
645
0
    n;
646
647
0
  ssize_t
648
0
    y;
649
650
0
  unsigned short
651
0
    *pixels;
652
653
  /*
654
    Configure CLAHE parameters.
655
  */
656
0
  assert(image != (Image *) NULL);
657
0
  assert(image->signature == MagickCoreSignature);
658
0
  if (IsEventLogging() != MagickFalse)
659
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
660
0
  range_info.min=0;
661
0
  range_info.max=NumberCLAHEGrays-1;
662
0
  tile_info.width=width;
663
0
  if (tile_info.width == 0)
664
0
    tile_info.width=image->columns >> 3;
665
0
  tile_info.height=height;
666
0
  if (tile_info.height == 0)
667
0
    tile_info.height=image->rows >> 3;
668
0
  tile_info.x=0;
669
0
  if ((image->columns % tile_info.width) != 0)
670
0
    tile_info.x=(ssize_t) (tile_info.width-(image->columns % tile_info.width));
671
0
  tile_info.y=0;
672
0
  if ((image->rows % tile_info.height) != 0)
673
0
    tile_info.y=(ssize_t) (tile_info.height-(image->rows % tile_info.height));
674
0
  clahe_info.width=(size_t) ((ssize_t) image->columns+tile_info.x);
675
0
  clahe_info.height=(size_t) ((ssize_t) image->rows+tile_info.y);
676
0
  clahe_info.x=(ssize_t) (clahe_info.width/tile_info.width);
677
0
  clahe_info.y=(ssize_t) (clahe_info.height/tile_info.height);
678
0
  pixel_cache=AcquireVirtualMemory(clahe_info.width,clahe_info.height*
679
0
    sizeof(*pixels));
680
0
  if (pixel_cache == (MemoryInfo *) NULL)
681
0
    ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
682
0
      image->filename);
683
0
  pixels=(unsigned short *) GetVirtualMemoryBlob(pixel_cache);
684
0
  colorspace=image->colorspace;
685
0
  if (TransformImageColorspace(image,LabColorspace,exception) == MagickFalse)
686
0
    {
687
0
      pixel_cache=RelinquishVirtualMemory(pixel_cache);
688
0
      return(MagickFalse);
689
0
    }
690
  /*
691
    Initialize CLAHE pixels.
692
  */
693
0
  image_view=AcquireVirtualCacheView(image,exception);
694
0
  progress=0;
695
0
  status=MagickTrue;
696
0
  n=0;
697
0
  for (y=0; y < (ssize_t) clahe_info.height; y++)
698
0
  {
699
0
    const Quantum
700
0
      *magick_restrict p;
701
702
0
    ssize_t
703
0
      x;
704
705
0
    if (status == MagickFalse)
706
0
      continue;
707
0
    p=GetCacheViewVirtualPixels(image_view,-(tile_info.x >> 1),y-
708
0
      (tile_info.y >> 1),clahe_info.width,1,exception);
709
0
    if (p == (const Quantum *) NULL)
710
0
      {
711
0
        status=MagickFalse;
712
0
        continue;
713
0
      }
714
0
    for (x=0; x < (ssize_t) clahe_info.width; x++)
715
0
    {
716
0
      pixels[n++]=ScaleQuantumToShort(p[0]);
717
0
      p+=(ptrdiff_t) GetPixelChannels(image);
718
0
    }
719
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
720
0
      {
721
0
        MagickBooleanType
722
0
          proceed;
723
724
0
        progress++;
725
0
        proceed=SetImageProgress(image,CLAHEImageTag,progress,2*
726
0
          GetPixelChannels(image));
727
0
        if (proceed == MagickFalse)
728
0
          status=MagickFalse;
729
0
      }
730
0
  }
731
0
  image_view=DestroyCacheView(image_view);
732
0
  status=CLAHE(&clahe_info,&tile_info,&range_info,number_bins == 0 ?
733
0
    (size_t) 128 : MagickMin(number_bins,256),clip_limit,pixels);
734
0
  if (status == MagickFalse)
735
0
    (void) ThrowMagickException(exception,GetMagickModule(),
736
0
      ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
737
  /*
738
    Push CLAHE pixels to CLAHE image.
739
  */
740
0
  image_view=AcquireAuthenticCacheView(image,exception);
741
0
  n=clahe_info.width*(size_t) (tile_info.y/2);
742
0
  for (y=0; y < (ssize_t) image->rows; y++)
743
0
  {
744
0
    Quantum
745
0
      *magick_restrict q;
746
747
0
    ssize_t
748
0
      x;
749
750
0
    if (status == MagickFalse)
751
0
      continue;
752
0
    q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
753
0
    if (q == (Quantum *) NULL)
754
0
      {
755
0
        status=MagickFalse;
756
0
        continue;
757
0
      }
758
0
    n+=(size_t) (tile_info.x/2);
759
0
    for (x=0; x < (ssize_t) image->columns; x++)
760
0
    {
761
0
      q[0]=ScaleShortToQuantum(pixels[n++]);
762
0
      q+=(ptrdiff_t) GetPixelChannels(image);
763
0
    }
764
0
    n+=(size_t) ((ssize_t) clahe_info.width-(ssize_t) image->columns-
765
0
      (tile_info.x/2));
766
0
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
767
0
      status=MagickFalse;
768
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
769
0
      {
770
0
        MagickBooleanType
771
0
          proceed;
772
773
0
        progress++;
774
0
        proceed=SetImageProgress(image,CLAHEImageTag,progress,2*
775
0
          GetPixelChannels(image));
776
0
        if (proceed == MagickFalse)
777
0
          status=MagickFalse;
778
0
      }
779
0
  }
780
0
  image_view=DestroyCacheView(image_view);
781
0
  pixel_cache=RelinquishVirtualMemory(pixel_cache);
782
0
  if (TransformImageColorspace(image,colorspace,exception) == MagickFalse)
783
0
    status=MagickFalse;
784
0
  return(status);
785
0
}
786

787
/*
788
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
789
%                                                                             %
790
%                                                                             %
791
%                                                                             %
792
%     C l u t I m a g e                                                       %
793
%                                                                             %
794
%                                                                             %
795
%                                                                             %
796
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
797
%
798
%  ClutImage() replaces each color value in the given image, by using it as an
799
%  index to lookup a replacement color value in a Color Look UP Table in the
800
%  form of an image.  The values are extracted along a diagonal of the CLUT
801
%  image so either a horizontal or vertical gradient image can be used.
802
%
803
%  Typically this is used to either re-color a gray-scale image according to a
804
%  color gradient in the CLUT image, or to perform a freeform histogram
805
%  (level) adjustment according to the (typically gray-scale) gradient in the
806
%  CLUT image.
807
%
808
%  When the 'channel' mask includes the matte/alpha transparency channel but
809
%  one image has no such channel it is assumed that image is a simple
810
%  gray-scale image that will effect the alpha channel values, either for
811
%  gray-scale coloring (with transparent or semi-transparent colors), or
812
%  a histogram adjustment of existing alpha channel values.   If both images
813
%  have matte channels, direct and normal indexing is applied, which is rarely
814
%  used.
815
%
816
%  The format of the ClutImage method is:
817
%
818
%      MagickBooleanType ClutImage(Image *image,Image *clut_image,
819
%        const PixelInterpolateMethod method,ExceptionInfo *exception)
820
%
821
%  A description of each parameter follows:
822
%
823
%    o image: the image, which is replaced by indexed CLUT values
824
%
825
%    o clut_image: the color lookup table image for replacement color values.
826
%
827
%    o method: the pixel interpolation method.
828
%
829
%    o exception: return any errors or warnings in this structure.
830
%
831
*/
832
MagickExport MagickBooleanType ClutImage(Image *image,const Image *clut_image,
833
  const PixelInterpolateMethod method,ExceptionInfo *exception)
834
0
{
835
0
#define ClutImageTag  "Clut/Image"
836
837
0
  CacheView
838
0
    *clut_view,
839
0
    *image_view;
840
841
0
  MagickBooleanType
842
0
    status;
843
844
0
  MagickOffsetType
845
0
    progress;
846
847
0
  PixelInfo
848
0
    *clut_map;
849
850
0
  ssize_t
851
0
    adjust,
852
0
    i,
853
0
    y;
854
855
0
  assert(image != (Image *) NULL);
856
0
  assert(image->signature == MagickCoreSignature);
857
0
  assert(clut_image != (Image *) NULL);
858
0
  assert(clut_image->signature == MagickCoreSignature);
859
0
  if (IsEventLogging() != MagickFalse)
860
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
861
0
  if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
862
0
    return(MagickFalse);
863
0
  if ((IsGrayColorspace(image->colorspace) != MagickFalse) &&
864
0
      (IsGrayColorspace(clut_image->colorspace) == MagickFalse))
865
0
    (void) SetImageColorspace(image,sRGBColorspace,exception);
866
0
  clut_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*clut_map));
867
0
  if (clut_map == (PixelInfo *) NULL)
868
0
    ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
869
0
      image->filename);
870
  /*
871
    Clut image.
872
  */
873
0
  status=MagickTrue;
874
0
  progress=0;
875
0
  adjust=(ssize_t) (method == IntegerInterpolatePixel ? 0 : 1);
876
0
  clut_view=AcquireVirtualCacheView(clut_image,exception);
877
0
  for (i=0; i <= (ssize_t) MaxMap; i++)
878
0
  {
879
0
    GetPixelInfo(clut_image,clut_map+i);
880
0
    status=InterpolatePixelInfo(clut_image,clut_view,method,(double) i*
881
0
      ((double) clut_image->columns-adjust)/MaxMap,(double) i*
882
0
      ((double) clut_image->rows-adjust)/MaxMap,clut_map+i,exception);
883
0
    if (status == MagickFalse)
884
0
      break;
885
0
  }
886
0
  clut_view=DestroyCacheView(clut_view);
887
0
  image_view=AcquireAuthenticCacheView(image,exception);
888
#if defined(MAGICKCORE_OPENMP_SUPPORT)
889
  #pragma omp parallel for schedule(static) shared(progress,status) \
890
    magick_number_threads(image,image,image->rows,1)
891
#endif
892
0
  for (y=0; y < (ssize_t) image->rows; y++)
893
0
  {
894
0
    PixelInfo
895
0
      pixel;
896
897
0
    Quantum
898
0
      *magick_restrict q;
899
900
0
    ssize_t
901
0
      x;
902
903
0
    if (status == MagickFalse)
904
0
      continue;
905
0
    q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
906
0
    if (q == (Quantum *) NULL)
907
0
      {
908
0
        status=MagickFalse;
909
0
        continue;
910
0
      }
911
0
    GetPixelInfo(image,&pixel);
912
0
    for (x=0; x < (ssize_t) image->columns; x++)
913
0
    {
914
0
      PixelTrait
915
0
        traits;
916
917
0
      GetPixelInfoPixel(image,q,&pixel);
918
0
      traits=GetPixelChannelTraits(image,RedPixelChannel);
919
0
      if ((traits & UpdatePixelTrait) != 0)
920
0
        pixel.red=clut_map[ScaleQuantumToMap(ClampToQuantum(
921
0
          pixel.red))].red;
922
0
      traits=GetPixelChannelTraits(image,GreenPixelChannel);
923
0
      if ((traits & UpdatePixelTrait) != 0)
924
0
        pixel.green=clut_map[ScaleQuantumToMap(ClampToQuantum(
925
0
          pixel.green))].green;
926
0
      traits=GetPixelChannelTraits(image,BluePixelChannel);
927
0
      if ((traits & UpdatePixelTrait) != 0)
928
0
        pixel.blue=clut_map[ScaleQuantumToMap(ClampToQuantum(
929
0
          pixel.blue))].blue;
930
0
      traits=GetPixelChannelTraits(image,BlackPixelChannel);
931
0
      if ((traits & UpdatePixelTrait) != 0)
932
0
        pixel.black=clut_map[ScaleQuantumToMap(ClampToQuantum(
933
0
          pixel.black))].black;
934
0
      traits=GetPixelChannelTraits(image,AlphaPixelChannel);
935
0
      if ((traits & UpdatePixelTrait) != 0)
936
0
        pixel.alpha=clut_map[ScaleQuantumToMap(ClampToQuantum(
937
0
          pixel.alpha))].alpha;
938
0
      SetPixelViaPixelInfo(image,&pixel,q);
939
0
      q+=(ptrdiff_t) GetPixelChannels(image);
940
0
    }
941
0
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
942
0
      status=MagickFalse;
943
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
944
0
      {
945
0
        MagickBooleanType
946
0
          proceed;
947
948
#if defined(MAGICKCORE_OPENMP_SUPPORT)
949
        #pragma omp atomic
950
#endif
951
0
        progress++;
952
0
        proceed=SetImageProgress(image,ClutImageTag,progress,image->rows);
953
0
        if (proceed == MagickFalse)
954
0
          status=MagickFalse;
955
0
      }
956
0
  }
957
0
  image_view=DestroyCacheView(image_view);
958
0
  clut_map=(PixelInfo *) RelinquishMagickMemory(clut_map);
959
0
  if ((clut_image->alpha_trait != UndefinedPixelTrait) &&
960
0
      ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0))
961
0
    (void) SetImageAlphaChannel(image,ActivateAlphaChannel,exception);
962
0
  return(status);
963
0
}
964

965
/*
966
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
967
%                                                                             %
968
%                                                                             %
969
%                                                                             %
970
%     C o l o r D e c i s i o n L i s t I m a g e                             %
971
%                                                                             %
972
%                                                                             %
973
%                                                                             %
974
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
975
%
976
%  ColorDecisionListImage() accepts a lightweight Color Correction Collection
977
%  (CCC) file which solely contains one or more color corrections and applies
978
%  the correction to the image.  Here is a sample CCC file:
979
%
980
%    <ColorCorrectionCollection xmlns="urn:ASC:CDL:v1.2">
981
%          <ColorCorrection id="cc03345">
982
%                <SOPNode>
983
%                     <Slope> 0.9 1.2 0.5 </Slope>
984
%                     <Offset> 0.4 -0.5 0.6 </Offset>
985
%                     <Power> 1.0 0.8 1.5 </Power>
986
%                </SOPNode>
987
%                <SATNode>
988
%                     <Saturation> 0.85 </Saturation>
989
%                </SATNode>
990
%          </ColorCorrection>
991
%    </ColorCorrectionCollection>
992
%
993
%  which includes the slop, offset, and power for each of the RGB channels
994
%  as well as the saturation.
995
%
996
%  The format of the ColorDecisionListImage method is:
997
%
998
%      MagickBooleanType ColorDecisionListImage(Image *image,
999
%        const char *color_correction_collection,ExceptionInfo *exception)
1000
%
1001
%  A description of each parameter follows:
1002
%
1003
%    o image: the image.
1004
%
1005
%    o color_correction_collection: the color correction collection in XML.
1006
%
1007
%    o exception: return any errors or warnings in this structure.
1008
%
1009
*/
1010
MagickExport MagickBooleanType ColorDecisionListImage(Image *image,
1011
  const char *color_correction_collection,ExceptionInfo *exception)
1012
0
{
1013
0
#define ColorDecisionListCorrectImageTag  "ColorDecisionList/Image"
1014
1015
0
  typedef struct _Correction
1016
0
  {
1017
0
    double
1018
0
      slope,
1019
0
      offset,
1020
0
      power;
1021
0
  } Correction;
1022
1023
0
  typedef struct _ColorCorrection
1024
0
  {
1025
0
    Correction
1026
0
      red,
1027
0
      green,
1028
0
      blue;
1029
1030
0
    double
1031
0
      saturation;
1032
0
  } ColorCorrection;
1033
1034
0
  CacheView
1035
0
    *image_view;
1036
1037
0
  char
1038
0
    token[MagickPathExtent];
1039
1040
0
  ColorCorrection
1041
0
    color_correction;
1042
1043
0
  const char
1044
0
    *content,
1045
0
    *p;
1046
1047
0
  MagickBooleanType
1048
0
    status;
1049
1050
0
  MagickOffsetType
1051
0
    progress;
1052
1053
0
  PixelInfo
1054
0
    *cdl_map;
1055
1056
0
  ssize_t
1057
0
    i;
1058
1059
0
  ssize_t
1060
0
    y;
1061
1062
0
  XMLTreeInfo
1063
0
    *cc,
1064
0
    *ccc,
1065
0
    *sat,
1066
0
    *sop;
1067
1068
  /*
1069
    Allocate and initialize cdl maps.
1070
  */
1071
0
  assert(image != (Image *) NULL);
1072
0
  assert(image->signature == MagickCoreSignature);
1073
0
  if (IsEventLogging() != MagickFalse)
1074
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1075
0
  if (color_correction_collection == (const char *) NULL)
1076
0
    return(MagickFalse);
1077
0
  ccc=NewXMLTree((const char *) color_correction_collection,exception);
1078
0
  if (ccc == (XMLTreeInfo *) NULL)
1079
0
    return(MagickFalse);
1080
0
  cc=GetXMLTreeChild(ccc,"ColorCorrection");
1081
0
  if (cc == (XMLTreeInfo *) NULL)
1082
0
    {
1083
0
      ccc=DestroyXMLTree(ccc);
1084
0
      return(MagickFalse);
1085
0
    }
1086
0
  color_correction.red.slope=1.0;
1087
0
  color_correction.red.offset=0.0;
1088
0
  color_correction.red.power=1.0;
1089
0
  color_correction.green.slope=1.0;
1090
0
  color_correction.green.offset=0.0;
1091
0
  color_correction.green.power=1.0;
1092
0
  color_correction.blue.slope=1.0;
1093
0
  color_correction.blue.offset=0.0;
1094
0
  color_correction.blue.power=1.0;
1095
0
  color_correction.saturation=0.0;
1096
0
  sop=GetXMLTreeChild(cc,"SOPNode");
1097
0
  if (sop != (XMLTreeInfo *) NULL)
1098
0
    {
1099
0
      XMLTreeInfo
1100
0
        *offset,
1101
0
        *power,
1102
0
        *slope;
1103
1104
0
      slope=GetXMLTreeChild(sop,"Slope");
1105
0
      if (slope != (XMLTreeInfo *) NULL)
1106
0
        {
1107
0
          content=GetXMLTreeContent(slope);
1108
0
          p=(const char *) content;
1109
0
          for (i=0; (*p != '\0') && (i < 3); i++)
1110
0
          {
1111
0
            (void) GetNextToken(p,&p,MagickPathExtent,token);
1112
0
            if (*token == ',')
1113
0
              (void) GetNextToken(p,&p,MagickPathExtent,token);
1114
0
            switch (i)
1115
0
            {
1116
0
              case 0:
1117
0
              {
1118
0
                color_correction.red.slope=StringToDouble(token,(char **) NULL);
1119
0
                break;
1120
0
              }
1121
0
              case 1:
1122
0
              {
1123
0
                color_correction.green.slope=StringToDouble(token,
1124
0
                  (char **) NULL);
1125
0
                break;
1126
0
              }
1127
0
              case 2:
1128
0
              {
1129
0
                color_correction.blue.slope=StringToDouble(token,
1130
0
                  (char **) NULL);
1131
0
                break;
1132
0
              }
1133
0
            }
1134
0
          }
1135
0
        }
1136
0
      offset=GetXMLTreeChild(sop,"Offset");
1137
0
      if (offset != (XMLTreeInfo *) NULL)
1138
0
        {
1139
0
          content=GetXMLTreeContent(offset);
1140
0
          p=(const char *) content;
1141
0
          for (i=0; (*p != '\0') && (i < 3); i++)
1142
0
          {
1143
0
            (void) GetNextToken(p,&p,MagickPathExtent,token);
1144
0
            if (*token == ',')
1145
0
              (void) GetNextToken(p,&p,MagickPathExtent,token);
1146
0
            switch (i)
1147
0
            {
1148
0
              case 0:
1149
0
              {
1150
0
                color_correction.red.offset=StringToDouble(token,
1151
0
                  (char **) NULL);
1152
0
                break;
1153
0
              }
1154
0
              case 1:
1155
0
              {
1156
0
                color_correction.green.offset=StringToDouble(token,
1157
0
                  (char **) NULL);
1158
0
                break;
1159
0
              }
1160
0
              case 2:
1161
0
              {
1162
0
                color_correction.blue.offset=StringToDouble(token,
1163
0
                  (char **) NULL);
1164
0
                break;
1165
0
              }
1166
0
            }
1167
0
          }
1168
0
        }
1169
0
      power=GetXMLTreeChild(sop,"Power");
1170
0
      if (power != (XMLTreeInfo *) NULL)
1171
0
        {
1172
0
          content=GetXMLTreeContent(power);
1173
0
          p=(const char *) content;
1174
0
          for (i=0; (*p != '\0') && (i < 3); i++)
1175
0
          {
1176
0
            (void) GetNextToken(p,&p,MagickPathExtent,token);
1177
0
            if (*token == ',')
1178
0
              (void) GetNextToken(p,&p,MagickPathExtent,token);
1179
0
            switch (i)
1180
0
            {
1181
0
              case 0:
1182
0
              {
1183
0
                color_correction.red.power=StringToDouble(token,(char **) NULL);
1184
0
                break;
1185
0
              }
1186
0
              case 1:
1187
0
              {
1188
0
                color_correction.green.power=StringToDouble(token,
1189
0
                  (char **) NULL);
1190
0
                break;
1191
0
              }
1192
0
              case 2:
1193
0
              {
1194
0
                color_correction.blue.power=StringToDouble(token,
1195
0
                  (char **) NULL);
1196
0
                break;
1197
0
              }
1198
0
            }
1199
0
          }
1200
0
        }
1201
0
    }
1202
0
  sat=GetXMLTreeChild(cc,"SATNode");
1203
0
  if (sat != (XMLTreeInfo *) NULL)
1204
0
    {
1205
0
      XMLTreeInfo
1206
0
        *saturation;
1207
1208
0
      saturation=GetXMLTreeChild(sat,"Saturation");
1209
0
      if (saturation != (XMLTreeInfo *) NULL)
1210
0
        {
1211
0
          content=GetXMLTreeContent(saturation);
1212
0
          p=(const char *) content;
1213
0
          (void) GetNextToken(p,&p,MagickPathExtent,token);
1214
0
          color_correction.saturation=StringToDouble(token,(char **) NULL);
1215
0
        }
1216
0
    }
1217
0
  ccc=DestroyXMLTree(ccc);
1218
0
  if (image->debug != MagickFalse)
1219
0
    {
1220
0
      (void) LogMagickEvent(TransformEvent,GetMagickModule(),
1221
0
        "  Color Correction Collection:");
1222
0
      (void) LogMagickEvent(TransformEvent,GetMagickModule(),
1223
0
        "  color_correction.red.slope: %g",color_correction.red.slope);
1224
0
      (void) LogMagickEvent(TransformEvent,GetMagickModule(),
1225
0
        "  color_correction.red.offset: %g",color_correction.red.offset);
1226
0
      (void) LogMagickEvent(TransformEvent,GetMagickModule(),
1227
0
        "  color_correction.red.power: %g",color_correction.red.power);
1228
0
      (void) LogMagickEvent(TransformEvent,GetMagickModule(),
1229
0
        "  color_correction.green.slope: %g",color_correction.green.slope);
1230
0
      (void) LogMagickEvent(TransformEvent,GetMagickModule(),
1231
0
        "  color_correction.green.offset: %g",color_correction.green.offset);
1232
0
      (void) LogMagickEvent(TransformEvent,GetMagickModule(),
1233
0
        "  color_correction.green.power: %g",color_correction.green.power);
1234
0
      (void) LogMagickEvent(TransformEvent,GetMagickModule(),
1235
0
        "  color_correction.blue.slope: %g",color_correction.blue.slope);
1236
0
      (void) LogMagickEvent(TransformEvent,GetMagickModule(),
1237
0
        "  color_correction.blue.offset: %g",color_correction.blue.offset);
1238
0
      (void) LogMagickEvent(TransformEvent,GetMagickModule(),
1239
0
        "  color_correction.blue.power: %g",color_correction.blue.power);
1240
0
      (void) LogMagickEvent(TransformEvent,GetMagickModule(),
1241
0
        "  color_correction.saturation: %g",color_correction.saturation);
1242
0
    }
1243
0
  cdl_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*cdl_map));
1244
0
  if (cdl_map == (PixelInfo *) NULL)
1245
0
    ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1246
0
      image->filename);
1247
0
  for (i=0; i <= (ssize_t) MaxMap; i++)
1248
0
  {
1249
0
    cdl_map[i].red=(double) ScaleMapToQuantum((double)
1250
0
      (MaxMap*(pow(color_correction.red.slope*i/MaxMap+
1251
0
      color_correction.red.offset,color_correction.red.power))));
1252
0
    cdl_map[i].green=(double) ScaleMapToQuantum((double)
1253
0
      (MaxMap*(pow(color_correction.green.slope*i/MaxMap+
1254
0
      color_correction.green.offset,color_correction.green.power))));
1255
0
    cdl_map[i].blue=(double) ScaleMapToQuantum((double)
1256
0
      (MaxMap*(pow(color_correction.blue.slope*i/MaxMap+
1257
0
      color_correction.blue.offset,color_correction.blue.power))));
1258
0
  }
1259
0
  if (image->storage_class == PseudoClass)
1260
0
    for (i=0; i < (ssize_t) image->colors; i++)
1261
0
    {
1262
      /*
1263
        Apply transfer function to colormap.
1264
      */
1265
0
      double
1266
0
        luma;
1267
1268
0
      luma=0.21267*image->colormap[i].red+0.71526*image->colormap[i].green+
1269
0
        0.07217*image->colormap[i].blue;
1270
0
      image->colormap[i].red=luma+color_correction.saturation*cdl_map[
1271
0
        ScaleQuantumToMap(ClampToQuantum(image->colormap[i].red))].red-luma;
1272
0
      image->colormap[i].green=luma+color_correction.saturation*cdl_map[
1273
0
        ScaleQuantumToMap(ClampToQuantum(image->colormap[i].green))].green-luma;
1274
0
      image->colormap[i].blue=luma+color_correction.saturation*cdl_map[
1275
0
        ScaleQuantumToMap(ClampToQuantum(image->colormap[i].blue))].blue-luma;
1276
0
    }
1277
  /*
1278
    Apply transfer function to image.
1279
  */
1280
0
  status=MagickTrue;
1281
0
  progress=0;
1282
0
  image_view=AcquireAuthenticCacheView(image,exception);
1283
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1284
  #pragma omp parallel for schedule(static) shared(progress,status) \
1285
    magick_number_threads(image,image,image->rows,1)
1286
#endif
1287
0
  for (y=0; y < (ssize_t) image->rows; y++)
1288
0
  {
1289
0
    double
1290
0
      luma;
1291
1292
0
    Quantum
1293
0
      *magick_restrict q;
1294
1295
0
    ssize_t
1296
0
      x;
1297
1298
0
    if (status == MagickFalse)
1299
0
      continue;
1300
0
    q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1301
0
    if (q == (Quantum *) NULL)
1302
0
      {
1303
0
        status=MagickFalse;
1304
0
        continue;
1305
0
      }
1306
0
    for (x=0; x < (ssize_t) image->columns; x++)
1307
0
    {
1308
0
      luma=0.21267*(double) GetPixelRed(image,q)+0.71526*(double)
1309
0
        GetPixelGreen(image,q)+0.07217*(double) GetPixelBlue(image,q);
1310
0
      SetPixelRed(image,ClampToQuantum(luma+color_correction.saturation*
1311
0
        (cdl_map[ScaleQuantumToMap(GetPixelRed(image,q))].red-luma)),q);
1312
0
      SetPixelGreen(image,ClampToQuantum(luma+color_correction.saturation*
1313
0
        (cdl_map[ScaleQuantumToMap(GetPixelGreen(image,q))].green-luma)),q);
1314
0
      SetPixelBlue(image,ClampToQuantum(luma+color_correction.saturation*
1315
0
        (cdl_map[ScaleQuantumToMap(GetPixelBlue(image,q))].blue-luma)),q);
1316
0
      q+=(ptrdiff_t) GetPixelChannels(image);
1317
0
    }
1318
0
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1319
0
      status=MagickFalse;
1320
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
1321
0
      {
1322
0
        MagickBooleanType
1323
0
          proceed;
1324
1325
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1326
        #pragma omp atomic
1327
#endif
1328
0
        progress++;
1329
0
        proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
1330
0
          progress,image->rows);
1331
0
        if (proceed == MagickFalse)
1332
0
          status=MagickFalse;
1333
0
      }
1334
0
  }
1335
0
  image_view=DestroyCacheView(image_view);
1336
0
  cdl_map=(PixelInfo *) RelinquishMagickMemory(cdl_map);
1337
0
  return(status);
1338
0
}
1339

1340
/*
1341
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1342
%                                                                             %
1343
%                                                                             %
1344
%                                                                             %
1345
%     C o n t r a s t I m a g e                                               %
1346
%                                                                             %
1347
%                                                                             %
1348
%                                                                             %
1349
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1350
%
1351
%  ContrastImage() enhances the intensity differences between the lighter and
1352
%  darker elements of the image.  Set sharpen to a MagickTrue to increase the
1353
%  image contrast otherwise the contrast is reduced.
1354
%
1355
%  The format of the ContrastImage method is:
1356
%
1357
%      MagickBooleanType ContrastImage(Image *image,
1358
%        const MagickBooleanType sharpen,ExceptionInfo *exception)
1359
%
1360
%  A description of each parameter follows:
1361
%
1362
%    o image: the image.
1363
%
1364
%    o sharpen: Increase or decrease image contrast.
1365
%
1366
%    o exception: return any errors or warnings in this structure.
1367
%
1368
*/
1369
1370
static inline void Contrast(const int sign,double *red,double *green,
1371
  double *blue)
1372
0
{
1373
0
  double
1374
0
    brightness = 0.0,
1375
0
    hue = 0.0,
1376
0
    saturation = 0.0;
1377
1378
  /*
1379
    Enhance contrast: dark color become darker, light color become lighter.
1380
  */
1381
0
  ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
1382
0
  brightness+=0.5*sign*(0.5*(sin((double) (MagickPI*(brightness-0.5)))+1.0)-
1383
0
    brightness);
1384
0
  if (brightness > 1.0)
1385
0
    brightness=1.0;
1386
0
  else
1387
0
    if (brightness < 0.0)
1388
0
      brightness=0.0;
1389
0
  ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
1390
0
}
1391
1392
MagickExport MagickBooleanType ContrastImage(Image *image,
1393
  const MagickBooleanType sharpen,ExceptionInfo *exception)
1394
0
{
1395
0
#define ContrastImageTag  "Contrast/Image"
1396
1397
0
  CacheView
1398
0
    *image_view;
1399
1400
0
  int
1401
0
    sign;
1402
1403
0
  MagickBooleanType
1404
0
    status;
1405
1406
0
  MagickOffsetType
1407
0
    progress;
1408
1409
0
  ssize_t
1410
0
    i;
1411
1412
0
  ssize_t
1413
0
    y;
1414
1415
0
  assert(image != (Image *) NULL);
1416
0
  assert(image->signature == MagickCoreSignature);
1417
#if defined(MAGICKCORE_OPENCL_SUPPORT)
1418
  if (AccelerateContrastImage(image,sharpen,exception) != MagickFalse)
1419
    return(MagickTrue);
1420
#endif
1421
0
  if (IsEventLogging() != MagickFalse)
1422
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1423
0
  sign=sharpen != MagickFalse ? 1 : -1;
1424
0
  if (image->storage_class == PseudoClass)
1425
0
    {
1426
      /*
1427
        Contrast enhance colormap.
1428
      */
1429
0
      for (i=0; i < (ssize_t) image->colors; i++)
1430
0
      {
1431
0
        double
1432
0
          blue,
1433
0
          green,
1434
0
          red;
1435
1436
0
        red=(double) image->colormap[i].red;
1437
0
        green=(double) image->colormap[i].green;
1438
0
        blue=(double) image->colormap[i].blue;
1439
0
        Contrast(sign,&red,&green,&blue);
1440
0
        image->colormap[i].red=(MagickRealType) red;
1441
0
        image->colormap[i].green=(MagickRealType) green;
1442
0
        image->colormap[i].blue=(MagickRealType) blue;
1443
0
      }
1444
0
    }
1445
  /*
1446
    Contrast enhance image.
1447
  */
1448
0
  status=MagickTrue;
1449
0
  progress=0;
1450
0
  image_view=AcquireAuthenticCacheView(image,exception);
1451
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1452
  #pragma omp parallel for schedule(static) shared(progress,status) \
1453
    magick_number_threads(image,image,image->rows,1)
1454
#endif
1455
0
  for (y=0; y < (ssize_t) image->rows; y++)
1456
0
  {
1457
0
    double
1458
0
      blue,
1459
0
      green,
1460
0
      red;
1461
1462
0
    Quantum
1463
0
      *magick_restrict q;
1464
1465
0
    ssize_t
1466
0
      x;
1467
1468
0
    if (status == MagickFalse)
1469
0
      continue;
1470
0
    q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1471
0
    if (q == (Quantum *) NULL)
1472
0
      {
1473
0
        status=MagickFalse;
1474
0
        continue;
1475
0
      }
1476
0
    for (x=0; x < (ssize_t) image->columns; x++)
1477
0
    {
1478
0
      red=(double) GetPixelRed(image,q);
1479
0
      green=(double) GetPixelGreen(image,q);
1480
0
      blue=(double) GetPixelBlue(image,q);
1481
0
      Contrast(sign,&red,&green,&blue);
1482
0
      SetPixelRed(image,ClampToQuantum(red),q);
1483
0
      SetPixelGreen(image,ClampToQuantum(green),q);
1484
0
      SetPixelBlue(image,ClampToQuantum(blue),q);
1485
0
      q+=(ptrdiff_t) GetPixelChannels(image);
1486
0
    }
1487
0
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1488
0
      status=MagickFalse;
1489
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
1490
0
      {
1491
0
        MagickBooleanType
1492
0
          proceed;
1493
1494
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1495
        #pragma omp atomic
1496
#endif
1497
0
        progress++;
1498
0
        proceed=SetImageProgress(image,ContrastImageTag,progress,image->rows);
1499
0
        if (proceed == MagickFalse)
1500
0
          status=MagickFalse;
1501
0
      }
1502
0
  }
1503
0
  image_view=DestroyCacheView(image_view);
1504
0
  return(status);
1505
0
}
1506

1507
/*
1508
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1509
%                                                                             %
1510
%                                                                             %
1511
%                                                                             %
1512
%     C o n t r a s t S t r e t c h I m a g e                                 %
1513
%                                                                             %
1514
%                                                                             %
1515
%                                                                             %
1516
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1517
%
1518
%  ContrastStretchImage() is a simple image enhancement technique that attempts
1519
%  to improve the contrast in an image by 'stretching' the range of intensity
1520
%  values it contains to span a desired range of values. It differs from the
1521
%  more sophisticated histogram equalization in that it can only apply a
1522
%  linear scaling function to the image pixel values.  As a result the
1523
%  'enhancement' is less harsh.
1524
%
1525
%  The format of the ContrastStretchImage method is:
1526
%
1527
%      MagickBooleanType ContrastStretchImage(Image *image,
1528
%        const char *levels,ExceptionInfo *exception)
1529
%
1530
%  A description of each parameter follows:
1531
%
1532
%    o image: the image.
1533
%
1534
%    o black_point: the black point.
1535
%
1536
%    o white_point: the white point.
1537
%
1538
%    o levels: Specify the levels where the black and white points have the
1539
%      range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
1540
%
1541
%    o exception: return any errors or warnings in this structure.
1542
%
1543
*/
1544
MagickExport MagickBooleanType ContrastStretchImage(Image *image,
1545
  const double black_point,const double white_point,ExceptionInfo *exception)
1546
2.03k
{
1547
2.03k
#define ContrastStretchImageTag  "ContrastStretch/Image"
1548
1549
2.03k
  CacheView
1550
2.03k
    *image_view;
1551
1552
2.03k
  char
1553
2.03k
    property[MagickPathExtent];
1554
1555
2.03k
  double
1556
2.03k
    *histogram;
1557
1558
2.03k
  ImageType
1559
2.03k
    type;
1560
1561
2.03k
  MagickBooleanType
1562
2.03k
    status;
1563
1564
2.03k
  MagickOffsetType
1565
2.03k
    progress;
1566
1567
2.03k
  Quantum
1568
2.03k
    *black,
1569
2.03k
    *stretch_map,
1570
2.03k
    *white;
1571
1572
2.03k
  ssize_t
1573
2.03k
    i;
1574
1575
2.03k
  ssize_t
1576
2.03k
    y;
1577
1578
  /*
1579
    Allocate histogram and stretch map.
1580
  */
1581
2.03k
  assert(image != (Image *) NULL);
1582
2.03k
  assert(image->signature == MagickCoreSignature);
1583
2.03k
  if (IsEventLogging() != MagickFalse)
1584
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1585
2.03k
  type=IdentifyImageType(image,exception);
1586
2.03k
  if (IsGrayImageType(type) != MagickFalse)
1587
2.03k
    (void) SetImageColorspace(image,GRAYColorspace,exception);
1588
2.03k
  black=(Quantum *) AcquireQuantumMemory(MaxPixelChannels,sizeof(*black));
1589
2.03k
  white=(Quantum *) AcquireQuantumMemory(MaxPixelChannels,sizeof(*white));
1590
2.03k
  stretch_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,MaxPixelChannels*
1591
2.03k
    sizeof(*stretch_map));
1592
2.03k
  histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,MaxPixelChannels*
1593
2.03k
    sizeof(*histogram));
1594
2.03k
  if ((black == (Quantum *) NULL) || (white == (Quantum *) NULL) ||
1595
2.03k
      (stretch_map == (Quantum *) NULL) || (histogram == (double *) NULL))
1596
0
    {
1597
0
      if (histogram != (double *) NULL)
1598
0
        histogram=(double *) RelinquishMagickMemory(histogram);
1599
0
      if (stretch_map != (Quantum *) NULL)
1600
0
        stretch_map=(Quantum *) RelinquishMagickMemory(stretch_map);
1601
0
      if (white != (Quantum *) NULL)
1602
0
        white=(Quantum *) RelinquishMagickMemory(white);
1603
0
      if (black != (Quantum *) NULL)
1604
0
        black=(Quantum *) RelinquishMagickMemory(black);
1605
0
      ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1606
0
        image->filename);
1607
0
    }
1608
  /*
1609
    Form histogram.
1610
  */
1611
2.03k
  status=MagickTrue;
1612
2.03k
  (void) memset(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1613
2.03k
    sizeof(*histogram));
1614
2.03k
  image_view=AcquireVirtualCacheView(image,exception);
1615
255k
  for (y=0; y < (ssize_t) image->rows; y++)
1616
253k
  {
1617
253k
    const Quantum
1618
253k
      *magick_restrict p;
1619
1620
253k
    ssize_t
1621
253k
      x;
1622
1623
253k
    if (status == MagickFalse)
1624
0
      continue;
1625
253k
    p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1626
253k
    if (p == (const Quantum *) NULL)
1627
0
      {
1628
0
        status=MagickFalse;
1629
0
        continue;
1630
0
      }
1631
104M
    for (x=0; x < (ssize_t) image->columns; x++)
1632
104M
    {
1633
104M
      double
1634
104M
        pixel;
1635
1636
104M
      pixel=GetPixelIntensity(image,p);
1637
308M
      for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1638
203M
      {
1639
203M
        if (image->channel_mask != AllChannels)
1640
0
          pixel=(double) p[i];
1641
203M
        histogram[GetPixelChannels(image)*ScaleQuantumToMap(
1642
203M
          ClampToQuantum(pixel))+(size_t) i]++;
1643
203M
      }
1644
104M
      p+=(ptrdiff_t) GetPixelChannels(image);
1645
104M
    }
1646
253k
  }
1647
2.03k
  image_view=DestroyCacheView(image_view);
1648
  /*
1649
    Find the histogram boundaries by locating the black/white levels.
1650
  */
1651
4.48k
  for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1652
2.45k
  {
1653
2.45k
    double
1654
2.45k
      intensity;
1655
1656
2.45k
    ssize_t
1657
2.45k
      j;
1658
1659
2.45k
    black[i]=(Quantum) 0;
1660
2.45k
    white[i]=(Quantum) ScaleQuantumToMap(QuantumRange);
1661
2.45k
    intensity=0.0;
1662
33.5M
    for (j=0; j <= (ssize_t) MaxMap; j++)
1663
33.5M
    {
1664
33.5M
      intensity+=histogram[(ssize_t) GetPixelChannels(image)*j+i];
1665
33.5M
      if (intensity > black_point)
1666
2.45k
        break;
1667
33.5M
    }
1668
2.45k
    black[i]=(Quantum) j;
1669
2.45k
    intensity=0.0;
1670
34.3M
    for (j=(ssize_t) MaxMap; j != 0; j--)
1671
34.3M
    {
1672
34.3M
      intensity+=histogram[(ssize_t) GetPixelChannels(image)*j+i];
1673
34.3M
      if (intensity > ((double) image->columns*image->rows-white_point))
1674
1.93k
        break;
1675
34.3M
    }
1676
2.45k
    white[i]=(Quantum) j;
1677
2.45k
  }
1678
2.03k
  histogram=(double *) RelinquishMagickMemory(histogram);
1679
  /*
1680
    Stretch the histogram to create the stretched image mapping.
1681
  */
1682
2.03k
  (void) memset(stretch_map,0,(MaxMap+1)*GetPixelChannels(image)*
1683
2.03k
    sizeof(*stretch_map));
1684
4.48k
  for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1685
2.45k
  {
1686
2.45k
    ssize_t
1687
2.45k
      j;
1688
1689
160M
    for (j=0; j <= (ssize_t) MaxMap; j++)
1690
160M
    {
1691
160M
      double
1692
160M
        gamma;
1693
1694
160M
      gamma=MagickSafeReciprocal(white[i]-black[i]);
1695
160M
      if (j < (ssize_t) black[i])
1696
33.5M
        stretch_map[(ssize_t) GetPixelChannels(image)*j+i]=(Quantum) 0;
1697
127M
      else
1698
127M
        if (j > (ssize_t) white[i])
1699
34.3M
          stretch_map[(ssize_t) GetPixelChannels(image)*j+i]=QuantumRange;
1700
92.9M
        else
1701
92.9M
          if (black[i] != white[i])
1702
92.9M
            stretch_map[(ssize_t) GetPixelChannels(image)*j+i]=
1703
92.9M
              ScaleMapToQuantum((double) (MaxMap*gamma*(j-(double) black[i])));
1704
160M
    }
1705
2.45k
  }
1706
2.03k
  if (image->storage_class == PseudoClass)
1707
280
    {
1708
280
      ssize_t
1709
280
        j;
1710
1711
      /*
1712
        Stretch-contrast colormap.
1713
      */
1714
33.3k
      for (j=0; j < (ssize_t) image->colors; j++)
1715
33.0k
      {
1716
33.0k
        if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1717
33.0k
          {
1718
33.0k
            i=GetPixelChannelOffset(image,RedPixelChannel);
1719
33.0k
            image->colormap[j].red=(MagickRealType) stretch_map[
1720
33.0k
              GetPixelChannels(image)*ScaleQuantumToMap(ClampToQuantum(
1721
33.0k
              image->colormap[j].red))+(size_t) i];
1722
33.0k
          }
1723
33.0k
        if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1724
33.0k
          {
1725
33.0k
            i=GetPixelChannelOffset(image,GreenPixelChannel);
1726
33.0k
            image->colormap[j].green=(MagickRealType) stretch_map[
1727
33.0k
              GetPixelChannels(image)*ScaleQuantumToMap(ClampToQuantum(
1728
33.0k
              image->colormap[j].green))+(size_t) i];
1729
33.0k
          }
1730
33.0k
        if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1731
33.0k
          {
1732
33.0k
            i=GetPixelChannelOffset(image,BluePixelChannel);
1733
33.0k
            image->colormap[j].blue=(MagickRealType) stretch_map[
1734
33.0k
              GetPixelChannels(image)*ScaleQuantumToMap(ClampToQuantum(
1735
33.0k
              image->colormap[j].blue))+(size_t) i];
1736
33.0k
          }
1737
33.0k
        if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1738
0
          {
1739
0
            i=GetPixelChannelOffset(image,AlphaPixelChannel);
1740
0
            image->colormap[j].alpha=(MagickRealType) stretch_map[
1741
0
              GetPixelChannels(image)*ScaleQuantumToMap(ClampToQuantum(
1742
0
              image->colormap[j].alpha))+(size_t) i];
1743
0
          }
1744
33.0k
      }
1745
280
    }
1746
  /*
1747
    Stretch-contrast image.
1748
  */
1749
2.03k
  status=MagickTrue;
1750
2.03k
  progress=0;
1751
2.03k
  image_view=AcquireAuthenticCacheView(image,exception);
1752
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1753
  #pragma omp parallel for schedule(static) shared(progress,status) \
1754
    magick_number_threads(image,image,image->rows,1)
1755
#endif
1756
255k
  for (y=0; y < (ssize_t) image->rows; y++)
1757
253k
  {
1758
253k
    Quantum
1759
253k
      *magick_restrict q;
1760
1761
253k
    ssize_t
1762
253k
      x;
1763
1764
253k
    if (status == MagickFalse)
1765
0
      continue;
1766
253k
    q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1767
253k
    if (q == (Quantum *) NULL)
1768
0
      {
1769
0
        status=MagickFalse;
1770
0
        continue;
1771
0
      }
1772
104M
    for (x=0; x < (ssize_t) image->columns; x++)
1773
104M
    {
1774
104M
      ssize_t
1775
104M
        j;
1776
1777
308M
      for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
1778
203M
      {
1779
203M
        PixelChannel channel = GetPixelChannelChannel(image,j);
1780
203M
        PixelTrait traits = GetPixelChannelTraits(image,channel);
1781
203M
        if ((traits & UpdatePixelTrait) == 0)
1782
99.0M
          continue;
1783
104M
        if (black[j] == white[j])
1784
100M
          continue;
1785
3.70M
        q[j]=ClampToQuantum(stretch_map[GetPixelChannels(image)*
1786
3.70M
          ScaleQuantumToMap(q[j])+(size_t) j]);
1787
3.70M
      }
1788
104M
      q+=(ptrdiff_t) GetPixelChannels(image);
1789
104M
    }
1790
253k
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1791
0
      status=MagickFalse;
1792
253k
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
1793
0
      {
1794
0
        MagickBooleanType
1795
0
          proceed;
1796
1797
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1798
        #pragma omp atomic
1799
#endif
1800
0
        progress++;
1801
0
        proceed=SetImageProgress(image,ContrastStretchImageTag,progress,
1802
0
          image->rows);
1803
0
        if (proceed == MagickFalse)
1804
0
          status=MagickFalse;
1805
0
      }
1806
253k
  }
1807
2.03k
  image_view=DestroyCacheView(image_view);
1808
2.03k
  (void) FormatLocaleString(property,MagickPathExtent,"%gx%g%%",100.0*
1809
2.03k
    QuantumScale*GetPixelIntensity(image,black),100.0*QuantumScale*
1810
2.03k
    GetPixelIntensity(image,white));
1811
2.03k
  (void) SetImageProperty(image,"histogram:contrast-stretch",property,
1812
2.03k
    exception);
1813
2.03k
  white=(Quantum *) RelinquishMagickMemory(white);
1814
2.03k
  black=(Quantum *) RelinquishMagickMemory(black);
1815
2.03k
  stretch_map=(Quantum *) RelinquishMagickMemory(stretch_map);
1816
2.03k
  return(status);
1817
2.03k
}
1818

1819
/*
1820
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1821
%                                                                             %
1822
%                                                                             %
1823
%                                                                             %
1824
%     E n h a n c e I m a g e                                                 %
1825
%                                                                             %
1826
%                                                                             %
1827
%                                                                             %
1828
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1829
%
1830
%  EnhanceImage() applies a digital filter that improves the quality of a
1831
%  noisy image.
1832
%
1833
%  The format of the EnhanceImage method is:
1834
%
1835
%      Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1836
%
1837
%  A description of each parameter follows:
1838
%
1839
%    o image: the image.
1840
%
1841
%    o exception: return any errors or warnings in this structure.
1842
%
1843
*/
1844
MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1845
0
{
1846
0
#define EnhanceImageTag  "Enhance/Image"
1847
0
#define EnhancePixel(weight) \
1848
0
  mean=QuantumScale*((double) GetPixelRed(image,r)+pixel.red)/2.0; \
1849
0
  distance=QuantumScale*((double) GetPixelRed(image,r)-pixel.red); \
1850
0
  distance_squared=(4.0+mean)*distance*distance; \
1851
0
  mean=QuantumScale*((double) GetPixelGreen(image,r)+pixel.green)/2.0; \
1852
0
  distance=QuantumScale*((double) GetPixelGreen(image,r)-pixel.green); \
1853
0
  distance_squared+=(7.0-mean)*distance*distance; \
1854
0
  mean=QuantumScale*((double) GetPixelBlue(image,r)+pixel.blue)/2.0; \
1855
0
  distance=QuantumScale*((double) GetPixelBlue(image,r)-pixel.blue); \
1856
0
  distance_squared+=(5.0-mean)*distance*distance; \
1857
0
  mean=QuantumScale*((double) GetPixelBlack(image,r)+pixel.black)/2.0; \
1858
0
  distance=QuantumScale*((double) GetPixelBlack(image,r)-pixel.black); \
1859
0
  distance_squared+=(5.0-mean)*distance*distance; \
1860
0
  mean=QuantumScale*((double) GetPixelAlpha(image,r)+pixel.alpha)/2.0; \
1861
0
  distance=QuantumScale*((double) GetPixelAlpha(image,r)-pixel.alpha); \
1862
0
  distance_squared+=(5.0-mean)*distance*distance; \
1863
0
  if (distance_squared < 0.069) \
1864
0
    { \
1865
0
      aggregate.red+=(weight)*(double) GetPixelRed(image,r); \
1866
0
      aggregate.green+=(weight)*(double) GetPixelGreen(image,r); \
1867
0
      aggregate.blue+=(weight)*(double) GetPixelBlue(image,r); \
1868
0
      aggregate.black+=(weight)*(double) GetPixelBlack(image,r); \
1869
0
      aggregate.alpha+=(weight)*(double) GetPixelAlpha(image,r); \
1870
0
      total_weight+=(weight); \
1871
0
    } \
1872
0
  r+=(ptrdiff_t) GetPixelChannels(image);
1873
1874
0
  CacheView
1875
0
    *enhance_view,
1876
0
    *image_view;
1877
1878
0
  Image
1879
0
    *enhance_image;
1880
1881
0
  MagickBooleanType
1882
0
    status;
1883
1884
0
  MagickOffsetType
1885
0
    progress;
1886
1887
0
  ssize_t
1888
0
    y;
1889
1890
  /*
1891
    Initialize enhanced image attributes.
1892
  */
1893
0
  assert(image != (const Image *) NULL);
1894
0
  assert(image->signature == MagickCoreSignature);
1895
0
  if (IsEventLogging() != MagickFalse)
1896
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1897
0
  assert(exception != (ExceptionInfo *) NULL);
1898
0
  assert(exception->signature == MagickCoreSignature);
1899
0
  enhance_image=CloneImage(image,0,0,MagickTrue,
1900
0
    exception);
1901
0
  if (enhance_image == (Image *) NULL)
1902
0
    return((Image *) NULL);
1903
0
  if (SetImageStorageClass(enhance_image,DirectClass,exception) == MagickFalse)
1904
0
    {
1905
0
      enhance_image=DestroyImage(enhance_image);
1906
0
      return((Image *) NULL);
1907
0
    }
1908
  /*
1909
    Enhance image.
1910
  */
1911
0
  status=MagickTrue;
1912
0
  progress=0;
1913
0
  image_view=AcquireVirtualCacheView(image,exception);
1914
0
  enhance_view=AcquireAuthenticCacheView(enhance_image,exception);
1915
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1916
  #pragma omp parallel for schedule(static) shared(progress,status) \
1917
    magick_number_threads(image,enhance_image,image->rows,1)
1918
#endif
1919
0
  for (y=0; y < (ssize_t) image->rows; y++)
1920
0
  {
1921
0
    PixelInfo
1922
0
      pixel;
1923
1924
0
    const Quantum
1925
0
      *magick_restrict p;
1926
1927
0
    Quantum
1928
0
      *magick_restrict q;
1929
1930
0
    ssize_t
1931
0
      x;
1932
1933
0
    ssize_t
1934
0
      center;
1935
1936
0
    if (status == MagickFalse)
1937
0
      continue;
1938
0
    p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1939
0
    q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1940
0
      exception);
1941
0
    if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1942
0
      {
1943
0
        status=MagickFalse;
1944
0
        continue;
1945
0
      }
1946
0
    center=(ssize_t) GetPixelChannels(image)*(2*((ssize_t) image->columns+4)+2);
1947
0
    GetPixelInfo(image,&pixel);
1948
0
    for (x=0; x < (ssize_t) image->columns; x++)
1949
0
    {
1950
0
      double
1951
0
        distance,
1952
0
        distance_squared,
1953
0
        mean,
1954
0
        total_weight;
1955
1956
0
      PixelInfo
1957
0
        aggregate;
1958
1959
0
      const Quantum
1960
0
        *magick_restrict r;
1961
1962
0
      GetPixelInfo(image,&aggregate);
1963
0
      total_weight=0.0;
1964
0
      GetPixelInfoPixel(image,p+center,&pixel);
1965
0
      r=p;
1966
0
      EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1967
0
        EnhancePixel(8.0); EnhancePixel(5.0);
1968
0
      r=p+GetPixelChannels(image)*(image->columns+4);
1969
0
      EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1970
0
        EnhancePixel(20.0); EnhancePixel(8.0);
1971
0
      r=p+2*GetPixelChannels(image)*(image->columns+4);
1972
0
      EnhancePixel(10.0); EnhancePixel(40.0); EnhancePixel(80.0);
1973
0
        EnhancePixel(40.0); EnhancePixel(10.0);
1974
0
      r=p+3*GetPixelChannels(image)*(image->columns+4);
1975
0
      EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1976
0
        EnhancePixel(20.0); EnhancePixel(8.0);
1977
0
      r=p+4*GetPixelChannels(image)*(image->columns+4);
1978
0
      EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1979
0
        EnhancePixel(8.0); EnhancePixel(5.0);
1980
0
      if (total_weight > MagickEpsilon)
1981
0
        {
1982
0
          pixel.red=((aggregate.red+total_weight/2.0)/total_weight);
1983
0
          pixel.green=((aggregate.green+total_weight/2.0)/total_weight);
1984
0
          pixel.blue=((aggregate.blue+total_weight/2.0)/total_weight);
1985
0
          pixel.black=((aggregate.black+total_weight/2.0)/total_weight);
1986
0
          pixel.alpha=((aggregate.alpha+total_weight/2.0)/total_weight);
1987
0
        }
1988
0
      SetPixelViaPixelInfo(enhance_image,&pixel,q);
1989
0
      p+=(ptrdiff_t) GetPixelChannels(image);
1990
0
      q+=(ptrdiff_t) GetPixelChannels(enhance_image);
1991
0
    }
1992
0
    if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1993
0
      status=MagickFalse;
1994
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
1995
0
      {
1996
0
        MagickBooleanType
1997
0
          proceed;
1998
1999
#if defined(MAGICKCORE_OPENMP_SUPPORT)
2000
        #pragma omp atomic
2001
#endif
2002
0
        progress++;
2003
0
        proceed=SetImageProgress(image,EnhanceImageTag,progress,image->rows);
2004
0
        if (proceed == MagickFalse)
2005
0
          status=MagickFalse;
2006
0
      }
2007
0
  }
2008
0
  enhance_view=DestroyCacheView(enhance_view);
2009
0
  image_view=DestroyCacheView(image_view);
2010
0
  if (status == MagickFalse)
2011
0
    enhance_image=DestroyImage(enhance_image);
2012
0
  return(enhance_image);
2013
0
}
2014

2015
/*
2016
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2017
%                                                                             %
2018
%                                                                             %
2019
%                                                                             %
2020
%     E q u a l i z e I m a g e                                               %
2021
%                                                                             %
2022
%                                                                             %
2023
%                                                                             %
2024
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2025
%
2026
%  EqualizeImage() applies a histogram equalization to the image.
2027
%
2028
%  The format of the EqualizeImage method is:
2029
%
2030
%      MagickBooleanType EqualizeImage(Image *image,ExceptionInfo *exception)
2031
%
2032
%  A description of each parameter follows:
2033
%
2034
%    o image: the image.
2035
%
2036
%    o exception: return any errors or warnings in this structure.
2037
%
2038
*/
2039
MagickExport MagickBooleanType EqualizeImage(Image *image,
2040
  ExceptionInfo *exception)
2041
0
{
2042
0
#define EqualizeImageTag  "Equalize/Image"
2043
2044
0
  CacheView
2045
0
    *image_view;
2046
2047
0
  double
2048
0
    black[2*CompositePixelChannel+1],
2049
0
    *equalize_map,
2050
0
    *histogram,
2051
0
    *map,
2052
0
    white[2*CompositePixelChannel+1];
2053
2054
0
  MagickBooleanType
2055
0
    status;
2056
2057
0
  MagickOffsetType
2058
0
    progress;
2059
2060
0
  ssize_t
2061
0
    i;
2062
2063
0
  ssize_t
2064
0
    y;
2065
2066
  /*
2067
    Allocate and initialize histogram arrays.
2068
  */
2069
0
  assert(image != (Image *) NULL);
2070
0
  assert(image->signature == MagickCoreSignature);
2071
#if defined(MAGICKCORE_OPENCL_SUPPORT)
2072
  if (AccelerateEqualizeImage(image,exception) != MagickFalse)
2073
    return(MagickTrue);
2074
#endif
2075
0
  if (IsEventLogging() != MagickFalse)
2076
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2077
0
  equalize_map=(double *) AcquireQuantumMemory(MaxMap+1UL,MaxPixelChannels*
2078
0
    sizeof(*equalize_map));
2079
0
  histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,MaxPixelChannels*
2080
0
    sizeof(*histogram));
2081
0
  map=(double *) AcquireQuantumMemory(MaxMap+1UL,MaxPixelChannels*sizeof(*map));
2082
0
  if ((equalize_map == (double *) NULL) || (histogram == (double *) NULL) ||
2083
0
      (map == (double *) NULL))
2084
0
    {
2085
0
      if (map != (double *) NULL)
2086
0
        map=(double *) RelinquishMagickMemory(map);
2087
0
      if (histogram != (double *) NULL)
2088
0
        histogram=(double *) RelinquishMagickMemory(histogram);
2089
0
      if (equalize_map != (double *) NULL)
2090
0
        equalize_map=(double *) RelinquishMagickMemory(equalize_map);
2091
0
      ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2092
0
        image->filename);
2093
0
    }
2094
  /*
2095
    Form histogram.
2096
  */
2097
0
  status=MagickTrue;
2098
0
  (void) memset(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
2099
0
    sizeof(*histogram));
2100
0
  image_view=AcquireVirtualCacheView(image,exception);
2101
0
  for (y=0; y < (ssize_t) image->rows; y++)
2102
0
  {
2103
0
    const Quantum
2104
0
      *magick_restrict p;
2105
2106
0
    ssize_t
2107
0
      x;
2108
2109
0
    if (status == MagickFalse)
2110
0
      continue;
2111
0
    p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
2112
0
    if (p == (const Quantum *) NULL)
2113
0
      {
2114
0
        status=MagickFalse;
2115
0
        continue;
2116
0
      }
2117
0
    for (x=0; x < (ssize_t) image->columns; x++)
2118
0
    {
2119
0
      for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2120
0
      {
2121
0
        double
2122
0
          intensity;
2123
2124
0
        intensity=(double) p[i];
2125
0
        if ((image->channel_mask & SyncChannels) != 0)
2126
0
          intensity=GetPixelIntensity(image,p);
2127
0
        histogram[GetPixelChannels(image)*ScaleQuantumToMap(
2128
0
          ClampToQuantum(intensity))+(size_t) i]++;
2129
0
      }
2130
0
      p+=(ptrdiff_t) GetPixelChannels(image);
2131
0
    }
2132
0
  }
2133
0
  image_view=DestroyCacheView(image_view);
2134
  /*
2135
    Integrate the histogram to get the equalization map.
2136
  */
2137
0
  for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2138
0
  {
2139
0
    double
2140
0
      intensity;
2141
2142
0
    ssize_t
2143
0
      j;
2144
2145
0
    intensity=0.0;
2146
0
    for (j=0; j <= (ssize_t) MaxMap; j++)
2147
0
    {
2148
0
      intensity+=histogram[(ssize_t) GetPixelChannels(image)*j+i];
2149
0
      map[(ssize_t) GetPixelChannels(image)*j+i]=intensity;
2150
0
    }
2151
0
  }
2152
0
  (void) memset(equalize_map,0,(MaxMap+1)*GetPixelChannels(image)*
2153
0
    sizeof(*equalize_map));
2154
0
  (void) memset(black,0,sizeof(*black));
2155
0
  (void) memset(white,0,sizeof(*white));
2156
0
  for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2157
0
  {
2158
0
    ssize_t
2159
0
      j;
2160
2161
0
    black[i]=map[i];
2162
0
    white[i]=map[GetPixelChannels(image)*MaxMap+(size_t) i];
2163
0
    if (black[i] != white[i])
2164
0
      for (j=0; j <= (ssize_t) MaxMap; j++)
2165
0
        equalize_map[GetPixelChannels(image)*(size_t) j+(size_t) i]=(double)
2166
0
          ScaleMapToQuantum((double) ((MaxMap*(map[GetPixelChannels(image)*
2167
0
          (size_t) j+(size_t) i]-black[i]))/(white[i]-black[i])));
2168
0
  }
2169
0
  histogram=(double *) RelinquishMagickMemory(histogram);
2170
0
  map=(double *) RelinquishMagickMemory(map);
2171
0
  if (image->storage_class == PseudoClass)
2172
0
    {
2173
0
      ssize_t
2174
0
        j;
2175
2176
      /*
2177
        Equalize colormap.
2178
      */
2179
0
      for (j=0; j < (ssize_t) image->colors; j++)
2180
0
      {
2181
0
        if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2182
0
          {
2183
0
            PixelChannel channel = GetPixelChannelChannel(image,
2184
0
              RedPixelChannel);
2185
0
            if (black[channel] != white[channel])
2186
0
              image->colormap[j].red=equalize_map[(ssize_t)
2187
0
                GetPixelChannels(image)*ScaleQuantumToMap(
2188
0
                ClampToQuantum(image->colormap[j].red))+channel];
2189
0
          }
2190
0
        if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2191
0
          {
2192
0
            PixelChannel channel = GetPixelChannelChannel(image,
2193
0
              GreenPixelChannel);
2194
0
            if (black[channel] != white[channel])
2195
0
              image->colormap[j].green=equalize_map[(ssize_t)
2196
0
                GetPixelChannels(image)*ScaleQuantumToMap(
2197
0
                ClampToQuantum(image->colormap[j].green))+channel];
2198
0
          }
2199
0
        if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2200
0
          {
2201
0
            PixelChannel channel = GetPixelChannelChannel(image,
2202
0
              BluePixelChannel);
2203
0
            if (black[channel] != white[channel])
2204
0
              image->colormap[j].blue=equalize_map[(ssize_t)
2205
0
                GetPixelChannels(image)*ScaleQuantumToMap(
2206
0
                ClampToQuantum(image->colormap[j].blue))+channel];
2207
0
          }
2208
0
        if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2209
0
          {
2210
0
            PixelChannel channel = GetPixelChannelChannel(image,
2211
0
              AlphaPixelChannel);
2212
0
            if (black[channel] != white[channel])
2213
0
              image->colormap[j].alpha=equalize_map[(ssize_t)
2214
0
                GetPixelChannels(image)*ScaleQuantumToMap(
2215
0
                ClampToQuantum(image->colormap[j].alpha))+channel];
2216
0
          }
2217
0
      }
2218
0
    }
2219
  /*
2220
    Equalize image.
2221
  */
2222
0
  progress=0;
2223
0
  image_view=AcquireAuthenticCacheView(image,exception);
2224
#if defined(MAGICKCORE_OPENMP_SUPPORT)
2225
  #pragma omp parallel for schedule(static) shared(progress,status) \
2226
    magick_number_threads(image,image,image->rows,1)
2227
#endif
2228
0
  for (y=0; y < (ssize_t) image->rows; y++)
2229
0
  {
2230
0
    Quantum
2231
0
      *magick_restrict q;
2232
2233
0
    ssize_t
2234
0
      x;
2235
2236
0
    if (status == MagickFalse)
2237
0
      continue;
2238
0
    q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2239
0
    if (q == (Quantum *) NULL)
2240
0
      {
2241
0
        status=MagickFalse;
2242
0
        continue;
2243
0
      }
2244
0
    for (x=0; x < (ssize_t) image->columns; x++)
2245
0
    {
2246
0
      ssize_t
2247
0
        j;
2248
2249
0
      for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
2250
0
      {
2251
0
        PixelChannel channel = GetPixelChannelChannel(image,j);
2252
0
        PixelTrait traits = GetPixelChannelTraits(image,channel);
2253
0
        if (((traits & UpdatePixelTrait) == 0) || (black[j] == white[j]))
2254
0
          continue;
2255
0
        q[j]=ClampToQuantum(equalize_map[GetPixelChannels(image)*
2256
0
          ScaleQuantumToMap(q[j])+(size_t) j]);
2257
0
      }
2258
0
      q+=(ptrdiff_t) GetPixelChannels(image);
2259
0
    }
2260
0
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2261
0
      status=MagickFalse;
2262
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
2263
0
      {
2264
0
        MagickBooleanType
2265
0
          proceed;
2266
2267
#if defined(MAGICKCORE_OPENMP_SUPPORT)
2268
        #pragma omp atomic
2269
#endif
2270
0
        progress++;
2271
0
        proceed=SetImageProgress(image,EqualizeImageTag,progress,image->rows);
2272
0
        if (proceed == MagickFalse)
2273
0
          status=MagickFalse;
2274
0
      }
2275
0
  }
2276
0
  image_view=DestroyCacheView(image_view);
2277
0
  equalize_map=(double *) RelinquishMagickMemory(equalize_map);
2278
0
  return(status);
2279
0
}
2280

2281
/*
2282
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2283
%                                                                             %
2284
%                                                                             %
2285
%                                                                             %
2286
%     G a m m a I m a g e                                                     %
2287
%                                                                             %
2288
%                                                                             %
2289
%                                                                             %
2290
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2291
%
2292
%  GammaImage() gamma-corrects a particular image channel.  The same
2293
%  image viewed on different devices will have perceptual differences in the
2294
%  way the image's intensities are represented on the screen.  Specify
2295
%  individual gamma levels for the red, green, and blue channels, or adjust
2296
%  all three with the gamma parameter.  Values typically range from 0.8 to 2.3.
2297
%
2298
%  You can also reduce the influence of a particular channel with a gamma
2299
%  value of 0.
2300
%
2301
%  The format of the GammaImage method is:
2302
%
2303
%      MagickBooleanType GammaImage(Image *image,const double gamma,
2304
%        ExceptionInfo *exception)
2305
%
2306
%  A description of each parameter follows:
2307
%
2308
%    o image: the image.
2309
%
2310
%    o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
2311
%
2312
%    o gamma: the image gamma.
2313
%
2314
*/
2315
2316
static inline double gamma_pow(const double value,const double gamma)
2317
0
{
2318
0
  return(value < 0.0 ? value : pow(value,gamma));
2319
0
}
2320
2321
MagickExport MagickBooleanType GammaImage(Image *image,const double gamma,
2322
  ExceptionInfo *exception)
2323
0
{
2324
0
#define GammaImageTag  "Gamma/Image"
2325
2326
0
  CacheView
2327
0
    *image_view;
2328
2329
0
  MagickBooleanType
2330
0
    status;
2331
2332
0
  MagickOffsetType
2333
0
    progress;
2334
2335
0
  Quantum
2336
0
    *gamma_map;
2337
2338
0
  ssize_t
2339
0
    i;
2340
2341
0
  ssize_t
2342
0
    y;
2343
2344
  /*
2345
    Allocate and initialize gamma maps.
2346
  */
2347
0
  assert(image != (Image *) NULL);
2348
0
  assert(image->signature == MagickCoreSignature);
2349
0
  if (IsEventLogging() != MagickFalse)
2350
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2351
0
  if (gamma == 1.0)
2352
0
    return(MagickTrue);
2353
0
  gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
2354
0
  if (gamma_map == (Quantum *) NULL)
2355
0
    ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2356
0
      image->filename);
2357
0
  (void) memset(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
2358
0
  if (gamma != 0.0)
2359
0
    for (i=0; i <= (ssize_t) MaxMap; i++)
2360
0
      gamma_map[i]=ScaleMapToQuantum((double) (MaxMap*pow((double) i/
2361
0
        MaxMap,MagickSafeReciprocal(gamma))));
2362
0
  if (image->storage_class == PseudoClass)
2363
0
    for (i=0; i < (ssize_t) image->colors; i++)
2364
0
    {
2365
      /*
2366
        Gamma-correct colormap.
2367
      */
2368
0
      if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2369
0
        image->colormap[i].red=(double) gamma_map[ScaleQuantumToMap(
2370
0
          ClampToQuantum(image->colormap[i].red))];
2371
0
      if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2372
0
        image->colormap[i].green=(double) gamma_map[ScaleQuantumToMap(
2373
0
          ClampToQuantum(image->colormap[i].green))];
2374
0
      if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2375
0
        image->colormap[i].blue=(double) gamma_map[ScaleQuantumToMap(
2376
0
          ClampToQuantum(image->colormap[i].blue))];
2377
0
      if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2378
0
        image->colormap[i].alpha=(double) gamma_map[ScaleQuantumToMap(
2379
0
          ClampToQuantum(image->colormap[i].alpha))];
2380
0
    }
2381
  /*
2382
    Gamma-correct image.
2383
  */
2384
0
  status=MagickTrue;
2385
0
  progress=0;
2386
0
  image_view=AcquireAuthenticCacheView(image,exception);
2387
#if defined(MAGICKCORE_OPENMP_SUPPORT)
2388
  #pragma omp parallel for schedule(static) shared(progress,status) \
2389
    magick_number_threads(image,image,image->rows,1)
2390
#endif
2391
0
  for (y=0; y < (ssize_t) image->rows; y++)
2392
0
  {
2393
0
    Quantum
2394
0
      *magick_restrict q;
2395
2396
0
    ssize_t
2397
0
      x;
2398
2399
0
    if (status == MagickFalse)
2400
0
      continue;
2401
0
    q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2402
0
    if (q == (Quantum *) NULL)
2403
0
      {
2404
0
        status=MagickFalse;
2405
0
        continue;
2406
0
      }
2407
0
    for (x=0; x < (ssize_t) image->columns; x++)
2408
0
    {
2409
0
      ssize_t
2410
0
        j;
2411
2412
0
      for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
2413
0
      {
2414
0
        PixelChannel channel = GetPixelChannelChannel(image,j);
2415
0
        PixelTrait traits = GetPixelChannelTraits(image,channel);
2416
0
        if ((traits & UpdatePixelTrait) == 0)
2417
0
          continue;
2418
0
        q[j]=gamma_map[ScaleQuantumToMap(ClampToQuantum((MagickRealType)
2419
0
          q[j]))];
2420
0
      }
2421
0
      q+=(ptrdiff_t) GetPixelChannels(image);
2422
0
    }
2423
0
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2424
0
      status=MagickFalse;
2425
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
2426
0
      {
2427
0
        MagickBooleanType
2428
0
          proceed;
2429
2430
#if defined(MAGICKCORE_OPENMP_SUPPORT)
2431
        #pragma omp atomic
2432
#endif
2433
0
        progress++;
2434
0
        proceed=SetImageProgress(image,GammaImageTag,progress,image->rows);
2435
0
        if (proceed == MagickFalse)
2436
0
          status=MagickFalse;
2437
0
      }
2438
0
  }
2439
0
  image_view=DestroyCacheView(image_view);
2440
0
  gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
2441
0
  if (image->gamma != 0.0)
2442
0
    image->gamma*=gamma;
2443
0
  return(status);
2444
0
}
2445

2446
/*
2447
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2448
%                                                                             %
2449
%                                                                             %
2450
%                                                                             %
2451
%     G r a y s c a l e I m a g e                                             %
2452
%                                                                             %
2453
%                                                                             %
2454
%                                                                             %
2455
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2456
%
2457
%  GrayscaleImage() converts the image to grayscale.
2458
%
2459
%  The format of the GrayscaleImage method is:
2460
%
2461
%      MagickBooleanType GrayscaleImage(Image *image,
2462
%        const PixelIntensityMethod method ,ExceptionInfo *exception)
2463
%
2464
%  A description of each parameter follows:
2465
%
2466
%    o image: the image.
2467
%
2468
%    o method: the pixel intensity method.
2469
%
2470
%    o exception: return any errors or warnings in this structure.
2471
%
2472
*/
2473
MagickExport MagickBooleanType GrayscaleImage(Image *image,
2474
  const PixelIntensityMethod method,ExceptionInfo *exception)
2475
0
{
2476
0
#define GrayscaleImageTag  "Grayscale/Image"
2477
2478
0
  CacheView
2479
0
    *image_view;
2480
2481
0
  MagickBooleanType
2482
0
    status;
2483
2484
0
  MagickOffsetType
2485
0
    progress;
2486
2487
0
  ssize_t
2488
0
    y;
2489
2490
0
  assert(image != (Image *) NULL);
2491
0
  assert(image->signature == MagickCoreSignature);
2492
0
  if (IsEventLogging() != MagickFalse)
2493
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2494
0
  if (image->storage_class == PseudoClass)
2495
0
    {
2496
0
      if (SyncImage(image,exception) == MagickFalse)
2497
0
        return(MagickFalse);
2498
0
      if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
2499
0
        return(MagickFalse);
2500
0
    }
2501
#if defined(MAGICKCORE_OPENCL_SUPPORT)
2502
  if (AccelerateGrayscaleImage(image,method,exception) != MagickFalse)
2503
    {
2504
      image->intensity=method;
2505
      image->type=GrayscaleType;
2506
      if ((method == Rec601LuminancePixelIntensityMethod) ||
2507
          (method == Rec709LuminancePixelIntensityMethod))
2508
        return(SetImageColorspace(image,LinearGRAYColorspace,exception));
2509
      return(SetImageColorspace(image,GRAYColorspace,exception));
2510
    }
2511
#endif
2512
  /*
2513
    Grayscale image.
2514
  */
2515
0
  status=MagickTrue;
2516
0
  progress=0;
2517
0
  image_view=AcquireAuthenticCacheView(image,exception);
2518
#if defined(MAGICKCORE_OPENMP_SUPPORT)
2519
  #pragma omp parallel for schedule(static) shared(progress,status) \
2520
    magick_number_threads(image,image,image->rows,1)
2521
#endif
2522
0
  for (y=0; y < (ssize_t) image->rows; y++)
2523
0
  {
2524
0
    Quantum
2525
0
      *magick_restrict q;
2526
2527
0
    ssize_t
2528
0
      x;
2529
2530
0
    if (status == MagickFalse)
2531
0
      continue;
2532
0
    q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2533
0
    if (q == (Quantum *) NULL)
2534
0
      {
2535
0
        status=MagickFalse;
2536
0
        continue;
2537
0
      }
2538
0
    for (x=0; x < (ssize_t) image->columns; x++)
2539
0
    {
2540
0
      MagickRealType
2541
0
        blue,
2542
0
        green,
2543
0
        red,
2544
0
        intensity;
2545
2546
0
      red=(MagickRealType) GetPixelRed(image,q);
2547
0
      green=(MagickRealType) GetPixelGreen(image,q);
2548
0
      blue=(MagickRealType) GetPixelBlue(image,q);
2549
0
      intensity=0.0;
2550
0
      switch (method)
2551
0
      {
2552
0
        case AveragePixelIntensityMethod:
2553
0
        {
2554
0
          intensity=(red+green+blue)/3.0;
2555
0
          break;
2556
0
        }
2557
0
        case BrightnessPixelIntensityMethod:
2558
0
        {
2559
0
          intensity=MagickMax(MagickMax(red,green),blue);
2560
0
          break;
2561
0
        }
2562
0
        case LightnessPixelIntensityMethod:
2563
0
        {
2564
0
          intensity=(MagickMin(MagickMin(red,green),blue)+
2565
0
            MagickMax(MagickMax(red,green),blue))/2.0;
2566
0
          break;
2567
0
        }
2568
0
        case MSPixelIntensityMethod:
2569
0
        {
2570
0
          intensity=(MagickRealType) (((double) red*red+green*green+
2571
0
            blue*blue)/3.0);
2572
0
          break;
2573
0
        }
2574
0
        case Rec601LumaPixelIntensityMethod:
2575
0
        {
2576
0
          if (image->colorspace == RGBColorspace)
2577
0
            {
2578
0
              red=EncodePixelGamma(red);
2579
0
              green=EncodePixelGamma(green);
2580
0
              blue=EncodePixelGamma(blue);
2581
0
            }
2582
0
          intensity=0.298839*red+0.586811*green+0.114350*blue;
2583
0
          break;
2584
0
        }
2585
0
        case Rec601LuminancePixelIntensityMethod:
2586
0
        {
2587
0
          if (image->colorspace == sRGBColorspace)
2588
0
            {
2589
0
              red=DecodePixelGamma(red);
2590
0
              green=DecodePixelGamma(green);
2591
0
              blue=DecodePixelGamma(blue);
2592
0
            }
2593
0
          intensity=0.298839*red+0.586811*green+0.114350*blue;
2594
0
          break;
2595
0
        }
2596
0
        case Rec709LumaPixelIntensityMethod:
2597
0
        default:
2598
0
        {
2599
0
          if (image->colorspace == RGBColorspace)
2600
0
            {
2601
0
              red=EncodePixelGamma(red);
2602
0
              green=EncodePixelGamma(green);
2603
0
              blue=EncodePixelGamma(blue);
2604
0
            }
2605
0
          intensity=0.212656*red+0.715158*green+0.072186*blue;
2606
0
          break;
2607
0
        }
2608
0
        case Rec709LuminancePixelIntensityMethod:
2609
0
        {
2610
0
          if (image->colorspace == sRGBColorspace)
2611
0
            {
2612
0
              red=DecodePixelGamma(red);
2613
0
              green=DecodePixelGamma(green);
2614
0
              blue=DecodePixelGamma(blue);
2615
0
            }
2616
0
          intensity=0.212656*red+0.715158*green+0.072186*blue;
2617
0
          break;
2618
0
        }
2619
0
        case RMSPixelIntensityMethod:
2620
0
        {
2621
0
          intensity=(MagickRealType) (sqrt((double) red*red+green*green+
2622
0
            blue*blue)/sqrt(3.0));
2623
0
          break;
2624
0
        }
2625
0
      }
2626
0
      SetPixelGray(image,ClampToQuantum(intensity),q);
2627
0
      q+=(ptrdiff_t) GetPixelChannels(image);
2628
0
    }
2629
0
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2630
0
      status=MagickFalse;
2631
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
2632
0
      {
2633
0
        MagickBooleanType
2634
0
          proceed;
2635
2636
#if defined(MAGICKCORE_OPENMP_SUPPORT)
2637
        #pragma omp atomic
2638
#endif
2639
0
        progress++;
2640
0
        proceed=SetImageProgress(image,GrayscaleImageTag,progress,image->rows);
2641
0
        if (proceed == MagickFalse)
2642
0
          status=MagickFalse;
2643
0
      }
2644
0
  }
2645
0
  image_view=DestroyCacheView(image_view);
2646
0
  image->intensity=method;
2647
0
  image->type=GrayscaleType;
2648
0
  if ((method == Rec601LuminancePixelIntensityMethod) ||
2649
0
      (method == Rec709LuminancePixelIntensityMethod))
2650
0
    return(SetImageColorspace(image,LinearGRAYColorspace,exception));
2651
0
  return(SetImageColorspace(image,GRAYColorspace,exception));
2652
0
}
2653

2654
/*
2655
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2656
%                                                                             %
2657
%                                                                             %
2658
%                                                                             %
2659
%     H a l d C l u t I m a g e                                               %
2660
%                                                                             %
2661
%                                                                             %
2662
%                                                                             %
2663
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2664
%
2665
%  HaldClutImage() applies a Hald color lookup table to the image.  A Hald
2666
%  color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2667
%  Create it with the HALD coder.  You can apply any color transformation to
2668
%  the Hald image and then use this method to apply the transform to the
2669
%  image.
2670
%
2671
%  The format of the HaldClutImage method is:
2672
%
2673
%      MagickBooleanType HaldClutImage(Image *image,Image *hald_image,
2674
%        ExceptionInfo *exception)
2675
%
2676
%  A description of each parameter follows:
2677
%
2678
%    o image: the image, which is replaced by indexed CLUT values
2679
%
2680
%    o hald_image: the color lookup table image for replacement color values.
2681
%
2682
%    o exception: return any errors or warnings in this structure.
2683
%
2684
*/
2685
MagickExport MagickBooleanType HaldClutImage(Image *image,
2686
  const Image *hald_image,ExceptionInfo *exception)
2687
0
{
2688
0
#define HaldClutImageTag  "Clut/Image"
2689
2690
0
  typedef struct _HaldInfo
2691
0
  {
2692
0
    double
2693
0
      x,
2694
0
      y,
2695
0
      z;
2696
0
  } HaldInfo;
2697
2698
0
  CacheView
2699
0
    *hald_view,
2700
0
    *image_view;
2701
2702
0
  double
2703
0
    width;
2704
2705
0
  MagickBooleanType
2706
0
    status;
2707
2708
0
  MagickOffsetType
2709
0
    progress;
2710
2711
0
  PixelInfo
2712
0
    zero;
2713
2714
0
  size_t
2715
0
    cube_size,
2716
0
    length,
2717
0
    level;
2718
2719
0
  ssize_t
2720
0
    y;
2721
2722
0
  assert(image != (Image *) NULL);
2723
0
  assert(image->signature == MagickCoreSignature);
2724
0
  if (IsEventLogging() != MagickFalse)
2725
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2726
0
  assert(hald_image != (Image *) NULL);
2727
0
  assert(hald_image->signature == MagickCoreSignature);
2728
0
  if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
2729
0
    return(MagickFalse);
2730
0
  if ((image->alpha_trait & BlendPixelTrait) == 0)
2731
0
    (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
2732
0
  if (image->colorspace != hald_image->colorspace)
2733
0
    (void) SetImageColorspace(image,hald_image->colorspace,exception);
2734
  /*
2735
    Hald clut image.
2736
  */
2737
0
  status=MagickTrue;
2738
0
  progress=0;
2739
0
  length=(size_t) MagickMin((MagickRealType) hald_image->columns,
2740
0
    (MagickRealType) hald_image->rows);
2741
0
  for (level=2; (level*level*level) < length; level++) ;
2742
0
  level*=level;
2743
0
  cube_size=level*level;
2744
0
  width=(double) hald_image->columns;
2745
0
  GetPixelInfo(hald_image,&zero);
2746
0
  hald_view=AcquireVirtualCacheView(hald_image,exception);
2747
0
  image_view=AcquireAuthenticCacheView(image,exception);
2748
#if defined(MAGICKCORE_OPENMP_SUPPORT)
2749
  #pragma omp parallel for schedule(static) shared(progress,status) \
2750
    magick_number_threads(image,image,image->rows,1)
2751
#endif
2752
0
  for (y=0; y < (ssize_t) image->rows; y++)
2753
0
  {
2754
0
    Quantum
2755
0
      *magick_restrict q;
2756
2757
0
    ssize_t
2758
0
      x;
2759
2760
0
    if (status == MagickFalse)
2761
0
      continue;
2762
0
    q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2763
0
    if (q == (Quantum *) NULL)
2764
0
      {
2765
0
        status=MagickFalse;
2766
0
        continue;
2767
0
      }
2768
0
    for (x=0; x < (ssize_t) image->columns; x++)
2769
0
    {
2770
0
      double
2771
0
        area = 0.0,
2772
0
        offset = 0.0;
2773
2774
0
      HaldInfo
2775
0
        point = { 0, 0, 0 };
2776
2777
0
      PixelInfo
2778
0
        pixel = zero,
2779
0
        pixel1 = zero,
2780
0
        pixel2 = zero,
2781
0
        pixel3 = zero,
2782
0
        pixel4 = zero;
2783
2784
0
      point.x=QuantumScale*(level-1.0)*(double) GetPixelRed(image,q);
2785
0
      point.y=QuantumScale*(level-1.0)*(double) GetPixelGreen(image,q);
2786
0
      point.z=QuantumScale*(level-1.0)*(double) GetPixelBlue(image,q);
2787
0
      offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2788
0
      point.x-=floor(point.x);
2789
0
      point.y-=floor(point.y);
2790
0
      point.z-=floor(point.z);
2791
0
      status=InterpolatePixelInfo(hald_image,hald_view,hald_image->interpolate,
2792
0
        fmod(offset,width),floor(offset/width),&pixel1,exception);
2793
0
      if (status == MagickFalse)
2794
0
        break;
2795
0
      status=InterpolatePixelInfo(hald_image,hald_view,hald_image->interpolate,
2796
0
        fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2797
0
      if (status == MagickFalse)
2798
0
        break;
2799
0
      area=point.y;
2800
0
      if (hald_image->interpolate == NearestInterpolatePixel)
2801
0
        area=(point.y < 0.5) ? 0.0 : 1.0;
2802
0
      CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2803
0
        area,&pixel3);
2804
0
      offset+=cube_size;
2805
0
      status=InterpolatePixelInfo(hald_image,hald_view,hald_image->interpolate,
2806
0
        fmod(offset,width),floor(offset/width),&pixel1,exception);
2807
0
      if (status == MagickFalse)
2808
0
        break;
2809
0
      status=InterpolatePixelInfo(hald_image,hald_view,hald_image->interpolate,
2810
0
        fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2811
0
      if (status == MagickFalse)
2812
0
        break;
2813
0
      CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2814
0
        area,&pixel4);
2815
0
      area=point.z;
2816
0
      if (hald_image->interpolate == NearestInterpolatePixel)
2817
0
        area=(point.z < 0.5)? 0.0 : 1.0;
2818
0
      CompositePixelInfoAreaBlend(&pixel3,pixel3.alpha,&pixel4,pixel4.alpha,
2819
0
        area,&pixel);
2820
0
      if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2821
0
        SetPixelRed(image,ClampToQuantum(pixel.red),q);
2822
0
      if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2823
0
        SetPixelGreen(image,ClampToQuantum(pixel.green),q);
2824
0
      if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2825
0
        SetPixelBlue(image,ClampToQuantum(pixel.blue),q);
2826
0
      if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2827
0
          (image->colorspace == CMYKColorspace))
2828
0
        SetPixelBlack(image,ClampToQuantum(pixel.black),q);
2829
0
      if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2830
0
          (image->alpha_trait != UndefinedPixelTrait))
2831
0
        SetPixelAlpha(image,ClampToQuantum(pixel.alpha),q);
2832
0
      q+=(ptrdiff_t) GetPixelChannels(image);
2833
0
    }
2834
0
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2835
0
      status=MagickFalse;
2836
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
2837
0
      {
2838
0
        MagickBooleanType
2839
0
          proceed;
2840
2841
#if defined(MAGICKCORE_OPENMP_SUPPORT)
2842
        #pragma omp atomic
2843
#endif
2844
0
        progress++;
2845
0
        proceed=SetImageProgress(image,HaldClutImageTag,progress,image->rows);
2846
0
        if (proceed == MagickFalse)
2847
0
          status=MagickFalse;
2848
0
      }
2849
0
  }
2850
0
  hald_view=DestroyCacheView(hald_view);
2851
0
  image_view=DestroyCacheView(image_view);
2852
0
  return(status);
2853
0
}
2854

2855
/*
2856
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2857
%                                                                             %
2858
%                                                                             %
2859
%                                                                             %
2860
%     L e v e l I m a g e                                                     %
2861
%                                                                             %
2862
%                                                                             %
2863
%                                                                             %
2864
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2865
%
2866
%  LevelImage() adjusts the levels of a particular image channel by
2867
%  scaling the colors falling between specified white and black points to
2868
%  the full available quantum range.
2869
%
2870
%  The parameters provided represent the black, and white points.  The black
2871
%  point specifies the darkest color in the image. Colors darker than the
2872
%  black point are set to zero.  White point specifies the lightest color in
2873
%  the image.  Colors brighter than the white point are set to the maximum
2874
%  quantum value.
2875
%
2876
%  If a '!' flag is given, map black and white colors to the given levels
2877
%  rather than mapping those levels to black and white.  See
2878
%  LevelizeImage() below.
2879
%
2880
%  Gamma specifies a gamma correction to apply to the image.
2881
%
2882
%  The format of the LevelImage method is:
2883
%
2884
%      MagickBooleanType LevelImage(Image *image,const double black_point,
2885
%        const double white_point,const double gamma,ExceptionInfo *exception)
2886
%
2887
%  A description of each parameter follows:
2888
%
2889
%    o image: the image.
2890
%
2891
%    o black_point: The level to map zero (black) to.
2892
%
2893
%    o white_point: The level to map QuantumRange (white) to.
2894
%
2895
%    o exception: return any errors or warnings in this structure.
2896
%
2897
*/
2898
2899
static inline double LevelPixel(const double black_point,
2900
  const double white_point,const double gamma,const double pixel)
2901
0
{
2902
0
  double
2903
0
    level_pixel,
2904
0
    scale;
2905
2906
0
  scale=MagickSafeReciprocal(white_point-black_point);
2907
0
  level_pixel=(double) QuantumRange*gamma_pow(scale*((double) pixel-(double)
2908
0
    black_point),MagickSafeReciprocal(gamma));
2909
0
  return(level_pixel);
2910
0
}
2911
2912
MagickExport MagickBooleanType LevelImage(Image *image,const double black_point,
2913
  const double white_point,const double gamma,ExceptionInfo *exception)
2914
0
{
2915
0
#define LevelImageTag  "Level/Image"
2916
2917
0
  CacheView
2918
0
    *image_view;
2919
2920
0
  MagickBooleanType
2921
0
    status;
2922
2923
0
  MagickOffsetType
2924
0
    progress;
2925
2926
0
  ssize_t
2927
0
    i,
2928
0
    y;
2929
2930
  /*
2931
    Allocate and initialize levels map.
2932
  */
2933
0
  assert(image != (Image *) NULL);
2934
0
  assert(image->signature == MagickCoreSignature);
2935
0
  if (IsEventLogging() != MagickFalse)
2936
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2937
0
  if (image->storage_class == PseudoClass)
2938
0
    for (i=0; i < (ssize_t) image->colors; i++)
2939
0
    {
2940
      /*
2941
        Level colormap.
2942
      */
2943
0
      if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2944
0
        image->colormap[i].red=(double) ClampToQuantum(LevelPixel(black_point,
2945
0
          white_point,gamma,image->colormap[i].red));
2946
0
      if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2947
0
        image->colormap[i].green=(double) ClampToQuantum(LevelPixel(black_point,
2948
0
          white_point,gamma,image->colormap[i].green));
2949
0
      if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2950
0
        image->colormap[i].blue=(double) ClampToQuantum(LevelPixel(black_point,
2951
0
          white_point,gamma,image->colormap[i].blue));
2952
0
      if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2953
0
        image->colormap[i].alpha=(double) ClampToQuantum(LevelPixel(black_point,
2954
0
          white_point,gamma,image->colormap[i].alpha));
2955
0
    }
2956
  /*
2957
    Level image.
2958
  */
2959
0
  status=MagickTrue;
2960
0
  progress=0;
2961
0
  image_view=AcquireAuthenticCacheView(image,exception);
2962
#if defined(MAGICKCORE_OPENMP_SUPPORT)
2963
  #pragma omp parallel for schedule(static) shared(progress,status) \
2964
    magick_number_threads(image,image,image->rows,1)
2965
#endif
2966
0
  for (y=0; y < (ssize_t) image->rows; y++)
2967
0
  {
2968
0
    Quantum
2969
0
      *magick_restrict q;
2970
2971
0
    ssize_t
2972
0
      x;
2973
2974
0
    if (status == MagickFalse)
2975
0
      continue;
2976
0
    q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2977
0
    if (q == (Quantum *) NULL)
2978
0
      {
2979
0
        status=MagickFalse;
2980
0
        continue;
2981
0
      }
2982
0
    for (x=0; x < (ssize_t) image->columns; x++)
2983
0
    {
2984
0
      ssize_t
2985
0
        j;
2986
2987
0
      for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
2988
0
      {
2989
0
        PixelChannel channel = GetPixelChannelChannel(image,j);
2990
0
        PixelTrait traits = GetPixelChannelTraits(image,channel);
2991
0
        if ((traits & UpdatePixelTrait) == 0)
2992
0
          continue;
2993
0
        q[j]=ClampToQuantum(LevelPixel(black_point,white_point,gamma,
2994
0
          (double) q[j]));
2995
0
      }
2996
0
      q+=(ptrdiff_t) GetPixelChannels(image);
2997
0
    }
2998
0
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2999
0
      status=MagickFalse;
3000
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
3001
0
      {
3002
0
        MagickBooleanType
3003
0
          proceed;
3004
3005
#if defined(MAGICKCORE_OPENMP_SUPPORT)
3006
        #pragma omp atomic
3007
#endif
3008
0
        progress++;
3009
0
        proceed=SetImageProgress(image,LevelImageTag,progress,image->rows);
3010
0
        if (proceed == MagickFalse)
3011
0
          status=MagickFalse;
3012
0
      }
3013
0
  }
3014
0
  image_view=DestroyCacheView(image_view);
3015
0
  (void) ClampImage(image,exception);
3016
0
  return(status);
3017
0
}
3018

3019
/*
3020
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3021
%                                                                             %
3022
%                                                                             %
3023
%                                                                             %
3024
%     L e v e l i z e I m a g e                                               %
3025
%                                                                             %
3026
%                                                                             %
3027
%                                                                             %
3028
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3029
%
3030
%  LevelizeImage() applies the reversed LevelImage() operation to just
3031
%  the specific channels specified.  It compresses the full range of color
3032
%  values, so that they lie between the given black and white points. Gamma is
3033
%  applied before the values are mapped.
3034
%
3035
%  LevelizeImage() can be called with by using a +level command line
3036
%  API option, or using a '!' on a -level or LevelImage() geometry string.
3037
%
3038
%  It can be used to de-contrast a greyscale image to the exact levels
3039
%  specified.  Or by using specific levels for each channel of an image you
3040
%  can convert a gray-scale image to any linear color gradient, according to
3041
%  those levels.
3042
%
3043
%  The format of the LevelizeImage method is:
3044
%
3045
%      MagickBooleanType LevelizeImage(Image *image,const double black_point,
3046
%        const double white_point,const double gamma,ExceptionInfo *exception)
3047
%
3048
%  A description of each parameter follows:
3049
%
3050
%    o image: the image.
3051
%
3052
%    o black_point: The level to map zero (black) to.
3053
%
3054
%    o white_point: The level to map QuantumRange (white) to.
3055
%
3056
%    o gamma: adjust gamma by this factor before mapping values.
3057
%
3058
%    o exception: return any errors or warnings in this structure.
3059
%
3060
*/
3061
MagickExport MagickBooleanType LevelizeImage(Image *image,
3062
  const double black_point,const double white_point,const double gamma,
3063
  ExceptionInfo *exception)
3064
0
{
3065
0
#define LevelizeImageTag  "Levelize/Image"
3066
0
#define LevelizeValue(x) ClampToQuantum(((MagickRealType) gamma_pow((double) \
3067
0
  (QuantumScale*((double) x)),gamma))*(white_point-black_point)+black_point)
3068
3069
0
  CacheView
3070
0
    *image_view;
3071
3072
0
  MagickBooleanType
3073
0
    status;
3074
3075
0
  MagickOffsetType
3076
0
    progress;
3077
3078
0
  ssize_t
3079
0
    i;
3080
3081
0
  ssize_t
3082
0
    y;
3083
3084
  /*
3085
    Allocate and initialize levels map.
3086
  */
3087
0
  assert(image != (Image *) NULL);
3088
0
  assert(image->signature == MagickCoreSignature);
3089
0
  if (IsEventLogging() != MagickFalse)
3090
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3091
0
  if (image->storage_class == PseudoClass)
3092
0
    for (i=0; i < (ssize_t) image->colors; i++)
3093
0
    {
3094
      /*
3095
        Level colormap.
3096
      */
3097
0
      if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3098
0
        image->colormap[i].red=(double) LevelizeValue(image->colormap[i].red);
3099
0
      if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3100
0
        image->colormap[i].green=(double) LevelizeValue(
3101
0
          image->colormap[i].green);
3102
0
      if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3103
0
        image->colormap[i].blue=(double) LevelizeValue(image->colormap[i].blue);
3104
0
      if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
3105
0
        image->colormap[i].alpha=(double) LevelizeValue(
3106
0
          image->colormap[i].alpha);
3107
0
    }
3108
  /*
3109
    Level image.
3110
  */
3111
0
  status=MagickTrue;
3112
0
  progress=0;
3113
0
  image_view=AcquireAuthenticCacheView(image,exception);
3114
#if defined(MAGICKCORE_OPENMP_SUPPORT)
3115
  #pragma omp parallel for schedule(static) shared(progress,status) \
3116
    magick_number_threads(image,image,image->rows,1)
3117
#endif
3118
0
  for (y=0; y < (ssize_t) image->rows; y++)
3119
0
  {
3120
0
    Quantum
3121
0
      *magick_restrict q;
3122
3123
0
    ssize_t
3124
0
      x;
3125
3126
0
    if (status == MagickFalse)
3127
0
      continue;
3128
0
    q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3129
0
    if (q == (Quantum *) NULL)
3130
0
      {
3131
0
        status=MagickFalse;
3132
0
        continue;
3133
0
      }
3134
0
    for (x=0; x < (ssize_t) image->columns; x++)
3135
0
    {
3136
0
      ssize_t
3137
0
        j;
3138
3139
0
      for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
3140
0
      {
3141
0
        PixelChannel channel = GetPixelChannelChannel(image,j);
3142
0
        PixelTrait traits = GetPixelChannelTraits(image,channel);
3143
0
        if ((traits & UpdatePixelTrait) == 0)
3144
0
          continue;
3145
0
        q[j]=LevelizeValue(q[j]);
3146
0
      }
3147
0
      q+=(ptrdiff_t) GetPixelChannels(image);
3148
0
    }
3149
0
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3150
0
      status=MagickFalse;
3151
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
3152
0
      {
3153
0
        MagickBooleanType
3154
0
          proceed;
3155
3156
#if defined(MAGICKCORE_OPENMP_SUPPORT)
3157
        #pragma omp atomic
3158
#endif
3159
0
        progress++;
3160
0
        proceed=SetImageProgress(image,LevelizeImageTag,progress,image->rows);
3161
0
        if (proceed == MagickFalse)
3162
0
          status=MagickFalse;
3163
0
      }
3164
0
  }
3165
0
  image_view=DestroyCacheView(image_view);
3166
0
  return(status);
3167
0
}
3168

3169
/*
3170
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3171
%                                                                             %
3172
%                                                                             %
3173
%                                                                             %
3174
%     L e v e l I m a g e C o l o r s                                         %
3175
%                                                                             %
3176
%                                                                             %
3177
%                                                                             %
3178
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3179
%
3180
%  LevelImageColors() maps the given color to "black" and "white" values,
3181
%  linearly spreading out the colors, and level values on a channel by channel
3182
%  bases, as per LevelImage().  The given colors allows you to specify
3183
%  different level ranges for each of the color channels separately.
3184
%
3185
%  If the boolean 'invert' is set true the image values will modified in the
3186
%  reverse direction. That is any existing "black" and "white" colors in the
3187
%  image will become the color values given, with all other values compressed
3188
%  appropriately.  This effectively maps a greyscale gradient into the given
3189
%  color gradient.
3190
%
3191
%  The format of the LevelImageColors method is:
3192
%
3193
%    MagickBooleanType LevelImageColors(Image *image,
3194
%      const PixelInfo *black_color,const PixelInfo *white_color,
3195
%      const MagickBooleanType invert,ExceptionInfo *exception)
3196
%
3197
%  A description of each parameter follows:
3198
%
3199
%    o image: the image.
3200
%
3201
%    o black_color: The color to map black to/from
3202
%
3203
%    o white_point: The color to map white to/from
3204
%
3205
%    o invert: if true map the colors (levelize), rather than from (level)
3206
%
3207
%    o exception: return any errors or warnings in this structure.
3208
%
3209
*/
3210
MagickExport MagickBooleanType LevelImageColors(Image *image,
3211
  const PixelInfo *black_color,const PixelInfo *white_color,
3212
  const MagickBooleanType invert,ExceptionInfo *exception)
3213
0
{
3214
0
  ChannelType
3215
0
    channel_mask;
3216
3217
0
  MagickStatusType
3218
0
    status;
3219
3220
  /*
3221
    Allocate and initialize levels map.
3222
  */
3223
0
  assert(image != (Image *) NULL);
3224
0
  assert(image->signature == MagickCoreSignature);
3225
0
  if (IsEventLogging() != MagickFalse)
3226
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3227
0
  if ((IsGrayColorspace(image->colorspace) != MagickFalse) &&
3228
0
      ((IsGrayColorspace(black_color->colorspace) == MagickFalse) ||
3229
0
       (IsGrayColorspace(white_color->colorspace) == MagickFalse)))
3230
0
    (void) SetImageColorspace(image,sRGBColorspace,exception);
3231
0
  status=MagickTrue;
3232
0
  if (invert == MagickFalse)
3233
0
    {
3234
0
      if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3235
0
        {
3236
0
          channel_mask=SetImageChannelMask(image,RedChannel);
3237
0
          status&=(MagickStatusType) LevelImage(image,black_color->red,
3238
0
            white_color->red,1.0,exception);
3239
0
          (void) SetImageChannelMask(image,channel_mask);
3240
0
        }
3241
0
      if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3242
0
        {
3243
0
          channel_mask=SetImageChannelMask(image,GreenChannel);
3244
0
          status&=(MagickStatusType) LevelImage(image,black_color->green,
3245
0
            white_color->green,1.0,exception);
3246
0
          (void) SetImageChannelMask(image,channel_mask);
3247
0
        }
3248
0
      if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3249
0
        {
3250
0
          channel_mask=SetImageChannelMask(image,BlueChannel);
3251
0
          status&=(MagickStatusType) LevelImage(image,black_color->blue,
3252
0
            white_color->blue,1.0,exception);
3253
0
          (void) SetImageChannelMask(image,channel_mask);
3254
0
        }
3255
0
      if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
3256
0
          (image->colorspace == CMYKColorspace))
3257
0
        {
3258
0
          channel_mask=SetImageChannelMask(image,BlackChannel);
3259
0
          status&=(MagickStatusType) LevelImage(image,black_color->black,
3260
0
            white_color->black,1.0,exception);
3261
0
          (void) SetImageChannelMask(image,channel_mask);
3262
0
        }
3263
0
      if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
3264
0
          (image->alpha_trait != UndefinedPixelTrait))
3265
0
        {
3266
0
          channel_mask=SetImageChannelMask(image,AlphaChannel);
3267
0
          status&=(MagickStatusType) LevelImage(image,black_color->alpha,
3268
0
            white_color->alpha,1.0,exception);
3269
0
          (void) SetImageChannelMask(image,channel_mask);
3270
0
        }
3271
0
    }
3272
0
  else
3273
0
    {
3274
0
      if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3275
0
        {
3276
0
          channel_mask=SetImageChannelMask(image,RedChannel);
3277
0
          status&=(MagickStatusType) LevelizeImage(image,black_color->red,
3278
0
            white_color->red,1.0,exception);
3279
0
          (void) SetImageChannelMask(image,channel_mask);
3280
0
        }
3281
0
      if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3282
0
        {
3283
0
          channel_mask=SetImageChannelMask(image,GreenChannel);
3284
0
          status&=(MagickStatusType) LevelizeImage(image,black_color->green,
3285
0
            white_color->green,1.0,exception);
3286
0
          (void) SetImageChannelMask(image,channel_mask);
3287
0
        }
3288
0
      if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3289
0
        {
3290
0
          channel_mask=SetImageChannelMask(image,BlueChannel);
3291
0
          status&=(MagickStatusType) LevelizeImage(image,black_color->blue,
3292
0
            white_color->blue,1.0,exception);
3293
0
          (void) SetImageChannelMask(image,channel_mask);
3294
0
        }
3295
0
      if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
3296
0
          (image->colorspace == CMYKColorspace))
3297
0
        {
3298
0
          channel_mask=SetImageChannelMask(image,BlackChannel);
3299
0
          status&=(MagickStatusType) LevelizeImage(image,black_color->black,
3300
0
            white_color->black,1.0,exception);
3301
0
          (void) SetImageChannelMask(image,channel_mask);
3302
0
        }
3303
0
      if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
3304
0
          (image->alpha_trait != UndefinedPixelTrait))
3305
0
        {
3306
0
          channel_mask=SetImageChannelMask(image,AlphaChannel);
3307
0
          status&=(MagickStatusType) LevelizeImage(image,black_color->alpha,
3308
0
            white_color->alpha,1.0,exception);
3309
0
          (void) SetImageChannelMask(image,channel_mask);
3310
0
        }
3311
0
    }
3312
0
  return(status != 0 ? MagickTrue : MagickFalse);
3313
0
}
3314

3315
/*
3316
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3317
%                                                                             %
3318
%                                                                             %
3319
%                                                                             %
3320
%     L i n e a r S t r e t c h I m a g e                                     %
3321
%                                                                             %
3322
%                                                                             %
3323
%                                                                             %
3324
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3325
%
3326
%  LinearStretchImage() discards any pixels below the black point and above
3327
%  the white point and levels the remaining pixels.
3328
%
3329
%  The format of the LinearStretchImage method is:
3330
%
3331
%      MagickBooleanType LinearStretchImage(Image *image,
3332
%        const double black_point,const double white_point,
3333
%        ExceptionInfo *exception)
3334
%
3335
%  A description of each parameter follows:
3336
%
3337
%    o image: the image.
3338
%
3339
%    o black_point: the black point.
3340
%
3341
%    o white_point: the white point.
3342
%
3343
%    o exception: return any errors or warnings in this structure.
3344
%
3345
*/
3346
MagickExport MagickBooleanType LinearStretchImage(Image *image,
3347
  const double black_point,const double white_point,ExceptionInfo *exception)
3348
0
{
3349
0
#define LinearStretchImageTag  "LinearStretch/Image"
3350
3351
0
  CacheView
3352
0
    *image_view;
3353
3354
0
  char
3355
0
    property[MagickPathExtent];
3356
3357
0
  double
3358
0
    *histogram,
3359
0
    intensity;
3360
3361
0
  MagickBooleanType
3362
0
    status;
3363
3364
0
  ssize_t
3365
0
    black,
3366
0
    white,
3367
0
    y;
3368
3369
  /*
3370
    Allocate histogram and linear map.
3371
  */
3372
0
  assert(image != (Image *) NULL);
3373
0
  assert(image->signature == MagickCoreSignature);
3374
0
  histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*histogram));
3375
0
  if (histogram == (double *) NULL)
3376
0
    ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3377
0
      image->filename);
3378
  /*
3379
    Form histogram.
3380
  */
3381
0
  (void) memset(histogram,0,(MaxMap+1)*sizeof(*histogram));
3382
0
  image_view=AcquireVirtualCacheView(image,exception);
3383
0
  for (y=0; y < (ssize_t) image->rows; y++)
3384
0
  {
3385
0
    const Quantum
3386
0
      *magick_restrict p;
3387
3388
0
    ssize_t
3389
0
      x;
3390
3391
0
    p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
3392
0
    if (p == (const Quantum *) NULL)
3393
0
      break;
3394
0
    for (x=0; x < (ssize_t) image->columns; x++)
3395
0
    {
3396
0
      intensity=GetPixelIntensity(image,p);
3397
0
      histogram[ScaleQuantumToMap(ClampToQuantum(intensity))]++;
3398
0
      p+=(ptrdiff_t) GetPixelChannels(image);
3399
0
    }
3400
0
  }
3401
0
  image_view=DestroyCacheView(image_view);
3402
  /*
3403
    Find the histogram boundaries by locating the black and white point levels.
3404
  */
3405
0
  intensity=0.0;
3406
0
  for (black=0; black < (ssize_t) MaxMap; black++)
3407
0
  {
3408
0
    intensity+=histogram[black];
3409
0
    if (intensity >= black_point)
3410
0
      break;
3411
0
  }
3412
0
  intensity=0.0;
3413
0
  for (white=(ssize_t) MaxMap; white != 0; white--)
3414
0
  {
3415
0
    intensity+=histogram[white];
3416
0
    if (intensity >= white_point)
3417
0
      break;
3418
0
  }
3419
0
  histogram=(double *) RelinquishMagickMemory(histogram);
3420
0
  status=LevelImage(image,(double) ScaleMapToQuantum((MagickRealType) black),
3421
0
    (double) ScaleMapToQuantum((MagickRealType) white),1.0,exception);
3422
0
  (void) FormatLocaleString(property,MagickPathExtent,"%gx%g%%",100.0*black/
3423
0
    MaxMap,100.0*white/MaxMap);
3424
0
  (void) SetImageProperty(image,"histogram:linear-stretch",property,exception);
3425
0
  return(status);
3426
0
}
3427

3428
/*
3429
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3430
%                                                                             %
3431
%                                                                             %
3432
%                                                                             %
3433
%     M o d u l a t e I m a g e                                               %
3434
%                                                                             %
3435
%                                                                             %
3436
%                                                                             %
3437
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3438
%
3439
%  ModulateImage() lets you control the brightness, saturation, and hue
3440
%  of an image.  Modulate represents the brightness, saturation, and hue
3441
%  as one parameter (e.g. 90,150,100).  If the image colorspace is HSL, the
3442
%  modulation is lightness, saturation, and hue.  For HWB, use blackness,
3443
%  whiteness, and hue. And for HCL, use chrome, luma, and hue.
3444
%
3445
%  The format of the ModulateImage method is:
3446
%
3447
%      MagickBooleanType ModulateImage(Image *image,const char *modulate,
3448
%        ExceptionInfo *exception)
3449
%
3450
%  A description of each parameter follows:
3451
%
3452
%    o image: the image.
3453
%
3454
%    o modulate: Define the percent change in brightness, saturation, and hue.
3455
%
3456
%    o exception: return any errors or warnings in this structure.
3457
%
3458
*/
3459
3460
static inline void ModulateHCL(const double percent_hue,
3461
  const double percent_chroma,const double percent_luma,double *red,
3462
  double *green,double *blue)
3463
0
{
3464
0
  double
3465
0
    hue,
3466
0
    luma,
3467
0
    chroma;
3468
3469
  /*
3470
    Increase or decrease color luma, chroma, or hue.
3471
  */
3472
0
  ConvertRGBToHCL(*red,*green,*blue,&hue,&chroma,&luma);
3473
0
  hue+=fmod((percent_hue-100.0),200.0)/200.0;
3474
0
  chroma*=0.01*percent_chroma;
3475
0
  luma*=0.01*percent_luma;
3476
0
  ConvertHCLToRGB(hue,chroma,luma,red,green,blue);
3477
0
}
3478
3479
static inline void ModulateHCLp(const double percent_hue,
3480
  const double percent_chroma,const double percent_luma,double *red,
3481
  double *green,double *blue)
3482
0
{
3483
0
  double
3484
0
    hue,
3485
0
    luma,
3486
0
    chroma;
3487
3488
  /*
3489
    Increase or decrease color luma, chroma, or hue.
3490
  */
3491
0
  ConvertRGBToHCLp(*red,*green,*blue,&hue,&chroma,&luma);
3492
0
  hue+=fmod((percent_hue-100.0),200.0)/200.0;
3493
0
  chroma*=0.01*percent_chroma;
3494
0
  luma*=0.01*percent_luma;
3495
0
  ConvertHCLpToRGB(hue,chroma,luma,red,green,blue);
3496
0
}
3497
3498
static inline void ModulateHSB(const double percent_hue,
3499
  const double percent_saturation,const double percent_brightness,double *red,
3500
  double *green,double *blue)
3501
0
{
3502
0
  double
3503
0
    brightness,
3504
0
    hue,
3505
0
    saturation;
3506
3507
  /*
3508
    Increase or decrease color brightness, saturation, or hue.
3509
  */
3510
0
  ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
3511
0
  hue+=fmod((percent_hue-100.0),200.0)/200.0;
3512
0
  saturation*=0.01*percent_saturation;
3513
0
  brightness*=0.01*percent_brightness;
3514
0
  ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
3515
0
}
3516
3517
static inline void ModulateHSI(const double percent_hue,
3518
  const double percent_saturation,const double percent_intensity,double *red,
3519
  double *green,double *blue)
3520
0
{
3521
0
  double
3522
0
    intensity,
3523
0
    hue,
3524
0
    saturation;
3525
3526
  /*
3527
    Increase or decrease color intensity, saturation, or hue.
3528
  */
3529
0
  ConvertRGBToHSI(*red,*green,*blue,&hue,&saturation,&intensity);
3530
0
  hue+=fmod((percent_hue-100.0),200.0)/200.0;
3531
0
  saturation*=0.01*percent_saturation;
3532
0
  intensity*=0.01*percent_intensity;
3533
0
  ConvertHSIToRGB(hue,saturation,intensity,red,green,blue);
3534
0
}
3535
3536
static inline void ModulateHSL(const double percent_hue,
3537
  const double percent_saturation,const double percent_lightness,double *red,
3538
  double *green,double *blue)
3539
0
{
3540
0
  double
3541
0
    hue,
3542
0
    lightness,
3543
0
    saturation;
3544
3545
  /*
3546
    Increase or decrease color lightness, saturation, or hue.
3547
  */
3548
0
  ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
3549
0
  hue+=fmod((percent_hue-100.0),200.0)/200.0;
3550
0
  saturation*=0.01*percent_saturation;
3551
0
  lightness*=0.01*percent_lightness;
3552
0
  ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
3553
0
}
3554
3555
static inline void ModulateHSV(const double percent_hue,
3556
  const double percent_saturation,const double percent_value,double *red,
3557
  double *green,double *blue)
3558
0
{
3559
0
  double
3560
0
    hue,
3561
0
    saturation,
3562
0
    value;
3563
3564
  /*
3565
    Increase or decrease color value, saturation, or hue.
3566
  */
3567
0
  ConvertRGBToHSV(*red,*green,*blue,&hue,&saturation,&value);
3568
0
  hue+=fmod((percent_hue-100.0),200.0)/200.0;
3569
0
  saturation*=0.01*percent_saturation;
3570
0
  value*=0.01*percent_value;
3571
0
  ConvertHSVToRGB(hue,saturation,value,red,green,blue);
3572
0
}
3573
3574
static inline void ModulateHWB(const double percent_hue,
3575
  const double percent_whiteness,const double percent_blackness,double *red,
3576
  double *green,double *blue)
3577
0
{
3578
0
  double
3579
0
    blackness,
3580
0
    hue,
3581
0
    whiteness;
3582
3583
  /*
3584
    Increase or decrease color blackness, whiteness, or hue.
3585
  */
3586
0
  ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
3587
0
  hue+=fmod((percent_hue-100.0),200.0)/200.0;
3588
0
  blackness*=0.01*percent_blackness;
3589
0
  whiteness*=0.01*percent_whiteness;
3590
0
  ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
3591
0
}
3592
3593
static inline void ModulateLCHab(const double percent_luma,
3594
  const double percent_chroma,const double percent_hue,
3595
  const IlluminantType illuminant,double *red,double *green,double *blue)
3596
0
{
3597
0
  double
3598
0
    hue,
3599
0
    luma,
3600
0
    chroma;
3601
3602
  /*
3603
    Increase or decrease color luma, chroma, or hue.
3604
  */
3605
0
  ConvertRGBToLCHab(*red,*green,*blue,illuminant,&luma,&chroma,&hue);
3606
0
  luma*=0.01*percent_luma;
3607
0
  chroma*=0.01*percent_chroma;
3608
0
  hue+=fmod((percent_hue-100.0),200.0)/200.0;
3609
0
  ConvertLCHabToRGB(luma,chroma,hue,illuminant,red,green,blue);
3610
0
}
3611
3612
static inline void ModulateLCHuv(const double percent_luma,
3613
  const double percent_chroma,const double percent_hue,
3614
  const IlluminantType illuminant,double *red,double *green,double *blue)
3615
0
{
3616
0
  double
3617
0
    hue,
3618
0
    luma,
3619
0
    chroma;
3620
3621
  /*
3622
    Increase or decrease color luma, chroma, or hue.
3623
  */
3624
0
  ConvertRGBToLCHuv(*red,*green,*blue,illuminant,&luma,&chroma,&hue);
3625
0
  luma*=0.01*percent_luma;
3626
0
  chroma*=0.01*percent_chroma;
3627
0
  hue+=fmod((percent_hue-100.0),200.0)/200.0;
3628
0
  ConvertLCHuvToRGB(luma,chroma,hue,illuminant,red,green,blue);
3629
0
}
3630
3631
MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate,
3632
  ExceptionInfo *exception)
3633
0
{
3634
0
#define ModulateImageTag  "Modulate/Image"
3635
3636
0
  CacheView
3637
0
    *image_view;
3638
3639
0
  ColorspaceType
3640
0
    colorspace = UndefinedColorspace;
3641
3642
0
  const char
3643
0
    *artifact;
3644
3645
0
  double
3646
0
    percent_brightness = 100.0,
3647
0
    percent_hue = 100.0,
3648
0
    percent_saturation = 100.0;
3649
3650
0
  GeometryInfo
3651
0
    geometry_info;
3652
3653
0
  IlluminantType
3654
0
    illuminant = D65Illuminant;
3655
3656
0
  MagickBooleanType
3657
0
    status;
3658
3659
0
  MagickOffsetType
3660
0
    progress;
3661
3662
0
  MagickStatusType
3663
0
    flags;
3664
3665
0
  ssize_t
3666
0
    i;
3667
3668
0
  ssize_t
3669
0
    y;
3670
3671
  /*
3672
    Initialize modulate table.
3673
  */
3674
0
  assert(image != (Image *) NULL);
3675
0
  assert(image->signature == MagickCoreSignature);
3676
0
  if (IsEventLogging() != MagickFalse)
3677
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3678
0
  if (modulate == (char *) NULL)
3679
0
    return(MagickFalse);
3680
0
  if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
3681
0
    (void) SetImageColorspace(image,sRGBColorspace,exception);
3682
0
  flags=ParseGeometry(modulate,&geometry_info);
3683
0
  if ((flags & RhoValue) != 0)
3684
0
    percent_brightness=geometry_info.rho;
3685
0
  if ((flags & SigmaValue) != 0)
3686
0
    percent_saturation=geometry_info.sigma;
3687
0
  if ((flags & XiValue) != 0)
3688
0
    percent_hue=geometry_info.xi;
3689
0
  artifact=GetImageArtifact(image,"modulate:colorspace");
3690
0
  if (artifact != (const char *) NULL)
3691
0
    colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions,
3692
0
      MagickFalse,artifact);
3693
0
  artifact=GetImageArtifact(image,"color:illuminant");
3694
0
  if (artifact != (const char *) NULL)
3695
0
    {
3696
0
      ssize_t
3697
0
        illuminant_type;
3698
3699
0
      illuminant_type=ParseCommandOption(MagickIlluminantOptions,MagickFalse,
3700
0
        artifact);
3701
0
      if (illuminant_type < 0)
3702
0
        {
3703
0
          illuminant=UndefinedIlluminant;
3704
0
          colorspace=UndefinedColorspace;
3705
0
        }
3706
0
      else
3707
0
        illuminant=(IlluminantType) illuminant_type;
3708
0
    }
3709
0
  if (image->storage_class == PseudoClass)
3710
0
    for (i=0; i < (ssize_t) image->colors; i++)
3711
0
    {
3712
0
      double
3713
0
        blue,
3714
0
        green,
3715
0
        red;
3716
3717
      /*
3718
        Modulate image colormap.
3719
      */
3720
0
      red=(double) image->colormap[i].red;
3721
0
      green=(double) image->colormap[i].green;
3722
0
      blue=(double) image->colormap[i].blue;
3723
0
      switch (colorspace)
3724
0
      {
3725
0
        case HCLColorspace:
3726
0
        {
3727
0
          ModulateHCL(percent_hue,percent_saturation,percent_brightness,
3728
0
            &red,&green,&blue);
3729
0
          break;
3730
0
        }
3731
0
        case HCLpColorspace:
3732
0
        {
3733
0
          ModulateHCLp(percent_hue,percent_saturation,percent_brightness,
3734
0
            &red,&green,&blue);
3735
0
          break;
3736
0
        }
3737
0
        case HSBColorspace:
3738
0
        {
3739
0
          ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3740
0
            &red,&green,&blue);
3741
0
          break;
3742
0
        }
3743
0
        case HSIColorspace:
3744
0
        {
3745
0
          ModulateHSI(percent_hue,percent_saturation,percent_brightness,
3746
0
            &red,&green,&blue);
3747
0
          break;
3748
0
        }
3749
0
        case HSLColorspace:
3750
0
        default:
3751
0
        {
3752
0
          ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3753
0
            &red,&green,&blue);
3754
0
          break;
3755
0
        }
3756
0
        case HSVColorspace:
3757
0
        {
3758
0
          ModulateHSV(percent_hue,percent_saturation,percent_brightness,
3759
0
            &red,&green,&blue);
3760
0
          break;
3761
0
        }
3762
0
        case HWBColorspace:
3763
0
        {
3764
0
          ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3765
0
            &red,&green,&blue);
3766
0
          break;
3767
0
        }
3768
0
        case LCHColorspace:
3769
0
        case LCHabColorspace:
3770
0
        {
3771
0
          ModulateLCHab(percent_brightness,percent_saturation,percent_hue,
3772
0
            illuminant,&red,&green,&blue);
3773
0
          break;
3774
0
        }
3775
0
        case LCHuvColorspace:
3776
0
        {
3777
0
          ModulateLCHuv(percent_brightness,percent_saturation,percent_hue,
3778
0
            illuminant,&red,&green,&blue);
3779
0
          break;
3780
0
        }
3781
0
      }
3782
0
      image->colormap[i].red=red;
3783
0
      image->colormap[i].green=green;
3784
0
      image->colormap[i].blue=blue;
3785
0
    }
3786
  /*
3787
    Modulate image.
3788
  */
3789
#if defined(MAGICKCORE_OPENCL_SUPPORT)
3790
  if (AccelerateModulateImage(image,percent_brightness,percent_hue,
3791
        percent_saturation,colorspace,exception) != MagickFalse)
3792
    return(MagickTrue);
3793
#endif
3794
0
  status=MagickTrue;
3795
0
  progress=0;
3796
0
  image_view=AcquireAuthenticCacheView(image,exception);
3797
#if defined(MAGICKCORE_OPENMP_SUPPORT)
3798
  #pragma omp parallel for schedule(static) shared(progress,status) \
3799
    magick_number_threads(image,image,image->rows,1)
3800
#endif
3801
0
  for (y=0; y < (ssize_t) image->rows; y++)
3802
0
  {
3803
0
    Quantum
3804
0
      *magick_restrict q;
3805
3806
0
    ssize_t
3807
0
      x;
3808
3809
0
    if (status == MagickFalse)
3810
0
      continue;
3811
0
    q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3812
0
    if (q == (Quantum *) NULL)
3813
0
      {
3814
0
        status=MagickFalse;
3815
0
        continue;
3816
0
      }
3817
0
    for (x=0; x < (ssize_t) image->columns; x++)
3818
0
    {
3819
0
      double
3820
0
        blue,
3821
0
        green,
3822
0
        red;
3823
3824
0
      red=(double) GetPixelRed(image,q);
3825
0
      green=(double) GetPixelGreen(image,q);
3826
0
      blue=(double) GetPixelBlue(image,q);
3827
0
      switch (colorspace)
3828
0
      {
3829
0
        case HCLColorspace:
3830
0
        {
3831
0
          ModulateHCL(percent_hue,percent_saturation,percent_brightness,
3832
0
            &red,&green,&blue);
3833
0
          break;
3834
0
        }
3835
0
        case HCLpColorspace:
3836
0
        {
3837
0
          ModulateHCLp(percent_hue,percent_saturation,percent_brightness,
3838
0
            &red,&green,&blue);
3839
0
          break;
3840
0
        }
3841
0
        case HSBColorspace:
3842
0
        {
3843
0
          ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3844
0
            &red,&green,&blue);
3845
0
          break;
3846
0
        }
3847
0
        case HSIColorspace:
3848
0
        {
3849
0
          ModulateHSI(percent_hue,percent_saturation,percent_brightness,
3850
0
            &red,&green,&blue);
3851
0
          break;
3852
0
        }
3853
0
        case HSLColorspace:
3854
0
        default:
3855
0
        {
3856
0
          ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3857
0
            &red,&green,&blue);
3858
0
          break;
3859
0
        }
3860
0
        case HSVColorspace:
3861
0
        {
3862
0
          ModulateHSV(percent_hue,percent_saturation,percent_brightness,
3863
0
            &red,&green,&blue);
3864
0
          break;
3865
0
        }
3866
0
        case HWBColorspace:
3867
0
        {
3868
0
          ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3869
0
            &red,&green,&blue);
3870
0
          break;
3871
0
        }
3872
0
        case LCHColorspace:
3873
0
        case LCHabColorspace:
3874
0
        {
3875
0
          ModulateLCHab(percent_brightness,percent_saturation,percent_hue,
3876
0
            illuminant,&red,&green,&blue);
3877
0
          break;
3878
0
        }
3879
0
        case LCHuvColorspace:
3880
0
        {
3881
0
          ModulateLCHuv(percent_brightness,percent_saturation,percent_hue,
3882
0
            illuminant,&red,&green,&blue);
3883
0
          break;
3884
0
        }
3885
0
      }
3886
0
      SetPixelRed(image,ClampToQuantum(red),q);
3887
0
      SetPixelGreen(image,ClampToQuantum(green),q);
3888
0
      SetPixelBlue(image,ClampToQuantum(blue),q);
3889
0
      q+=(ptrdiff_t) GetPixelChannels(image);
3890
0
    }
3891
0
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3892
0
      status=MagickFalse;
3893
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
3894
0
      {
3895
0
        MagickBooleanType
3896
0
          proceed;
3897
3898
#if defined(MAGICKCORE_OPENMP_SUPPORT)
3899
        #pragma omp atomic
3900
#endif
3901
0
        progress++;
3902
0
        proceed=SetImageProgress(image,ModulateImageTag,progress,image->rows);
3903
0
        if (proceed == MagickFalse)
3904
0
          status=MagickFalse;
3905
0
      }
3906
0
  }
3907
0
  image_view=DestroyCacheView(image_view);
3908
0
  return(status);
3909
0
}
3910

3911
/*
3912
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3913
%                                                                             %
3914
%                                                                             %
3915
%                                                                             %
3916
%     N e g a t e I m a g e                                                   %
3917
%                                                                             %
3918
%                                                                             %
3919
%                                                                             %
3920
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3921
%
3922
%  NegateImage() negates the colors in the reference image.  The grayscale
3923
%  option means that only grayscale values within the image are negated.
3924
%
3925
%  The format of the NegateImage method is:
3926
%
3927
%      MagickBooleanType NegateImage(Image *image,
3928
%        const MagickBooleanType grayscale,ExceptionInfo *exception)
3929
%
3930
%  A description of each parameter follows:
3931
%
3932
%    o image: the image.
3933
%
3934
%    o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3935
%
3936
%    o exception: return any errors or warnings in this structure.
3937
%
3938
*/
3939
MagickExport MagickBooleanType NegateImage(Image *image,
3940
  const MagickBooleanType grayscale,ExceptionInfo *exception)
3941
183k
{
3942
183k
#define NegateImageTag  "Negate/Image"
3943
3944
183k
  CacheView
3945
183k
    *image_view;
3946
3947
183k
  MagickBooleanType
3948
183k
    status;
3949
3950
183k
  MagickOffsetType
3951
183k
    progress;
3952
3953
183k
  ssize_t
3954
183k
    i;
3955
3956
183k
  ssize_t
3957
183k
    y;
3958
3959
183k
  assert(image != (Image *) NULL);
3960
183k
  assert(image->signature == MagickCoreSignature);
3961
183k
  if (IsEventLogging() != MagickFalse)
3962
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3963
183k
  if (image->storage_class == PseudoClass)
3964
0
    for (i=0; i < (ssize_t) image->colors; i++)
3965
0
    {
3966
      /*
3967
        Negate colormap.
3968
      */
3969
0
      if (grayscale != MagickFalse)
3970
0
        if ((image->colormap[i].red != image->colormap[i].green) ||
3971
0
            (image->colormap[i].green != image->colormap[i].blue))
3972
0
          continue;
3973
0
      if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3974
0
        image->colormap[i].red=(double) QuantumRange-image->colormap[i].red;
3975
0
      if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3976
0
        image->colormap[i].green=(double) QuantumRange-image->colormap[i].green;
3977
0
      if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3978
0
        image->colormap[i].blue=(double) QuantumRange-image->colormap[i].blue;
3979
0
    }
3980
  /*
3981
    Negate image.
3982
  */
3983
183k
  status=MagickTrue;
3984
183k
  progress=0;
3985
183k
  image_view=AcquireAuthenticCacheView(image,exception);
3986
183k
  if( grayscale != MagickFalse )
3987
0
    {
3988
0
      for (y=0; y < (ssize_t) image->rows; y++)
3989
0
      {
3990
0
        MagickBooleanType
3991
0
          sync;
3992
3993
0
        Quantum
3994
0
          *magick_restrict q;
3995
3996
0
        ssize_t
3997
0
          x;
3998
3999
0
        if (status == MagickFalse)
4000
0
          continue;
4001
0
        q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
4002
0
          exception);
4003
0
        if (q == (Quantum *) NULL)
4004
0
          {
4005
0
            status=MagickFalse;
4006
0
            continue;
4007
0
          }
4008
0
        for (x=0; x < (ssize_t) image->columns; x++)
4009
0
        {
4010
0
          ssize_t
4011
0
            j;
4012
4013
0
          if (IsPixelGray(image,q) == MagickFalse)
4014
0
            {
4015
0
              q+=(ptrdiff_t) GetPixelChannels(image);
4016
0
              continue;
4017
0
            }
4018
0
          for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
4019
0
          {
4020
0
            PixelChannel channel = GetPixelChannelChannel(image,j);
4021
0
            PixelTrait traits = GetPixelChannelTraits(image,channel);
4022
0
            if ((traits & UpdatePixelTrait) == 0)
4023
0
              continue;
4024
0
            q[j]=QuantumRange-q[j];
4025
0
          }
4026
0
          q+=(ptrdiff_t) GetPixelChannels(image);
4027
0
        }
4028
0
        sync=SyncCacheViewAuthenticPixels(image_view,exception);
4029
0
        if (sync == MagickFalse)
4030
0
          status=MagickFalse;
4031
0
        if (image->progress_monitor != (MagickProgressMonitor) NULL)
4032
0
          {
4033
0
            MagickBooleanType
4034
0
              proceed;
4035
4036
0
            progress++;
4037
0
            proceed=SetImageProgress(image,NegateImageTag,progress,image->rows);
4038
0
            if (proceed == MagickFalse)
4039
0
              status=MagickFalse;
4040
0
          }
4041
0
      }
4042
0
      image_view=DestroyCacheView(image_view);
4043
0
      return(MagickTrue);
4044
0
    }
4045
  /*
4046
    Negate image.
4047
  */
4048
#if defined(MAGICKCORE_OPENMP_SUPPORT)
4049
  #pragma omp parallel for schedule(static) shared(progress,status) \
4050
    magick_number_threads(image,image,image->rows,1)
4051
#endif
4052
4.27M
  for (y=0; y < (ssize_t) image->rows; y++)
4053
4.08M
  {
4054
4.08M
    Quantum
4055
4.08M
      *magick_restrict q;
4056
4057
4.08M
    ssize_t
4058
4.08M
      x;
4059
4060
4.08M
    if (status == MagickFalse)
4061
0
      continue;
4062
4.08M
    q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
4063
4.08M
    if (q == (Quantum *) NULL)
4064
0
      {
4065
0
        status=MagickFalse;
4066
0
        continue;
4067
0
      }
4068
238M
    for (x=0; x < (ssize_t) image->columns; x++)
4069
234M
    {
4070
234M
      ssize_t
4071
234M
        j;
4072
4073
580M
      for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
4074
345M
      {
4075
345M
        PixelChannel channel = GetPixelChannelChannel(image,j);
4076
345M
        PixelTrait traits = GetPixelChannelTraits(image,channel);
4077
345M
        if ((traits & UpdatePixelTrait) == 0)
4078
111M
          continue;
4079
234M
        q[j]=QuantumRange-q[j];
4080
234M
      }
4081
234M
      q+=(ptrdiff_t) GetPixelChannels(image);
4082
234M
    }
4083
4.08M
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
4084
0
      status=MagickFalse;
4085
4.08M
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
4086
0
      {
4087
0
        MagickBooleanType
4088
0
          proceed;
4089
4090
#if defined(MAGICKCORE_OPENMP_SUPPORT)
4091
        #pragma omp atomic
4092
#endif
4093
0
        progress++;
4094
0
        proceed=SetImageProgress(image,NegateImageTag,progress,image->rows);
4095
0
        if (proceed == MagickFalse)
4096
0
          status=MagickFalse;
4097
0
      }
4098
4.08M
  }
4099
183k
  image_view=DestroyCacheView(image_view);
4100
183k
  return(status);
4101
183k
}
4102

4103
/*
4104
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4105
%                                                                             %
4106
%                                                                             %
4107
%                                                                             %
4108
%     N o r m a l i z e I m a g e                                             %
4109
%                                                                             %
4110
%                                                                             %
4111
%                                                                             %
4112
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4113
%
4114
%  The NormalizeImage() method enhances the contrast of a color image by
4115
%  mapping the darkest 2 percent of all pixel to black and the brightest
4116
%  1 percent to white.
4117
%
4118
%  The format of the NormalizeImage method is:
4119
%
4120
%      MagickBooleanType NormalizeImage(Image *image,ExceptionInfo *exception)
4121
%
4122
%  A description of each parameter follows:
4123
%
4124
%    o image: the image.
4125
%
4126
%    o exception: return any errors or warnings in this structure.
4127
%
4128
*/
4129
MagickExport MagickBooleanType NormalizeImage(Image *image,
4130
  ExceptionInfo *exception)
4131
2.03k
{
4132
2.03k
  double
4133
2.03k
    black_point,
4134
2.03k
    white_point;
4135
4136
2.03k
  black_point=0.02*image->columns*image->rows;
4137
2.03k
  white_point=0.99*image->columns*image->rows;
4138
2.03k
  return(ContrastStretchImage(image,black_point,white_point,exception));
4139
2.03k
}
4140

4141
/*
4142
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4143
%                                                                             %
4144
%                                                                             %
4145
%                                                                             %
4146
%     S i g m o i d a l C o n t r a s t I m a g e                             %
4147
%                                                                             %
4148
%                                                                             %
4149
%                                                                             %
4150
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4151
%
4152
%  SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
4153
%  sigmoidal contrast algorithm.  Increase the contrast of the image using a
4154
%  sigmoidal transfer function without saturating highlights or shadows.
4155
%  Contrast indicates how much to increase the contrast (0 is none; 3 is
4156
%  typical; 20 is pushing it); mid-point indicates where midtones fall in the
4157
%  resultant image (0 is white; 50% is middle-gray; 100% is black).  Set
4158
%  sharpen to MagickTrue to increase the image contrast otherwise the contrast
4159
%  is reduced.
4160
%
4161
%  The format of the SigmoidalContrastImage method is:
4162
%
4163
%      MagickBooleanType SigmoidalContrastImage(Image *image,
4164
%        const MagickBooleanType sharpen,const char *levels,
4165
%        ExceptionInfo *exception)
4166
%
4167
%  A description of each parameter follows:
4168
%
4169
%    o image: the image.
4170
%
4171
%    o sharpen: Increase or decrease image contrast.
4172
%
4173
%    o contrast: strength of the contrast, the larger the number the more
4174
%      'threshold-like' it becomes.
4175
%
4176
%    o midpoint: midpoint of the function as a color value 0 to QuantumRange.
4177
%
4178
%    o exception: return any errors or warnings in this structure.
4179
%
4180
*/
4181
4182
/*
4183
  ImageMagick 6 has a version of this function which uses LUTs.
4184
*/
4185
4186
/*
4187
  Sigmoidal function Sigmoidal with inflexion point moved to b and "slope
4188
  constant" set to a.
4189
4190
  The first version, based on the hyperbolic tangent tanh, when combined with
4191
  the scaling step, is an exact arithmetic clone of the sigmoid function
4192
  based on the logistic curve. The equivalence is based on the identity
4193
4194
    1/(1+exp(-t)) = (1+tanh(t/2))/2
4195
4196
  (http://de.wikipedia.org/wiki/Sigmoidfunktion) and the fact that the
4197
  scaled sigmoidal derivation is invariant under affine transformations of
4198
  the ordinate.
4199
4200
  The tanh version is almost certainly more accurate and cheaper.  The 0.5
4201
  factor in the argument is to clone the legacy ImageMagick behavior. The
4202
  reason for making the define depend on atanh even though it only uses tanh
4203
  has to do with the construction of the inverse of the scaled sigmoidal.
4204
*/
4205
#if defined(MAGICKCORE_HAVE_ATANH)
4206
0
#define Sigmoidal(a,b,x) ( tanh((0.5*(a))*((x)-(b))) )
4207
#else
4208
#define Sigmoidal(a,b,x) ( 1.0/(1.0+exp((a)*((b)-(x)))) )
4209
#endif
4210
/*
4211
  Scaled sigmoidal function:
4212
4213
    ( Sigmoidal(a,b,x) - Sigmoidal(a,b,0) ) /
4214
    ( Sigmoidal(a,b,1) - Sigmoidal(a,b,0) )
4215
4216
  See http://osdir.com/ml/video.image-magick.devel/2005-04/msg00006.html and
4217
  http://www.cs.dartmouth.edu/farid/downloads/tutorials/fip.pdf.  The limit
4218
  of ScaledSigmoidal as a->0 is the identity, but a=0 gives a division by
4219
  zero. This is fixed below by exiting immediately when contrast is small,
4220
  leaving the image (or colormap) unmodified. This appears to be safe because
4221
  the series expansion of the logistic sigmoidal function around x=b is
4222
4223
  1/2-a*(b-x)/4+...
4224
4225
  so that the key denominator s(1)-s(0) is about a/4 (a/2 with tanh).
4226
*/
4227
0
#define ScaledSigmoidal(a,b,x) (                    \
4228
0
  (Sigmoidal((a),(b),(x))-Sigmoidal((a),(b),0.0)) / \
4229
0
  (Sigmoidal((a),(b),1.0)-Sigmoidal((a),(b),0.0)) )
4230
/*
4231
  Inverse of ScaledSigmoidal, used for +sigmoidal-contrast.  Because b
4232
  may be 0 or 1, the argument of the hyperbolic tangent (resp. logistic
4233
  sigmoidal) may be outside of the interval (-1,1) (resp. (0,1)), even
4234
  when creating a LUT from in gamut values, hence the branching.  In
4235
  addition, HDRI may have out of gamut values.
4236
  InverseScaledSigmoidal is not a two-sided inverse of ScaledSigmoidal:
4237
  It is only a right inverse. This is unavoidable.
4238
*/
4239
static inline double InverseScaledSigmoidal(const double a,const double b,
4240
  const double x)
4241
0
{
4242
0
  const double sig0=Sigmoidal(a,b,0.0);
4243
0
  const double sig1=Sigmoidal(a,b,1.0);
4244
0
  const double argument=(sig1-sig0)*x+sig0;
4245
0
  const double clamped=
4246
0
    (
4247
0
#if defined(MAGICKCORE_HAVE_ATANH)
4248
0
      argument < -1+MagickEpsilon
4249
0
      ?
4250
0
      -1+MagickEpsilon
4251
0
      :
4252
0
      ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
4253
0
    );
4254
0
  return(b+(2.0/a)*atanh(clamped));
4255
#else
4256
      argument < MagickEpsilon
4257
      ?
4258
      MagickEpsilon
4259
      :
4260
      ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
4261
    );
4262
  return(b-log(1.0/clamped-1.0)/a);
4263
#endif
4264
0
}
4265
4266
MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
4267
  const MagickBooleanType sharpen,const double contrast,const double midpoint,
4268
  ExceptionInfo *exception)
4269
0
{
4270
0
#define SigmoidalContrastImageTag  "SigmoidalContrast/Image"
4271
0
#define ScaledSig(x) (ClampToQuantum((double) QuantumRange* \
4272
0
  ScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale*((double) x))) )
4273
0
#define InverseScaledSig(x) (ClampToQuantum((double) QuantumRange* \
4274
0
  InverseScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale* \
4275
0
  ((double) x))) )
4276
4277
0
  CacheView
4278
0
    *image_view;
4279
4280
0
  MagickBooleanType
4281
0
    status;
4282
4283
0
  MagickOffsetType
4284
0
    progress;
4285
4286
0
  ssize_t
4287
0
    y;
4288
4289
  /*
4290
    Convenience macros.
4291
  */
4292
0
  assert(image != (Image *) NULL);
4293
0
  assert(image->signature == MagickCoreSignature);
4294
0
  if (IsEventLogging() != MagickFalse)
4295
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4296
  /*
4297
    Side effect: may clamp values unless contrast<MagickEpsilon, in which
4298
    case nothing is done.
4299
  */
4300
0
  if (contrast < MagickEpsilon)
4301
0
    return(MagickTrue);
4302
  /*
4303
    Sigmoidal-contrast enhance colormap.
4304
  */
4305
0
  if (image->storage_class == PseudoClass)
4306
0
    {
4307
0
      ssize_t
4308
0
        i;
4309
4310
0
      if( sharpen != MagickFalse )
4311
0
        for (i=0; i < (ssize_t) image->colors; i++)
4312
0
        {
4313
0
          if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
4314
0
            image->colormap[i].red=(MagickRealType) ScaledSig(
4315
0
              image->colormap[i].red);
4316
0
          if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
4317
0
            image->colormap[i].green=(MagickRealType) ScaledSig(
4318
0
              image->colormap[i].green);
4319
0
          if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
4320
0
            image->colormap[i].blue=(MagickRealType) ScaledSig(
4321
0
              image->colormap[i].blue);
4322
0
          if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
4323
0
            image->colormap[i].alpha=(MagickRealType) ScaledSig(
4324
0
              image->colormap[i].alpha);
4325
0
        }
4326
0
      else
4327
0
        for (i=0; i < (ssize_t) image->colors; i++)
4328
0
        {
4329
0
          if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
4330
0
            image->colormap[i].red=(MagickRealType) InverseScaledSig(
4331
0
              image->colormap[i].red);
4332
0
          if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
4333
0
            image->colormap[i].green=(MagickRealType) InverseScaledSig(
4334
0
              image->colormap[i].green);
4335
0
          if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
4336
0
            image->colormap[i].blue=(MagickRealType) InverseScaledSig(
4337
0
              image->colormap[i].blue);
4338
0
          if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
4339
0
            image->colormap[i].alpha=(MagickRealType) InverseScaledSig(
4340
0
              image->colormap[i].alpha);
4341
0
        }
4342
0
    }
4343
  /*
4344
    Sigmoidal-contrast enhance image.
4345
  */
4346
0
  status=MagickTrue;
4347
0
  progress=0;
4348
0
  image_view=AcquireAuthenticCacheView(image,exception);
4349
#if defined(MAGICKCORE_OPENMP_SUPPORT)
4350
  #pragma omp parallel for schedule(static) shared(progress,status) \
4351
    magick_number_threads(image,image,image->rows,1)
4352
#endif
4353
0
  for (y=0; y < (ssize_t) image->rows; y++)
4354
0
  {
4355
0
    Quantum
4356
0
      *magick_restrict q;
4357
4358
0
    ssize_t
4359
0
      x;
4360
4361
0
    if (status == MagickFalse)
4362
0
      continue;
4363
0
    q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
4364
0
    if (q == (Quantum *) NULL)
4365
0
      {
4366
0
        status=MagickFalse;
4367
0
        continue;
4368
0
      }
4369
0
    for (x=0; x < (ssize_t) image->columns; x++)
4370
0
    {
4371
0
      ssize_t
4372
0
        i;
4373
4374
0
      for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4375
0
      {
4376
0
        PixelChannel channel = GetPixelChannelChannel(image,i);
4377
0
        PixelTrait traits = GetPixelChannelTraits(image,channel);
4378
0
        if ((traits & UpdatePixelTrait) == 0)
4379
0
          continue;
4380
0
        if( sharpen != MagickFalse )
4381
0
          q[i]=ScaledSig(q[i]);
4382
0
        else
4383
0
          q[i]=InverseScaledSig(q[i]);
4384
0
      }
4385
0
      q+=(ptrdiff_t) GetPixelChannels(image);
4386
0
    }
4387
0
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
4388
0
      status=MagickFalse;
4389
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
4390
0
      {
4391
0
        MagickBooleanType
4392
0
          proceed;
4393
4394
#if defined(MAGICKCORE_OPENMP_SUPPORT)
4395
        #pragma omp atomic
4396
#endif
4397
0
        progress++;
4398
0
        proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress,
4399
0
          image->rows);
4400
0
        if (proceed == MagickFalse)
4401
0
          status=MagickFalse;
4402
0
      }
4403
0
  }
4404
0
  image_view=DestroyCacheView(image_view);
4405
0
  return(status);
4406
0
}
4407

4408
/*
4409
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4410
%                                                                             %
4411
%                                                                             %
4412
%                                                                             %
4413
%     W h i t e B a l a n c e I m a g e                                       %
4414
%                                                                             %
4415
%                                                                             %
4416
%                                                                             %
4417
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4418
%
4419
%  WhiteBalanceImage() applies white balancing to an image according to a
4420
%  grayworld assumption in the LAB colorspace.
4421
%
4422
%  The format of the WhiteBalanceImage method is:
4423
%
4424
%      MagickBooleanType WhiteBalanceImage(Image *image,
4425
%        ExceptionInfo *exception)
4426
%
4427
%  A description of each parameter follows:
4428
%
4429
%    o image: The image to auto-level
4430
%
4431
%    o exception: return any errors or warnings in this structure.
4432
%
4433
*/
4434
MagickExport MagickBooleanType WhiteBalanceImage(Image *image,
4435
  ExceptionInfo *exception)
4436
0
{
4437
0
#define WhiteBalanceImageTag  "WhiteBalance/Image"
4438
4439
0
  CacheView
4440
0
    *image_view;
4441
4442
0
  const char
4443
0
    *artifact;
4444
4445
0
  double
4446
0
    a_mean,
4447
0
    b_mean;
4448
4449
0
  MagickOffsetType
4450
0
    progress;
4451
4452
0
  MagickStatusType
4453
0
    status;
4454
4455
0
  ssize_t
4456
0
    y;
4457
4458
  /*
4459
    White balance image.
4460
  */
4461
0
  assert(image != (Image *) NULL);
4462
0
  assert(image->signature == MagickCoreSignature);
4463
0
  if (IsEventLogging() != MagickFalse)
4464
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4465
0
  if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
4466
0
    return(MagickFalse);
4467
0
  status=TransformImageColorspace(image,LabColorspace,exception);
4468
0
  a_mean=0.0;
4469
0
  b_mean=0.0;
4470
0
  image_view=AcquireAuthenticCacheView(image,exception);
4471
0
  for (y=0; y < (ssize_t) image->rows; y++)
4472
0
  {
4473
0
    const Quantum
4474
0
      *magick_restrict p;
4475
4476
0
    ssize_t
4477
0
      x;
4478
4479
0
    if (status == MagickFalse)
4480
0
      continue;
4481
0
    p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
4482
0
    if (p == (Quantum *) NULL)
4483
0
      {
4484
0
        status=MagickFalse;
4485
0
        continue;
4486
0
      }
4487
0
    for (x=0; x < (ssize_t) image->columns; x++)
4488
0
    {
4489
0
      a_mean+=QuantumScale*(double) GetPixela(image,p)-0.5;
4490
0
      b_mean+=QuantumScale*(double) GetPixelb(image,p)-0.5;
4491
0
      p+=(ptrdiff_t) GetPixelChannels(image);
4492
0
    }
4493
0
  }
4494
0
  a_mean/=((double) image->columns*image->rows);
4495
0
  b_mean/=((double) image->columns*image->rows);
4496
0
  progress=0;
4497
#if defined(MAGICKCORE_OPENMP_SUPPORT)
4498
  #pragma omp parallel for schedule(static) shared(progress,status) \
4499
    magick_number_threads(image,image,image->rows,1)
4500
#endif
4501
0
  for (y=0; y < (ssize_t) image->rows; y++)
4502
0
  {
4503
0
    Quantum
4504
0
      *magick_restrict q;
4505
4506
0
    ssize_t
4507
0
      x;
4508
4509
0
    if (status == MagickFalse)
4510
0
      continue;
4511
0
    q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
4512
0
    if (q == (Quantum *) NULL)
4513
0
      {
4514
0
        status=MagickFalse;
4515
0
        continue;
4516
0
      }
4517
0
    for (x=0; x < (ssize_t) image->columns; x++)
4518
0
    {
4519
0
      double
4520
0
        a,
4521
0
        b;
4522
4523
      /*
4524
        Scale the chroma distance shifted according to amount of luminance.
4525
      */
4526
0
      a=(double) GetPixela(image,q)-1.1*(double) GetPixelL(image,q)*a_mean;
4527
0
      b=(double) GetPixelb(image,q)-1.1*(double) GetPixelL(image,q)*b_mean;
4528
0
      SetPixela(image,ClampToQuantum(a),q);
4529
0
      SetPixelb(image,ClampToQuantum(b),q);
4530
0
      q+=(ptrdiff_t) GetPixelChannels(image);
4531
0
    }
4532
0
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
4533
0
      status=MagickFalse;
4534
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
4535
0
      {
4536
0
        MagickBooleanType
4537
0
          proceed;
4538
4539
#if defined(MAGICKCORE_OPENMP_SUPPORT)
4540
        #pragma omp atomic
4541
#endif
4542
0
        progress++;
4543
0
        proceed=SetImageProgress(image,WhiteBalanceImageTag,progress,image->rows);
4544
0
        if (proceed == MagickFalse)
4545
0
          status=MagickFalse;
4546
0
      }
4547
0
  }
4548
0
  image_view=DestroyCacheView(image_view);
4549
0
  artifact=GetImageArtifact(image,"white-balance:vibrance");
4550
0
  if (artifact != (const char *) NULL)
4551
0
    {
4552
0
      ChannelType
4553
0
        channel_mask;
4554
4555
0
      double
4556
0
        black_point = 0.0;
4557
4558
0
      GeometryInfo
4559
0
        geometry_info;
4560
4561
0
      MagickStatusType
4562
0
        flags;
4563
4564
      /*
4565
        Level the a & b channels.
4566
      */
4567
0
      flags=ParseGeometry(artifact,&geometry_info);
4568
0
      if ((flags & RhoValue) != 0)
4569
0
        black_point=geometry_info.rho;
4570
0
      if ((flags & PercentValue) != 0)
4571
0
        black_point*=((double) QuantumRange/100.0);
4572
0
      channel_mask=SetImageChannelMask(image,(ChannelType) (aChannel |
4573
0
        bChannel));
4574
0
      status&=(MagickStatusType) LevelImage(image,black_point,(double)
4575
0
        QuantumRange-black_point,1.0,exception);
4576
0
      (void) SetImageChannelMask(image,channel_mask);
4577
0
    }
4578
0
  status&=(MagickStatusType) TransformImageColorspace(image,sRGBColorspace,
4579
0
    exception);
4580
0
  return(status != 0 ? MagickTrue : MagickFalse);
4581
0
}