Coverage Report

Created: 2025-12-31 07:53

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/graphicsmagick/magick/enhance.c
Line
Count
Source
1
/*
2
% Copyright (C) 2003 - 2019 GraphicsMagick Group
3
% Copyright (C) 2002 ImageMagick Studio
4
% Copyright 1991-1999 E. I. du Pont de Nemours and Company
5
%
6
% This program is covered by multiple licenses, which are described in
7
% Copyright.txt. You should have received a copy of Copyright.txt with this
8
% package; otherwise see http://www.graphicsmagick.org/www/Copyright.html.
9
%
10
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
11
%                                                                             %
12
%                                                                             %
13
%                                                                             %
14
%              EEEEE  N   N  H   H   AAA   N   N   CCCC  EEEEE                %
15
%              E      NN  N  H   H  A   A  NN  N  C      E                    %
16
%              EEE    N N N  HHHHH  AAAAA  N N N  C      EEE                  %
17
%              E      N  NN  H   H  A   A  N  NN  C      E                    %
18
%              EEEEE  N   N  H   H  A   A  N   N   CCCC  EEEEE                %
19
%                                                                             %
20
%                                                                             %
21
%                  GraphicsMagick Image Enhancement Methods                   %
22
%                                                                             %
23
%                                                                             %
24
%                              Software Design                                %
25
%                                John Cristy                                  %
26
%                                 July 1992                                   %
27
%                                 Re-Written                                  %
28
%                              Bob Friesenhahn                                %
29
%                                  May 2008                                   %
30
%                                                                             %
31
%                                                                             %
32
%                                                                             %
33
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
34
%
35
%
36
%
37
*/
38

39
/*
40
  Include declarations.
41
*/
42
#include "magick/studio.h"
43
#include "magick/attribute.h"
44
#include "magick/color.h"
45
#include "magick/enhance.h"
46
#include "magick/gem.h"
47
#include "magick/pixel_iterator.h"
48
#include "magick/log.h"
49
#include "magick/monitor.h"
50
#include "magick/utility.h"
51
52
static MagickPassFail
53
BuildChannelHistogramsCB(void *mutable_data,          /* User provided mutable data */
54
                         const void *immutable_data,  /* User provided immutable data */
55
                         const Image * restrict const_image,    /* Input image */
56
                         const PixelPacket * restrict pixels,   /* Pixel row */
57
                         const IndexPacket * restrict indexes,  /* Pixel indexes */
58
                         const long npixels,          /* Number of pixels in row */
59
                         ExceptionInfo *exception     /* Exception report */
60
                         )
61
0
{
62
  /*
63
    Built an image histogram in the supplied table.
64
65
    Should be executed by just one thread in order to avoid contention
66
    for the histogram table.
67
  */
68
0
  DoublePixelPacket
69
0
    *histogram = (DoublePixelPacket *) mutable_data;
70
71
0
  register long
72
0
    i;
73
74
0
  ARG_NOT_USED(immutable_data);
75
0
  ARG_NOT_USED(indexes);
76
0
  ARG_NOT_USED(exception);
77
78
0
  for (i=0; i < npixels; i++)
79
0
    {
80
0
      histogram[ScaleQuantumToMap(pixels[i].red)].red++;
81
0
      histogram[ScaleQuantumToMap(pixels[i].green)].green++;
82
0
      histogram[ScaleQuantumToMap(pixels[i].blue)].blue++;
83
0
      if (const_image->matte)
84
0
        histogram[ScaleQuantumToMap(pixels[i].opacity)].opacity++;
85
0
    }
86
87
0
  return MagickPass;
88
0
}
89
90
static DoublePixelPacket *
91
BuildChannelHistograms(const Image *image, ExceptionInfo *exception)
92
0
{
93
0
  MagickPassFail
94
0
    status = MagickPass;
95
96
0
  DoublePixelPacket
97
0
    *histogram;
98
99
0
  histogram=MagickAllocateArray(DoublePixelPacket *,(MaxMap+1),
100
0
                                sizeof(DoublePixelPacket));
101
0
  if (histogram == (DoublePixelPacket *) NULL)
102
0
    {
103
0
      ThrowException(exception,ResourceLimitError,MemoryAllocationFailed,
104
0
                     image->filename);
105
0
      return (DoublePixelPacket *) NULL;
106
0
    }
107
108
0
  (void) memset(histogram,0,(MaxMap+1)*sizeof(DoublePixelPacket));
109
110
0
  {
111
0
    PixelIteratorOptions
112
0
      iterator_options;
113
114
0
    InitializePixelIteratorOptions(&iterator_options,exception);
115
0
    iterator_options.max_threads=1;
116
0
    status=PixelIterateMonoRead(BuildChannelHistogramsCB,
117
0
                                &iterator_options,
118
0
                                "[%s] Building histogram...",
119
0
                                histogram,NULL,
120
0
                                0,0,image->columns,image->rows,
121
0
                                image,exception);
122
0
  }
123
124
0
  if (status == MagickFail)
125
0
    MagickFreeMemory(histogram);
126
127
0
  return histogram;
128
0
}
129

130
/*
131
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
132
%                                                                             %
133
%                                                                             %
134
%     C o n t r a s t I m a g e                                               %
135
%                                                                             %
136
%                                                                             %
137
%                                                                             %
138
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
139
%
140
%  ContrastImage() enhances the intensity differences between the lighter and
141
%  darker elements of the image.  Set sharpen to a value other than 0 to
142
%  increase the image contrast otherwise the contrast is reduced.
143
%
144
%  The format of the ContrastImage method is:
145
%
146
%      unsigned int ContrastImage(Image *image,const unsigned int sharpen)
147
%
148
%  A description of each parameter follows:
149
%
150
%    o image: The image.
151
%
152
%    o sharpen: Increase or decrease image contrast.
153
%
154
%
155
*/
156
static MagickPassFail
157
ContrastImagePixels(void *mutable_data,         /* User provided mutable data */
158
                    const void *immutable_data, /* User provided immutable data */
159
                    Image * restrict image,               /* Modify image */
160
                    PixelPacket * restrict pixels,        /* Pixel row */
161
                    IndexPacket * restrict indexes,       /* Pixel row indexes */
162
                    const long npixels,         /* Number of pixels in row */
163
                    ExceptionInfo *exception)   /* Exception report */
164
0
{
165
  /*
166
    Modulate pixels contrast.
167
  */
168
0
  const double
169
0
    sign = *((const double *) immutable_data);
170
171
0
  static const double
172
0
    alpha=0.50000000000099997787827987849595956504344940185546875; /* 0.5+MagickEpsilon */
173
174
0
  register long
175
0
    i;
176
177
0
  ARG_NOT_USED(mutable_data);
178
0
  ARG_NOT_USED(image);
179
0
  ARG_NOT_USED(indexes);
180
0
  ARG_NOT_USED(exception);
181
182
0
  for (i=0; i < npixels; i++)
183
0
    {
184
0
      double
185
0
        brightness,
186
0
        hue,
187
0
        saturation;
188
189
0
      TransformHSL(pixels[i].red,pixels[i].green,pixels[i].blue,
190
0
                   &hue,&saturation,&brightness);
191
0
      brightness+=
192
0
        alpha*sign*(alpha*(sin(MagickPI*(brightness-alpha))+1.0)-brightness);
193
0
      if (brightness > 1.0)
194
0
        brightness=1.0;
195
0
      if (brightness < 0.0)
196
0
        brightness=0.0;
197
0
      HSLTransform(hue,saturation,brightness,&pixels[i].red,
198
0
                   &pixels[i].green,&pixels[i].blue);
199
0
    }
200
201
0
  return MagickPass;
202
0
}
203
0
#define DullContrastImageText "[%s] Dulling contrast..."
204
0
#define SharpenContrastImageText "[%s] Sharpening contrast..."
205
MagickExport MagickPassFail ContrastImage(Image *image,const unsigned int sharpen)
206
0
{
207
0
  double
208
0
    sign;
209
210
0
  const char
211
0
    *progress_message;
212
213
0
  unsigned int
214
0
    is_grayscale;
215
216
0
  MagickPassFail
217
0
    status=MagickPass;
218
219
0
  assert(image != (Image *) NULL);
220
0
  assert(image->signature == MagickSignature);
221
0
  is_grayscale=image->is_grayscale;
222
0
  sign=sharpen ? 1.0 : -1.0;
223
0
  progress_message=sharpen ? SharpenContrastImageText : DullContrastImageText;
224
0
  if (image->storage_class == PseudoClass)
225
0
    {
226
0
      (void) ContrastImagePixels(NULL,&sign,image,image->colormap,
227
0
                                 (IndexPacket *) NULL,image->colors,
228
0
                                 &image->exception);
229
0
      status=SyncImage(image);
230
0
    }
231
0
  else
232
0
    {
233
0
      status=PixelIterateMonoModify(ContrastImagePixels,NULL,
234
0
                                    progress_message,
235
0
                                    NULL,&sign,0,0,image->columns,image->rows,
236
0
                                    image,&image->exception);
237
0
    }
238
0
  image->is_grayscale=is_grayscale;
239
0
  return(status);
240
0
}
241

242
/*
243
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
244
%                                                                             %
245
%                                                                             %
246
%     E q u a l i z e I m a g e                                               %
247
%                                                                             %
248
%                                                                             %
249
%                                                                             %
250
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
251
%
252
%  EqualizeImage() applies a histogram equalization to the image.
253
%
254
%  The format of the EqualizeImage method is:
255
%
256
%      unsigned int EqualizeImage(Image *image)
257
%
258
%  A description of each parameter follows:
259
%
260
%    o image: The image.
261
%
262
*/
263
typedef struct _ApplyLevelsOptions_t
264
{
265
  PixelPacket
266
    *map;
267
268
  MagickBool
269
    level_red,
270
    level_green,
271
    level_blue,
272
    level_opacity;
273
} ApplyLevels_t;
274
275
static MagickPassFail
276
ApplyLevels(void *mutable_data,          /* User provided mutable data */
277
            const void *immutable_data,  /* User provided immutable data */
278
            Image * restrict image,                /* Modify image */
279
            PixelPacket * restrict pixels,         /* Pixel row */
280
            IndexPacket * restrict indexes,        /* Pixel row indexes */
281
            const long npixels,          /* Number of pixels in row */
282
            ExceptionInfo *exception)    /* Exception report */
283
0
{
284
  /*
285
    Apply a levels transformation based on a supplied look-up table.
286
  */
287
0
  const ApplyLevels_t
288
0
    *options = (const ApplyLevels_t *) immutable_data;
289
290
0
  register long
291
0
    i;
292
293
0
  PixelPacket
294
0
    *map=options->map;
295
296
0
  MagickBool
297
0
    level_red=options->level_red,
298
0
    level_green=options->level_green,
299
0
    level_blue=options->level_blue,
300
0
    level_opacity=options->level_opacity;
301
302
0
  ARG_NOT_USED(mutable_data);
303
0
  ARG_NOT_USED(image);
304
0
  ARG_NOT_USED(indexes);
305
0
  ARG_NOT_USED(exception);
306
307
0
  for (i=0; i < npixels; i++)
308
0
    {
309
0
      if (level_red)
310
0
        pixels[i].red=map[ScaleQuantumToMap(pixels[i].red)].red;
311
0
      if (level_green)
312
0
        pixels[i].green=map[ScaleQuantumToMap(pixels[i].green)].green;
313
0
      if (level_blue)
314
0
        pixels[i].blue=map[ScaleQuantumToMap(pixels[i].blue)].blue;
315
0
      if (level_opacity)
316
0
        pixels[i].opacity=map[ScaleQuantumToMap(pixels[i].opacity)].opacity;
317
0
    }
318
0
  return MagickPass;
319
0
}
320
321
#define EqualizeImageText "Equalize...  "
322
MagickExport MagickPassFail EqualizeImage(Image *image)
323
0
{
324
0
  DoublePixelPacket
325
0
    high,
326
0
    *histogram,
327
0
    intensity,
328
0
    low,
329
0
    *map;
330
331
0
  ApplyLevels_t
332
0
    levels;
333
334
0
  register long
335
0
    i;
336
337
0
  unsigned int
338
0
    is_grayscale;
339
340
0
  MagickPassFail
341
0
    status=MagickPass;
342
343
  /*
344
    Allocate and initialize arrays.
345
  */
346
0
  assert(image != (Image *) NULL);
347
0
  assert(image->signature == MagickSignature);
348
0
  is_grayscale=image->is_grayscale;
349
0
  map=MagickAllocateMemory(DoublePixelPacket *,(MaxMap+1)*sizeof(DoublePixelPacket));
350
0
  levels.map=MagickAllocateMemory(PixelPacket *,(MaxMap+1)*sizeof(PixelPacket));
351
0
  if ((map == (DoublePixelPacket *) NULL) ||
352
0
      (levels.map == (PixelPacket *) NULL))
353
0
    {
354
0
      MagickFreeMemory(map);
355
0
      MagickFreeMemory(levels.map);
356
0
      ThrowBinaryException(ResourceLimitError,MemoryAllocationFailed,
357
0
                           MagickMsg(OptionError,UnableToEqualizeImage));
358
0
    }
359
  /*
360
    Build histogram.
361
  */
362
0
  histogram=BuildChannelHistograms(image,&image->exception);
363
0
  if (histogram == (DoublePixelPacket *) NULL)
364
0
    {
365
0
      MagickFreeMemory(map);
366
0
      MagickFreeMemory(levels.map);
367
0
      return MagickFail;
368
0
    }
369
  /*
370
    Integrate the histogram to get the equalization map.
371
  */
372
0
  (void) memset(&intensity,0,sizeof(DoublePixelPacket));
373
0
  for (i=0; i <= (long) MaxMap; i++)
374
0
    {
375
0
      intensity.red+=histogram[i].red;
376
0
      intensity.green+=histogram[i].green;
377
0
      intensity.blue+=histogram[i].blue;
378
0
      if (image->matte)
379
0
        intensity.opacity+=histogram[i].opacity;
380
0
      map[i]=intensity;
381
0
    }
382
0
  low=map[0];
383
0
  high=map[MaxMap];
384
0
  (void) memset(levels.map,0,(MaxMap+1)*sizeof(PixelPacket));
385
0
  levels.level_red = (low.red != high.red);
386
0
  levels.level_green = (low.green != high.green);
387
0
  levels.level_blue = (low.blue != high.blue);
388
0
  levels.level_opacity= (image->matte && (low.opacity != high.opacity));
389
0
  for (i=0; i <= (long) MaxMap; i++)
390
0
  {
391
0
    if (levels.level_red)
392
0
      levels.map[i].red=ScaleMapToQuantum(
393
0
        (MaxMap*(map[i].red-low.red))/(high.red-low.red));
394
0
    if (levels.level_green)
395
0
      levels.map[i].green=ScaleMapToQuantum(
396
0
        (MaxMap*(map[i].green-low.green))/(high.green-low.green));
397
0
    if (levels.level_blue)
398
0
      levels.map[i].blue=ScaleMapToQuantum(
399
0
        (MaxMap*(map[i].blue-low.blue))/(high.blue-low.blue));
400
0
    if (levels.level_opacity)
401
0
      levels.map[i].opacity=ScaleMapToQuantum(
402
0
        (MaxMap*(map[i].opacity-low.opacity))/(high.opacity-low.opacity));
403
0
  }
404
0
  MagickFreeMemory(histogram);
405
0
  MagickFreeMemory(map);
406
  /*
407
    Stretch the histogram based on the map.
408
  */
409
0
  if (image->storage_class == PseudoClass)
410
0
    {
411
0
      (void) ApplyLevels(NULL,&levels,image,image->colormap,
412
0
                         (IndexPacket *) NULL,image->colors,
413
0
                         &image->exception);
414
0
      status=SyncImage(image);
415
0
    }
416
0
  else
417
0
    {
418
0
      status=PixelIterateMonoModify(ApplyLevels,
419
0
                                    NULL,
420
0
                                    "[%s] Applying histogram equalization...",
421
0
                                    NULL,&levels,
422
0
                                    0,0,image->columns,image->rows,
423
0
                                    image,
424
0
                                    &image->exception);
425
0
    }
426
0
  MagickFreeMemory(levels.map);
427
0
  image->is_grayscale=is_grayscale;
428
0
  return(status);
429
0
}
430

431
/*
432
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
433
%                                                                             %
434
%                                                                             %
435
%     G a m m a I m a g e                                                     %
436
%                                                                             %
437
%                                                                             %
438
%                                                                             %
439
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
440
%
441
%  Use GammaImage() to gamma-correct an image.  The same image viewed on
442
%  different devices will have perceptual differences in the way the
443
%  image's intensities are represented on the screen.  Specify individual
444
%  gamma levels for the red, green, and blue channels (e.g. "1.0,2.2,0.45"),
445
%  or adjust all three with a single gamma parameter.  Values typically range
446
%  from 0.45 to 2.6.
447
%
448
%  You can also reduce the influence of a particular channel with a gamma
449
%  value of 0.
450
%
451
%  The format of the GammaImage method is:
452
%
453
%      MagickPassFail GammaImage(Image *image,const char *level)
454
%
455
%  A description of each parameter follows:
456
%
457
%    o image: The image.
458
%
459
%    o level: Define the level of gamma correction.
460
%
461
%
462
*/
463
#if MaxMap == MaxRGB
464
typedef struct _ApplyLevelsDiscrete_t
465
{
466
  Quantum
467
    * restrict color,    /* red, green, & blue */
468
    * restrict red,      /* red */
469
    * restrict green,    /* green */
470
    * restrict blue,     /* blue */
471
    * restrict opacity;  /* Opacity */
472
} ApplyLevelsDiscrete_t;
473
474
static MagickPassFail
475
ApplyLevelsDiscrete(void *mutable_data,             /* User provided mutable data */
476
                    const void *immutable_data,     /* User provided immutable data */
477
                    Image * restrict image,                   /* Modify image */
478
                    PixelPacket * restrict pixels,  /* Pixel row */
479
                    IndexPacket * restrict indexes, /* Pixel row indexes */
480
                    const long npixels,             /* Number of pixels in row */
481
                    ExceptionInfo *exception)       /* Exception report */
482
0
{
483
  /*
484
    Apply a levels transformation based on a supplied look-up table.
485
  */
486
0
  const ApplyLevelsDiscrete_t
487
0
    levels = *(const ApplyLevelsDiscrete_t *) immutable_data;
488
489
0
  register long
490
0
    i;
491
492
0
  ARG_NOT_USED(mutable_data);
493
0
  ARG_NOT_USED(image);
494
0
  ARG_NOT_USED(indexes);
495
0
  ARG_NOT_USED(exception);
496
497
0
  for (i=0; i < npixels; i++)
498
0
    {
499
0
      if (levels.color)
500
0
        {
501
0
          pixels[i].red=levels.color[ScaleQuantumToMap(pixels[i].red)];
502
0
          pixels[i].green=levels.color[ScaleQuantumToMap(pixels[i].green)];
503
0
          pixels[i].blue=levels.color[ScaleQuantumToMap(pixels[i].blue)];
504
0
        }
505
0
      else
506
0
        {
507
0
          if (levels.red)
508
0
            pixels[i].red=levels.red[ScaleQuantumToMap(pixels[i].red)];
509
0
          if (levels.green)
510
0
            pixels[i].green=levels.green[ScaleQuantumToMap(pixels[i].green)];
511
0
          if (levels.blue)
512
0
            pixels[i].blue=levels.blue[ScaleQuantumToMap(pixels[i].blue)];
513
0
        }
514
0
      if (levels.opacity)
515
0
        pixels[i].opacity=levels.opacity[ScaleQuantumToMap(pixels[i].opacity)];
516
0
    }
517
518
0
  return MagickPass;
519
0
}
520
#endif /* if MaxMap == MaxRGB */
521
522
/*
523
  Gamma correct value in range of 0.0 to 1.0
524
*/
525
static inline double
526
GammaCorrect(const double value, const double gamma)
527
0
{
528
0
  return pow(value,1.0/gamma);
529
0
}
530
531
#if MaxMap != MaxRGB
532
typedef DoublePixelPacket GammaCorrectPixelsOptions_t;
533
534
static MagickPassFail
535
GammaCorrectPixels(void *mutable_data,             /* User provided mutable data */
536
                   const void *immutable_data,     /* User provided immutable data */
537
                   Image * restrict image,                   /* Modify image */
538
                   PixelPacket * restrict pixels,  /* Pixel row */
539
                   IndexPacket * restrict indexes, /* Pixel row indexes */
540
                   const long npixels,             /* Number of pixels in row */
541
                   ExceptionInfo *exception)       /* Exception report */
542
{
543
  /*
544
    Apply a gamma transformation based on slow accurate math.
545
  */
546
  const GammaCorrectPixelsOptions_t
547
    options = *(const GammaCorrectPixelsOptions_t *) immutable_data;
548
549
  register long
550
    i;
551
552
  MagickBool
553
    red_flag,
554
    green_flag,
555
    blue_flag;
556
557
  ARG_NOT_USED(mutable_data);
558
  ARG_NOT_USED(image);
559
  ARG_NOT_USED(indexes);
560
  ARG_NOT_USED(exception);
561
562
  red_flag=(options.red != 1.0);
563
  green_flag=(options.green != 1.0);
564
  blue_flag=(options.blue != 1.0);
565
566
  for (i=0; i < npixels; i++)
567
    {
568
      double
569
        value;
570
571
      if (red_flag)
572
        {
573
          value=MaxRGBDouble*GammaCorrect(pixels[i].red/MaxRGBDouble,options.red);
574
          pixels[i].red=RoundDoubleToQuantum(value);
575
        }
576
      if (green_flag)
577
        {
578
          value=MaxRGBDouble*GammaCorrect(pixels[i].green/MaxRGBDouble,options.green);
579
          pixels[i].green=RoundDoubleToQuantum(value);
580
        }
581
      if (blue_flag)
582
        {
583
          value=MaxRGBDouble*GammaCorrect(pixels[i].blue/MaxRGBDouble,options.blue);
584
          pixels[i].blue=RoundDoubleToQuantum(value);
585
        }
586
    }
587
588
  return MagickPass;
589
}
590
#endif /* MaxMap != MaxRGB */
591
592
MagickExport MagickPassFail GammaImage(Image *image,const char *level)
593
0
{
594
0
  double
595
0
#if MaxMap == MaxRGB
596
0
    gamma_color=0.0,
597
0
#endif /* if MaxMap == MaxRGB */
598
0
    gamma_red=1.0,
599
0
    gamma_green=1.0,
600
0
    gamma_blue=1.0;
601
602
0
  long
603
0
    count;
604
605
0
  unsigned int
606
0
    is_grayscale;
607
608
0
  MagickBool
609
0
    level_color=MagickFalse,
610
0
    level_red=MagickFalse,
611
0
    level_green=MagickFalse,
612
0
    level_blue=MagickFalse,
613
0
    unity_gamma;
614
615
0
  MagickPassFail
616
0
    status=MagickPass;
617
618
0
  assert(image != (Image *) NULL);
619
0
  assert(image->signature == MagickSignature);
620
0
  if (level == (char *) NULL)
621
0
    return(MagickFail);
622
0
  count=sscanf(level,"%lf%*[,/]%lf%*[,/]%lf",&gamma_red,&gamma_green,
623
0
               &gamma_blue);
624
0
  if (count == 1)
625
0
    {
626
0
      gamma_green=gamma_red;
627
0
      gamma_blue=gamma_red;
628
0
    }
629
630
0
  unity_gamma=((gamma_red == gamma_green) && (gamma_green == gamma_blue));
631
632
0
  if ((unity_gamma) && (gamma_red != 1.0))
633
0
    {
634
0
#if MaxMap == MaxRGB
635
0
      gamma_color=gamma_red;
636
0
#endif /* if MaxMap == MaxRGB */
637
0
      level_color = MagickTrue;
638
0
    }
639
0
  else
640
0
    {
641
0
      level_red = ((gamma_red != 0.0) && (gamma_red != 1.0));
642
0
      level_green = ((gamma_green != 0.0) && (gamma_green != 1.0));
643
0
      level_blue = ((gamma_blue != 0.0) && (gamma_blue != 1.0));
644
0
    }
645
646
0
  is_grayscale=((image->is_grayscale) && (unity_gamma));
647
648
0
  if (!level_color && !level_red && !level_green && !level_blue)
649
0
    return(MagickPass);
650
651
0
#if MaxMap == MaxRGB
652
0
  {
653
0
    ApplyLevelsDiscrete_t
654
0
      levels;
655
656
0
    register long
657
0
      i;
658
659
    /*
660
      Allocate and initialize gamma maps.
661
    */
662
0
    (void) memset(&levels,0,sizeof(levels));
663
0
    if (level_color)
664
0
      levels.color=MagickAllocateArray(Quantum *,(MaxMap+1),sizeof(Quantum));
665
0
    if (level_red)
666
0
      levels.red=MagickAllocateArray(Quantum *,(MaxMap+1),sizeof(Quantum));
667
0
    if (level_green)
668
0
      levels.green=MagickAllocateArray(Quantum *,(MaxMap+1),sizeof(Quantum));
669
0
    if (level_blue)
670
0
      levels.blue=MagickAllocateArray(Quantum *,(MaxMap+1),sizeof(Quantum));
671
0
    if ((level_color && !levels.color) ||
672
0
        (level_red && !levels.red) ||
673
0
        (level_green && !levels.green) ||
674
0
        (level_blue && !levels.blue))
675
0
      {
676
0
        MagickFreeMemory(levels.color);
677
0
        MagickFreeMemory(levels.red);
678
0
        MagickFreeMemory(levels.green);
679
0
        MagickFreeMemory(levels.blue);
680
0
        ThrowBinaryException3(ResourceLimitError,MemoryAllocationFailed,
681
0
                              UnableToGammaCorrectImage);
682
0
      }
683
#if (MaxMap > 256) && defined(HAVE_OPENMP)
684
#  pragma omp parallel for
685
#endif
686
0
    for (i=0; i <= (long) MaxMap; i++)
687
0
      {
688
0
        if (levels.color)
689
0
          levels.color[i]=
690
0
            ScaleMapToQuantum(MaxMap*GammaCorrect((double) i/MaxMap,gamma_color));
691
0
        if (levels.red)
692
0
          levels.red[i]=
693
0
            ScaleMapToQuantum(MaxMap*GammaCorrect((double) i/MaxMap,gamma_red));
694
0
        if (levels.green)
695
0
          levels.green[i]=
696
0
            ScaleMapToQuantum(MaxMap*GammaCorrect((double) i/MaxMap,gamma_green));
697
0
        if (levels.blue)
698
0
          levels.blue[i]=
699
0
            ScaleMapToQuantum(MaxMap*GammaCorrect((double) i/MaxMap,gamma_blue));
700
0
      }
701
    /*
702
      Apply gamma.
703
    */
704
0
    if (image->storage_class == PseudoClass)
705
0
      {
706
0
        (void) ApplyLevelsDiscrete(NULL,&levels,image,image->colormap,
707
0
                                   (IndexPacket *) NULL,image->colors,
708
0
                                   &image->exception);
709
0
        status=SyncImage(image);
710
0
      }
711
0
    else
712
0
      {
713
0
        status=PixelIterateMonoModify(ApplyLevelsDiscrete,
714
0
                                      NULL,
715
0
                                      "[%s] Applying gamma correction...",
716
0
                                      NULL,&levels,
717
0
                                      0,0,image->columns,image->rows,
718
0
                                      image,
719
0
                                      &image->exception);
720
0
      }
721
0
    MagickFreeMemory(levels.color);
722
0
    MagickFreeMemory(levels.red);
723
0
    MagickFreeMemory(levels.green);
724
0
    MagickFreeMemory(levels.blue);
725
0
  }
726
#else /* if MaxMap == MaxRGB */
727
  {
728
    GammaCorrectPixelsOptions_t
729
      levels;
730
731
    levels.red=gamma_red;
732
    levels.green=gamma_green;
733
    levels.blue=gamma_blue;
734
    levels.opacity=OpaqueOpacity;
735
736
    /*
737
      Apply gamma.
738
    */
739
    if (image->storage_class == PseudoClass)
740
      {
741
        (void) GammaCorrectPixels(NULL,&levels,image,image->colormap,
742
                                   (IndexPacket *) NULL,image->colors,
743
                                   &image->exception);
744
        status=SyncImage(image);
745
      }
746
    else
747
      {
748
        status=PixelIterateMonoModify(GammaCorrectPixels,
749
                                      NULL,
750
                                      "[%s] Applying gamma correction...",
751
                                      NULL,&levels,
752
                                      0,0,image->columns,image->rows,
753
                                      image,
754
                                      &image->exception);
755
      }
756
  }
757
758
#endif/* if MaxMap != MaxRGB */
759
760
0
  if (image->gamma != 0.0)
761
0
    image->gamma*=(gamma_red+gamma_green+gamma_blue)/3.0;
762
0
  image->is_grayscale=is_grayscale;
763
0
  return(status);
764
0
}
765

766
/*
767
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
768
%                                                                             %
769
%                                                                             %
770
%     L e v e l I m a g e                                                     %
771
%                                                                             %
772
%                                                                             %
773
%                                                                             %
774
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
775
%
776
%  LevelImage() adjusts the levels of an image by scaling the colors falling
777
%  between specified white and black points to the full available quantum
778
%  range. The parameters provided represent the black, mid (gamma), and white
779
%  points. The black point specifies the darkest color in the image. Colors
780
%  darker than the black point are set to zero. Mid point specifies a gamma
781
%  correction to apply to the image.  White point specifies the lightest color
782
%  in the image. Colors brighter than the white point are set to the maximum
783
%  quantum value.
784
%
785
%  The format of the LevelImage method is:
786
%
787
%      unsigned int LevelImage(Image *image,const char *level)
788
%
789
%  A description of each parameter follows:
790
%
791
%    o image: The image.
792
%
793
%    o levels: Specify the levels as a string of the form "black/mid/white"
794
%      (e.g. "10,1.0,65000) where black and white have the range of 0-MaxRGB,
795
%      and mid has the range 0-10.
796
%
797
%
798
*/
799
#define LevelImageText "Level...  "
800
MagickExport MagickPassFail LevelImage(Image *image,const char *levels)
801
0
{
802
0
  double
803
0
    black_point,
804
0
    mid_point,
805
0
    white_point;
806
807
0
  MagickPassFail
808
0
    status=MagickPass;
809
810
0
  assert(image != (Image *) NULL);
811
0
  assert(image->signature == MagickSignature);
812
0
  assert(levels != (char *) NULL);
813
814
0
  {
815
    /*
816
      Parse levels argument.
817
    */
818
0
    char
819
0
      buffer[MaxTextExtent];
820
821
0
    MagickBool
822
0
      percent = MagickFalse;
823
824
0
    int
825
0
      count;
826
827
0
    register long
828
0
      i;
829
830
0
    const char
831
0
      *lp;
832
833
0
    char
834
0
      *cp;
835
836
0
    black_point=0.0;
837
0
    mid_point=1.0;
838
0
    white_point=MaxRGB;
839
840
0
    cp=buffer;
841
0
    lp=levels;
842
0
    for (i=sizeof(buffer)-1 ; (*lp != 0) && (i != 0) ; lp++)
843
0
      {
844
0
        if (*lp == '%')
845
0
          {
846
0
            percent = MagickTrue;
847
0
          }
848
0
        else
849
0
          {
850
0
            *cp++=*lp;
851
0
            i--;
852
0
          }
853
0
      }
854
0
    *cp=0;
855
856
0
    count=sscanf(buffer,"%lf%*[,/]%lf%*[,/]%lf",&black_point,&mid_point,
857
0
                 &white_point);
858
0
    if (percent)
859
0
      {
860
0
        if (count > 0)
861
0
          black_point*=MaxRGB/100.0;
862
0
        if (count > 2)
863
0
          white_point*=MaxRGB/100.0;
864
0
      }
865
0
    black_point=ConstrainToQuantum(black_point);
866
0
    white_point=ConstrainToQuantum(white_point);
867
0
    if (count == 1)
868
0
      white_point=MaxRGB-black_point;
869
0
  }
870
871
0
  status=LevelImageChannel(image,AllChannels,black_point,mid_point,white_point);
872
873
0
  return status;
874
0
}
875

876
/*
877
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
878
%                                                                             %
879
%                                                                             %
880
%     L e v e l I m a g e C h a n n e l                                       %
881
%                                                                             %
882
%                                                                             %
883
%                                                                             %
884
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
885
%
886
%  LevelImageChannel() adjusts the levels of one or more channels by
887
%  scaling the colors falling between specified white and black points to
888
%  the full available quantum range. The parameters provided represent the
889
%  black, mid (gamma), and white points.  The black point specifies the
890
%  darkest color in the image. Colors darker than the black point are set to
891
%  zero. Mid point specifies a gamma correction to apply to the image.
892
%  White point specifies the lightest color in the image.  Colors brighter
893
%  than the white point are set to the maximum quantum value.
894
%
895
%  The format of the LevelImage method is:
896
%
897
%      MagickPassFail LevelImageChannel(Image *image,
898
%                                       const ChannelType channel,
899
%                                       const double black_point,
900
%                                       const double mid_point,
901
%                                       const double white_point)
902
%
903
%  A description of each parameter follows:
904
%
905
%    o image: The image.
906
%
907
%    o channel: Identify which channel to level: Red, Cyan, Green, Magenta,
908
%      Blue, Yellow, Opacity, or All.
909
%
910
%    o black_point, mid_point, white_point: Specify the levels where the black
911
%      and white points have the range of 0-MaxRGB, and mid has the range 0-10.
912
%
913
%
914
*/
915
MagickExport MagickPassFail LevelImageChannel(Image *image,
916
  const ChannelType channel,const double black_point,const double mid_point,
917
  const double white_point)
918
0
{
919
0
  double
920
0
    black,
921
0
    value,
922
0
    white;
923
924
0
  ApplyLevels_t
925
0
    levels;
926
927
0
  unsigned int
928
0
    is_grayscale = MagickFalse;
929
930
0
  register long
931
0
    i;
932
933
0
  MagickPassFail
934
0
    status=MagickPass;
935
936
  /*
937
    Allocate and initialize levels map.
938
  */
939
0
  assert(image != (Image *) NULL);
940
0
  assert(image->signature == MagickSignature);
941
0
  levels.map=MagickAllocateArray(PixelPacket *,(MaxMap+1),sizeof(PixelPacket));
942
0
  if (levels.map == (PixelPacket *) NULL)
943
0
    ThrowBinaryException3(ResourceLimitError,MemoryAllocationFailed,
944
0
                          UnableToLevelImage);
945
  /*
946
    Determine which channels to operate on.
947
  */
948
0
  levels.level_red=MagickFalse;
949
0
  levels.level_green=MagickFalse;
950
0
  levels.level_blue=MagickFalse;
951
0
  levels.level_opacity=MagickFalse;
952
0
  switch (channel)
953
0
    {
954
0
    case RedChannel:
955
0
    case CyanChannel:
956
0
      levels.level_red=MagickTrue;
957
0
        break;
958
0
    case GreenChannel:
959
0
    case MagentaChannel:
960
0
      levels.level_green=MagickTrue;
961
0
      break;
962
0
    case BlueChannel:
963
0
    case YellowChannel:
964
0
      levels.level_blue=MagickTrue;
965
0
      break;
966
0
    case OpacityChannel:
967
0
    case BlackChannel:
968
0
      levels.level_opacity=MagickTrue;
969
0
      break;
970
0
    case AllChannels:
971
0
      levels.level_red=MagickTrue;
972
0
      levels.level_green=MagickTrue;
973
0
      levels.level_blue=MagickTrue;
974
0
      is_grayscale=image->is_grayscale;
975
0
      break;
976
0
    default:
977
0
      break;
978
0
    }
979
  /*
980
    Build leveling map.
981
  */
982
0
  black=ScaleQuantumToMap(black_point);
983
0
  white=ScaleQuantumToMap(white_point);
984
0
  for (i=0; i <= (long) MaxMap; i++)
985
0
    {
986
0
      if (i < black)
987
0
        {
988
0
          levels.map[i].red=levels.map[i].green=levels.map[i].blue=levels.map[i].opacity=0;
989
0
          continue;
990
0
        }
991
0
      if (i > white)
992
0
        {
993
0
          levels.map[i].red=levels.map[i].green=levels.map[i].blue=levels.map[i].opacity=MaxRGB;
994
0
          continue;
995
0
        }
996
0
      value=MaxRGB*(pow(((double) i-black)/(white-black),1.0/mid_point));
997
0
      levels.map[i].red=levels.map[i].green=levels.map[i].blue=levels.map[i].opacity=
998
0
        RoundDoubleToQuantum(value);
999
0
    }
1000
  /*
1001
    Apply levels
1002
  */
1003
0
  if (image->storage_class == PseudoClass)
1004
0
    {
1005
0
      (void) ApplyLevels(NULL,&levels,image,image->colormap,
1006
0
                         (IndexPacket *) NULL,image->colors,
1007
0
                         &image->exception);
1008
0
      status=SyncImage(image);
1009
0
    }
1010
0
  else
1011
0
    {
1012
0
      status=PixelIterateMonoModify(ApplyLevels,
1013
0
                                    NULL,
1014
0
                                    "[%s] Leveling channels...",
1015
0
                                    NULL,&levels,
1016
0
                                    0,0,image->columns,image->rows,
1017
0
                                    image,
1018
0
                                    &image->exception);
1019
0
    }
1020
0
  MagickFreeMemory(levels.map);
1021
1022
0
  image->is_grayscale=is_grayscale;
1023
1024
0
  return(status);
1025
0
}
1026

1027
/*
1028
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1029
%                                                                             %
1030
%                                                                             %
1031
%     M o d u l a t e I m a g e                                               %
1032
%                                                                             %
1033
%                                                                             %
1034
%                                                                             %
1035
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1036
%
1037
%  ModulateImage() lets you control the brightness, saturation, and hue
1038
%  of an image.  Modulate represents the brightness, saturation, and hue
1039
%  as one parameter (e.g. 90,150,100).
1040
%
1041
%  The format of the ModulateImage method is:
1042
%
1043
%      unsigned int ModulateImage(Image *image,const char *modulate)
1044
%
1045
%  A description of each parameter follows:
1046
%
1047
%    o image: The image.
1048
%
1049
%    o modulate: Define the percent change in brightness, saturation, and
1050
%      hue.
1051
%
1052
%
1053
*/
1054
typedef struct _ModulateImageParameters_t
1055
{
1056
  double
1057
    percent_brightness,
1058
    percent_hue,
1059
    percent_saturation;
1060
} ModulateImageParameters_t;
1061
1062
static MagickPassFail
1063
ModulateImagePixels(void *mutable_data,         /* User provided mutable data */
1064
                    const void *immutable_data, /* User provided immutable data */
1065
                    Image * restrict image,               /* Modify image */
1066
                    PixelPacket * restrict pixels,        /* Pixel row */
1067
                    IndexPacket * restrict indexes,       /* Pixel row indexes */
1068
                    const long npixels,         /* Number of pixels in row */
1069
                    ExceptionInfo *exception)   /* Exception report */
1070
0
{
1071
  /*
1072
    Modulate image pixels according to options.
1073
  */
1074
0
  const ModulateImageParameters_t
1075
0
    param = *(const ModulateImageParameters_t *) immutable_data;
1076
1077
0
  register long
1078
0
    i;
1079
1080
0
  ARG_NOT_USED(mutable_data);
1081
0
  ARG_NOT_USED(image);
1082
0
  ARG_NOT_USED(indexes);
1083
0
  ARG_NOT_USED(exception);
1084
1085
0
  for (i=0; i < npixels; i++)
1086
0
    {
1087
0
      double
1088
0
        brightness,
1089
0
        hue,
1090
0
        saturation;
1091
1092
0
      TransformHSL(pixels[i].red,pixels[i].green,pixels[i].blue,
1093
0
                   &hue,&saturation,&brightness);
1094
0
      brightness*=(0.01+MagickEpsilon)*param.percent_brightness;
1095
0
      if (brightness > 1.0)
1096
0
        brightness=1.0;
1097
0
      saturation*=(0.01+MagickEpsilon)*param.percent_saturation;
1098
0
      if (saturation > 1.0)
1099
0
        saturation=1.0;
1100
1101
0
      hue += (param.percent_hue/200.0 - 0.5);
1102
0
      while (hue < 0.0)
1103
0
        hue += 1.0;
1104
0
      while (hue > 1.0)
1105
0
        hue -= 1.0;
1106
0
      HSLTransform(hue,saturation,brightness,
1107
0
                   &pixels[i].red,&pixels[i].green,&pixels[i].blue);
1108
0
    }
1109
1110
0
  return MagickPass;
1111
0
}
1112
1113
MagickExport MagickPassFail ModulateImage(Image *image,const char *modulate)
1114
0
{
1115
0
  char
1116
0
    progress_message[MaxTextExtent];
1117
1118
0
  ModulateImageParameters_t
1119
0
    param;
1120
1121
0
  unsigned int
1122
0
    is_grayscale;
1123
1124
0
  MagickPassFail
1125
0
    status=MagickPass;
1126
1127
0
  assert(image != (Image *) NULL);
1128
0
  assert(image->signature == MagickSignature);
1129
0
  if (modulate == (char *) NULL)
1130
0
    return(MagickFail);
1131
0
  is_grayscale=image->is_grayscale;
1132
0
  param.percent_brightness=100.0;
1133
0
  param.percent_saturation=100.0;
1134
0
  param.percent_hue=100.0;
1135
0
  (void) sscanf(modulate,"%lf%*[,/]%lf%*[,/]%lf",&param.percent_brightness,
1136
0
    &param.percent_saturation,&param.percent_hue);
1137
  /*
1138
    Ensure that adjustment values are positive so they don't need to
1139
    be checked in Modulate.
1140
  */
1141
0
  param.percent_brightness=AbsoluteValue(param.percent_brightness);
1142
0
  param.percent_saturation=AbsoluteValue(param.percent_saturation);
1143
0
  param.percent_hue=AbsoluteValue(param.percent_hue);
1144
1145
0
  FormatString(progress_message,"[%%s] Modulate %g/%g/%g...",
1146
0
               param.percent_brightness,param.percent_saturation,
1147
0
               param.percent_hue);
1148
0
  TransformColorspace(image,RGBColorspace);
1149
0
  if (image->storage_class == PseudoClass)
1150
0
    {
1151
0
      (void) ModulateImagePixels(NULL,&param,image,image->colormap,
1152
0
                                 (IndexPacket *) NULL,image->colors,
1153
0
                                 &image->exception);
1154
0
      status=MagickMonitorFormatted(image->colors,(size_t) image->colors+1,&image->exception,
1155
0
                             progress_message,image->filename);
1156
0
      status&=SyncImage(image);
1157
0
    }
1158
0
  else
1159
0
    {
1160
0
      status=PixelIterateMonoModify(ModulateImagePixels,NULL,progress_message,
1161
0
                                    NULL,&param,0,0,image->columns,image->rows,
1162
0
                                    image,&image->exception);
1163
0
    }
1164
1165
0
  image->is_grayscale=is_grayscale;
1166
0
  return(status);
1167
0
}
1168

1169
/*
1170
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1171
%                                                                             %
1172
%                                                                             %
1173
%     N e g a t e I m a g e                                                   %
1174
%                                                                             %
1175
%                                                                             %
1176
%                                                                             %
1177
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1178
%
1179
%  Method NegateImage negates the colors in the reference image.  The
1180
%  Grayscale option means that only grayscale values within the image are
1181
%  negated.
1182
%
1183
%  The format of the NegateImage method is:
1184
%
1185
%      unsigned int NegateImage(Image *image,const unsigned int grayscale)
1186
%
1187
%  A description of each parameter follows:
1188
%
1189
%    o image: The image.
1190
%
1191
%
1192
*/
1193
static MagickPassFail
1194
NegateImagePixels(void *mutable_data,         /* User provided mutable data */
1195
                  const void *immutable_data, /* User provided immutable data */
1196
                  Image * restrict image,               /* Modify image */
1197
                  PixelPacket * restrict pixels,        /* Pixel row */
1198
                  IndexPacket * restrict indexes,       /* Pixel row indexes */
1199
                  const long npixels,         /* Number of pixels in row */
1200
                  ExceptionInfo *exception)   /* Exception report */
1201
1.22M
{
1202
  /*
1203
    Negate the pixels.
1204
  */
1205
1.22M
  const unsigned int
1206
1.22M
    grayscale = *((const unsigned int *) immutable_data);
1207
1208
1.22M
  register long
1209
1.22M
    i;
1210
1211
1.22M
  ARG_NOT_USED(mutable_data);
1212
1.22M
  ARG_NOT_USED(image);
1213
1.22M
  ARG_NOT_USED(indexes);
1214
1.22M
  ARG_NOT_USED(exception);
1215
1216
1.22M
  if (grayscale)
1217
0
    {
1218
      /* Process only the non-gray pixels */
1219
0
      for (i=0; i < npixels; i++)
1220
0
        {
1221
0
          if (!IsGray(pixels[i]))
1222
0
            continue;
1223
0
          pixels[i].red=(~pixels[i].red);
1224
0
          pixels[i].green=(~pixels[i].green);
1225
0
          pixels[i].blue=(~pixels[i].blue);
1226
0
          if (image->colorspace == CMYKColorspace)
1227
0
            pixels[i].opacity=(~pixels[i].opacity);
1228
0
        }
1229
0
    }
1230
1.22M
  else
1231
1.22M
    {
1232
      /* Process all pixels */
1233
619M
      for (i=0; i < npixels; i++)
1234
617M
        {
1235
617M
          pixels[i].red=(~pixels[i].red);
1236
617M
          pixels[i].green=(~pixels[i].green);
1237
617M
          pixels[i].blue=(~pixels[i].blue);
1238
617M
          if (image->colorspace == CMYKColorspace)
1239
0
            pixels[i].opacity=(~pixels[i].opacity);
1240
617M
        }
1241
1.22M
    }
1242
1243
1.22M
  return MagickPass;
1244
1.22M
}
1245
1246
6.93k
#define NegateImageText "[%s] Negate..."
1247
MagickExport MagickPassFail NegateImage(Image *image,const unsigned int grayscale)
1248
9.94k
{
1249
9.94k
  unsigned int
1250
9.94k
    non_gray = grayscale;
1251
1252
9.94k
  unsigned int
1253
9.94k
    is_grayscale;
1254
1255
9.94k
  MagickPassFail
1256
9.94k
    status=MagickPass;
1257
1258
9.94k
  assert(image != (Image *) NULL);
1259
9.94k
  assert(image->signature == MagickSignature);
1260
9.94k
  is_grayscale=image->is_grayscale;
1261
9.94k
  if (*ImageGetClipMaskInlined(image))
1262
398
    image->storage_class=DirectClass;
1263
1264
9.94k
  if (image->storage_class == PseudoClass)
1265
3.01k
    {
1266
3.01k
      (void) NegateImagePixels(NULL,&non_gray,image,image->colormap,
1267
3.01k
                               (IndexPacket *) NULL,image->colors,
1268
3.01k
                               &image->exception);
1269
3.01k
      status=SyncImage(image);
1270
3.01k
    }
1271
6.93k
  else
1272
6.93k
    {
1273
6.93k
      status=PixelIterateMonoModify(NegateImagePixels,NULL,NegateImageText,
1274
6.93k
                                    NULL,&non_gray,0,0,image->columns,image->rows,
1275
6.93k
                                    image,&image->exception);
1276
6.93k
    }
1277
1278
9.94k
  image->is_grayscale=is_grayscale;
1279
9.94k
  return(status);
1280
9.94k
}
1281

1282
/*
1283
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1284
%                                                                             %
1285
%                                                                             %
1286
%     N o r m a l i z e I m a g e                                             %
1287
%                                                                             %
1288
%                                                                             %
1289
%                                                                             %
1290
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1291
%
1292
%  The NormalizeImage() method enhances the contrast of a color image by
1293
%  adjusting the pixels color to span the entire range of colors available.
1294
%
1295
%  The format of the NormalizeImage method is:
1296
%
1297
%      unsigned int NormalizeImage(Image *image)
1298
%
1299
%  A description of each parameter follows:
1300
%
1301
%    o image: The image.
1302
%
1303
%
1304
*/
1305
#define MaxRange(color)  ScaleQuantumToMap(color)
1306
/*
1307
  Find histogram bounds based on a minimum threshold value.
1308
*/
1309
#define FindHistogramBoundsAlg(Q,threshold,low,high,histogram)  \
1310
0
  {                                                             \
1311
0
    double                                                      \
1312
0
      intensity;                                                \
1313
0
                                                                \
1314
0
    intensity=0.0;                                              \
1315
0
    for (low.Q=0.0; low.Q < MaxMapDouble; low.Q += 1.0)         \
1316
0
      {                                                         \
1317
0
        intensity+=histogram[(long) low.Q].Q;                   \
1318
0
        if (intensity > threshold)                              \
1319
0
          break;                                                \
1320
0
      }                                                         \
1321
0
    intensity=0.0;                                              \
1322
0
    for (high.Q=MaxMapDouble; high.Q >= 1.0; high.Q -= 1.0)     \
1323
0
      {                                                         \
1324
0
        intensity+=histogram[(long) high.Q].Q;                  \
1325
0
        if (intensity > threshold)                              \
1326
0
          break;                                                \
1327
0
      }                                                         \
1328
0
}
1329
/*
1330
  Find histogram bounds, but with additional fallback in case
1331
  contrast is not reasonable.
1332
*/
1333
#define FindHistogramBounds(Q,threshold,low,high,histogram)     \
1334
0
  {                                                             \
1335
0
    FindHistogramBoundsAlg(Q,threshold,low,high,histogram);     \
1336
0
    if (low.red == high.red)                                    \
1337
0
      FindHistogramBoundsAlg(Q,0.0,low,high,histogram);         \
1338
0
}
1339
/*
1340
  Compute levels map entry for a quantum.
1341
*/
1342
0
#define ComputeHistogramMapQuantum(Q,levels,low,high)                   \
1343
0
{                                                                       \
1344
0
  if (i < (long) low.Q)                                                 \
1345
0
    levels.map[i].Q=0;                                                  \
1346
0
  else                                                                  \
1347
0
    if (i > (long) high.Q)                                              \
1348
0
      levels.map[i].Q=MaxRGB;                                           \
1349
0
    else                                                                \
1350
0
      if (low.Q != high.Q)                                              \
1351
0
        levels.map[i].Q=                                                \
1352
0
          ScaleMapToQuantum((MaxMapDouble*(i-low.Q))/(high.Q-low.Q));   \
1353
0
}
1354
MagickExport MagickPassFail NormalizeImage(Image *image)
1355
0
{
1356
0
  DoublePixelPacket
1357
0
    high,
1358
0
    *histogram,
1359
0
    low;
1360
1361
0
  ApplyLevels_t
1362
0
    levels;
1363
1364
0
  register long
1365
0
    i;
1366
1367
0
  unsigned int
1368
0
    is_grayscale;
1369
1370
0
  double
1371
0
    threshold,
1372
0
    threshold_percent;
1373
1374
0
  MagickPassFail
1375
0
    status=MagickPass;
1376
1377
  /*
1378
    Allocate histogram and normalize map.
1379
  */
1380
0
  assert(image != (Image *) NULL);
1381
0
  assert(image->signature == MagickSignature);
1382
0
  is_grayscale=image->is_grayscale;
1383
0
  levels.map=MagickAllocateMemory(PixelPacket *,(MaxMap+1)*sizeof(PixelPacket));
1384
0
  if (levels.map == (PixelPacket *) NULL)
1385
0
    ThrowBinaryException3(ResourceLimitError,MemoryAllocationFailed,
1386
0
                          UnableToNormalizeImage);
1387
  /*
1388
    Form histogram.
1389
  */
1390
0
  histogram=BuildChannelHistograms(image,&image->exception);
1391
0
  if (histogram == (DoublePixelPacket *) NULL)
1392
0
    {
1393
0
      MagickFreeMemory(levels.map);
1394
0
      return MagickFail;
1395
0
    }
1396
  /*
1397
    Find the histogram boundaries by locating the 0.1 percent levels.
1398
  */
1399
0
  threshold_percent=0.1;
1400
0
  MagickAttributeToDouble(image,"histogram-threshold",threshold_percent);
1401
0
  threshold=(long) ((double) image->columns*image->rows*0.01*threshold_percent);
1402
0
  (void) LogMagickEvent(TransformEvent,GetMagickModule(),
1403
0
                        "Histogram Threshold = %g%% (%g)", threshold_percent, threshold);
1404
0
  FindHistogramBounds(red,threshold,low,high,histogram);
1405
0
  FindHistogramBounds(green,threshold,low,high,histogram);
1406
0
  FindHistogramBounds(blue,threshold,low,high,histogram);
1407
0
  high.opacity=0.0;
1408
0
  low.opacity=0.0;
1409
0
  if (image->matte)
1410
0
    FindHistogramBounds(opacity,threshold,low,high,histogram);
1411
1412
0
  MagickFreeMemory(histogram);
1413
1414
  /*
1415
    Stretch the histogram to create the normalized image mapping.
1416
  */
1417
0
  (void) memset(levels.map,0,(MaxMap+1)*sizeof(PixelPacket));
1418
1419
0
  for (i=0; i <= (long) MaxMap; i++)
1420
0
    {
1421
0
      ComputeHistogramMapQuantum(red,levels,low,high);
1422
0
      ComputeHistogramMapQuantum(green,levels,low,high);
1423
0
      ComputeHistogramMapQuantum(blue,levels,low,high);
1424
0
      levels.map[i].opacity=OpaqueOpacity;
1425
0
      if (image->matte)
1426
0
        ComputeHistogramMapQuantum(opacity,levels,low,high);
1427
0
    }
1428
1429
  /*
1430
    Normalize the image.
1431
  */
1432
0
  levels.level_red = (low.red != high.red);
1433
0
  levels.level_green = (low.green != high.green);
1434
0
  levels.level_blue = (low.blue != high.blue);
1435
0
  levels.level_opacity= (image->matte && (low.opacity != high.opacity));
1436
0
  if (image->storage_class == PseudoClass)
1437
0
    {
1438
0
      (void) ApplyLevels(NULL,&levels,image,image->colormap,
1439
0
                         (IndexPacket *) NULL,image->colors,
1440
0
                         &image->exception);
1441
0
      status=SyncImage(image);
1442
0
    }
1443
0
  else
1444
0
    {
1445
0
      status=PixelIterateMonoModify(ApplyLevels,
1446
0
                                    NULL,
1447
0
                                    "[%s] Applying histogram normalization...",
1448
0
                                    NULL,&levels,
1449
0
                                    0,0,image->columns,image->rows,
1450
0
                                    image,
1451
0
                                    &image->exception);
1452
0
    }
1453
0
  MagickFreeMemory(levels.map);
1454
0
  image->is_grayscale=is_grayscale;
1455
0
  return(status);
1456
0
}