Coverage Report

Created: 2026-02-14 07:11

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