Coverage Report

Created: 2026-07-20 07:19

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
    {
1269
0
      for (i=0; i < (ssize_t) image->colors; i++)
1270
0
      {
1271
        /*
1272
          Apply transfer function to colormap.
1273
        */
1274
0
        double
1275
0
          luma;
1276
1277
0
        luma=0.21267*image->colormap[i].red+0.71526*image->colormap[i].green+
1278
0
          0.07217*image->colormap[i].blue;
1279
0
        image->colormap[i].red=luma+color_correction.saturation*cdl_map[
1280
0
          ScaleQuantumToMap(ClampToQuantum(image->colormap[i].red))].red-luma;
1281
0
        image->colormap[i].green=luma+color_correction.saturation*cdl_map[
1282
0
          ScaleQuantumToMap(ClampToQuantum(image->colormap[i].green))].green-
1283
0
          luma;
1284
0
        image->colormap[i].blue=luma+color_correction.saturation*cdl_map[
1285
0
          ScaleQuantumToMap(ClampToQuantum(image->colormap[i].blue))].blue-luma;
1286
0
      }
1287
0
      cdl_map=(PixelInfo *) RelinquishMagickMemory(cdl_map);
1288
0
      (void) SyncImage(image, exception);
1289
0
      return(MagickTrue);
1290
0
    }
1291
  /*
1292
    Apply transfer function to image.
1293
  */
1294
0
  status=MagickTrue;
1295
0
  progress=0;
1296
0
  image_view=AcquireAuthenticCacheView(image,exception);
1297
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1298
  #pragma omp parallel for schedule(static) shared(progress,status) \
1299
    magick_number_threads(image,image,image->rows,1)
1300
#endif
1301
0
  for (y=0; y < (ssize_t) image->rows; y++)
1302
0
  {
1303
0
    double
1304
0
      luma;
1305
1306
0
    Quantum
1307
0
      *magick_restrict q;
1308
1309
0
    ssize_t
1310
0
      x;
1311
1312
0
    if (status == MagickFalse)
1313
0
      continue;
1314
0
    q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1315
0
    if (q == (Quantum *) NULL)
1316
0
      {
1317
0
        status=MagickFalse;
1318
0
        continue;
1319
0
      }
1320
0
    for (x=0; x < (ssize_t) image->columns; x++)
1321
0
    {
1322
0
      luma=0.21267*(double) GetPixelRed(image,q)+0.71526*(double)
1323
0
        GetPixelGreen(image,q)+0.07217*(double) GetPixelBlue(image,q);
1324
0
      SetPixelRed(image,ClampToQuantum(luma+color_correction.saturation*
1325
0
        (cdl_map[ScaleQuantumToMap(GetPixelRed(image,q))].red-luma)),q);
1326
0
      SetPixelGreen(image,ClampToQuantum(luma+color_correction.saturation*
1327
0
        (cdl_map[ScaleQuantumToMap(GetPixelGreen(image,q))].green-luma)),q);
1328
0
      SetPixelBlue(image,ClampToQuantum(luma+color_correction.saturation*
1329
0
        (cdl_map[ScaleQuantumToMap(GetPixelBlue(image,q))].blue-luma)),q);
1330
0
      q+=(ptrdiff_t) GetPixelChannels(image);
1331
0
    }
1332
0
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1333
0
      status=MagickFalse;
1334
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
1335
0
      {
1336
0
        MagickBooleanType
1337
0
          proceed;
1338
1339
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1340
        #pragma omp atomic
1341
#endif
1342
0
        progress++;
1343
0
        proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
1344
0
          progress,image->rows);
1345
0
        if (proceed == MagickFalse)
1346
0
          status=MagickFalse;
1347
0
      }
1348
0
  }
1349
0
  image_view=DestroyCacheView(image_view);
1350
0
  cdl_map=(PixelInfo *) RelinquishMagickMemory(cdl_map);
1351
0
  return(status);
1352
0
}
1353

1354
/*
1355
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1356
%                                                                             %
1357
%                                                                             %
1358
%                                                                             %
1359
%     C o n t r a s t I m a g e                                               %
1360
%                                                                             %
1361
%                                                                             %
1362
%                                                                             %
1363
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1364
%
1365
%  ContrastImage() enhances the intensity differences between the lighter and
1366
%  darker elements of the image.  Set sharpen to a MagickTrue to increase the
1367
%  image contrast otherwise the contrast is reduced.
1368
%
1369
%  The format of the ContrastImage method is:
1370
%
1371
%      MagickBooleanType ContrastImage(Image *image,
1372
%        const MagickBooleanType sharpen,ExceptionInfo *exception)
1373
%
1374
%  A description of each parameter follows:
1375
%
1376
%    o image: the image.
1377
%
1378
%    o sharpen: Increase or decrease image contrast.
1379
%
1380
%    o exception: return any errors or warnings in this structure.
1381
%
1382
*/
1383
1384
static inline void Contrast(const int sign,double *red,double *green,
1385
  double *blue)
1386
0
{
1387
0
  double
1388
0
    brightness = 0.0,
1389
0
    hue = 0.0,
1390
0
    saturation = 0.0;
1391
1392
  /*
1393
    Enhance contrast: dark color become darker, light color become lighter.
1394
  */
1395
0
  ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
1396
0
  brightness+=0.5*sign*(0.5*(sin((double) (MagickPI*(brightness-0.5)))+1.0)-
1397
0
    brightness);
1398
0
  if (brightness > 1.0)
1399
0
    brightness=1.0;
1400
0
  else
1401
0
    if (brightness < 0.0)
1402
0
      brightness=0.0;
1403
0
  ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
1404
0
}
1405
1406
MagickExport MagickBooleanType ContrastImage(Image *image,
1407
  const MagickBooleanType sharpen,ExceptionInfo *exception)
1408
0
{
1409
0
#define ContrastImageTag  "Contrast/Image"
1410
1411
0
  CacheView
1412
0
    *image_view;
1413
1414
0
  int
1415
0
    sign;
1416
1417
0
  MagickBooleanType
1418
0
    status;
1419
1420
0
  MagickOffsetType
1421
0
    progress;
1422
1423
0
  ssize_t
1424
0
    i;
1425
1426
0
  ssize_t
1427
0
    y;
1428
1429
0
  assert(image != (Image *) NULL);
1430
0
  assert(image->signature == MagickCoreSignature);
1431
#if defined(MAGICKCORE_OPENCL_SUPPORT)
1432
  if (AccelerateContrastImage(image,sharpen,exception) != MagickFalse)
1433
    return(MagickTrue);
1434
#endif
1435
0
  if (IsEventLogging() != MagickFalse)
1436
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1437
0
  sign=sharpen != MagickFalse ? 1 : -1;
1438
0
  if (image->storage_class == PseudoClass)
1439
0
    {
1440
      /*
1441
        Contrast enhance colormap.
1442
      */
1443
0
      for (i=0; i < (ssize_t) image->colors; i++)
1444
0
      {
1445
0
        double
1446
0
          blue,
1447
0
          green,
1448
0
          red;
1449
1450
0
        red=(double) image->colormap[i].red;
1451
0
        green=(double) image->colormap[i].green;
1452
0
        blue=(double) image->colormap[i].blue;
1453
0
        Contrast(sign,&red,&green,&blue);
1454
0
        image->colormap[i].red=(MagickRealType) red;
1455
0
        image->colormap[i].green=(MagickRealType) green;
1456
0
        image->colormap[i].blue=(MagickRealType) blue;
1457
0
      }
1458
0
    }
1459
  /*
1460
    Contrast enhance image.
1461
  */
1462
0
  status=MagickTrue;
1463
0
  progress=0;
1464
0
  image_view=AcquireAuthenticCacheView(image,exception);
1465
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1466
  #pragma omp parallel for schedule(static) shared(progress,status) \
1467
    magick_number_threads(image,image,image->rows,1)
1468
#endif
1469
0
  for (y=0; y < (ssize_t) image->rows; y++)
1470
0
  {
1471
0
    double
1472
0
      blue,
1473
0
      green,
1474
0
      red;
1475
1476
0
    Quantum
1477
0
      *magick_restrict q;
1478
1479
0
    ssize_t
1480
0
      x;
1481
1482
0
    if (status == MagickFalse)
1483
0
      continue;
1484
0
    q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1485
0
    if (q == (Quantum *) NULL)
1486
0
      {
1487
0
        status=MagickFalse;
1488
0
        continue;
1489
0
      }
1490
0
    for (x=0; x < (ssize_t) image->columns; x++)
1491
0
    {
1492
0
      red=(double) GetPixelRed(image,q);
1493
0
      green=(double) GetPixelGreen(image,q);
1494
0
      blue=(double) GetPixelBlue(image,q);
1495
0
      Contrast(sign,&red,&green,&blue);
1496
0
      SetPixelRed(image,ClampToQuantum(red),q);
1497
0
      SetPixelGreen(image,ClampToQuantum(green),q);
1498
0
      SetPixelBlue(image,ClampToQuantum(blue),q);
1499
0
      q+=(ptrdiff_t) GetPixelChannels(image);
1500
0
    }
1501
0
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1502
0
      status=MagickFalse;
1503
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
1504
0
      {
1505
0
        MagickBooleanType
1506
0
          proceed;
1507
1508
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1509
        #pragma omp atomic
1510
#endif
1511
0
        progress++;
1512
0
        proceed=SetImageProgress(image,ContrastImageTag,progress,image->rows);
1513
0
        if (proceed == MagickFalse)
1514
0
          status=MagickFalse;
1515
0
      }
1516
0
  }
1517
0
  image_view=DestroyCacheView(image_view);
1518
0
  return(status);
1519
0
}
1520

1521
/*
1522
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1523
%                                                                             %
1524
%                                                                             %
1525
%                                                                             %
1526
%     C o n t r a s t S t r e t c h I m a g e                                 %
1527
%                                                                             %
1528
%                                                                             %
1529
%                                                                             %
1530
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1531
%
1532
%  ContrastStretchImage() is a simple image enhancement technique that attempts
1533
%  to improve the contrast in an image by 'stretching' the range of intensity
1534
%  values it contains to span a desired range of values. It differs from the
1535
%  more sophisticated histogram equalization in that it can only apply a
1536
%  linear scaling function to the image pixel values.  As a result the
1537
%  'enhancement' is less harsh.
1538
%
1539
%  The format of the ContrastStretchImage method is:
1540
%
1541
%      MagickBooleanType ContrastStretchImage(Image *image,
1542
%        const char *levels,ExceptionInfo *exception)
1543
%
1544
%  A description of each parameter follows:
1545
%
1546
%    o image: the image.
1547
%
1548
%    o black_point: the black point.
1549
%
1550
%    o white_point: the white point.
1551
%
1552
%    o levels: Specify the levels where the black and white points have the
1553
%      range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
1554
%
1555
%    o exception: return any errors or warnings in this structure.
1556
%
1557
*/
1558
MagickExport MagickBooleanType ContrastStretchImage(Image *image,
1559
  const double black_point,const double white_point,ExceptionInfo *exception)
1560
2.07k
{
1561
2.07k
#define ContrastStretchImageTag  "ContrastStretch/Image"
1562
1563
2.07k
  CacheView
1564
2.07k
    *image_view;
1565
1566
2.07k
  char
1567
2.07k
    property[MagickPathExtent];
1568
1569
2.07k
  double
1570
2.07k
    *histogram;
1571
1572
2.07k
  ImageType
1573
2.07k
    type;
1574
1575
2.07k
  MagickBooleanType
1576
2.07k
    status;
1577
1578
2.07k
  MagickOffsetType
1579
2.07k
    progress;
1580
1581
2.07k
  Quantum
1582
2.07k
    *black,
1583
2.07k
    *stretch_map,
1584
2.07k
    *white;
1585
1586
2.07k
  ssize_t
1587
2.07k
    i;
1588
1589
2.07k
  ssize_t
1590
2.07k
    y;
1591
1592
  /*
1593
    Allocate histogram and stretch map.
1594
  */
1595
2.07k
  assert(image != (Image *) NULL);
1596
2.07k
  assert(image->signature == MagickCoreSignature);
1597
2.07k
  if (IsEventLogging() != MagickFalse)
1598
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1599
2.07k
  type=IdentifyImageType(image,exception);
1600
2.07k
  if (IsGrayImageType(type) != MagickFalse)
1601
2.07k
    (void) SetImageColorspace(image,GRAYColorspace,exception);
1602
2.07k
  black=(Quantum *) AcquireQuantumMemory(MaxPixelChannels,sizeof(*black));
1603
2.07k
  white=(Quantum *) AcquireQuantumMemory(MaxPixelChannels,sizeof(*white));
1604
2.07k
  stretch_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,MaxPixelChannels*
1605
2.07k
    sizeof(*stretch_map));
1606
2.07k
  histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,MaxPixelChannels*
1607
2.07k
    sizeof(*histogram));
1608
2.07k
  if ((black == (Quantum *) NULL) || (white == (Quantum *) NULL) ||
1609
2.07k
      (stretch_map == (Quantum *) NULL) || (histogram == (double *) NULL))
1610
0
    {
1611
0
      if (histogram != (double *) NULL)
1612
0
        histogram=(double *) RelinquishMagickMemory(histogram);
1613
0
      if (stretch_map != (Quantum *) NULL)
1614
0
        stretch_map=(Quantum *) RelinquishMagickMemory(stretch_map);
1615
0
      if (white != (Quantum *) NULL)
1616
0
        white=(Quantum *) RelinquishMagickMemory(white);
1617
0
      if (black != (Quantum *) NULL)
1618
0
        black=(Quantum *) RelinquishMagickMemory(black);
1619
0
      ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1620
0
        image->filename);
1621
0
    }
1622
  /*
1623
    Form histogram.
1624
  */
1625
2.07k
  status=MagickTrue;
1626
2.07k
  (void) memset(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1627
2.07k
    sizeof(*histogram));
1628
2.07k
  image_view=AcquireVirtualCacheView(image,exception);
1629
150k
  for (y=0; y < (ssize_t) image->rows; y++)
1630
147k
  {
1631
147k
    const Quantum
1632
147k
      *magick_restrict p;
1633
1634
147k
    ssize_t
1635
147k
      x;
1636
1637
147k
    if (status == MagickFalse)
1638
0
      continue;
1639
147k
    p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1640
147k
    if (p == (const Quantum *) NULL)
1641
0
      {
1642
0
        status=MagickFalse;
1643
0
        continue;
1644
0
      }
1645
5.33M
    for (x=0; x < (ssize_t) image->columns; x++)
1646
5.19M
    {
1647
5.19M
      double
1648
5.19M
        pixel;
1649
1650
5.19M
      pixel=GetPixelIntensity(image,p);
1651
11.9M
      for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1652
6.79M
      {
1653
6.79M
        if (image->channel_mask != AllChannels)
1654
0
          pixel=(double) p[i];
1655
6.79M
        histogram[GetPixelChannels(image)*ScaleQuantumToMap(
1656
6.79M
          ClampToQuantum(pixel))+(size_t) i]++;
1657
6.79M
      }
1658
5.19M
      p+=(ptrdiff_t) GetPixelChannels(image);
1659
5.19M
    }
1660
147k
  }
1661
2.07k
  image_view=DestroyCacheView(image_view);
1662
  /*
1663
    Find the histogram boundaries by locating the black/white levels.
1664
  */
1665
4.48k
  for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1666
2.41k
  {
1667
2.41k
    double
1668
2.41k
      intensity;
1669
1670
2.41k
    ssize_t
1671
2.41k
      j;
1672
1673
2.41k
    black[i]=(Quantum) 0;
1674
2.41k
    white[i]=(Quantum) ScaleQuantumToMap(QuantumRange);
1675
2.41k
    intensity=0.0;
1676
28.8M
    for (j=0; j <= (ssize_t) MaxMap; j++)
1677
28.8M
    {
1678
28.8M
      intensity+=histogram[(ssize_t) GetPixelChannels(image)*j+i];
1679
28.8M
      if (intensity > black_point)
1680
2.41k
        break;
1681
28.8M
    }
1682
2.41k
    black[i]=(Quantum) j;
1683
2.41k
    intensity=0.0;
1684
24.5M
    for (j=(ssize_t) MaxMap; j != 0; j--)
1685
24.5M
    {
1686
24.5M
      intensity+=histogram[(ssize_t) GetPixelChannels(image)*j+i];
1687
24.5M
      if (intensity > ((double) image->columns*image->rows-white_point))
1688
2.03k
        break;
1689
24.5M
    }
1690
2.41k
    white[i]=(Quantum) j;
1691
2.41k
  }
1692
2.07k
  histogram=(double *) RelinquishMagickMemory(histogram);
1693
  /*
1694
    Stretch the histogram to create the stretched image mapping.
1695
  */
1696
2.07k
  (void) memset(stretch_map,0,(MaxMap+1)*GetPixelChannels(image)*
1697
2.07k
    sizeof(*stretch_map));
1698
4.48k
  for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1699
2.41k
  {
1700
2.41k
    ssize_t
1701
2.41k
      j;
1702
1703
158M
    for (j=0; j <= (ssize_t) MaxMap; j++)
1704
158M
    {
1705
158M
      double
1706
158M
        gamma;
1707
1708
158M
      gamma=MagickSafeReciprocal(white[i]-black[i]);
1709
158M
      if (j < (ssize_t) black[i])
1710
28.8M
        stretch_map[(ssize_t) GetPixelChannels(image)*j+i]=(Quantum) 0;
1711
129M
      else
1712
129M
        if (j > (ssize_t) white[i])
1713
24.5M
          stretch_map[(ssize_t) GetPixelChannels(image)*j+i]=QuantumRange;
1714
104M
        else
1715
104M
          if (black[i] != white[i])
1716
104M
            stretch_map[(ssize_t) GetPixelChannels(image)*j+i]=
1717
104M
              ScaleMapToQuantum((double) (MaxMap*gamma*(j-(double) black[i])));
1718
158M
    }
1719
2.41k
  }
1720
2.07k
  if (image->storage_class == PseudoClass)
1721
218
    {
1722
218
      ssize_t
1723
218
        j;
1724
1725
      /*
1726
        Stretch-contrast colormap.
1727
      */
1728
14.7k
      for (j=0; j < (ssize_t) image->colors; j++)
1729
14.5k
      {
1730
14.5k
        if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1731
14.5k
          {
1732
14.5k
            i=GetPixelChannelOffset(image,RedPixelChannel);
1733
14.5k
            image->colormap[j].red=(MagickRealType) stretch_map[
1734
14.5k
              GetPixelChannels(image)*ScaleQuantumToMap(ClampToQuantum(
1735
14.5k
              image->colormap[j].red))+(size_t) i];
1736
14.5k
          }
1737
14.5k
        if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1738
14.5k
          {
1739
14.5k
            i=GetPixelChannelOffset(image,GreenPixelChannel);
1740
14.5k
            image->colormap[j].green=(MagickRealType) stretch_map[
1741
14.5k
              GetPixelChannels(image)*ScaleQuantumToMap(ClampToQuantum(
1742
14.5k
              image->colormap[j].green))+(size_t) i];
1743
14.5k
          }
1744
14.5k
        if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1745
14.5k
          {
1746
14.5k
            i=GetPixelChannelOffset(image,BluePixelChannel);
1747
14.5k
            image->colormap[j].blue=(MagickRealType) stretch_map[
1748
14.5k
              GetPixelChannels(image)*ScaleQuantumToMap(ClampToQuantum(
1749
14.5k
              image->colormap[j].blue))+(size_t) i];
1750
14.5k
          }
1751
14.5k
        if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1752
0
          {
1753
0
            i=GetPixelChannelOffset(image,AlphaPixelChannel);
1754
0
            image->colormap[j].alpha=(MagickRealType) stretch_map[
1755
0
              GetPixelChannels(image)*ScaleQuantumToMap(ClampToQuantum(
1756
0
              image->colormap[j].alpha))+(size_t) i];
1757
0
          }
1758
14.5k
      }
1759
218
    }
1760
  /*
1761
    Stretch-contrast image.
1762
  */
1763
2.07k
  status=MagickTrue;
1764
2.07k
  progress=0;
1765
2.07k
  image_view=AcquireAuthenticCacheView(image,exception);
1766
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1767
  #pragma omp parallel for schedule(static) shared(progress,status) \
1768
    magick_number_threads(image,image,image->rows,1)
1769
#endif
1770
150k
  for (y=0; y < (ssize_t) image->rows; y++)
1771
147k
  {
1772
147k
    Quantum
1773
147k
      *magick_restrict q;
1774
1775
147k
    ssize_t
1776
147k
      x;
1777
1778
147k
    if (status == MagickFalse)
1779
0
      continue;
1780
147k
    q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1781
147k
    if (q == (Quantum *) NULL)
1782
0
      {
1783
0
        status=MagickFalse;
1784
0
        continue;
1785
0
      }
1786
5.33M
    for (x=0; x < (ssize_t) image->columns; x++)
1787
5.19M
    {
1788
5.19M
      ssize_t
1789
5.19M
        j;
1790
1791
11.9M
      for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
1792
6.79M
      {
1793
6.79M
        PixelChannel channel = GetPixelChannelChannel(image,j);
1794
6.79M
        PixelTrait traits = GetPixelChannelTraits(image,channel);
1795
6.79M
        if ((traits & UpdatePixelTrait) == 0)
1796
1.58M
          continue;
1797
5.20M
        if (black[j] == white[j])
1798
100k
          continue;
1799
5.10M
        q[j]=ClampToQuantum(stretch_map[GetPixelChannels(image)*
1800
5.10M
          ScaleQuantumToMap(q[j])+(size_t) j]);
1801
5.10M
      }
1802
5.19M
      q+=(ptrdiff_t) GetPixelChannels(image);
1803
5.19M
    }
1804
147k
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1805
0
      status=MagickFalse;
1806
147k
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
1807
0
      {
1808
0
        MagickBooleanType
1809
0
          proceed;
1810
1811
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1812
        #pragma omp atomic
1813
#endif
1814
0
        progress++;
1815
0
        proceed=SetImageProgress(image,ContrastStretchImageTag,progress,
1816
0
          image->rows);
1817
0
        if (proceed == MagickFalse)
1818
0
          status=MagickFalse;
1819
0
      }
1820
147k
  }
1821
2.07k
  image_view=DestroyCacheView(image_view);
1822
2.07k
  (void) FormatLocaleString(property,MagickPathExtent,"%gx%g%%",100.0*
1823
2.07k
    QuantumScale*GetPixelIntensity(image,black),100.0*QuantumScale*
1824
2.07k
    GetPixelIntensity(image,white));
1825
2.07k
  (void) SetImageProperty(image,"histogram:contrast-stretch",property,
1826
2.07k
    exception);
1827
2.07k
  white=(Quantum *) RelinquishMagickMemory(white);
1828
2.07k
  black=(Quantum *) RelinquishMagickMemory(black);
1829
2.07k
  stretch_map=(Quantum *) RelinquishMagickMemory(stretch_map);
1830
2.07k
  return(status);
1831
2.07k
}
1832

1833
/*
1834
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1835
%                                                                             %
1836
%                                                                             %
1837
%                                                                             %
1838
%     E n h a n c e I m a g e                                                 %
1839
%                                                                             %
1840
%                                                                             %
1841
%                                                                             %
1842
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1843
%
1844
%  EnhanceImage() applies a digital filter that improves the quality of a
1845
%  noisy image.
1846
%
1847
%  The format of the EnhanceImage method is:
1848
%
1849
%      Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1850
%
1851
%  A description of each parameter follows:
1852
%
1853
%    o image: the image.
1854
%
1855
%    o exception: return any errors or warnings in this structure.
1856
%
1857
*/
1858
MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1859
0
{
1860
0
#define EnhanceImageTag  "Enhance/Image"
1861
0
#define EnhancePixel(weight) \
1862
0
  mean=QuantumScale*((double) GetPixelRed(image,r)+pixel.red)/2.0; \
1863
0
  distance=QuantumScale*((double) GetPixelRed(image,r)-pixel.red); \
1864
0
  distance_squared=(4.0+mean)*distance*distance; \
1865
0
  mean=QuantumScale*((double) GetPixelGreen(image,r)+pixel.green)/2.0; \
1866
0
  distance=QuantumScale*((double) GetPixelGreen(image,r)-pixel.green); \
1867
0
  distance_squared+=(7.0-mean)*distance*distance; \
1868
0
  mean=QuantumScale*((double) GetPixelBlue(image,r)+pixel.blue)/2.0; \
1869
0
  distance=QuantumScale*((double) GetPixelBlue(image,r)-pixel.blue); \
1870
0
  distance_squared+=(5.0-mean)*distance*distance; \
1871
0
  mean=QuantumScale*((double) GetPixelBlack(image,r)+pixel.black)/2.0; \
1872
0
  distance=QuantumScale*((double) GetPixelBlack(image,r)-pixel.black); \
1873
0
  distance_squared+=(5.0-mean)*distance*distance; \
1874
0
  mean=QuantumScale*((double) GetPixelAlpha(image,r)+pixel.alpha)/2.0; \
1875
0
  distance=QuantumScale*((double) GetPixelAlpha(image,r)-pixel.alpha); \
1876
0
  distance_squared+=(5.0-mean)*distance*distance; \
1877
0
  if (distance_squared < 0.069) \
1878
0
    { \
1879
0
      aggregate.red+=(weight)*(double) GetPixelRed(image,r); \
1880
0
      aggregate.green+=(weight)*(double) GetPixelGreen(image,r); \
1881
0
      aggregate.blue+=(weight)*(double) GetPixelBlue(image,r); \
1882
0
      aggregate.black+=(weight)*(double) GetPixelBlack(image,r); \
1883
0
      aggregate.alpha+=(weight)*(double) GetPixelAlpha(image,r); \
1884
0
      total_weight+=(weight); \
1885
0
    } \
1886
0
  r+=(ptrdiff_t) GetPixelChannels(image);
1887
1888
0
  CacheView
1889
0
    *enhance_view,
1890
0
    *image_view;
1891
1892
0
  Image
1893
0
    *enhance_image;
1894
1895
0
  MagickBooleanType
1896
0
    status;
1897
1898
0
  MagickOffsetType
1899
0
    progress;
1900
1901
0
  ssize_t
1902
0
    y;
1903
1904
  /*
1905
    Initialize enhanced image attributes.
1906
  */
1907
0
  assert(image != (const Image *) NULL);
1908
0
  assert(image->signature == MagickCoreSignature);
1909
0
  if (IsEventLogging() != MagickFalse)
1910
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1911
0
  assert(exception != (ExceptionInfo *) NULL);
1912
0
  assert(exception->signature == MagickCoreSignature);
1913
0
  enhance_image=CloneImage(image,0,0,MagickTrue,
1914
0
    exception);
1915
0
  if (enhance_image == (Image *) NULL)
1916
0
    return((Image *) NULL);
1917
0
  if (SetImageStorageClass(enhance_image,DirectClass,exception) == MagickFalse)
1918
0
    {
1919
0
      enhance_image=DestroyImage(enhance_image);
1920
0
      return((Image *) NULL);
1921
0
    }
1922
  /*
1923
    Enhance image.
1924
  */
1925
0
  status=MagickTrue;
1926
0
  progress=0;
1927
0
  image_view=AcquireVirtualCacheView(image,exception);
1928
0
  enhance_view=AcquireAuthenticCacheView(enhance_image,exception);
1929
#if defined(MAGICKCORE_OPENMP_SUPPORT)
1930
  #pragma omp parallel for schedule(static) shared(progress,status) \
1931
    magick_number_threads(image,enhance_image,image->rows,1)
1932
#endif
1933
0
  for (y=0; y < (ssize_t) image->rows; y++)
1934
0
  {
1935
0
    PixelInfo
1936
0
      pixel;
1937
1938
0
    const Quantum
1939
0
      *magick_restrict p;
1940
1941
0
    Quantum
1942
0
      *magick_restrict q;
1943
1944
0
    ssize_t
1945
0
      x;
1946
1947
0
    ssize_t
1948
0
      center;
1949
1950
0
    if (status == MagickFalse)
1951
0
      continue;
1952
0
    p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1953
0
    q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1954
0
      exception);
1955
0
    if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1956
0
      {
1957
0
        status=MagickFalse;
1958
0
        continue;
1959
0
      }
1960
0
    center=(ssize_t) GetPixelChannels(image)*(2*((ssize_t) image->columns+4)+2);
1961
0
    GetPixelInfo(image,&pixel);
1962
0
    for (x=0; x < (ssize_t) image->columns; x++)
1963
0
    {
1964
0
      double
1965
0
        distance,
1966
0
        distance_squared,
1967
0
        mean,
1968
0
        total_weight;
1969
1970
0
      PixelInfo
1971
0
        aggregate;
1972
1973
0
      const Quantum
1974
0
        *magick_restrict r;
1975
1976
0
      GetPixelInfo(image,&aggregate);
1977
0
      total_weight=0.0;
1978
0
      GetPixelInfoPixel(image,p+center,&pixel);
1979
0
      r=p;
1980
0
      EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1981
0
        EnhancePixel(8.0); EnhancePixel(5.0);
1982
0
      r=p+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+2*GetPixelChannels(image)*(image->columns+4);
1986
0
      EnhancePixel(10.0); EnhancePixel(40.0); EnhancePixel(80.0);
1987
0
        EnhancePixel(40.0); EnhancePixel(10.0);
1988
0
      r=p+3*GetPixelChannels(image)*(image->columns+4);
1989
0
      EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1990
0
        EnhancePixel(20.0); EnhancePixel(8.0);
1991
0
      r=p+4*GetPixelChannels(image)*(image->columns+4);
1992
0
      EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1993
0
        EnhancePixel(8.0); EnhancePixel(5.0);
1994
0
      if (total_weight > MagickEpsilon)
1995
0
        {
1996
0
          pixel.red=((aggregate.red+total_weight/2.0)/total_weight);
1997
0
          pixel.green=((aggregate.green+total_weight/2.0)/total_weight);
1998
0
          pixel.blue=((aggregate.blue+total_weight/2.0)/total_weight);
1999
0
          pixel.black=((aggregate.black+total_weight/2.0)/total_weight);
2000
0
          pixel.alpha=((aggregate.alpha+total_weight/2.0)/total_weight);
2001
0
        }
2002
0
      SetPixelViaPixelInfo(enhance_image,&pixel,q);
2003
0
      p+=(ptrdiff_t) GetPixelChannels(image);
2004
0
      q+=(ptrdiff_t) GetPixelChannels(enhance_image);
2005
0
    }
2006
0
    if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
2007
0
      status=MagickFalse;
2008
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
2009
0
      {
2010
0
        MagickBooleanType
2011
0
          proceed;
2012
2013
#if defined(MAGICKCORE_OPENMP_SUPPORT)
2014
        #pragma omp atomic
2015
#endif
2016
0
        progress++;
2017
0
        proceed=SetImageProgress(image,EnhanceImageTag,progress,image->rows);
2018
0
        if (proceed == MagickFalse)
2019
0
          status=MagickFalse;
2020
0
      }
2021
0
  }
2022
0
  enhance_view=DestroyCacheView(enhance_view);
2023
0
  image_view=DestroyCacheView(image_view);
2024
0
  if (status == MagickFalse)
2025
0
    enhance_image=DestroyImage(enhance_image);
2026
0
  return(enhance_image);
2027
0
}
2028

2029
/*
2030
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2031
%                                                                             %
2032
%                                                                             %
2033
%                                                                             %
2034
%     E q u a l i z e I m a g e                                               %
2035
%                                                                             %
2036
%                                                                             %
2037
%                                                                             %
2038
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2039
%
2040
%  EqualizeImage() applies a histogram equalization to the image.
2041
%
2042
%  The format of the EqualizeImage method is:
2043
%
2044
%      MagickBooleanType EqualizeImage(Image *image,ExceptionInfo *exception)
2045
%
2046
%  A description of each parameter follows:
2047
%
2048
%    o image: the image.
2049
%
2050
%    o exception: return any errors or warnings in this structure.
2051
%
2052
*/
2053
MagickExport MagickBooleanType EqualizeImage(Image *image,
2054
  ExceptionInfo *exception)
2055
0
{
2056
0
#define EqualizeImageTag  "Equalize/Image"
2057
2058
0
  CacheView
2059
0
    *image_view;
2060
2061
0
  double
2062
0
    black[2*CompositePixelChannel+1],
2063
0
    *equalize_map,
2064
0
    *histogram,
2065
0
    *map,
2066
0
    white[2*CompositePixelChannel+1];
2067
2068
0
  MagickBooleanType
2069
0
    status;
2070
2071
0
  MagickOffsetType
2072
0
    progress;
2073
2074
0
  ssize_t
2075
0
    i;
2076
2077
0
  ssize_t
2078
0
    y;
2079
2080
  /*
2081
    Allocate and initialize histogram arrays.
2082
  */
2083
0
  assert(image != (Image *) NULL);
2084
0
  assert(image->signature == MagickCoreSignature);
2085
#if defined(MAGICKCORE_OPENCL_SUPPORT)
2086
  if (AccelerateEqualizeImage(image,exception) != MagickFalse)
2087
    return(MagickTrue);
2088
#endif
2089
0
  if (IsEventLogging() != MagickFalse)
2090
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2091
0
  equalize_map=(double *) AcquireQuantumMemory(MaxMap+1UL,MaxPixelChannels*
2092
0
    sizeof(*equalize_map));
2093
0
  histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,MaxPixelChannels*
2094
0
    sizeof(*histogram));
2095
0
  map=(double *) AcquireQuantumMemory(MaxMap+1UL,MaxPixelChannels*sizeof(*map));
2096
0
  if ((equalize_map == (double *) NULL) || (histogram == (double *) NULL) ||
2097
0
      (map == (double *) NULL))
2098
0
    {
2099
0
      if (map != (double *) NULL)
2100
0
        map=(double *) RelinquishMagickMemory(map);
2101
0
      if (histogram != (double *) NULL)
2102
0
        histogram=(double *) RelinquishMagickMemory(histogram);
2103
0
      if (equalize_map != (double *) NULL)
2104
0
        equalize_map=(double *) RelinquishMagickMemory(equalize_map);
2105
0
      ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2106
0
        image->filename);
2107
0
    }
2108
  /*
2109
    Form histogram.
2110
  */
2111
0
  status=MagickTrue;
2112
0
  (void) memset(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
2113
0
    sizeof(*histogram));
2114
0
  image_view=AcquireVirtualCacheView(image,exception);
2115
0
  for (y=0; y < (ssize_t) image->rows; y++)
2116
0
  {
2117
0
    const Quantum
2118
0
      *magick_restrict p;
2119
2120
0
    ssize_t
2121
0
      x;
2122
2123
0
    if (status == MagickFalse)
2124
0
      continue;
2125
0
    p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
2126
0
    if (p == (const Quantum *) NULL)
2127
0
      {
2128
0
        status=MagickFalse;
2129
0
        continue;
2130
0
      }
2131
0
    for (x=0; x < (ssize_t) image->columns; x++)
2132
0
    {
2133
0
      for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2134
0
      {
2135
0
        double
2136
0
          intensity;
2137
2138
0
        intensity=(double) p[i];
2139
0
        if ((image->channel_mask & SyncChannels) != 0)
2140
0
          intensity=GetPixelIntensity(image,p);
2141
0
        histogram[GetPixelChannels(image)*ScaleQuantumToMap(
2142
0
          ClampToQuantum(intensity))+(size_t) i]++;
2143
0
      }
2144
0
      p+=(ptrdiff_t) GetPixelChannels(image);
2145
0
    }
2146
0
  }
2147
0
  image_view=DestroyCacheView(image_view);
2148
  /*
2149
    Integrate the histogram to get the equalization map.
2150
  */
2151
0
  for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2152
0
  {
2153
0
    double
2154
0
      intensity;
2155
2156
0
    ssize_t
2157
0
      j;
2158
2159
0
    intensity=0.0;
2160
0
    for (j=0; j <= (ssize_t) MaxMap; j++)
2161
0
    {
2162
0
      intensity+=histogram[(ssize_t) GetPixelChannels(image)*j+i];
2163
0
      map[(ssize_t) GetPixelChannels(image)*j+i]=intensity;
2164
0
    }
2165
0
  }
2166
0
  (void) memset(equalize_map,0,(MaxMap+1)*GetPixelChannels(image)*
2167
0
    sizeof(*equalize_map));
2168
0
  (void) memset(black,0,sizeof(*black));
2169
0
  (void) memset(white,0,sizeof(*white));
2170
0
  for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2171
0
  {
2172
0
    ssize_t
2173
0
      j;
2174
2175
0
    black[i]=map[i];
2176
0
    white[i]=map[GetPixelChannels(image)*MaxMap+(size_t) i];
2177
0
    if (black[i] != white[i])
2178
0
      for (j=0; j <= (ssize_t) MaxMap; j++)
2179
0
        equalize_map[GetPixelChannels(image)*(size_t) j+(size_t) i]=(double)
2180
0
          ScaleMapToQuantum((double) ((MaxMap*(map[GetPixelChannels(image)*
2181
0
          (size_t) j+(size_t) i]-black[i]))/(white[i]-black[i])));
2182
0
  }
2183
0
  histogram=(double *) RelinquishMagickMemory(histogram);
2184
0
  map=(double *) RelinquishMagickMemory(map);
2185
0
  if (image->storage_class == PseudoClass)
2186
0
    {
2187
0
      ssize_t
2188
0
        j;
2189
2190
      /*
2191
        Equalize colormap.
2192
      */
2193
0
      for (j=0; j < (ssize_t) image->colors; j++)
2194
0
      {
2195
0
        if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2196
0
          {
2197
0
            PixelChannel channel = GetPixelChannelChannel(image,
2198
0
              RedPixelChannel);
2199
0
            if (black[channel] != white[channel])
2200
0
              image->colormap[j].red=equalize_map[(ssize_t)
2201
0
                GetPixelChannels(image)*ScaleQuantumToMap(
2202
0
                ClampToQuantum(image->colormap[j].red))+channel];
2203
0
          }
2204
0
        if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2205
0
          {
2206
0
            PixelChannel channel = GetPixelChannelChannel(image,
2207
0
              GreenPixelChannel);
2208
0
            if (black[channel] != white[channel])
2209
0
              image->colormap[j].green=equalize_map[(ssize_t)
2210
0
                GetPixelChannels(image)*ScaleQuantumToMap(
2211
0
                ClampToQuantum(image->colormap[j].green))+channel];
2212
0
          }
2213
0
        if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2214
0
          {
2215
0
            PixelChannel channel = GetPixelChannelChannel(image,
2216
0
              BluePixelChannel);
2217
0
            if (black[channel] != white[channel])
2218
0
              image->colormap[j].blue=equalize_map[(ssize_t)
2219
0
                GetPixelChannels(image)*ScaleQuantumToMap(
2220
0
                ClampToQuantum(image->colormap[j].blue))+channel];
2221
0
          }
2222
0
        if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2223
0
          {
2224
0
            PixelChannel channel = GetPixelChannelChannel(image,
2225
0
              AlphaPixelChannel);
2226
0
            if (black[channel] != white[channel])
2227
0
              image->colormap[j].alpha=equalize_map[(ssize_t)
2228
0
                GetPixelChannels(image)*ScaleQuantumToMap(
2229
0
                ClampToQuantum(image->colormap[j].alpha))+channel];
2230
0
          }
2231
0
      }
2232
0
    }
2233
  /*
2234
    Equalize image.
2235
  */
2236
0
  progress=0;
2237
0
  image_view=AcquireAuthenticCacheView(image,exception);
2238
#if defined(MAGICKCORE_OPENMP_SUPPORT)
2239
  #pragma omp parallel for schedule(static) shared(progress,status) \
2240
    magick_number_threads(image,image,image->rows,1)
2241
#endif
2242
0
  for (y=0; y < (ssize_t) image->rows; y++)
2243
0
  {
2244
0
    Quantum
2245
0
      *magick_restrict q;
2246
2247
0
    ssize_t
2248
0
      x;
2249
2250
0
    if (status == MagickFalse)
2251
0
      continue;
2252
0
    q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2253
0
    if (q == (Quantum *) NULL)
2254
0
      {
2255
0
        status=MagickFalse;
2256
0
        continue;
2257
0
      }
2258
0
    for (x=0; x < (ssize_t) image->columns; x++)
2259
0
    {
2260
0
      ssize_t
2261
0
        j;
2262
2263
0
      for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
2264
0
      {
2265
0
        PixelChannel channel = GetPixelChannelChannel(image,j);
2266
0
        PixelTrait traits = GetPixelChannelTraits(image,channel);
2267
0
        if (((traits & UpdatePixelTrait) == 0) || (black[j] == white[j]))
2268
0
          continue;
2269
0
        q[j]=ClampToQuantum(equalize_map[GetPixelChannels(image)*
2270
0
          ScaleQuantumToMap(q[j])+(size_t) j]);
2271
0
      }
2272
0
      q+=(ptrdiff_t) GetPixelChannels(image);
2273
0
    }
2274
0
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2275
0
      status=MagickFalse;
2276
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
2277
0
      {
2278
0
        MagickBooleanType
2279
0
          proceed;
2280
2281
#if defined(MAGICKCORE_OPENMP_SUPPORT)
2282
        #pragma omp atomic
2283
#endif
2284
0
        progress++;
2285
0
        proceed=SetImageProgress(image,EqualizeImageTag,progress,image->rows);
2286
0
        if (proceed == MagickFalse)
2287
0
          status=MagickFalse;
2288
0
      }
2289
0
  }
2290
0
  image_view=DestroyCacheView(image_view);
2291
0
  equalize_map=(double *) RelinquishMagickMemory(equalize_map);
2292
0
  return(status);
2293
0
}
2294

2295
/*
2296
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2297
%                                                                             %
2298
%                                                                             %
2299
%                                                                             %
2300
%     G a m m a I m a g e                                                     %
2301
%                                                                             %
2302
%                                                                             %
2303
%                                                                             %
2304
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2305
%
2306
%  GammaImage() gamma-corrects a particular image channel.  The same
2307
%  image viewed on different devices will have perceptual differences in the
2308
%  way the image's intensities are represented on the screen.  Specify
2309
%  individual gamma levels for the red, green, and blue channels, or adjust
2310
%  all three with the gamma parameter.  Values typically range from 0.8 to 2.3.
2311
%
2312
%  You can also reduce the influence of a particular channel with a gamma
2313
%  value of 0.
2314
%
2315
%  The format of the GammaImage method is:
2316
%
2317
%      MagickBooleanType GammaImage(Image *image,const double gamma,
2318
%        ExceptionInfo *exception)
2319
%
2320
%  A description of each parameter follows:
2321
%
2322
%    o image: the image.
2323
%
2324
%    o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
2325
%
2326
%    o gamma: the image gamma.
2327
%
2328
*/
2329
2330
static inline double gamma_pow(const double value,const double gamma)
2331
0
{
2332
0
  return(value < 0.0 ? value : pow(value,gamma));
2333
0
}
2334
2335
MagickExport MagickBooleanType GammaImage(Image *image,const double gamma,
2336
  ExceptionInfo *exception)
2337
0
{
2338
0
#define GammaImageTag  "Gamma/Image"
2339
2340
0
  CacheView
2341
0
    *image_view;
2342
2343
0
  MagickBooleanType
2344
0
    status;
2345
2346
0
  MagickOffsetType
2347
0
    progress;
2348
2349
0
  Quantum
2350
0
    *gamma_map;
2351
2352
0
  ssize_t
2353
0
    i;
2354
2355
0
  ssize_t
2356
0
    y;
2357
2358
  /*
2359
    Allocate and initialize gamma maps.
2360
  */
2361
0
  assert(image != (Image *) NULL);
2362
0
  assert(image->signature == MagickCoreSignature);
2363
0
  if (IsEventLogging() != MagickFalse)
2364
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2365
0
  if (gamma == 1.0)
2366
0
    return(MagickTrue);
2367
0
  gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
2368
0
  if (gamma_map == (Quantum *) NULL)
2369
0
    ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2370
0
      image->filename);
2371
0
  (void) memset(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
2372
0
  if (gamma != 0.0)
2373
0
    for (i=0; i <= (ssize_t) MaxMap; i++)
2374
0
      gamma_map[i]=ScaleMapToQuantum((double) (MaxMap*pow((double) i/
2375
0
        MaxMap,MagickSafeReciprocal(gamma))));
2376
0
  if (image->storage_class == PseudoClass)
2377
0
    for (i=0; i < (ssize_t) image->colors; i++)
2378
0
    {
2379
      /*
2380
        Gamma-correct colormap.
2381
      */
2382
0
      if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2383
0
        image->colormap[i].red=(double) gamma_map[ScaleQuantumToMap(
2384
0
          ClampToQuantum(image->colormap[i].red))];
2385
0
      if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2386
0
        image->colormap[i].green=(double) gamma_map[ScaleQuantumToMap(
2387
0
          ClampToQuantum(image->colormap[i].green))];
2388
0
      if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2389
0
        image->colormap[i].blue=(double) gamma_map[ScaleQuantumToMap(
2390
0
          ClampToQuantum(image->colormap[i].blue))];
2391
0
      if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2392
0
        image->colormap[i].alpha=(double) gamma_map[ScaleQuantumToMap(
2393
0
          ClampToQuantum(image->colormap[i].alpha))];
2394
0
    }
2395
  /*
2396
    Gamma-correct image.
2397
  */
2398
0
  status=MagickTrue;
2399
0
  progress=0;
2400
0
  image_view=AcquireAuthenticCacheView(image,exception);
2401
#if defined(MAGICKCORE_OPENMP_SUPPORT)
2402
  #pragma omp parallel for schedule(static) shared(progress,status) \
2403
    magick_number_threads(image,image,image->rows,1)
2404
#endif
2405
0
  for (y=0; y < (ssize_t) image->rows; y++)
2406
0
  {
2407
0
    Quantum
2408
0
      *magick_restrict q;
2409
2410
0
    ssize_t
2411
0
      x;
2412
2413
0
    if (status == MagickFalse)
2414
0
      continue;
2415
0
    q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2416
0
    if (q == (Quantum *) NULL)
2417
0
      {
2418
0
        status=MagickFalse;
2419
0
        continue;
2420
0
      }
2421
0
    for (x=0; x < (ssize_t) image->columns; x++)
2422
0
    {
2423
0
      ssize_t
2424
0
        j;
2425
2426
0
      for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
2427
0
      {
2428
0
        PixelChannel channel = GetPixelChannelChannel(image,j);
2429
0
        PixelTrait traits = GetPixelChannelTraits(image,channel);
2430
0
        if ((traits & UpdatePixelTrait) == 0)
2431
0
          continue;
2432
0
        q[j]=gamma_map[ScaleQuantumToMap(ClampToQuantum((MagickRealType)
2433
0
          q[j]))];
2434
0
      }
2435
0
      q+=(ptrdiff_t) GetPixelChannels(image);
2436
0
    }
2437
0
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2438
0
      status=MagickFalse;
2439
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
2440
0
      {
2441
0
        MagickBooleanType
2442
0
          proceed;
2443
2444
#if defined(MAGICKCORE_OPENMP_SUPPORT)
2445
        #pragma omp atomic
2446
#endif
2447
0
        progress++;
2448
0
        proceed=SetImageProgress(image,GammaImageTag,progress,image->rows);
2449
0
        if (proceed == MagickFalse)
2450
0
          status=MagickFalse;
2451
0
      }
2452
0
  }
2453
0
  image_view=DestroyCacheView(image_view);
2454
0
  gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
2455
0
  if (image->gamma != 0.0)
2456
0
    image->gamma*=gamma;
2457
0
  return(status);
2458
0
}
2459

2460
/*
2461
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2462
%                                                                             %
2463
%                                                                             %
2464
%                                                                             %
2465
%     G r a y s c a l e I m a g e                                             %
2466
%                                                                             %
2467
%                                                                             %
2468
%                                                                             %
2469
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2470
%
2471
%  GrayscaleImage() converts the image to grayscale.
2472
%
2473
%  The format of the GrayscaleImage method is:
2474
%
2475
%      MagickBooleanType GrayscaleImage(Image *image,
2476
%        const PixelIntensityMethod method ,ExceptionInfo *exception)
2477
%
2478
%  A description of each parameter follows:
2479
%
2480
%    o image: the image.
2481
%
2482
%    o method: the pixel intensity method.
2483
%
2484
%    o exception: return any errors or warnings in this structure.
2485
%
2486
*/
2487
MagickExport MagickBooleanType GrayscaleImage(Image *image,
2488
  const PixelIntensityMethod method,ExceptionInfo *exception)
2489
0
{
2490
0
#define GrayscaleImageTag  "Grayscale/Image"
2491
2492
0
  CacheView
2493
0
    *image_view;
2494
2495
0
  MagickBooleanType
2496
0
    status;
2497
2498
0
  MagickOffsetType
2499
0
    progress;
2500
2501
0
  ssize_t
2502
0
    y;
2503
2504
0
  assert(image != (Image *) NULL);
2505
0
  assert(image->signature == MagickCoreSignature);
2506
0
  if (IsEventLogging() != MagickFalse)
2507
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2508
0
  if (image->storage_class == PseudoClass)
2509
0
    {
2510
0
      if (SyncImage(image,exception) == MagickFalse)
2511
0
        return(MagickFalse);
2512
0
      if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
2513
0
        return(MagickFalse);
2514
0
    }
2515
#if defined(MAGICKCORE_OPENCL_SUPPORT)
2516
  if (AccelerateGrayscaleImage(image,method,exception) != MagickFalse)
2517
    {
2518
      image->intensity=method;
2519
      image->type=GrayscaleType;
2520
      if ((method == Rec601LuminancePixelIntensityMethod) ||
2521
          (method == Rec709LuminancePixelIntensityMethod))
2522
        return(SetImageColorspace(image,LinearGRAYColorspace,exception));
2523
      return(SetImageColorspace(image,GRAYColorspace,exception));
2524
    }
2525
#endif
2526
  /*
2527
    Grayscale image.
2528
  */
2529
0
  status=MagickTrue;
2530
0
  progress=0;
2531
0
  image_view=AcquireAuthenticCacheView(image,exception);
2532
#if defined(MAGICKCORE_OPENMP_SUPPORT)
2533
  #pragma omp parallel for schedule(static) shared(progress,status) \
2534
    magick_number_threads(image,image,image->rows,1)
2535
#endif
2536
0
  for (y=0; y < (ssize_t) image->rows; y++)
2537
0
  {
2538
0
    Quantum
2539
0
      *magick_restrict q;
2540
2541
0
    ssize_t
2542
0
      x;
2543
2544
0
    if (status == MagickFalse)
2545
0
      continue;
2546
0
    q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2547
0
    if (q == (Quantum *) NULL)
2548
0
      {
2549
0
        status=MagickFalse;
2550
0
        continue;
2551
0
      }
2552
0
    for (x=0; x < (ssize_t) image->columns; x++)
2553
0
    {
2554
0
      MagickRealType
2555
0
        blue,
2556
0
        green,
2557
0
        red,
2558
0
        intensity;
2559
2560
0
      red=(MagickRealType) GetPixelRed(image,q);
2561
0
      green=(MagickRealType) GetPixelGreen(image,q);
2562
0
      blue=(MagickRealType) GetPixelBlue(image,q);
2563
0
      intensity=0.0;
2564
0
      switch (method)
2565
0
      {
2566
0
        case AveragePixelIntensityMethod:
2567
0
        {
2568
0
          intensity=(red+green+blue)/3.0;
2569
0
          break;
2570
0
        }
2571
0
        case BrightnessPixelIntensityMethod:
2572
0
        {
2573
0
          intensity=MagickMax(MagickMax(red,green),blue);
2574
0
          break;
2575
0
        }
2576
0
        case LightnessPixelIntensityMethod:
2577
0
        {
2578
0
          intensity=(MagickMin(MagickMin(red,green),blue)+
2579
0
            MagickMax(MagickMax(red,green),blue))/2.0;
2580
0
          break;
2581
0
        }
2582
0
        case MSPixelIntensityMethod:
2583
0
        {
2584
0
          intensity=(MagickRealType) (((double) red*red+green*green+
2585
0
            blue*blue)/3.0);
2586
0
          break;
2587
0
        }
2588
0
        case Rec601LumaPixelIntensityMethod:
2589
0
        {
2590
0
          if (image->colorspace == RGBColorspace)
2591
0
            {
2592
0
              red=EncodePixelGamma(red);
2593
0
              green=EncodePixelGamma(green);
2594
0
              blue=EncodePixelGamma(blue);
2595
0
            }
2596
0
          intensity=0.298839*red+0.586811*green+0.114350*blue;
2597
0
          break;
2598
0
        }
2599
0
        case Rec601LuminancePixelIntensityMethod:
2600
0
        {
2601
0
          if (image->colorspace == sRGBColorspace)
2602
0
            {
2603
0
              red=DecodePixelGamma(red);
2604
0
              green=DecodePixelGamma(green);
2605
0
              blue=DecodePixelGamma(blue);
2606
0
            }
2607
0
          intensity=0.298839*red+0.586811*green+0.114350*blue;
2608
0
          break;
2609
0
        }
2610
0
        case Rec709LumaPixelIntensityMethod:
2611
0
        default:
2612
0
        {
2613
0
          if (image->colorspace == RGBColorspace)
2614
0
            {
2615
0
              red=EncodePixelGamma(red);
2616
0
              green=EncodePixelGamma(green);
2617
0
              blue=EncodePixelGamma(blue);
2618
0
            }
2619
0
          intensity=0.212656*red+0.715158*green+0.072186*blue;
2620
0
          break;
2621
0
        }
2622
0
        case Rec709LuminancePixelIntensityMethod:
2623
0
        {
2624
0
          if (image->colorspace == sRGBColorspace)
2625
0
            {
2626
0
              red=DecodePixelGamma(red);
2627
0
              green=DecodePixelGamma(green);
2628
0
              blue=DecodePixelGamma(blue);
2629
0
            }
2630
0
          intensity=0.212656*red+0.715158*green+0.072186*blue;
2631
0
          break;
2632
0
        }
2633
0
        case RMSPixelIntensityMethod:
2634
0
        {
2635
0
          intensity=(MagickRealType) (sqrt((double) red*red+green*green+
2636
0
            blue*blue)/sqrt(3.0));
2637
0
          break;
2638
0
        }
2639
0
      }
2640
0
      SetPixelGray(image,ClampToQuantum(intensity),q);
2641
0
      q+=(ptrdiff_t) GetPixelChannels(image);
2642
0
    }
2643
0
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2644
0
      status=MagickFalse;
2645
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
2646
0
      {
2647
0
        MagickBooleanType
2648
0
          proceed;
2649
2650
#if defined(MAGICKCORE_OPENMP_SUPPORT)
2651
        #pragma omp atomic
2652
#endif
2653
0
        progress++;
2654
0
        proceed=SetImageProgress(image,GrayscaleImageTag,progress,image->rows);
2655
0
        if (proceed == MagickFalse)
2656
0
          status=MagickFalse;
2657
0
      }
2658
0
  }
2659
0
  image_view=DestroyCacheView(image_view);
2660
0
  image->intensity=method;
2661
0
  image->type=GrayscaleType;
2662
0
  if ((method == Rec601LuminancePixelIntensityMethod) ||
2663
0
      (method == Rec709LuminancePixelIntensityMethod))
2664
0
    return(SetImageColorspace(image,LinearGRAYColorspace,exception));
2665
0
  return(SetImageColorspace(image,GRAYColorspace,exception));
2666
0
}
2667

2668
/*
2669
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2670
%                                                                             %
2671
%                                                                             %
2672
%                                                                             %
2673
%     H a l d C l u t I m a g e                                               %
2674
%                                                                             %
2675
%                                                                             %
2676
%                                                                             %
2677
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2678
%
2679
%  HaldClutImage() applies a Hald color lookup table to the image.  A Hald
2680
%  color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2681
%  Create it with the HALD coder.  You can apply any color transformation to
2682
%  the Hald image and then use this method to apply the transform to the
2683
%  image.
2684
%
2685
%  The format of the HaldClutImage method is:
2686
%
2687
%      MagickBooleanType HaldClutImage(Image *image,Image *hald_image,
2688
%        ExceptionInfo *exception)
2689
%
2690
%  A description of each parameter follows:
2691
%
2692
%    o image: the image, which is replaced by indexed CLUT values
2693
%
2694
%    o hald_image: the color lookup table image for replacement color values.
2695
%
2696
%    o exception: return any errors or warnings in this structure.
2697
%
2698
*/
2699
MagickExport MagickBooleanType HaldClutImage(Image *image,
2700
  const Image *hald_image,ExceptionInfo *exception)
2701
0
{
2702
0
#define HaldClutImageTag  "Clut/Image"
2703
2704
0
  typedef struct _HaldInfo
2705
0
  {
2706
0
    double
2707
0
      x,
2708
0
      y,
2709
0
      z;
2710
0
  } HaldInfo;
2711
2712
0
  CacheView
2713
0
    *hald_view,
2714
0
    *image_view;
2715
2716
0
  double
2717
0
    width;
2718
2719
0
  MagickBooleanType
2720
0
    status;
2721
2722
0
  MagickOffsetType
2723
0
    progress;
2724
2725
0
  PixelInfo
2726
0
    zero;
2727
2728
0
  size_t
2729
0
    cube_size,
2730
0
    length,
2731
0
    level;
2732
2733
0
  ssize_t
2734
0
    y;
2735
2736
0
  assert(image != (Image *) NULL);
2737
0
  assert(image->signature == MagickCoreSignature);
2738
0
  if (IsEventLogging() != MagickFalse)
2739
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2740
0
  assert(hald_image != (Image *) NULL);
2741
0
  assert(hald_image->signature == MagickCoreSignature);
2742
0
  if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
2743
0
    return(MagickFalse);
2744
0
  if ((image->alpha_trait & BlendPixelTrait) == 0)
2745
0
    (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
2746
0
  if (image->colorspace != hald_image->colorspace)
2747
0
    (void) SetImageColorspace(image,hald_image->colorspace,exception);
2748
  /*
2749
    Hald clut image.
2750
  */
2751
0
  status=MagickTrue;
2752
0
  progress=0;
2753
0
  length=(size_t) MagickMin((MagickRealType) hald_image->columns,
2754
0
    (MagickRealType) hald_image->rows);
2755
0
  for (level=2; (level*level*level) < length; level++) ;
2756
0
  level*=level;
2757
0
  cube_size=level*level;
2758
0
  width=(double) hald_image->columns;
2759
0
  GetPixelInfo(hald_image,&zero);
2760
0
  hald_view=AcquireVirtualCacheView(hald_image,exception);
2761
0
  image_view=AcquireAuthenticCacheView(image,exception);
2762
#if defined(MAGICKCORE_OPENMP_SUPPORT)
2763
  #pragma omp parallel for schedule(static) shared(progress,status) \
2764
    magick_number_threads(image,image,image->rows,1)
2765
#endif
2766
0
  for (y=0; y < (ssize_t) image->rows; y++)
2767
0
  {
2768
0
    Quantum
2769
0
      *magick_restrict q;
2770
2771
0
    ssize_t
2772
0
      x;
2773
2774
0
    if (status == MagickFalse)
2775
0
      continue;
2776
0
    q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2777
0
    if (q == (Quantum *) NULL)
2778
0
      {
2779
0
        status=MagickFalse;
2780
0
        continue;
2781
0
      }
2782
0
    for (x=0; x < (ssize_t) image->columns; x++)
2783
0
    {
2784
0
      double
2785
0
        area = 0.0,
2786
0
        offset = 0.0;
2787
2788
0
      HaldInfo
2789
0
        point = { 0, 0, 0 };
2790
2791
0
      PixelInfo
2792
0
        pixel = zero,
2793
0
        pixel1 = zero,
2794
0
        pixel2 = zero,
2795
0
        pixel3 = zero,
2796
0
        pixel4 = zero;
2797
2798
0
      point.x=QuantumScale*(level-1.0)*(double) GetPixelRed(image,q);
2799
0
      point.y=QuantumScale*(level-1.0)*(double) GetPixelGreen(image,q);
2800
0
      point.z=QuantumScale*(level-1.0)*(double) GetPixelBlue(image,q);
2801
0
      offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2802
0
      point.x-=floor(point.x);
2803
0
      point.y-=floor(point.y);
2804
0
      point.z-=floor(point.z);
2805
0
      status=InterpolatePixelInfo(hald_image,hald_view,hald_image->interpolate,
2806
0
        fmod(offset,width),floor(offset/width),&pixel1,exception);
2807
0
      if (status == MagickFalse)
2808
0
        break;
2809
0
      status=InterpolatePixelInfo(hald_image,hald_view,hald_image->interpolate,
2810
0
        fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2811
0
      if (status == MagickFalse)
2812
0
        break;
2813
0
      area=point.y;
2814
0
      if (hald_image->interpolate == NearestInterpolatePixel)
2815
0
        area=(point.y < 0.5) ? 0.0 : 1.0;
2816
0
      CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2817
0
        area,&pixel3);
2818
0
      offset+=cube_size;
2819
0
      status=InterpolatePixelInfo(hald_image,hald_view,hald_image->interpolate,
2820
0
        fmod(offset,width),floor(offset/width),&pixel1,exception);
2821
0
      if (status == MagickFalse)
2822
0
        break;
2823
0
      status=InterpolatePixelInfo(hald_image,hald_view,hald_image->interpolate,
2824
0
        fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2825
0
      if (status == MagickFalse)
2826
0
        break;
2827
0
      CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2828
0
        area,&pixel4);
2829
0
      area=point.z;
2830
0
      if (hald_image->interpolate == NearestInterpolatePixel)
2831
0
        area=(point.z < 0.5)? 0.0 : 1.0;
2832
0
      CompositePixelInfoAreaBlend(&pixel3,pixel3.alpha,&pixel4,pixel4.alpha,
2833
0
        area,&pixel);
2834
0
      if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2835
0
        SetPixelRed(image,ClampToQuantum(pixel.red),q);
2836
0
      if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2837
0
        SetPixelGreen(image,ClampToQuantum(pixel.green),q);
2838
0
      if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2839
0
        SetPixelBlue(image,ClampToQuantum(pixel.blue),q);
2840
0
      if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2841
0
          (image->colorspace == CMYKColorspace))
2842
0
        SetPixelBlack(image,ClampToQuantum(pixel.black),q);
2843
0
      if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2844
0
          (image->alpha_trait != UndefinedPixelTrait))
2845
0
        SetPixelAlpha(image,ClampToQuantum(pixel.alpha),q);
2846
0
      q+=(ptrdiff_t) GetPixelChannels(image);
2847
0
    }
2848
0
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2849
0
      status=MagickFalse;
2850
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
2851
0
      {
2852
0
        MagickBooleanType
2853
0
          proceed;
2854
2855
#if defined(MAGICKCORE_OPENMP_SUPPORT)
2856
        #pragma omp atomic
2857
#endif
2858
0
        progress++;
2859
0
        proceed=SetImageProgress(image,HaldClutImageTag,progress,image->rows);
2860
0
        if (proceed == MagickFalse)
2861
0
          status=MagickFalse;
2862
0
      }
2863
0
  }
2864
0
  hald_view=DestroyCacheView(hald_view);
2865
0
  image_view=DestroyCacheView(image_view);
2866
0
  return(status);
2867
0
}
2868

2869
/*
2870
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2871
%                                                                             %
2872
%                                                                             %
2873
%                                                                             %
2874
%     L e v e l I m a g e                                                     %
2875
%                                                                             %
2876
%                                                                             %
2877
%                                                                             %
2878
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2879
%
2880
%  LevelImage() adjusts the levels of a particular image channel by
2881
%  scaling the colors falling between specified white and black points to
2882
%  the full available quantum range.
2883
%
2884
%  The parameters provided represent the black, and white points.  The black
2885
%  point specifies the darkest color in the image. Colors darker than the
2886
%  black point are set to zero.  White point specifies the lightest color in
2887
%  the image.  Colors brighter than the white point are set to the maximum
2888
%  quantum value.
2889
%
2890
%  If a '!' flag is given, map black and white colors to the given levels
2891
%  rather than mapping those levels to black and white.  See
2892
%  LevelizeImage() below.
2893
%
2894
%  Gamma specifies a gamma correction to apply to the image.
2895
%
2896
%  The format of the LevelImage method is:
2897
%
2898
%      MagickBooleanType LevelImage(Image *image,const double black_point,
2899
%        const double white_point,const double gamma,ExceptionInfo *exception)
2900
%
2901
%  A description of each parameter follows:
2902
%
2903
%    o image: the image.
2904
%
2905
%    o black_point: The level to map zero (black) to.
2906
%
2907
%    o white_point: The level to map QuantumRange (white) to.
2908
%
2909
%    o exception: return any errors or warnings in this structure.
2910
%
2911
*/
2912
2913
static inline double LevelPixel(const double black_point,
2914
  const double white_point,const double gamma,const double pixel)
2915
0
{
2916
0
  double
2917
0
    level_pixel,
2918
0
    scale;
2919
2920
0
  scale=MagickSafeReciprocal(white_point-black_point);
2921
0
  level_pixel=(double) QuantumRange*gamma_pow(scale*((double) pixel-(double)
2922
0
    black_point),MagickSafeReciprocal(gamma));
2923
0
  return(level_pixel);
2924
0
}
2925
2926
MagickExport MagickBooleanType LevelImage(Image *image,const double black_point,
2927
  const double white_point,const double gamma,ExceptionInfo *exception)
2928
0
{
2929
0
#define LevelImageTag  "Level/Image"
2930
2931
0
  CacheView
2932
0
    *image_view;
2933
2934
0
  MagickBooleanType
2935
0
    status;
2936
2937
0
  MagickOffsetType
2938
0
    progress;
2939
2940
0
  ssize_t
2941
0
    i,
2942
0
    y;
2943
2944
  /*
2945
    Allocate and initialize levels map.
2946
  */
2947
0
  assert(image != (Image *) NULL);
2948
0
  assert(image->signature == MagickCoreSignature);
2949
0
  if (IsEventLogging() != MagickFalse)
2950
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2951
0
  if (image->storage_class == PseudoClass)
2952
0
    for (i=0; i < (ssize_t) image->colors; i++)
2953
0
    {
2954
      /*
2955
        Level colormap.
2956
      */
2957
0
      if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2958
0
        image->colormap[i].red=(double) ClampToQuantum(LevelPixel(black_point,
2959
0
          white_point,gamma,image->colormap[i].red));
2960
0
      if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2961
0
        image->colormap[i].green=(double) ClampToQuantum(LevelPixel(black_point,
2962
0
          white_point,gamma,image->colormap[i].green));
2963
0
      if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2964
0
        image->colormap[i].blue=(double) ClampToQuantum(LevelPixel(black_point,
2965
0
          white_point,gamma,image->colormap[i].blue));
2966
0
      if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2967
0
        image->colormap[i].alpha=(double) ClampToQuantum(LevelPixel(black_point,
2968
0
          white_point,gamma,image->colormap[i].alpha));
2969
0
    }
2970
  /*
2971
    Level image.
2972
  */
2973
0
  status=MagickTrue;
2974
0
  progress=0;
2975
0
  image_view=AcquireAuthenticCacheView(image,exception);
2976
#if defined(MAGICKCORE_OPENMP_SUPPORT)
2977
  #pragma omp parallel for schedule(static) shared(progress,status) \
2978
    magick_number_threads(image,image,image->rows,1)
2979
#endif
2980
0
  for (y=0; y < (ssize_t) image->rows; y++)
2981
0
  {
2982
0
    Quantum
2983
0
      *magick_restrict q;
2984
2985
0
    ssize_t
2986
0
      x;
2987
2988
0
    if (status == MagickFalse)
2989
0
      continue;
2990
0
    q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2991
0
    if (q == (Quantum *) NULL)
2992
0
      {
2993
0
        status=MagickFalse;
2994
0
        continue;
2995
0
      }
2996
0
    for (x=0; x < (ssize_t) image->columns; x++)
2997
0
    {
2998
0
      ssize_t
2999
0
        j;
3000
3001
0
      for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
3002
0
      {
3003
0
        PixelChannel channel = GetPixelChannelChannel(image,j);
3004
0
        PixelTrait traits = GetPixelChannelTraits(image,channel);
3005
0
        if ((traits & UpdatePixelTrait) == 0)
3006
0
          continue;
3007
0
        q[j]=ClampToQuantum(LevelPixel(black_point,white_point,gamma,
3008
0
          (double) q[j]));
3009
0
      }
3010
0
      q+=(ptrdiff_t) GetPixelChannels(image);
3011
0
    }
3012
0
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3013
0
      status=MagickFalse;
3014
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
3015
0
      {
3016
0
        MagickBooleanType
3017
0
          proceed;
3018
3019
#if defined(MAGICKCORE_OPENMP_SUPPORT)
3020
        #pragma omp atomic
3021
#endif
3022
0
        progress++;
3023
0
        proceed=SetImageProgress(image,LevelImageTag,progress,image->rows);
3024
0
        if (proceed == MagickFalse)
3025
0
          status=MagickFalse;
3026
0
      }
3027
0
  }
3028
0
  image_view=DestroyCacheView(image_view);
3029
0
  (void) ClampImage(image,exception);
3030
0
  return(status);
3031
0
}
3032

3033
/*
3034
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3035
%                                                                             %
3036
%                                                                             %
3037
%                                                                             %
3038
%     L e v e l i z e I m a g e                                               %
3039
%                                                                             %
3040
%                                                                             %
3041
%                                                                             %
3042
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3043
%
3044
%  LevelizeImage() applies the reversed LevelImage() operation to just
3045
%  the specific channels specified.  It compresses the full range of color
3046
%  values, so that they lie between the given black and white points. Gamma is
3047
%  applied before the values are mapped.
3048
%
3049
%  LevelizeImage() can be called with by using a +level command line
3050
%  API option, or using a '!' on a -level or LevelImage() geometry string.
3051
%
3052
%  It can be used to de-contrast a greyscale image to the exact levels
3053
%  specified.  Or by using specific levels for each channel of an image you
3054
%  can convert a gray-scale image to any linear color gradient, according to
3055
%  those levels.
3056
%
3057
%  The format of the LevelizeImage method is:
3058
%
3059
%      MagickBooleanType LevelizeImage(Image *image,const double black_point,
3060
%        const double white_point,const double gamma,ExceptionInfo *exception)
3061
%
3062
%  A description of each parameter follows:
3063
%
3064
%    o image: the image.
3065
%
3066
%    o black_point: The level to map zero (black) to.
3067
%
3068
%    o white_point: The level to map QuantumRange (white) to.
3069
%
3070
%    o gamma: adjust gamma by this factor before mapping values.
3071
%
3072
%    o exception: return any errors or warnings in this structure.
3073
%
3074
*/
3075
MagickExport MagickBooleanType LevelizeImage(Image *image,
3076
  const double black_point,const double white_point,const double gamma,
3077
  ExceptionInfo *exception)
3078
0
{
3079
0
#define LevelizeImageTag  "Levelize/Image"
3080
0
#define LevelizeValue(x) ClampToQuantum(((MagickRealType) gamma_pow((double) \
3081
0
  (QuantumScale*((double) x)),gamma))*(white_point-black_point)+black_point)
3082
3083
0
  CacheView
3084
0
    *image_view;
3085
3086
0
  MagickBooleanType
3087
0
    status;
3088
3089
0
  MagickOffsetType
3090
0
    progress;
3091
3092
0
  ssize_t
3093
0
    i;
3094
3095
0
  ssize_t
3096
0
    y;
3097
3098
  /*
3099
    Allocate and initialize levels map.
3100
  */
3101
0
  assert(image != (Image *) NULL);
3102
0
  assert(image->signature == MagickCoreSignature);
3103
0
  if (IsEventLogging() != MagickFalse)
3104
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3105
0
  if (image->storage_class == PseudoClass)
3106
0
    for (i=0; i < (ssize_t) image->colors; i++)
3107
0
    {
3108
      /*
3109
        Level colormap.
3110
      */
3111
0
      if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3112
0
        image->colormap[i].red=(double) LevelizeValue(image->colormap[i].red);
3113
0
      if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3114
0
        image->colormap[i].green=(double) LevelizeValue(
3115
0
          image->colormap[i].green);
3116
0
      if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3117
0
        image->colormap[i].blue=(double) LevelizeValue(image->colormap[i].blue);
3118
0
      if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
3119
0
        image->colormap[i].alpha=(double) LevelizeValue(
3120
0
          image->colormap[i].alpha);
3121
0
    }
3122
  /*
3123
    Level image.
3124
  */
3125
0
  status=MagickTrue;
3126
0
  progress=0;
3127
0
  image_view=AcquireAuthenticCacheView(image,exception);
3128
#if defined(MAGICKCORE_OPENMP_SUPPORT)
3129
  #pragma omp parallel for schedule(static) shared(progress,status) \
3130
    magick_number_threads(image,image,image->rows,1)
3131
#endif
3132
0
  for (y=0; y < (ssize_t) image->rows; y++)
3133
0
  {
3134
0
    Quantum
3135
0
      *magick_restrict q;
3136
3137
0
    ssize_t
3138
0
      x;
3139
3140
0
    if (status == MagickFalse)
3141
0
      continue;
3142
0
    q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3143
0
    if (q == (Quantum *) NULL)
3144
0
      {
3145
0
        status=MagickFalse;
3146
0
        continue;
3147
0
      }
3148
0
    for (x=0; x < (ssize_t) image->columns; x++)
3149
0
    {
3150
0
      ssize_t
3151
0
        j;
3152
3153
0
      for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
3154
0
      {
3155
0
        PixelChannel channel = GetPixelChannelChannel(image,j);
3156
0
        PixelTrait traits = GetPixelChannelTraits(image,channel);
3157
0
        if ((traits & UpdatePixelTrait) == 0)
3158
0
          continue;
3159
0
        q[j]=LevelizeValue(q[j]);
3160
0
      }
3161
0
      q+=(ptrdiff_t) GetPixelChannels(image);
3162
0
    }
3163
0
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3164
0
      status=MagickFalse;
3165
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
3166
0
      {
3167
0
        MagickBooleanType
3168
0
          proceed;
3169
3170
#if defined(MAGICKCORE_OPENMP_SUPPORT)
3171
        #pragma omp atomic
3172
#endif
3173
0
        progress++;
3174
0
        proceed=SetImageProgress(image,LevelizeImageTag,progress,image->rows);
3175
0
        if (proceed == MagickFalse)
3176
0
          status=MagickFalse;
3177
0
      }
3178
0
  }
3179
0
  image_view=DestroyCacheView(image_view);
3180
0
  return(status);
3181
0
}
3182

3183
/*
3184
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3185
%                                                                             %
3186
%                                                                             %
3187
%                                                                             %
3188
%     L e v e l I m a g e C o l o r s                                         %
3189
%                                                                             %
3190
%                                                                             %
3191
%                                                                             %
3192
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3193
%
3194
%  LevelImageColors() maps the given color to "black" and "white" values,
3195
%  linearly spreading out the colors, and level values on a channel by channel
3196
%  bases, as per LevelImage().  The given colors allows you to specify
3197
%  different level ranges for each of the color channels separately.
3198
%
3199
%  If the boolean 'invert' is set true the image values will modified in the
3200
%  reverse direction. That is any existing "black" and "white" colors in the
3201
%  image will become the color values given, with all other values compressed
3202
%  appropriately.  This effectively maps a greyscale gradient into the given
3203
%  color gradient.
3204
%
3205
%  The format of the LevelImageColors method is:
3206
%
3207
%    MagickBooleanType LevelImageColors(Image *image,
3208
%      const PixelInfo *black_color,const PixelInfo *white_color,
3209
%      const MagickBooleanType invert,ExceptionInfo *exception)
3210
%
3211
%  A description of each parameter follows:
3212
%
3213
%    o image: the image.
3214
%
3215
%    o black_color: The color to map black to/from
3216
%
3217
%    o white_point: The color to map white to/from
3218
%
3219
%    o invert: if true map the colors (levelize), rather than from (level)
3220
%
3221
%    o exception: return any errors or warnings in this structure.
3222
%
3223
*/
3224
MagickExport MagickBooleanType LevelImageColors(Image *image,
3225
  const PixelInfo *black_color,const PixelInfo *white_color,
3226
  const MagickBooleanType invert,ExceptionInfo *exception)
3227
0
{
3228
0
  ChannelType
3229
0
    channel_mask;
3230
3231
0
  MagickStatusType
3232
0
    status;
3233
3234
  /*
3235
    Allocate and initialize levels map.
3236
  */
3237
0
  assert(image != (Image *) NULL);
3238
0
  assert(image->signature == MagickCoreSignature);
3239
0
  if (IsEventLogging() != MagickFalse)
3240
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3241
0
  if ((IsGrayColorspace(image->colorspace) != MagickFalse) &&
3242
0
      ((IsGrayColorspace(black_color->colorspace) == MagickFalse) ||
3243
0
       (IsGrayColorspace(white_color->colorspace) == MagickFalse)))
3244
0
    (void) SetImageColorspace(image,sRGBColorspace,exception);
3245
0
  status=MagickTrue;
3246
0
  if (invert == MagickFalse)
3247
0
    {
3248
0
      if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3249
0
        {
3250
0
          channel_mask=SetImageChannelMask(image,RedChannel);
3251
0
          status&=(MagickStatusType) LevelImage(image,black_color->red,
3252
0
            white_color->red,1.0,exception);
3253
0
          (void) SetImageChannelMask(image,channel_mask);
3254
0
        }
3255
0
      if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3256
0
        {
3257
0
          channel_mask=SetImageChannelMask(image,GreenChannel);
3258
0
          status&=(MagickStatusType) LevelImage(image,black_color->green,
3259
0
            white_color->green,1.0,exception);
3260
0
          (void) SetImageChannelMask(image,channel_mask);
3261
0
        }
3262
0
      if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3263
0
        {
3264
0
          channel_mask=SetImageChannelMask(image,BlueChannel);
3265
0
          status&=(MagickStatusType) LevelImage(image,black_color->blue,
3266
0
            white_color->blue,1.0,exception);
3267
0
          (void) SetImageChannelMask(image,channel_mask);
3268
0
        }
3269
0
      if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
3270
0
          (image->colorspace == CMYKColorspace))
3271
0
        {
3272
0
          channel_mask=SetImageChannelMask(image,BlackChannel);
3273
0
          status&=(MagickStatusType) LevelImage(image,black_color->black,
3274
0
            white_color->black,1.0,exception);
3275
0
          (void) SetImageChannelMask(image,channel_mask);
3276
0
        }
3277
0
      if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
3278
0
          (image->alpha_trait != UndefinedPixelTrait))
3279
0
        {
3280
0
          channel_mask=SetImageChannelMask(image,AlphaChannel);
3281
0
          status&=(MagickStatusType) LevelImage(image,black_color->alpha,
3282
0
            white_color->alpha,1.0,exception);
3283
0
          (void) SetImageChannelMask(image,channel_mask);
3284
0
        }
3285
0
    }
3286
0
  else
3287
0
    {
3288
0
      if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3289
0
        {
3290
0
          channel_mask=SetImageChannelMask(image,RedChannel);
3291
0
          status&=(MagickStatusType) LevelizeImage(image,black_color->red,
3292
0
            white_color->red,1.0,exception);
3293
0
          (void) SetImageChannelMask(image,channel_mask);
3294
0
        }
3295
0
      if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3296
0
        {
3297
0
          channel_mask=SetImageChannelMask(image,GreenChannel);
3298
0
          status&=(MagickStatusType) LevelizeImage(image,black_color->green,
3299
0
            white_color->green,1.0,exception);
3300
0
          (void) SetImageChannelMask(image,channel_mask);
3301
0
        }
3302
0
      if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3303
0
        {
3304
0
          channel_mask=SetImageChannelMask(image,BlueChannel);
3305
0
          status&=(MagickStatusType) LevelizeImage(image,black_color->blue,
3306
0
            white_color->blue,1.0,exception);
3307
0
          (void) SetImageChannelMask(image,channel_mask);
3308
0
        }
3309
0
      if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
3310
0
          (image->colorspace == CMYKColorspace))
3311
0
        {
3312
0
          channel_mask=SetImageChannelMask(image,BlackChannel);
3313
0
          status&=(MagickStatusType) LevelizeImage(image,black_color->black,
3314
0
            white_color->black,1.0,exception);
3315
0
          (void) SetImageChannelMask(image,channel_mask);
3316
0
        }
3317
0
      if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
3318
0
          (image->alpha_trait != UndefinedPixelTrait))
3319
0
        {
3320
0
          channel_mask=SetImageChannelMask(image,AlphaChannel);
3321
0
          status&=(MagickStatusType) LevelizeImage(image,black_color->alpha,
3322
0
            white_color->alpha,1.0,exception);
3323
0
          (void) SetImageChannelMask(image,channel_mask);
3324
0
        }
3325
0
    }
3326
0
  return(status != 0 ? MagickTrue : MagickFalse);
3327
0
}
3328

3329
/*
3330
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3331
%                                                                             %
3332
%                                                                             %
3333
%                                                                             %
3334
%     L i n e a r S t r e t c h I m a g e                                     %
3335
%                                                                             %
3336
%                                                                             %
3337
%                                                                             %
3338
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3339
%
3340
%  LinearStretchImage() discards any pixels below the black point and above
3341
%  the white point and levels the remaining pixels.
3342
%
3343
%  The format of the LinearStretchImage method is:
3344
%
3345
%      MagickBooleanType LinearStretchImage(Image *image,
3346
%        const double black_point,const double white_point,
3347
%        ExceptionInfo *exception)
3348
%
3349
%  A description of each parameter follows:
3350
%
3351
%    o image: the image.
3352
%
3353
%    o black_point: the black point.
3354
%
3355
%    o white_point: the white point.
3356
%
3357
%    o exception: return any errors or warnings in this structure.
3358
%
3359
*/
3360
MagickExport MagickBooleanType LinearStretchImage(Image *image,
3361
  const double black_point,const double white_point,ExceptionInfo *exception)
3362
0
{
3363
0
#define LinearStretchImageTag  "LinearStretch/Image"
3364
3365
0
  CacheView
3366
0
    *image_view;
3367
3368
0
  char
3369
0
    property[MagickPathExtent];
3370
3371
0
  double
3372
0
    *histogram,
3373
0
    intensity;
3374
3375
0
  MagickBooleanType
3376
0
    status;
3377
3378
0
  ssize_t
3379
0
    black,
3380
0
    white,
3381
0
    y;
3382
3383
  /*
3384
    Allocate histogram and linear map.
3385
  */
3386
0
  assert(image != (Image *) NULL);
3387
0
  assert(image->signature == MagickCoreSignature);
3388
0
  histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*histogram));
3389
0
  if (histogram == (double *) NULL)
3390
0
    ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3391
0
      image->filename);
3392
  /*
3393
    Form histogram.
3394
  */
3395
0
  (void) memset(histogram,0,(MaxMap+1)*sizeof(*histogram));
3396
0
  image_view=AcquireVirtualCacheView(image,exception);
3397
0
  for (y=0; y < (ssize_t) image->rows; y++)
3398
0
  {
3399
0
    const Quantum
3400
0
      *magick_restrict p;
3401
3402
0
    ssize_t
3403
0
      x;
3404
3405
0
    p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
3406
0
    if (p == (const Quantum *) NULL)
3407
0
      break;
3408
0
    for (x=0; x < (ssize_t) image->columns; x++)
3409
0
    {
3410
0
      intensity=GetPixelIntensity(image,p);
3411
0
      histogram[ScaleQuantumToMap(ClampToQuantum(intensity))]++;
3412
0
      p+=(ptrdiff_t) GetPixelChannels(image);
3413
0
    }
3414
0
  }
3415
0
  image_view=DestroyCacheView(image_view);
3416
  /*
3417
    Find the histogram boundaries by locating the black and white point levels.
3418
  */
3419
0
  intensity=0.0;
3420
0
  for (black=0; black < (ssize_t) MaxMap; black++)
3421
0
  {
3422
0
    intensity+=histogram[black];
3423
0
    if (intensity >= black_point)
3424
0
      break;
3425
0
  }
3426
0
  intensity=0.0;
3427
0
  for (white=(ssize_t) MaxMap; white != 0; white--)
3428
0
  {
3429
0
    intensity+=histogram[white];
3430
0
    if (intensity >= white_point)
3431
0
      break;
3432
0
  }
3433
0
  histogram=(double *) RelinquishMagickMemory(histogram);
3434
0
  status=LevelImage(image,(double) ScaleMapToQuantum((MagickRealType) black),
3435
0
    (double) ScaleMapToQuantum((MagickRealType) white),1.0,exception);
3436
0
  (void) FormatLocaleString(property,MagickPathExtent,"%gx%g%%",100.0*black/
3437
0
    MaxMap,100.0*white/MaxMap);
3438
0
  (void) SetImageProperty(image,"histogram:linear-stretch",property,exception);
3439
0
  return(status);
3440
0
}
3441

3442
/*
3443
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3444
%                                                                             %
3445
%                                                                             %
3446
%                                                                             %
3447
%     M o d u l a t e I m a g e                                               %
3448
%                                                                             %
3449
%                                                                             %
3450
%                                                                             %
3451
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3452
%
3453
%  ModulateImage() lets you control the brightness, saturation, and hue
3454
%  of an image.  Modulate represents the brightness, saturation, and hue
3455
%  as one parameter (e.g. 90,150,100).  If the image colorspace is HSL, the
3456
%  modulation is lightness, saturation, and hue.  For HWB, use blackness,
3457
%  whiteness, and hue. And for HCL, use chrome, luma, and hue.
3458
%
3459
%  The format of the ModulateImage method is:
3460
%
3461
%      MagickBooleanType ModulateImage(Image *image,const char *modulate,
3462
%        ExceptionInfo *exception)
3463
%
3464
%  A description of each parameter follows:
3465
%
3466
%    o image: the image.
3467
%
3468
%    o modulate: Define the percent change in brightness, saturation, and hue.
3469
%
3470
%    o exception: return any errors or warnings in this structure.
3471
%
3472
*/
3473
3474
static inline void ModulateHCL(const double percent_hue,
3475
  const double percent_chroma,const double percent_luma,double *red,
3476
  double *green,double *blue)
3477
0
{
3478
0
  double
3479
0
    hue,
3480
0
    luma,
3481
0
    chroma;
3482
3483
  /*
3484
    Increase or decrease color luma, chroma, or hue.
3485
  */
3486
0
  ConvertRGBToHCL(*red,*green,*blue,&hue,&chroma,&luma);
3487
0
  hue+=fmod((percent_hue-100.0),200.0)/200.0;
3488
0
  chroma*=0.01*percent_chroma;
3489
0
  luma*=0.01*percent_luma;
3490
0
  ConvertHCLToRGB(hue,chroma,luma,red,green,blue);
3491
0
}
3492
3493
static inline void ModulateHCLp(const double percent_hue,
3494
  const double percent_chroma,const double percent_luma,double *red,
3495
  double *green,double *blue)
3496
0
{
3497
0
  double
3498
0
    hue,
3499
0
    luma,
3500
0
    chroma;
3501
3502
  /*
3503
    Increase or decrease color luma, chroma, or hue.
3504
  */
3505
0
  ConvertRGBToHCLp(*red,*green,*blue,&hue,&chroma,&luma);
3506
0
  hue+=fmod((percent_hue-100.0),200.0)/200.0;
3507
0
  chroma*=0.01*percent_chroma;
3508
0
  luma*=0.01*percent_luma;
3509
0
  ConvertHCLpToRGB(hue,chroma,luma,red,green,blue);
3510
0
}
3511
3512
static inline void ModulateHSB(const double percent_hue,
3513
  const double percent_saturation,const double percent_brightness,double *red,
3514
  double *green,double *blue)
3515
0
{
3516
0
  double
3517
0
    brightness,
3518
0
    hue,
3519
0
    saturation;
3520
3521
  /*
3522
    Increase or decrease color brightness, saturation, or hue.
3523
  */
3524
0
  ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
3525
0
  hue+=fmod((percent_hue-100.0),200.0)/200.0;
3526
0
  saturation*=0.01*percent_saturation;
3527
0
  brightness*=0.01*percent_brightness;
3528
0
  ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
3529
0
}
3530
3531
static inline void ModulateHSI(const double percent_hue,
3532
  const double percent_saturation,const double percent_intensity,double *red,
3533
  double *green,double *blue)
3534
0
{
3535
0
  double
3536
0
    intensity,
3537
0
    hue,
3538
0
    saturation;
3539
3540
  /*
3541
    Increase or decrease color intensity, saturation, or hue.
3542
  */
3543
0
  ConvertRGBToHSI(*red,*green,*blue,&hue,&saturation,&intensity);
3544
0
  hue+=fmod((percent_hue-100.0),200.0)/200.0;
3545
0
  saturation*=0.01*percent_saturation;
3546
0
  intensity*=0.01*percent_intensity;
3547
0
  ConvertHSIToRGB(hue,saturation,intensity,red,green,blue);
3548
0
}
3549
3550
static inline void ModulateHSL(const double percent_hue,
3551
  const double percent_saturation,const double percent_lightness,double *red,
3552
  double *green,double *blue)
3553
0
{
3554
0
  double
3555
0
    hue,
3556
0
    lightness,
3557
0
    saturation;
3558
3559
  /*
3560
    Increase or decrease color lightness, saturation, or hue.
3561
  */
3562
0
  ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
3563
0
  hue+=fmod((percent_hue-100.0),200.0)/200.0;
3564
0
  saturation*=0.01*percent_saturation;
3565
0
  lightness*=0.01*percent_lightness;
3566
0
  ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
3567
0
}
3568
3569
static inline void ModulateHSV(const double percent_hue,
3570
  const double percent_saturation,const double percent_value,double *red,
3571
  double *green,double *blue)
3572
0
{
3573
0
  double
3574
0
    hue,
3575
0
    saturation,
3576
0
    value;
3577
3578
  /*
3579
    Increase or decrease color value, saturation, or hue.
3580
  */
3581
0
  ConvertRGBToHSV(*red,*green,*blue,&hue,&saturation,&value);
3582
0
  hue+=fmod((percent_hue-100.0),200.0)/200.0;
3583
0
  saturation*=0.01*percent_saturation;
3584
0
  value*=0.01*percent_value;
3585
0
  ConvertHSVToRGB(hue,saturation,value,red,green,blue);
3586
0
}
3587
3588
static inline void ModulateHWB(const double percent_hue,
3589
  const double percent_whiteness,const double percent_blackness,double *red,
3590
  double *green,double *blue)
3591
0
{
3592
0
  double
3593
0
    blackness,
3594
0
    hue,
3595
0
    whiteness;
3596
3597
  /*
3598
    Increase or decrease color blackness, whiteness, or hue.
3599
  */
3600
0
  ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
3601
0
  hue+=fmod((percent_hue-100.0),200.0)/200.0;
3602
0
  blackness*=0.01*percent_blackness;
3603
0
  whiteness*=0.01*percent_whiteness;
3604
0
  ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
3605
0
}
3606
3607
static inline void ModulateLCHab(const double percent_luma,
3608
  const double percent_chroma,const double percent_hue,
3609
  const IlluminantType illuminant,double *red,double *green,double *blue)
3610
0
{
3611
0
  double
3612
0
    hue,
3613
0
    luma,
3614
0
    chroma;
3615
3616
  /*
3617
    Increase or decrease color luma, chroma, or hue.
3618
  */
3619
0
  ConvertRGBToLCHab(*red,*green,*blue,illuminant,&luma,&chroma,&hue);
3620
0
  luma*=0.01*percent_luma;
3621
0
  chroma*=0.01*percent_chroma;
3622
0
  hue+=fmod((percent_hue-100.0),200.0)/200.0;
3623
0
  ConvertLCHabToRGB(luma,chroma,hue,illuminant,red,green,blue);
3624
0
}
3625
3626
static inline void ModulateLCHuv(const double percent_luma,
3627
  const double percent_chroma,const double percent_hue,
3628
  const IlluminantType illuminant,double *red,double *green,double *blue)
3629
0
{
3630
0
  double
3631
0
    hue,
3632
0
    luma,
3633
0
    chroma;
3634
3635
  /*
3636
    Increase or decrease color luma, chroma, or hue.
3637
  */
3638
0
  ConvertRGBToLCHuv(*red,*green,*blue,illuminant,&luma,&chroma,&hue);
3639
0
  luma*=0.01*percent_luma;
3640
0
  chroma*=0.01*percent_chroma;
3641
0
  hue+=fmod((percent_hue-100.0),200.0)/200.0;
3642
0
  ConvertLCHuvToRGB(luma,chroma,hue,illuminant,red,green,blue);
3643
0
}
3644
3645
MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate,
3646
  ExceptionInfo *exception)
3647
0
{
3648
0
#define ModulateImageTag  "Modulate/Image"
3649
3650
0
  CacheView
3651
0
    *image_view;
3652
3653
0
  ColorspaceType
3654
0
    colorspace = UndefinedColorspace;
3655
3656
0
  const char
3657
0
    *artifact;
3658
3659
0
  double
3660
0
    percent_brightness = 100.0,
3661
0
    percent_hue = 100.0,
3662
0
    percent_saturation = 100.0;
3663
3664
0
  GeometryInfo
3665
0
    geometry_info;
3666
3667
0
  IlluminantType
3668
0
    illuminant = D65Illuminant;
3669
3670
0
  MagickBooleanType
3671
0
    status;
3672
3673
0
  MagickOffsetType
3674
0
    progress;
3675
3676
0
  MagickStatusType
3677
0
    flags;
3678
3679
0
  ssize_t
3680
0
    i;
3681
3682
0
  ssize_t
3683
0
    y;
3684
3685
  /*
3686
    Initialize modulate table.
3687
  */
3688
0
  assert(image != (Image *) NULL);
3689
0
  assert(image->signature == MagickCoreSignature);
3690
0
  if (IsEventLogging() != MagickFalse)
3691
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3692
0
  if (modulate == (char *) NULL)
3693
0
    return(MagickFalse);
3694
0
  if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
3695
0
    (void) SetImageColorspace(image,sRGBColorspace,exception);
3696
0
  flags=ParseGeometry(modulate,&geometry_info);
3697
0
  if ((flags & RhoValue) != 0)
3698
0
    percent_brightness=geometry_info.rho;
3699
0
  if ((flags & SigmaValue) != 0)
3700
0
    percent_saturation=geometry_info.sigma;
3701
0
  if ((flags & XiValue) != 0)
3702
0
    percent_hue=geometry_info.xi;
3703
0
  artifact=GetImageArtifact(image,"modulate:colorspace");
3704
0
  if (artifact != (const char *) NULL)
3705
0
    colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions,
3706
0
      MagickFalse,artifact);
3707
0
  artifact=GetImageArtifact(image,"color:illuminant");
3708
0
  if (artifact != (const char *) NULL)
3709
0
    {
3710
0
      ssize_t
3711
0
        illuminant_type;
3712
3713
0
      illuminant_type=ParseCommandOption(MagickIlluminantOptions,MagickFalse,
3714
0
        artifact);
3715
0
      if (illuminant_type < 0)
3716
0
        {
3717
0
          illuminant=UndefinedIlluminant;
3718
0
          colorspace=UndefinedColorspace;
3719
0
        }
3720
0
      else
3721
0
        illuminant=(IlluminantType) illuminant_type;
3722
0
    }
3723
0
  if (image->storage_class == PseudoClass)
3724
0
    for (i=0; i < (ssize_t) image->colors; i++)
3725
0
    {
3726
0
      double
3727
0
        blue,
3728
0
        green,
3729
0
        red;
3730
3731
      /*
3732
        Modulate image colormap.
3733
      */
3734
0
      red=(double) image->colormap[i].red;
3735
0
      green=(double) image->colormap[i].green;
3736
0
      blue=(double) image->colormap[i].blue;
3737
0
      switch (colorspace)
3738
0
      {
3739
0
        case HCLColorspace:
3740
0
        {
3741
0
          ModulateHCL(percent_hue,percent_saturation,percent_brightness,
3742
0
            &red,&green,&blue);
3743
0
          break;
3744
0
        }
3745
0
        case HCLpColorspace:
3746
0
        {
3747
0
          ModulateHCLp(percent_hue,percent_saturation,percent_brightness,
3748
0
            &red,&green,&blue);
3749
0
          break;
3750
0
        }
3751
0
        case HSBColorspace:
3752
0
        {
3753
0
          ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3754
0
            &red,&green,&blue);
3755
0
          break;
3756
0
        }
3757
0
        case HSIColorspace:
3758
0
        {
3759
0
          ModulateHSI(percent_hue,percent_saturation,percent_brightness,
3760
0
            &red,&green,&blue);
3761
0
          break;
3762
0
        }
3763
0
        case HSLColorspace:
3764
0
        default:
3765
0
        {
3766
0
          ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3767
0
            &red,&green,&blue);
3768
0
          break;
3769
0
        }
3770
0
        case HSVColorspace:
3771
0
        {
3772
0
          ModulateHSV(percent_hue,percent_saturation,percent_brightness,
3773
0
            &red,&green,&blue);
3774
0
          break;
3775
0
        }
3776
0
        case HWBColorspace:
3777
0
        {
3778
0
          ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3779
0
            &red,&green,&blue);
3780
0
          break;
3781
0
        }
3782
0
        case LCHColorspace:
3783
0
        case LCHabColorspace:
3784
0
        {
3785
0
          ModulateLCHab(percent_brightness,percent_saturation,percent_hue,
3786
0
            illuminant,&red,&green,&blue);
3787
0
          break;
3788
0
        }
3789
0
        case LCHuvColorspace:
3790
0
        {
3791
0
          ModulateLCHuv(percent_brightness,percent_saturation,percent_hue,
3792
0
            illuminant,&red,&green,&blue);
3793
0
          break;
3794
0
        }
3795
0
      }
3796
0
      image->colormap[i].red=red;
3797
0
      image->colormap[i].green=green;
3798
0
      image->colormap[i].blue=blue;
3799
0
    }
3800
  /*
3801
    Modulate image.
3802
  */
3803
#if defined(MAGICKCORE_OPENCL_SUPPORT)
3804
  if (AccelerateModulateImage(image,percent_brightness,percent_hue,
3805
        percent_saturation,colorspace,exception) != MagickFalse)
3806
    return(MagickTrue);
3807
#endif
3808
0
  status=MagickTrue;
3809
0
  progress=0;
3810
0
  image_view=AcquireAuthenticCacheView(image,exception);
3811
#if defined(MAGICKCORE_OPENMP_SUPPORT)
3812
  #pragma omp parallel for schedule(static) shared(progress,status) \
3813
    magick_number_threads(image,image,image->rows,1)
3814
#endif
3815
0
  for (y=0; y < (ssize_t) image->rows; y++)
3816
0
  {
3817
0
    Quantum
3818
0
      *magick_restrict q;
3819
3820
0
    ssize_t
3821
0
      x;
3822
3823
0
    if (status == MagickFalse)
3824
0
      continue;
3825
0
    q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3826
0
    if (q == (Quantum *) NULL)
3827
0
      {
3828
0
        status=MagickFalse;
3829
0
        continue;
3830
0
      }
3831
0
    for (x=0; x < (ssize_t) image->columns; x++)
3832
0
    {
3833
0
      double
3834
0
        blue,
3835
0
        green,
3836
0
        red;
3837
3838
0
      red=(double) GetPixelRed(image,q);
3839
0
      green=(double) GetPixelGreen(image,q);
3840
0
      blue=(double) GetPixelBlue(image,q);
3841
0
      switch (colorspace)
3842
0
      {
3843
0
        case HCLColorspace:
3844
0
        {
3845
0
          ModulateHCL(percent_hue,percent_saturation,percent_brightness,
3846
0
            &red,&green,&blue);
3847
0
          break;
3848
0
        }
3849
0
        case HCLpColorspace:
3850
0
        {
3851
0
          ModulateHCLp(percent_hue,percent_saturation,percent_brightness,
3852
0
            &red,&green,&blue);
3853
0
          break;
3854
0
        }
3855
0
        case HSBColorspace:
3856
0
        {
3857
0
          ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3858
0
            &red,&green,&blue);
3859
0
          break;
3860
0
        }
3861
0
        case HSIColorspace:
3862
0
        {
3863
0
          ModulateHSI(percent_hue,percent_saturation,percent_brightness,
3864
0
            &red,&green,&blue);
3865
0
          break;
3866
0
        }
3867
0
        case HSLColorspace:
3868
0
        default:
3869
0
        {
3870
0
          ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3871
0
            &red,&green,&blue);
3872
0
          break;
3873
0
        }
3874
0
        case HSVColorspace:
3875
0
        {
3876
0
          ModulateHSV(percent_hue,percent_saturation,percent_brightness,
3877
0
            &red,&green,&blue);
3878
0
          break;
3879
0
        }
3880
0
        case HWBColorspace:
3881
0
        {
3882
0
          ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3883
0
            &red,&green,&blue);
3884
0
          break;
3885
0
        }
3886
0
        case LCHColorspace:
3887
0
        case LCHabColorspace:
3888
0
        {
3889
0
          ModulateLCHab(percent_brightness,percent_saturation,percent_hue,
3890
0
            illuminant,&red,&green,&blue);
3891
0
          break;
3892
0
        }
3893
0
        case LCHuvColorspace:
3894
0
        {
3895
0
          ModulateLCHuv(percent_brightness,percent_saturation,percent_hue,
3896
0
            illuminant,&red,&green,&blue);
3897
0
          break;
3898
0
        }
3899
0
      }
3900
0
      SetPixelRed(image,ClampToQuantum(red),q);
3901
0
      SetPixelGreen(image,ClampToQuantum(green),q);
3902
0
      SetPixelBlue(image,ClampToQuantum(blue),q);
3903
0
      q+=(ptrdiff_t) GetPixelChannels(image);
3904
0
    }
3905
0
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3906
0
      status=MagickFalse;
3907
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
3908
0
      {
3909
0
        MagickBooleanType
3910
0
          proceed;
3911
3912
#if defined(MAGICKCORE_OPENMP_SUPPORT)
3913
        #pragma omp atomic
3914
#endif
3915
0
        progress++;
3916
0
        proceed=SetImageProgress(image,ModulateImageTag,progress,image->rows);
3917
0
        if (proceed == MagickFalse)
3918
0
          status=MagickFalse;
3919
0
      }
3920
0
  }
3921
0
  image_view=DestroyCacheView(image_view);
3922
0
  return(status);
3923
0
}
3924

3925
/*
3926
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3927
%                                                                             %
3928
%                                                                             %
3929
%                                                                             %
3930
%     N e g a t e I m a g e                                                   %
3931
%                                                                             %
3932
%                                                                             %
3933
%                                                                             %
3934
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3935
%
3936
%  NegateImage() negates the colors in the reference image.  The grayscale
3937
%  option means that only grayscale values within the image are negated.
3938
%
3939
%  The format of the NegateImage method is:
3940
%
3941
%      MagickBooleanType NegateImage(Image *image,
3942
%        const MagickBooleanType grayscale,ExceptionInfo *exception)
3943
%
3944
%  A description of each parameter follows:
3945
%
3946
%    o image: the image.
3947
%
3948
%    o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3949
%
3950
%    o exception: return any errors or warnings in this structure.
3951
%
3952
*/
3953
MagickExport MagickBooleanType NegateImage(Image *image,
3954
  const MagickBooleanType grayscale,ExceptionInfo *exception)
3955
353k
{
3956
353k
#define NegateImageTag  "Negate/Image"
3957
3958
353k
  CacheView
3959
353k
    *image_view;
3960
3961
353k
  MagickBooleanType
3962
353k
    status;
3963
3964
353k
  MagickOffsetType
3965
353k
    progress;
3966
3967
353k
  ssize_t
3968
353k
    i;
3969
3970
353k
  ssize_t
3971
353k
    y;
3972
3973
353k
  assert(image != (Image *) NULL);
3974
353k
  assert(image->signature == MagickCoreSignature);
3975
353k
  if (IsEventLogging() != MagickFalse)
3976
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3977
353k
  if (image->storage_class == PseudoClass)
3978
14.7k
    for (i=0; i < (ssize_t) image->colors; i++)
3979
14.6k
    {
3980
      /*
3981
        Negate colormap.
3982
      */
3983
14.6k
      if (grayscale != MagickFalse)
3984
0
        if ((image->colormap[i].red != image->colormap[i].green) ||
3985
0
            (image->colormap[i].green != image->colormap[i].blue))
3986
0
          continue;
3987
14.6k
      if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3988
14.6k
        image->colormap[i].red=(double) QuantumRange-image->colormap[i].red;
3989
14.6k
      if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3990
14.6k
        image->colormap[i].green=(double) QuantumRange-image->colormap[i].green;
3991
14.6k
      if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3992
14.6k
        image->colormap[i].blue=(double) QuantumRange-image->colormap[i].blue;
3993
14.6k
    }
3994
  /*
3995
    Negate image.
3996
  */
3997
353k
  status=MagickTrue;
3998
353k
  progress=0;
3999
353k
  image_view=AcquireAuthenticCacheView(image,exception);
4000
353k
  if( grayscale != MagickFalse )
4001
0
    {
4002
0
      for (y=0; y < (ssize_t) image->rows; y++)
4003
0
      {
4004
0
        MagickBooleanType
4005
0
          sync;
4006
4007
0
        Quantum
4008
0
          *magick_restrict q;
4009
4010
0
        ssize_t
4011
0
          x;
4012
4013
0
        if (status == MagickFalse)
4014
0
          continue;
4015
0
        q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
4016
0
          exception);
4017
0
        if (q == (Quantum *) NULL)
4018
0
          {
4019
0
            status=MagickFalse;
4020
0
            continue;
4021
0
          }
4022
0
        for (x=0; x < (ssize_t) image->columns; x++)
4023
0
        {
4024
0
          ssize_t
4025
0
            j;
4026
4027
0
          if (IsPixelGray(image,q) == MagickFalse)
4028
0
            {
4029
0
              q+=(ptrdiff_t) GetPixelChannels(image);
4030
0
              continue;
4031
0
            }
4032
0
          for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
4033
0
          {
4034
0
            PixelChannel channel = GetPixelChannelChannel(image,j);
4035
0
            PixelTrait traits = GetPixelChannelTraits(image,channel);
4036
0
            if ((traits & UpdatePixelTrait) == 0)
4037
0
              continue;
4038
0
            q[j]=QuantumRange-q[j];
4039
0
          }
4040
0
          q+=(ptrdiff_t) GetPixelChannels(image);
4041
0
        }
4042
0
        sync=SyncCacheViewAuthenticPixels(image_view,exception);
4043
0
        if (sync == MagickFalse)
4044
0
          status=MagickFalse;
4045
0
        if (image->progress_monitor != (MagickProgressMonitor) NULL)
4046
0
          {
4047
0
            MagickBooleanType
4048
0
              proceed;
4049
4050
0
            progress++;
4051
0
            proceed=SetImageProgress(image,NegateImageTag,progress,image->rows);
4052
0
            if (proceed == MagickFalse)
4053
0
              status=MagickFalse;
4054
0
          }
4055
0
      }
4056
0
      image_view=DestroyCacheView(image_view);
4057
0
      return(MagickTrue);
4058
0
    }
4059
  /*
4060
    Negate image.
4061
  */
4062
#if defined(MAGICKCORE_OPENMP_SUPPORT)
4063
  #pragma omp parallel for schedule(static) shared(progress,status) \
4064
    magick_number_threads(image,image,image->rows,1)
4065
#endif
4066
9.24M
  for (y=0; y < (ssize_t) image->rows; y++)
4067
8.89M
  {
4068
8.89M
    Quantum
4069
8.89M
      *magick_restrict q;
4070
4071
8.89M
    ssize_t
4072
8.89M
      x;
4073
4074
8.89M
    if (status == MagickFalse)
4075
0
      continue;
4076
8.89M
    q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
4077
8.89M
    if (q == (Quantum *) NULL)
4078
0
      {
4079
0
        status=MagickFalse;
4080
0
        continue;
4081
0
      }
4082
393M
    for (x=0; x < (ssize_t) image->columns; x++)
4083
384M
    {
4084
384M
      ssize_t
4085
384M
        j;
4086
4087
957M
      for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
4088
572M
      {
4089
572M
        PixelChannel channel = GetPixelChannelChannel(image,j);
4090
572M
        PixelTrait traits = GetPixelChannelTraits(image,channel);
4091
572M
        if ((traits & UpdatePixelTrait) == 0)
4092
187M
          continue;
4093
384M
        q[j]=QuantumRange-q[j];
4094
384M
      }
4095
384M
      q+=(ptrdiff_t) GetPixelChannels(image);
4096
384M
    }
4097
8.89M
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
4098
0
      status=MagickFalse;
4099
8.89M
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
4100
0
      {
4101
0
        MagickBooleanType
4102
0
          proceed;
4103
4104
#if defined(MAGICKCORE_OPENMP_SUPPORT)
4105
        #pragma omp atomic
4106
#endif
4107
0
        progress++;
4108
0
        proceed=SetImageProgress(image,NegateImageTag,progress,image->rows);
4109
0
        if (proceed == MagickFalse)
4110
0
          status=MagickFalse;
4111
0
      }
4112
8.89M
  }
4113
353k
  image_view=DestroyCacheView(image_view);
4114
353k
  return(status);
4115
353k
}
4116

4117
/*
4118
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4119
%                                                                             %
4120
%                                                                             %
4121
%                                                                             %
4122
%     N o r m a l i z e I m a g e                                             %
4123
%                                                                             %
4124
%                                                                             %
4125
%                                                                             %
4126
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4127
%
4128
%  The NormalizeImage() method enhances the contrast of a color image by
4129
%  mapping the darkest 2 percent of all pixel to black and the brightest
4130
%  1 percent to white.
4131
%
4132
%  The format of the NormalizeImage method is:
4133
%
4134
%      MagickBooleanType NormalizeImage(Image *image,ExceptionInfo *exception)
4135
%
4136
%  A description of each parameter follows:
4137
%
4138
%    o image: the image.
4139
%
4140
%    o exception: return any errors or warnings in this structure.
4141
%
4142
*/
4143
MagickExport MagickBooleanType NormalizeImage(Image *image,
4144
  ExceptionInfo *exception)
4145
2.07k
{
4146
2.07k
  double
4147
2.07k
    black_point,
4148
2.07k
    white_point;
4149
4150
2.07k
  black_point=0.02*image->columns*image->rows;
4151
2.07k
  white_point=0.99*image->columns*image->rows;
4152
2.07k
  return(ContrastStretchImage(image,black_point,white_point,exception));
4153
2.07k
}
4154

4155
/*
4156
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4157
%                                                                             %
4158
%                                                                             %
4159
%                                                                             %
4160
%     S i g m o i d a l C o n t r a s t I m a g e                             %
4161
%                                                                             %
4162
%                                                                             %
4163
%                                                                             %
4164
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4165
%
4166
%  SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
4167
%  sigmoidal contrast algorithm.  Increase the contrast of the image using a
4168
%  sigmoidal transfer function without saturating highlights or shadows.
4169
%  Contrast indicates how much to increase the contrast (0 is none; 3 is
4170
%  typical; 20 is pushing it); mid-point indicates where midtones fall in the
4171
%  resultant image (0 is white; 50% is middle-gray; 100% is black).  Set
4172
%  sharpen to MagickTrue to increase the image contrast otherwise the contrast
4173
%  is reduced.
4174
%
4175
%  The format of the SigmoidalContrastImage method is:
4176
%
4177
%      MagickBooleanType SigmoidalContrastImage(Image *image,
4178
%        const MagickBooleanType sharpen,const char *levels,
4179
%        ExceptionInfo *exception)
4180
%
4181
%  A description of each parameter follows:
4182
%
4183
%    o image: the image.
4184
%
4185
%    o sharpen: Increase or decrease image contrast.
4186
%
4187
%    o contrast: strength of the contrast, the larger the number the more
4188
%      'threshold-like' it becomes.
4189
%
4190
%    o midpoint: midpoint of the function as a color value 0 to QuantumRange.
4191
%
4192
%    o exception: return any errors or warnings in this structure.
4193
%
4194
*/
4195
4196
/*
4197
  ImageMagick 6 has a version of this function which uses LUTs.
4198
*/
4199
4200
/*
4201
  Sigmoidal function Sigmoidal with inflexion point moved to b and "slope
4202
  constant" set to a.
4203
4204
  The first version, based on the hyperbolic tangent tanh, when combined with
4205
  the scaling step, is an exact arithmetic clone of the sigmoid function
4206
  based on the logistic curve. The equivalence is based on the identity
4207
4208
    1/(1+exp(-t)) = (1+tanh(t/2))/2
4209
4210
  (http://de.wikipedia.org/wiki/Sigmoidfunktion) and the fact that the
4211
  scaled sigmoidal derivation is invariant under affine transformations of
4212
  the ordinate.
4213
4214
  The tanh version is almost certainly more accurate and cheaper.  The 0.5
4215
  factor in the argument is to clone the legacy ImageMagick behavior. The
4216
  reason for making the define depend on atanh even though it only uses tanh
4217
  has to do with the construction of the inverse of the scaled sigmoidal.
4218
*/
4219
#if defined(MAGICKCORE_HAVE_ATANH)
4220
0
#define Sigmoidal(a,b,x) ( tanh((0.5*(a))*((x)-(b))) )
4221
#else
4222
#define Sigmoidal(a,b,x) ( 1.0/(1.0+exp((a)*((b)-(x)))) )
4223
#endif
4224
/*
4225
  Scaled sigmoidal function:
4226
4227
    ( Sigmoidal(a,b,x) - Sigmoidal(a,b,0) ) /
4228
    ( Sigmoidal(a,b,1) - Sigmoidal(a,b,0) )
4229
4230
  See http://osdir.com/ml/video.image-magick.devel/2005-04/msg00006.html and
4231
  http://www.cs.dartmouth.edu/farid/downloads/tutorials/fip.pdf.  The limit
4232
  of ScaledSigmoidal as a->0 is the identity, but a=0 gives a division by
4233
  zero. This is fixed below by exiting immediately when contrast is small,
4234
  leaving the image (or colormap) unmodified. This appears to be safe because
4235
  the series expansion of the logistic sigmoidal function around x=b is
4236
4237
  1/2-a*(b-x)/4+...
4238
4239
  so that the key denominator s(1)-s(0) is about a/4 (a/2 with tanh).
4240
*/
4241
0
#define ScaledSigmoidal(a,b,x) (                    \
4242
0
  (Sigmoidal((a),(b),(x))-Sigmoidal((a),(b),0.0)) / \
4243
0
  (Sigmoidal((a),(b),1.0)-Sigmoidal((a),(b),0.0)) )
4244
/*
4245
  Inverse of ScaledSigmoidal, used for +sigmoidal-contrast.  Because b
4246
  may be 0 or 1, the argument of the hyperbolic tangent (resp. logistic
4247
  sigmoidal) may be outside of the interval (-1,1) (resp. (0,1)), even
4248
  when creating a LUT from in gamut values, hence the branching.  In
4249
  addition, HDRI may have out of gamut values.
4250
  InverseScaledSigmoidal is not a two-sided inverse of ScaledSigmoidal:
4251
  It is only a right inverse. This is unavoidable.
4252
*/
4253
static inline double InverseScaledSigmoidal(const double a,const double b,
4254
  const double x)
4255
0
{
4256
0
  const double sig0=Sigmoidal(a,b,0.0);
4257
0
  const double sig1=Sigmoidal(a,b,1.0);
4258
0
  const double argument=(sig1-sig0)*x+sig0;
4259
0
  const double clamped=
4260
0
    (
4261
0
#if defined(MAGICKCORE_HAVE_ATANH)
4262
0
      argument < -1+MagickEpsilon
4263
0
      ?
4264
0
      -1+MagickEpsilon
4265
0
      :
4266
0
      ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
4267
0
    );
4268
0
  return(b+(2.0/a)*atanh(clamped));
4269
#else
4270
      argument < MagickEpsilon
4271
      ?
4272
      MagickEpsilon
4273
      :
4274
      ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
4275
    );
4276
  return(b-log(1.0/clamped-1.0)/a);
4277
#endif
4278
0
}
4279
4280
MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
4281
  const MagickBooleanType sharpen,const double contrast,const double midpoint,
4282
  ExceptionInfo *exception)
4283
0
{
4284
0
#define SigmoidalContrastImageTag  "SigmoidalContrast/Image"
4285
0
#define ScaledSig(x) (ClampToQuantum((double) QuantumRange* \
4286
0
  ScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale*((double) x))) )
4287
0
#define InverseScaledSig(x) (ClampToQuantum((double) QuantumRange* \
4288
0
  InverseScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale* \
4289
0
  ((double) x))) )
4290
4291
0
  CacheView
4292
0
    *image_view;
4293
4294
0
  MagickBooleanType
4295
0
    status;
4296
4297
0
  MagickOffsetType
4298
0
    progress;
4299
4300
0
  ssize_t
4301
0
    y;
4302
4303
  /*
4304
    Convenience macros.
4305
  */
4306
0
  assert(image != (Image *) NULL);
4307
0
  assert(image->signature == MagickCoreSignature);
4308
0
  if (IsEventLogging() != MagickFalse)
4309
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4310
  /*
4311
    Side effect: may clamp values unless contrast<MagickEpsilon, in which
4312
    case nothing is done.
4313
  */
4314
0
  if (contrast < MagickEpsilon)
4315
0
    return(MagickTrue);
4316
  /*
4317
    Sigmoidal-contrast enhance colormap.
4318
  */
4319
0
  if (image->storage_class == PseudoClass)
4320
0
    {
4321
0
      ssize_t
4322
0
        i;
4323
4324
0
      if( sharpen != MagickFalse )
4325
0
        for (i=0; i < (ssize_t) image->colors; i++)
4326
0
        {
4327
0
          if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
4328
0
            image->colormap[i].red=(MagickRealType) ScaledSig(
4329
0
              image->colormap[i].red);
4330
0
          if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
4331
0
            image->colormap[i].green=(MagickRealType) ScaledSig(
4332
0
              image->colormap[i].green);
4333
0
          if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
4334
0
            image->colormap[i].blue=(MagickRealType) ScaledSig(
4335
0
              image->colormap[i].blue);
4336
0
          if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
4337
0
            image->colormap[i].alpha=(MagickRealType) ScaledSig(
4338
0
              image->colormap[i].alpha);
4339
0
        }
4340
0
      else
4341
0
        for (i=0; i < (ssize_t) image->colors; i++)
4342
0
        {
4343
0
          if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
4344
0
            image->colormap[i].red=(MagickRealType) InverseScaledSig(
4345
0
              image->colormap[i].red);
4346
0
          if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
4347
0
            image->colormap[i].green=(MagickRealType) InverseScaledSig(
4348
0
              image->colormap[i].green);
4349
0
          if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
4350
0
            image->colormap[i].blue=(MagickRealType) InverseScaledSig(
4351
0
              image->colormap[i].blue);
4352
0
          if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
4353
0
            image->colormap[i].alpha=(MagickRealType) InverseScaledSig(
4354
0
              image->colormap[i].alpha);
4355
0
        }
4356
0
    }
4357
  /*
4358
    Sigmoidal-contrast enhance image.
4359
  */
4360
0
  status=MagickTrue;
4361
0
  progress=0;
4362
0
  image_view=AcquireAuthenticCacheView(image,exception);
4363
#if defined(MAGICKCORE_OPENMP_SUPPORT)
4364
  #pragma omp parallel for schedule(static) shared(progress,status) \
4365
    magick_number_threads(image,image,image->rows,1)
4366
#endif
4367
0
  for (y=0; y < (ssize_t) image->rows; y++)
4368
0
  {
4369
0
    Quantum
4370
0
      *magick_restrict q;
4371
4372
0
    ssize_t
4373
0
      x;
4374
4375
0
    if (status == MagickFalse)
4376
0
      continue;
4377
0
    q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
4378
0
    if (q == (Quantum *) NULL)
4379
0
      {
4380
0
        status=MagickFalse;
4381
0
        continue;
4382
0
      }
4383
0
    for (x=0; x < (ssize_t) image->columns; x++)
4384
0
    {
4385
0
      ssize_t
4386
0
        i;
4387
4388
0
      for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4389
0
      {
4390
0
        PixelChannel channel = GetPixelChannelChannel(image,i);
4391
0
        PixelTrait traits = GetPixelChannelTraits(image,channel);
4392
0
        if ((traits & UpdatePixelTrait) == 0)
4393
0
          continue;
4394
0
        if( sharpen != MagickFalse )
4395
0
          q[i]=ScaledSig(q[i]);
4396
0
        else
4397
0
          q[i]=InverseScaledSig(q[i]);
4398
0
      }
4399
0
      q+=(ptrdiff_t) GetPixelChannels(image);
4400
0
    }
4401
0
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
4402
0
      status=MagickFalse;
4403
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
4404
0
      {
4405
0
        MagickBooleanType
4406
0
          proceed;
4407
4408
#if defined(MAGICKCORE_OPENMP_SUPPORT)
4409
        #pragma omp atomic
4410
#endif
4411
0
        progress++;
4412
0
        proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress,
4413
0
          image->rows);
4414
0
        if (proceed == MagickFalse)
4415
0
          status=MagickFalse;
4416
0
      }
4417
0
  }
4418
0
  image_view=DestroyCacheView(image_view);
4419
0
  return(status);
4420
0
}
4421

4422
/*
4423
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4424
%                                                                             %
4425
%                                                                             %
4426
%                                                                             %
4427
%     W h i t e B a l a n c e I m a g e                                       %
4428
%                                                                             %
4429
%                                                                             %
4430
%                                                                             %
4431
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4432
%
4433
%  WhiteBalanceImage() applies white balancing to an image according to a
4434
%  grayworld assumption in the LAB colorspace.
4435
%
4436
%  The format of the WhiteBalanceImage method is:
4437
%
4438
%      MagickBooleanType WhiteBalanceImage(Image *image,
4439
%        ExceptionInfo *exception)
4440
%
4441
%  A description of each parameter follows:
4442
%
4443
%    o image: The image to auto-level
4444
%
4445
%    o exception: return any errors or warnings in this structure.
4446
%
4447
*/
4448
MagickExport MagickBooleanType WhiteBalanceImage(Image *image,
4449
  ExceptionInfo *exception)
4450
0
{
4451
0
#define WhiteBalanceImageTag  "WhiteBalance/Image"
4452
4453
0
  CacheView
4454
0
    *image_view;
4455
4456
0
  const char
4457
0
    *artifact;
4458
4459
0
  double
4460
0
    a_mean,
4461
0
    b_mean;
4462
4463
0
  MagickOffsetType
4464
0
    progress;
4465
4466
0
  MagickStatusType
4467
0
    status;
4468
4469
0
  ssize_t
4470
0
    y;
4471
4472
  /*
4473
    White balance image.
4474
  */
4475
0
  assert(image != (Image *) NULL);
4476
0
  assert(image->signature == MagickCoreSignature);
4477
0
  if (IsEventLogging() != MagickFalse)
4478
0
    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4479
0
  if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
4480
0
    return(MagickFalse);
4481
0
  status=TransformImageColorspace(image,LabColorspace,exception);
4482
0
  a_mean=0.0;
4483
0
  b_mean=0.0;
4484
0
  image_view=AcquireAuthenticCacheView(image,exception);
4485
0
  for (y=0; y < (ssize_t) image->rows; y++)
4486
0
  {
4487
0
    const Quantum
4488
0
      *magick_restrict p;
4489
4490
0
    ssize_t
4491
0
      x;
4492
4493
0
    if (status == MagickFalse)
4494
0
      continue;
4495
0
    p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
4496
0
    if (p == (Quantum *) NULL)
4497
0
      {
4498
0
        status=MagickFalse;
4499
0
        continue;
4500
0
      }
4501
0
    for (x=0; x < (ssize_t) image->columns; x++)
4502
0
    {
4503
0
      a_mean+=QuantumScale*(double) GetPixela(image,p)-0.5;
4504
0
      b_mean+=QuantumScale*(double) GetPixelb(image,p)-0.5;
4505
0
      p+=(ptrdiff_t) GetPixelChannels(image);
4506
0
    }
4507
0
  }
4508
0
  a_mean/=((double) image->columns*image->rows);
4509
0
  b_mean/=((double) image->columns*image->rows);
4510
0
  progress=0;
4511
#if defined(MAGICKCORE_OPENMP_SUPPORT)
4512
  #pragma omp parallel for schedule(static) shared(progress,status) \
4513
    magick_number_threads(image,image,image->rows,1)
4514
#endif
4515
0
  for (y=0; y < (ssize_t) image->rows; y++)
4516
0
  {
4517
0
    Quantum
4518
0
      *magick_restrict q;
4519
4520
0
    ssize_t
4521
0
      x;
4522
4523
0
    if (status == MagickFalse)
4524
0
      continue;
4525
0
    q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
4526
0
    if (q == (Quantum *) NULL)
4527
0
      {
4528
0
        status=MagickFalse;
4529
0
        continue;
4530
0
      }
4531
0
    for (x=0; x < (ssize_t) image->columns; x++)
4532
0
    {
4533
0
      double
4534
0
        a,
4535
0
        b;
4536
4537
      /*
4538
        Scale the chroma distance shifted according to amount of luminance.
4539
      */
4540
0
      a=(double) GetPixela(image,q)-1.1*(double) GetPixelL(image,q)*a_mean;
4541
0
      b=(double) GetPixelb(image,q)-1.1*(double) GetPixelL(image,q)*b_mean;
4542
0
      SetPixela(image,ClampToQuantum(a),q);
4543
0
      SetPixelb(image,ClampToQuantum(b),q);
4544
0
      q+=(ptrdiff_t) GetPixelChannels(image);
4545
0
    }
4546
0
    if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
4547
0
      status=MagickFalse;
4548
0
    if (image->progress_monitor != (MagickProgressMonitor) NULL)
4549
0
      {
4550
0
        MagickBooleanType
4551
0
          proceed;
4552
4553
#if defined(MAGICKCORE_OPENMP_SUPPORT)
4554
        #pragma omp atomic
4555
#endif
4556
0
        progress++;
4557
0
        proceed=SetImageProgress(image,WhiteBalanceImageTag,progress,
4558
0
          image->rows);
4559
0
        if (proceed == MagickFalse)
4560
0
          status=MagickFalse;
4561
0
      }
4562
0
  }
4563
0
  image_view=DestroyCacheView(image_view);
4564
0
  artifact=GetImageArtifact(image,"white-balance:vibrance");
4565
0
  if (artifact != (const char *) NULL)
4566
0
    {
4567
0
      ChannelType
4568
0
        channel_mask;
4569
4570
0
      double
4571
0
        black_point = 0.0;
4572
4573
0
      GeometryInfo
4574
0
        geometry_info;
4575
4576
0
      MagickStatusType
4577
0
        flags;
4578
4579
      /*
4580
        Level the a & b channels.
4581
      */
4582
0
      flags=ParseGeometry(artifact,&geometry_info);
4583
0
      if ((flags & RhoValue) != 0)
4584
0
        black_point=geometry_info.rho;
4585
0
      if ((flags & PercentValue) != 0)
4586
0
        black_point*=((double) QuantumRange/100.0);
4587
0
      channel_mask=SetImageChannelMask(image,(ChannelType) (aChannel |
4588
0
        bChannel));
4589
0
      status&=(MagickStatusType) LevelImage(image,black_point,(double)
4590
0
        QuantumRange-black_point,1.0,exception);
4591
0
      (void) SetImageChannelMask(image,channel_mask);
4592
0
    }
4593
0
  status&=(MagickStatusType) TransformImageColorspace(image,sRGBColorspace,
4594
0
    exception);
4595
0
  return(status != 0 ? MagickTrue : MagickFalse);
4596
0
}