MagickCore  7.1.2-29
Convert, Edit, Or Compose Bitmap Images
enhance.c
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 {
116  double
117  gamma,
118  log_mean,
119  mean,
120  sans;
121 
122  MagickStatusType
123  status;
124 
125  ssize_t
126  i;
127 
128  log_mean=log(0.5);
129  if (image->channel_mask == AllChannels)
130  {
131  /*
132  Apply gamma correction equally across all given channels.
133  */
134  (void) GetImageMean(image,&mean,&sans,exception);
135  gamma=log(mean*QuantumScale)/log_mean;
136  return(LevelImage(image,0.0,(double) QuantumRange,gamma,exception));
137  }
138  /*
139  Auto-gamma each channel separately.
140  */
141  status=MagickTrue;
142  for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
143  {
144  ChannelType
145  channel_mask;
146 
147  PixelChannel channel = GetPixelChannelChannel(image,i);
148  PixelTrait traits = GetPixelChannelTraits(image,channel);
149  if ((traits & UpdatePixelTrait) == 0)
150  continue;
151  channel_mask=SetImageChannelMask(image,(ChannelType) (1UL << i));
152  status=GetImageMean(image,&mean,&sans,exception);
153  gamma=log(mean*QuantumScale)/log_mean;
154  status&=(MagickStatusType) LevelImage(image,0.0,(double) QuantumRange,gamma,
155  exception);
156  (void) SetImageChannelMask(image,channel_mask);
157  if (status == MagickFalse)
158  break;
159  }
160  return(status != 0 ? MagickTrue : MagickFalse);
161 }
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 {
191  return(MinMaxStretchImage(image,0.0,0.0,1.0,exception));
192 }
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 {
228 #define BrightnessContrastImageTag "BrightnessContrast/Image"
229 
230  double
231  coefficients[2],
232  intercept,
233  slope;
234 
235  MagickBooleanType
236  status;
237 
238  /*
239  Compute slope and intercept.
240  */
241  assert(image != (Image *) NULL);
242  assert(image->signature == MagickCoreSignature);
243  if (IsEventLogging() != MagickFalse)
244  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
245  slope=100.0*MagickSafeReciprocal(100.0-contrast);
246  if (contrast < 0.0)
247  slope=0.01*contrast+1.0;
248  intercept=(0.01*brightness-0.5)*slope+0.5;
249  coefficients[0]=slope;
250  coefficients[1]=intercept;
251  status=FunctionImage(image,PolynomialFunction,2,coefficients,exception);
252  return(status);
253 }
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 {
306 #define NumberCLAHEGrays (65536)
307 
308  ssize_t
309  cumulative_excess,
310  excess,
311  i,
312  previous_excess,
313  step;
314 
315  /*
316  Compute total number of excess pixels.
317  */
318  if (number_bins == 0)
319  return;
320  cumulative_excess=0;
321  for (i=0; i < (ssize_t) number_bins; i++)
322  if (histogram[i] > clip_limit)
323  cumulative_excess+=(ssize_t) (histogram[i]-clip_limit);
324  /*
325  Clip histogram and redistribute excess pixels across all bins.
326  */
327  step=cumulative_excess/(ssize_t) number_bins;
328  excess=(ssize_t) (clip_limit-step);
329  for (i=0; i < (ssize_t) number_bins; i++)
330  {
331  if ((double) histogram[i] > clip_limit)
332  histogram[i]=(size_t) clip_limit;
333  else
334  if ((ssize_t) histogram[i] > excess)
335  {
336  cumulative_excess-=(ssize_t) histogram[i]-excess;
337  histogram[i]=(size_t) clip_limit;
338  }
339  else
340  {
341  cumulative_excess-=step;
342  histogram[i]+=(size_t) step;
343  }
344  }
345  /*
346  Redistribute remaining excess.
347  */
348  do
349  {
350  size_t
351  *p;
352 
353  size_t
354  *q;
355 
356  previous_excess=cumulative_excess;
357  p=histogram;
358  q=histogram+number_bins;
359  while ((cumulative_excess != 0) && (p < q))
360  {
361  step=(ssize_t) number_bins/cumulative_excess;
362  if (step < 1)
363  step=1;
364  for (p=histogram; (p < q) && (cumulative_excess != 0); p+=(ptrdiff_t) step)
365  if ((double) *p < clip_limit)
366  {
367  (*p)++;
368  cumulative_excess--;
369  }
370  p++;
371  }
372  } while ((cumulative_excess != 0) && (cumulative_excess < previous_excess));
373 }
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 {
379  const unsigned short
380  *p;
381 
382  ssize_t
383  i;
384 
385  /*
386  Classify the pixels into a gray histogram.
387  */
388  for (i=0; i < (ssize_t) number_bins; i++)
389  histogram[i]=0L;
390  p=pixels;
391  for (i=0; i < (ssize_t) tile_info->height; i++)
392  {
393  const unsigned short
394  *q;
395 
396  q=p+tile_info->width;
397  while (p < q)
398  histogram[lut[*p++]]++;
399  q+=(ptrdiff_t) clahe_info->width;
400  p=q-tile_info->width;
401  }
402 }
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 {
408  ssize_t
409  y;
410 
411  unsigned short
412  intensity;
413 
414  /*
415  Bilinear interpolate four tiles to eliminate boundary artifacts.
416  */
417  for (y=(ssize_t) tile->height; y > 0; y--)
418  {
419  ssize_t
420  x;
421 
422  for (x=(ssize_t) tile->width; x > 0; x--)
423  {
424  intensity=lut[*pixels];
425  *pixels++=(unsigned short) (MagickSafeReciprocal((double) tile->width*
426  tile->height)*(y*((double) x*Q12[intensity]+((double) tile->width-x)*
427  Q22[intensity])+((double) tile->height-y)*((double) x*Q11[intensity]+
428  ((double) tile->width-x)*Q21[intensity])));
429  }
430  pixels+=(clahe_info->width-tile->width);
431  }
432 }
433 
434 static void GenerateCLAHELut(const RangeInfo *range_info,
435  const size_t number_bins,unsigned short *lut)
436 {
437  ssize_t
438  i;
439 
440  unsigned short
441  delta;
442 
443  /*
444  Scale input image [intensity min,max] to [0,number_bins-1].
445  */
446  delta=(unsigned short) ((range_info->max-range_info->min)/number_bins+1);
447  for (i=(ssize_t) range_info->min; i <= (ssize_t) range_info->max; i++)
448  lut[i]=(unsigned short) ((i-range_info->min)/delta);
449 }
450 
451 static void MapCLAHEHistogram(const RangeInfo *range_info,
452  const size_t number_bins,const size_t number_pixels,size_t *histogram)
453 {
454  double
455  scale,
456  sum;
457 
458  ssize_t
459  i;
460 
461  /*
462  Rescale histogram to range [min-intensity .. max-intensity].
463  */
464  scale=(double) (range_info->max-range_info->min)/number_pixels;
465  sum=0.0;
466  for (i=0; i < (ssize_t) number_bins; i++)
467  {
468  sum+=histogram[i];
469  histogram[i]=(size_t) (range_info->min+scale*sum);
470  if (histogram[i] > range_info->max)
471  histogram[i]=range_info->max;
472  }
473 }
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 {
479  MemoryInfo
480  *tile_cache;
481 
482  size_t
483  limit,
484  *tiles;
485 
486  ssize_t
487  y;
488 
489  unsigned short
490  *lut,
491  *p;
492 
493  /*
494  Contrast limited adapted histogram equalization.
495  */
496  if (clip_limit == 1.0)
497  return(MagickTrue);
498  tile_cache=AcquireVirtualMemory((size_t) clahe_info->x*number_bins,(size_t)
499  clahe_info->y*sizeof(*tiles));
500  if (tile_cache == (MemoryInfo *) NULL)
501  return(MagickFalse);
502  lut=(unsigned short *) AcquireQuantumMemory(NumberCLAHEGrays,sizeof(*lut));
503  if (lut == (unsigned short *) NULL)
504  {
505  tile_cache=RelinquishVirtualMemory(tile_cache);
506  return(MagickFalse);
507  }
508  tiles=(size_t *) GetVirtualMemoryBlob(tile_cache);
509  limit=(size_t) (clip_limit*((double) tile_info->width*tile_info->height)/
510  number_bins);
511  if (limit < 1UL)
512  limit=1UL;
513  /*
514  Generate greylevel mappings for each tile.
515  */
516  GenerateCLAHELut(range_info,number_bins,lut);
517  p=pixels;
518  for (y=0; y < (ssize_t) clahe_info->y; y++)
519  {
520  ssize_t
521  x;
522 
523  for (x=0; x < (ssize_t) clahe_info->x; x++)
524  {
525  size_t
526  *histogram;
527 
528  histogram=tiles+((ssize_t) number_bins*(y*clahe_info->x+x));
529  GenerateCLAHEHistogram(clahe_info,tile_info,number_bins,lut,p,histogram);
530  ClipCLAHEHistogram((double) limit,number_bins,histogram);
531  MapCLAHEHistogram(range_info,number_bins,tile_info->width*
532  tile_info->height,histogram);
533  p+=(ptrdiff_t) tile_info->width;
534  }
535  p+=CastDoubleToPtrdiffT((double) clahe_info->width*(tile_info->height-1));
536  }
537  /*
538  Interpolate greylevel mappings to get CLAHE image.
539  */
540  p=pixels;
541  for (y=0; y <= (ssize_t) clahe_info->y; y++)
542  {
543  OffsetInfo
544  offset;
545 
547  tile;
548 
549  ssize_t
550  x;
551 
552  tile.height=tile_info->height;
553  tile.y=y-1;
554  offset.y=tile.y+1;
555  if (y == 0)
556  {
557  /*
558  Top row.
559  */
560  tile.height=tile_info->height >> 1;
561  tile.y=0;
562  offset.y=0;
563  }
564  else
565  if (y == (ssize_t) clahe_info->y)
566  {
567  /*
568  Bottom row.
569  */
570  tile.height=(tile_info->height+1) >> 1;
571  tile.y=clahe_info->y-1;
572  offset.y=tile.y;
573  }
574  for (x=0; x <= (ssize_t) clahe_info->x; x++)
575  {
576  double
577  Q11,
578  Q12,
579  Q21,
580  Q22;
581 
582  tile.width=tile_info->width;
583  tile.x=x-1;
584  offset.x=tile.x+1;
585  if (x == 0)
586  {
587  /*
588  Left column.
589  */
590  tile.width=tile_info->width >> 1;
591  tile.x=0;
592  offset.x=0;
593  }
594  else
595  if (x == (ssize_t) clahe_info->x)
596  {
597  /*
598  Right column.
599  */
600  tile.width=(tile_info->width+1) >> 1;
601  tile.x=clahe_info->x-1;
602  offset.x=tile.x;
603  }
604  Q12=(double) number_bins*(tile.y*clahe_info->x+tile.x);
605  Q22=(double) number_bins*(tile.y*clahe_info->x+offset.x);
606  Q11=(double) number_bins*(offset.y*clahe_info->x+tile.x);
607  Q21=(double) number_bins*(offset.y*clahe_info->x+offset.x);
608  InterpolateCLAHE(clahe_info,tiles+CastDoubleToPtrdiffT(Q12),
609  tiles+CastDoubleToPtrdiffT(Q22),tiles+CastDoubleToPtrdiffT(Q11),
610  tiles+CastDoubleToPtrdiffT(Q21),&tile,lut,p);
611  p+=(ptrdiff_t) tile.width;
612  }
613  p+=CastDoubleToPtrdiffT((double) clahe_info->width*(tile.height-1));
614  }
615  lut=(unsigned short *) RelinquishMagickMemory(lut);
616  tile_cache=RelinquishVirtualMemory(tile_cache);
617  return(MagickTrue);
618 }
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 {
624 #define CLAHEImageTag "CLAHE/Image"
625 
626  CacheView
627  *image_view;
628 
629  ColorspaceType
630  colorspace;
631 
632  MagickBooleanType
633  status;
634 
635  MagickOffsetType
636  progress;
637 
638  MemoryInfo
639  *pixel_cache;
640 
641  RangeInfo
642  range_info;
643 
645  clahe_info,
646  tile_info;
647 
648  size_t
649  n;
650 
651  ssize_t
652  y;
653 
654  unsigned short
655  *pixels;
656 
657  /*
658  Configure CLAHE parameters.
659  */
660  assert(image != (Image *) NULL);
661  assert(image->signature == MagickCoreSignature);
662  if (IsEventLogging() != MagickFalse)
663  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
664  range_info.min=0;
665  range_info.max=NumberCLAHEGrays-1;
666  tile_info.width=width;
667  if (tile_info.width == 0)
668  tile_info.width=image->columns >> 3;
669  if (tile_info.width < 2)
670  tile_info.width=2;
671  tile_info.height=height;
672  if (tile_info.height == 0)
673  tile_info.height=image->rows >> 3;
674  if (tile_info.height < 2)
675  tile_info.height=2;
676  tile_info.x=0;
677  if ((image->columns % tile_info.width) != 0)
678  tile_info.x=(ssize_t) (tile_info.width-(image->columns % tile_info.width));
679  tile_info.y=0;
680  if ((image->rows % tile_info.height) != 0)
681  tile_info.y=(ssize_t) (tile_info.height-(image->rows % tile_info.height));
682  clahe_info.width=(size_t) ((ssize_t) image->columns+tile_info.x);
683  clahe_info.height=(size_t) ((ssize_t) image->rows+tile_info.y);
684  clahe_info.x=(ssize_t) (clahe_info.width/tile_info.width);
685  clahe_info.y=(ssize_t) (clahe_info.height/tile_info.height);
686  pixel_cache=AcquireVirtualMemory(clahe_info.width,clahe_info.height*
687  sizeof(*pixels));
688  if (pixel_cache == (MemoryInfo *) NULL)
689  ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
690  image->filename);
691  pixels=(unsigned short *) GetVirtualMemoryBlob(pixel_cache);
692  colorspace=image->colorspace;
693  if (TransformImageColorspace(image,LabColorspace,exception) == MagickFalse)
694  {
695  pixel_cache=RelinquishVirtualMemory(pixel_cache);
696  return(MagickFalse);
697  }
698  /*
699  Initialize CLAHE pixels.
700  */
701  image_view=AcquireVirtualCacheView(image,exception);
702  progress=0;
703  status=MagickTrue;
704  n=0;
705  for (y=0; y < (ssize_t) clahe_info.height; y++)
706  {
707  const Quantum
708  *magick_restrict p;
709 
710  ssize_t
711  x;
712 
713  if (status == MagickFalse)
714  continue;
715  p=GetCacheViewVirtualPixels(image_view,-(tile_info.x >> 1),y-
716  (tile_info.y >> 1),clahe_info.width,1,exception);
717  if (p == (const Quantum *) NULL)
718  {
719  status=MagickFalse;
720  continue;
721  }
722  for (x=0; x < (ssize_t) clahe_info.width; x++)
723  {
724  pixels[n++]=ScaleQuantumToShort(p[0]);
725  p+=(ptrdiff_t) GetPixelChannels(image);
726  }
727  if (image->progress_monitor != (MagickProgressMonitor) NULL)
728  {
729  MagickBooleanType
730  proceed;
731 
732  progress++;
733  proceed=SetImageProgress(image,CLAHEImageTag,progress,2*
734  GetPixelChannels(image));
735  if (proceed == MagickFalse)
736  status=MagickFalse;
737  }
738  }
739  image_view=DestroyCacheView(image_view);
740  status=CLAHE(&clahe_info,&tile_info,&range_info,number_bins == 0 ?
741  (size_t) 128 : MagickMin(number_bins,256),clip_limit,pixels);
742  if (status == MagickFalse)
743  (void) ThrowMagickException(exception,GetMagickModule(),
744  ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename);
745  /*
746  Push CLAHE pixels to CLAHE image.
747  */
748  image_view=AcquireAuthenticCacheView(image,exception);
749  n=clahe_info.width*(size_t) (tile_info.y/2);
750  for (y=0; y < (ssize_t) image->rows; y++)
751  {
752  Quantum
753  *magick_restrict q;
754 
755  ssize_t
756  x;
757 
758  if (status == MagickFalse)
759  continue;
760  q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
761  if (q == (Quantum *) NULL)
762  {
763  status=MagickFalse;
764  continue;
765  }
766  n+=(size_t) (tile_info.x/2);
767  for (x=0; x < (ssize_t) image->columns; x++)
768  {
769  q[0]=ScaleShortToQuantum(pixels[n++]);
770  q+=(ptrdiff_t) GetPixelChannels(image);
771  }
772  n+=(size_t) ((ssize_t) clahe_info.width-(ssize_t) image->columns-
773  (tile_info.x/2));
774  if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
775  status=MagickFalse;
776  if (image->progress_monitor != (MagickProgressMonitor) NULL)
777  {
778  MagickBooleanType
779  proceed;
780 
781  progress++;
782  proceed=SetImageProgress(image,CLAHEImageTag,progress,2*
783  GetPixelChannels(image));
784  if (proceed == MagickFalse)
785  status=MagickFalse;
786  }
787  }
788  image_view=DestroyCacheView(image_view);
789  pixel_cache=RelinquishVirtualMemory(pixel_cache);
790  if (TransformImageColorspace(image,colorspace,exception) == MagickFalse)
791  status=MagickFalse;
792  return(status);
793 }
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 {
843 #define ClutImageTag "Clut/Image"
844 
845  CacheView
846  *clut_view,
847  *image_view;
848 
849  MagickBooleanType
850  status;
851 
852  MagickOffsetType
853  progress;
854 
855  PixelInfo
856  *clut_map;
857 
858  ssize_t
859  adjust,
860  i,
861  y;
862 
863  assert(image != (Image *) NULL);
864  assert(image->signature == MagickCoreSignature);
865  assert(clut_image != (Image *) NULL);
866  assert(clut_image->signature == MagickCoreSignature);
867  if (IsEventLogging() != MagickFalse)
868  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
869  if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
870  return(MagickFalse);
871  if ((IsGrayColorspace(image->colorspace) != MagickFalse) &&
872  (IsGrayColorspace(clut_image->colorspace) == MagickFalse))
873  (void) SetImageColorspace(image,sRGBColorspace,exception);
874  clut_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*clut_map));
875  if (clut_map == (PixelInfo *) NULL)
876  ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
877  image->filename);
878  /*
879  Clut image.
880  */
881  status=MagickTrue;
882  progress=0;
883  adjust=(ssize_t) (method == IntegerInterpolatePixel ? 0 : 1);
884  clut_view=AcquireVirtualCacheView(clut_image,exception);
885  for (i=0; i <= (ssize_t) MaxMap; i++)
886  {
887  GetPixelInfo(clut_image,clut_map+i);
888  status=InterpolatePixelInfo(clut_image,clut_view,method,(double) i*
889  ((double) clut_image->columns-adjust)/MaxMap,(double) i*
890  ((double) clut_image->rows-adjust)/MaxMap,clut_map+i,exception);
891  if (status == MagickFalse)
892  break;
893  }
894  clut_view=DestroyCacheView(clut_view);
895  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  for (y=0; y < (ssize_t) image->rows; y++)
901  {
902  PixelInfo
903  pixel;
904 
905  Quantum
906  *magick_restrict q;
907 
908  ssize_t
909  x;
910 
911  if (status == MagickFalse)
912  continue;
913  q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
914  if (q == (Quantum *) NULL)
915  {
916  status=MagickFalse;
917  continue;
918  }
919  GetPixelInfo(image,&pixel);
920  for (x=0; x < (ssize_t) image->columns; x++)
921  {
922  PixelTrait
923  traits;
924 
925  GetPixelInfoPixel(image,q,&pixel);
926  traits=GetPixelChannelTraits(image,RedPixelChannel);
927  if ((traits & UpdatePixelTrait) != 0)
928  pixel.red=clut_map[ScaleQuantumToMap(ClampToQuantum(
929  pixel.red))].red;
930  traits=GetPixelChannelTraits(image,GreenPixelChannel);
931  if ((traits & UpdatePixelTrait) != 0)
932  pixel.green=clut_map[ScaleQuantumToMap(ClampToQuantum(
933  pixel.green))].green;
934  traits=GetPixelChannelTraits(image,BluePixelChannel);
935  if ((traits & UpdatePixelTrait) != 0)
936  pixel.blue=clut_map[ScaleQuantumToMap(ClampToQuantum(
937  pixel.blue))].blue;
938  traits=GetPixelChannelTraits(image,BlackPixelChannel);
939  if ((traits & UpdatePixelTrait) != 0)
940  pixel.black=clut_map[ScaleQuantumToMap(ClampToQuantum(
941  pixel.black))].black;
942  traits=GetPixelChannelTraits(image,AlphaPixelChannel);
943  if ((traits & UpdatePixelTrait) != 0)
944  pixel.alpha=clut_map[ScaleQuantumToMap(ClampToQuantum(
945  pixel.alpha))].alpha;
946  SetPixelViaPixelInfo(image,&pixel,q);
947  q+=(ptrdiff_t) GetPixelChannels(image);
948  }
949  if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
950  status=MagickFalse;
951  if (image->progress_monitor != (MagickProgressMonitor) NULL)
952  {
953  MagickBooleanType
954  proceed;
955 
956 #if defined(MAGICKCORE_OPENMP_SUPPORT)
957  #pragma omp atomic
958 #endif
959  progress++;
960  proceed=SetImageProgress(image,ClutImageTag,progress,image->rows);
961  if (proceed == MagickFalse)
962  status=MagickFalse;
963  }
964  }
965  image_view=DestroyCacheView(image_view);
966  clut_map=(PixelInfo *) RelinquishMagickMemory(clut_map);
967  if ((clut_image->alpha_trait != UndefinedPixelTrait) &&
968  ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0))
969  (void) SetImageAlphaChannel(image,ActivateAlphaChannel,exception);
970  return(status);
971 }
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 {
1021 #define ColorDecisionListCorrectImageTag "ColorDecisionList/Image"
1022 
1023  typedef struct _Correction
1024  {
1025  double
1026  slope,
1027  offset,
1028  power;
1029  } Correction;
1030 
1031  typedef struct _ColorCorrection
1032  {
1033  Correction
1034  red,
1035  green,
1036  blue;
1037 
1038  double
1039  saturation;
1040  } ColorCorrection;
1041 
1042  CacheView
1043  *image_view;
1044 
1045  char
1046  token[MagickPathExtent];
1047 
1048  ColorCorrection
1049  color_correction;
1050 
1051  const char
1052  *content,
1053  *p;
1054 
1055  MagickBooleanType
1056  status;
1057 
1058  MagickOffsetType
1059  progress;
1060 
1061  PixelInfo
1062  *cdl_map;
1063 
1064  ssize_t
1065  i;
1066 
1067  ssize_t
1068  y;
1069 
1070  XMLTreeInfo
1071  *cc,
1072  *ccc,
1073  *sat,
1074  *sop;
1075 
1076  /*
1077  Allocate and initialize cdl maps.
1078  */
1079  assert(image != (Image *) NULL);
1080  assert(image->signature == MagickCoreSignature);
1081  if (IsEventLogging() != MagickFalse)
1082  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1083  if (color_correction_collection == (const char *) NULL)
1084  return(MagickFalse);
1085  ccc=NewXMLTree((const char *) color_correction_collection,exception);
1086  if (ccc == (XMLTreeInfo *) NULL)
1087  return(MagickFalse);
1088  cc=GetXMLTreeChild(ccc,"ColorCorrection");
1089  if (cc == (XMLTreeInfo *) NULL)
1090  {
1091  ccc=DestroyXMLTree(ccc);
1092  return(MagickFalse);
1093  }
1094  color_correction.red.slope=1.0;
1095  color_correction.red.offset=0.0;
1096  color_correction.red.power=1.0;
1097  color_correction.green.slope=1.0;
1098  color_correction.green.offset=0.0;
1099  color_correction.green.power=1.0;
1100  color_correction.blue.slope=1.0;
1101  color_correction.blue.offset=0.0;
1102  color_correction.blue.power=1.0;
1103  color_correction.saturation=0.0;
1104  sop=GetXMLTreeChild(cc,"SOPNode");
1105  if (sop != (XMLTreeInfo *) NULL)
1106  {
1107  XMLTreeInfo
1108  *offset,
1109  *power,
1110  *slope;
1111 
1112  slope=GetXMLTreeChild(sop,"Slope");
1113  if (slope != (XMLTreeInfo *) NULL)
1114  {
1115  content=GetXMLTreeContent(slope);
1116  p=(const char *) content;
1117  for (i=0; (*p != '\0') && (i < 3); i++)
1118  {
1119  (void) GetNextToken(p,&p,MagickPathExtent,token);
1120  if (*token == ',')
1121  (void) GetNextToken(p,&p,MagickPathExtent,token);
1122  switch (i)
1123  {
1124  case 0:
1125  {
1126  color_correction.red.slope=StringToDouble(token,(char **) NULL);
1127  break;
1128  }
1129  case 1:
1130  {
1131  color_correction.green.slope=StringToDouble(token,
1132  (char **) NULL);
1133  break;
1134  }
1135  case 2:
1136  {
1137  color_correction.blue.slope=StringToDouble(token,
1138  (char **) NULL);
1139  break;
1140  }
1141  }
1142  }
1143  }
1144  offset=GetXMLTreeChild(sop,"Offset");
1145  if (offset != (XMLTreeInfo *) NULL)
1146  {
1147  content=GetXMLTreeContent(offset);
1148  p=(const char *) content;
1149  for (i=0; (*p != '\0') && (i < 3); i++)
1150  {
1151  (void) GetNextToken(p,&p,MagickPathExtent,token);
1152  if (*token == ',')
1153  (void) GetNextToken(p,&p,MagickPathExtent,token);
1154  switch (i)
1155  {
1156  case 0:
1157  {
1158  color_correction.red.offset=StringToDouble(token,
1159  (char **) NULL);
1160  break;
1161  }
1162  case 1:
1163  {
1164  color_correction.green.offset=StringToDouble(token,
1165  (char **) NULL);
1166  break;
1167  }
1168  case 2:
1169  {
1170  color_correction.blue.offset=StringToDouble(token,
1171  (char **) NULL);
1172  break;
1173  }
1174  }
1175  }
1176  }
1177  power=GetXMLTreeChild(sop,"Power");
1178  if (power != (XMLTreeInfo *) NULL)
1179  {
1180  content=GetXMLTreeContent(power);
1181  p=(const char *) content;
1182  for (i=0; (*p != '\0') && (i < 3); i++)
1183  {
1184  (void) GetNextToken(p,&p,MagickPathExtent,token);
1185  if (*token == ',')
1186  (void) GetNextToken(p,&p,MagickPathExtent,token);
1187  switch (i)
1188  {
1189  case 0:
1190  {
1191  color_correction.red.power=StringToDouble(token,(char **) NULL);
1192  break;
1193  }
1194  case 1:
1195  {
1196  color_correction.green.power=StringToDouble(token,
1197  (char **) NULL);
1198  break;
1199  }
1200  case 2:
1201  {
1202  color_correction.blue.power=StringToDouble(token,
1203  (char **) NULL);
1204  break;
1205  }
1206  }
1207  }
1208  }
1209  }
1210  sat=GetXMLTreeChild(cc,"SATNode");
1211  if (sat != (XMLTreeInfo *) NULL)
1212  {
1213  XMLTreeInfo
1214  *saturation;
1215 
1216  saturation=GetXMLTreeChild(sat,"Saturation");
1217  if (saturation != (XMLTreeInfo *) NULL)
1218  {
1219  content=GetXMLTreeContent(saturation);
1220  p=(const char *) content;
1221  (void) GetNextToken(p,&p,MagickPathExtent,token);
1222  color_correction.saturation=StringToDouble(token,(char **) NULL);
1223  }
1224  }
1225  ccc=DestroyXMLTree(ccc);
1226  if (image->debug != MagickFalse)
1227  {
1228  (void) LogMagickEvent(TransformEvent,GetMagickModule(),
1229  " Color Correction Collection:");
1230  (void) LogMagickEvent(TransformEvent,GetMagickModule(),
1231  " color_correction.red.slope: %g",color_correction.red.slope);
1232  (void) LogMagickEvent(TransformEvent,GetMagickModule(),
1233  " color_correction.red.offset: %g",color_correction.red.offset);
1234  (void) LogMagickEvent(TransformEvent,GetMagickModule(),
1235  " color_correction.red.power: %g",color_correction.red.power);
1236  (void) LogMagickEvent(TransformEvent,GetMagickModule(),
1237  " color_correction.green.slope: %g",color_correction.green.slope);
1238  (void) LogMagickEvent(TransformEvent,GetMagickModule(),
1239  " color_correction.green.offset: %g",color_correction.green.offset);
1240  (void) LogMagickEvent(TransformEvent,GetMagickModule(),
1241  " color_correction.green.power: %g",color_correction.green.power);
1242  (void) LogMagickEvent(TransformEvent,GetMagickModule(),
1243  " color_correction.blue.slope: %g",color_correction.blue.slope);
1244  (void) LogMagickEvent(TransformEvent,GetMagickModule(),
1245  " color_correction.blue.offset: %g",color_correction.blue.offset);
1246  (void) LogMagickEvent(TransformEvent,GetMagickModule(),
1247  " color_correction.blue.power: %g",color_correction.blue.power);
1248  (void) LogMagickEvent(TransformEvent,GetMagickModule(),
1249  " color_correction.saturation: %g",color_correction.saturation);
1250  }
1251  cdl_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*cdl_map));
1252  if (cdl_map == (PixelInfo *) NULL)
1253  ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1254  image->filename);
1255  for (i=0; i <= (ssize_t) MaxMap; i++)
1256  {
1257  cdl_map[i].red=(double) ScaleMapToQuantum((double)
1258  (MaxMap*(pow(color_correction.red.slope*i/MaxMap+
1259  color_correction.red.offset,color_correction.red.power))));
1260  cdl_map[i].green=(double) ScaleMapToQuantum((double)
1261  (MaxMap*(pow(color_correction.green.slope*i/MaxMap+
1262  color_correction.green.offset,color_correction.green.power))));
1263  cdl_map[i].blue=(double) ScaleMapToQuantum((double)
1264  (MaxMap*(pow(color_correction.blue.slope*i/MaxMap+
1265  color_correction.blue.offset,color_correction.blue.power))));
1266  }
1267  if (image->storage_class == PseudoClass)
1268  {
1269  for (i=0; i < (ssize_t) image->colors; i++)
1270  {
1271  /*
1272  Apply transfer function to colormap.
1273  */
1274  double
1275  luma;
1276 
1277  luma=0.21267*image->colormap[i].red+0.71526*image->colormap[i].green+
1278  0.07217*image->colormap[i].blue;
1279  image->colormap[i].red=luma+color_correction.saturation*cdl_map[
1280  ScaleQuantumToMap(ClampToQuantum(image->colormap[i].red))].red-luma;
1281  image->colormap[i].green=luma+color_correction.saturation*cdl_map[
1282  ScaleQuantumToMap(ClampToQuantum(image->colormap[i].green))].green-
1283  luma;
1284  image->colormap[i].blue=luma+color_correction.saturation*cdl_map[
1285  ScaleQuantumToMap(ClampToQuantum(image->colormap[i].blue))].blue-luma;
1286  }
1287  cdl_map=(PixelInfo *) RelinquishMagickMemory(cdl_map);
1288  (void) SyncImage(image, exception);
1289  return(MagickTrue);
1290  }
1291  /*
1292  Apply transfer function to image.
1293  */
1294  status=MagickTrue;
1295  progress=0;
1296  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  for (y=0; y < (ssize_t) image->rows; y++)
1302  {
1303  double
1304  luma;
1305 
1306  Quantum
1307  *magick_restrict q;
1308 
1309  ssize_t
1310  x;
1311 
1312  if (status == MagickFalse)
1313  continue;
1314  q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1315  if (q == (Quantum *) NULL)
1316  {
1317  status=MagickFalse;
1318  continue;
1319  }
1320  for (x=0; x < (ssize_t) image->columns; x++)
1321  {
1322  luma=0.21267*(double) GetPixelRed(image,q)+0.71526*(double)
1323  GetPixelGreen(image,q)+0.07217*(double) GetPixelBlue(image,q);
1324  SetPixelRed(image,ClampToQuantum(luma+color_correction.saturation*
1325  (cdl_map[ScaleQuantumToMap(GetPixelRed(image,q))].red-luma)),q);
1326  SetPixelGreen(image,ClampToQuantum(luma+color_correction.saturation*
1327  (cdl_map[ScaleQuantumToMap(GetPixelGreen(image,q))].green-luma)),q);
1328  SetPixelBlue(image,ClampToQuantum(luma+color_correction.saturation*
1329  (cdl_map[ScaleQuantumToMap(GetPixelBlue(image,q))].blue-luma)),q);
1330  q+=(ptrdiff_t) GetPixelChannels(image);
1331  }
1332  if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1333  status=MagickFalse;
1334  if (image->progress_monitor != (MagickProgressMonitor) NULL)
1335  {
1336  MagickBooleanType
1337  proceed;
1338 
1339 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1340  #pragma omp atomic
1341 #endif
1342  progress++;
1343  proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
1344  progress,image->rows);
1345  if (proceed == MagickFalse)
1346  status=MagickFalse;
1347  }
1348  }
1349  image_view=DestroyCacheView(image_view);
1350  cdl_map=(PixelInfo *) RelinquishMagickMemory(cdl_map);
1351  return(status);
1352 }
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 {
1387  double
1388  brightness = 0.0,
1389  hue = 0.0,
1390  saturation = 0.0;
1391 
1392  /*
1393  Enhance contrast: dark color become darker, light color become lighter.
1394  */
1395  ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
1396  brightness+=0.5*sign*(0.5*(sin((double) (MagickPI*(brightness-0.5)))+1.0)-
1397  brightness);
1398  if (brightness > 1.0)
1399  brightness=1.0;
1400  else
1401  if (brightness < 0.0)
1402  brightness=0.0;
1403  ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
1404 }
1405 
1406 MagickExport MagickBooleanType ContrastImage(Image *image,
1407  const MagickBooleanType sharpen,ExceptionInfo *exception)
1408 {
1409 #define ContrastImageTag "Contrast/Image"
1410 
1411  CacheView
1412  *image_view;
1413 
1414  int
1415  sign;
1416 
1417  MagickBooleanType
1418  status;
1419 
1420  MagickOffsetType
1421  progress;
1422 
1423  ssize_t
1424  i;
1425 
1426  ssize_t
1427  y;
1428 
1429  assert(image != (Image *) NULL);
1430  assert(image->signature == MagickCoreSignature);
1431 #if defined(MAGICKCORE_OPENCL_SUPPORT)
1432  if (AccelerateContrastImage(image,sharpen,exception) != MagickFalse)
1433  return(MagickTrue);
1434 #endif
1435  if (IsEventLogging() != MagickFalse)
1436  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1437  sign=sharpen != MagickFalse ? 1 : -1;
1438  if (image->storage_class == PseudoClass)
1439  {
1440  /*
1441  Contrast enhance colormap.
1442  */
1443  for (i=0; i < (ssize_t) image->colors; i++)
1444  {
1445  double
1446  blue,
1447  green,
1448  red;
1449 
1450  red=(double) image->colormap[i].red;
1451  green=(double) image->colormap[i].green;
1452  blue=(double) image->colormap[i].blue;
1453  Contrast(sign,&red,&green,&blue);
1454  image->colormap[i].red=(MagickRealType) red;
1455  image->colormap[i].green=(MagickRealType) green;
1456  image->colormap[i].blue=(MagickRealType) blue;
1457  }
1458  }
1459  /*
1460  Contrast enhance image.
1461  */
1462  status=MagickTrue;
1463  progress=0;
1464  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  for (y=0; y < (ssize_t) image->rows; y++)
1470  {
1471  double
1472  blue,
1473  green,
1474  red;
1475 
1476  Quantum
1477  *magick_restrict q;
1478 
1479  ssize_t
1480  x;
1481 
1482  if (status == MagickFalse)
1483  continue;
1484  q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1485  if (q == (Quantum *) NULL)
1486  {
1487  status=MagickFalse;
1488  continue;
1489  }
1490  for (x=0; x < (ssize_t) image->columns; x++)
1491  {
1492  red=(double) GetPixelRed(image,q);
1493  green=(double) GetPixelGreen(image,q);
1494  blue=(double) GetPixelBlue(image,q);
1495  Contrast(sign,&red,&green,&blue);
1496  SetPixelRed(image,ClampToQuantum(red),q);
1497  SetPixelGreen(image,ClampToQuantum(green),q);
1498  SetPixelBlue(image,ClampToQuantum(blue),q);
1499  q+=(ptrdiff_t) GetPixelChannels(image);
1500  }
1501  if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1502  status=MagickFalse;
1503  if (image->progress_monitor != (MagickProgressMonitor) NULL)
1504  {
1505  MagickBooleanType
1506  proceed;
1507 
1508 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1509  #pragma omp atomic
1510 #endif
1511  progress++;
1512  proceed=SetImageProgress(image,ContrastImageTag,progress,image->rows);
1513  if (proceed == MagickFalse)
1514  status=MagickFalse;
1515  }
1516  }
1517  image_view=DestroyCacheView(image_view);
1518  return(status);
1519 }
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 {
1561 #define ContrastStretchImageTag "ContrastStretch/Image"
1562 
1563  CacheView
1564  *image_view;
1565 
1566  char
1567  property[MagickPathExtent];
1568 
1569  double
1570  *histogram;
1571 
1572  ImageType
1573  type;
1574 
1575  MagickBooleanType
1576  status;
1577 
1578  MagickOffsetType
1579  progress;
1580 
1581  Quantum
1582  *black,
1583  *stretch_map,
1584  *white;
1585 
1586  ssize_t
1587  i;
1588 
1589  ssize_t
1590  y;
1591 
1592  /*
1593  Allocate histogram and stretch map.
1594  */
1595  assert(image != (Image *) NULL);
1596  assert(image->signature == MagickCoreSignature);
1597  if (IsEventLogging() != MagickFalse)
1598  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1599  type=IdentifyImageType(image,exception);
1600  if (IsGrayImageType(type) != MagickFalse)
1601  (void) SetImageColorspace(image,GRAYColorspace,exception);
1602  black=(Quantum *) AcquireQuantumMemory(MaxPixelChannels,sizeof(*black));
1603  white=(Quantum *) AcquireQuantumMemory(MaxPixelChannels,sizeof(*white));
1604  stretch_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,MaxPixelChannels*
1605  sizeof(*stretch_map));
1606  histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,MaxPixelChannels*
1607  sizeof(*histogram));
1608  if ((black == (Quantum *) NULL) || (white == (Quantum *) NULL) ||
1609  (stretch_map == (Quantum *) NULL) || (histogram == (double *) NULL))
1610  {
1611  if (histogram != (double *) NULL)
1612  histogram=(double *) RelinquishMagickMemory(histogram);
1613  if (stretch_map != (Quantum *) NULL)
1614  stretch_map=(Quantum *) RelinquishMagickMemory(stretch_map);
1615  if (white != (Quantum *) NULL)
1616  white=(Quantum *) RelinquishMagickMemory(white);
1617  if (black != (Quantum *) NULL)
1618  black=(Quantum *) RelinquishMagickMemory(black);
1619  ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1620  image->filename);
1621  }
1622  /*
1623  Form histogram.
1624  */
1625  status=MagickTrue;
1626  (void) memset(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1627  sizeof(*histogram));
1628  image_view=AcquireVirtualCacheView(image,exception);
1629  for (y=0; y < (ssize_t) image->rows; y++)
1630  {
1631  const Quantum
1632  *magick_restrict p;
1633 
1634  ssize_t
1635  x;
1636 
1637  if (status == MagickFalse)
1638  continue;
1639  p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
1640  if (p == (const Quantum *) NULL)
1641  {
1642  status=MagickFalse;
1643  continue;
1644  }
1645  for (x=0; x < (ssize_t) image->columns; x++)
1646  {
1647  double
1648  pixel;
1649 
1650  pixel=GetPixelIntensity(image,p);
1651  for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1652  {
1653  if (image->channel_mask != AllChannels)
1654  pixel=(double) p[i];
1655  histogram[GetPixelChannels(image)*ScaleQuantumToMap(
1656  ClampToQuantum(pixel))+(size_t) i]++;
1657  }
1658  p+=(ptrdiff_t) GetPixelChannels(image);
1659  }
1660  }
1661  image_view=DestroyCacheView(image_view);
1662  /*
1663  Find the histogram boundaries by locating the black/white levels.
1664  */
1665  for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1666  {
1667  double
1668  intensity;
1669 
1670  ssize_t
1671  j;
1672 
1673  black[i]=(Quantum) 0;
1674  white[i]=(Quantum) ScaleQuantumToMap(QuantumRange);
1675  intensity=0.0;
1676  for (j=0; j <= (ssize_t) MaxMap; j++)
1677  {
1678  intensity+=histogram[(ssize_t) GetPixelChannels(image)*j+i];
1679  if (intensity > black_point)
1680  break;
1681  }
1682  black[i]=(Quantum) j;
1683  intensity=0.0;
1684  for (j=(ssize_t) MaxMap; j != 0; j--)
1685  {
1686  intensity+=histogram[(ssize_t) GetPixelChannels(image)*j+i];
1687  if (intensity > ((double) image->columns*image->rows-white_point))
1688  break;
1689  }
1690  white[i]=(Quantum) j;
1691  }
1692  histogram=(double *) RelinquishMagickMemory(histogram);
1693  /*
1694  Stretch the histogram to create the stretched image mapping.
1695  */
1696  (void) memset(stretch_map,0,(MaxMap+1)*GetPixelChannels(image)*
1697  sizeof(*stretch_map));
1698  for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1699  {
1700  ssize_t
1701  j;
1702 
1703  for (j=0; j <= (ssize_t) MaxMap; j++)
1704  {
1705  double
1706  gamma;
1707 
1708  gamma=MagickSafeReciprocal(white[i]-black[i]);
1709  if (j < (ssize_t) black[i])
1710  stretch_map[(ssize_t) GetPixelChannels(image)*j+i]=(Quantum) 0;
1711  else
1712  if (j > (ssize_t) white[i])
1713  stretch_map[(ssize_t) GetPixelChannels(image)*j+i]=QuantumRange;
1714  else
1715  if (black[i] != white[i])
1716  stretch_map[(ssize_t) GetPixelChannels(image)*j+i]=
1717  ScaleMapToQuantum((double) (MaxMap*gamma*(j-(double) black[i])));
1718  }
1719  }
1720  if (image->storage_class == PseudoClass)
1721  {
1722  ssize_t
1723  j;
1724 
1725  /*
1726  Stretch-contrast colormap.
1727  */
1728  for (j=0; j < (ssize_t) image->colors; j++)
1729  {
1730  if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
1731  {
1732  i=GetPixelChannelOffset(image,RedPixelChannel);
1733  image->colormap[j].red=(MagickRealType) stretch_map[
1734  GetPixelChannels(image)*ScaleQuantumToMap(ClampToQuantum(
1735  image->colormap[j].red))+(size_t) i];
1736  }
1737  if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1738  {
1739  i=GetPixelChannelOffset(image,GreenPixelChannel);
1740  image->colormap[j].green=(MagickRealType) stretch_map[
1741  GetPixelChannels(image)*ScaleQuantumToMap(ClampToQuantum(
1742  image->colormap[j].green))+(size_t) i];
1743  }
1744  if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1745  {
1746  i=GetPixelChannelOffset(image,BluePixelChannel);
1747  image->colormap[j].blue=(MagickRealType) stretch_map[
1748  GetPixelChannels(image)*ScaleQuantumToMap(ClampToQuantum(
1749  image->colormap[j].blue))+(size_t) i];
1750  }
1751  if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1752  {
1753  i=GetPixelChannelOffset(image,AlphaPixelChannel);
1754  image->colormap[j].alpha=(MagickRealType) stretch_map[
1755  GetPixelChannels(image)*ScaleQuantumToMap(ClampToQuantum(
1756  image->colormap[j].alpha))+(size_t) i];
1757  }
1758  }
1759  }
1760  /*
1761  Stretch-contrast image.
1762  */
1763  status=MagickTrue;
1764  progress=0;
1765  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  for (y=0; y < (ssize_t) image->rows; y++)
1771  {
1772  Quantum
1773  *magick_restrict q;
1774 
1775  ssize_t
1776  x;
1777 
1778  if (status == MagickFalse)
1779  continue;
1780  q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
1781  if (q == (Quantum *) NULL)
1782  {
1783  status=MagickFalse;
1784  continue;
1785  }
1786  for (x=0; x < (ssize_t) image->columns; x++)
1787  {
1788  ssize_t
1789  j;
1790 
1791  for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
1792  {
1793  PixelChannel channel = GetPixelChannelChannel(image,j);
1794  PixelTrait traits = GetPixelChannelTraits(image,channel);
1795  if ((traits & UpdatePixelTrait) == 0)
1796  continue;
1797  if (black[j] == white[j])
1798  continue;
1799  q[j]=ClampToQuantum(stretch_map[GetPixelChannels(image)*
1800  ScaleQuantumToMap(q[j])+(size_t) j]);
1801  }
1802  q+=(ptrdiff_t) GetPixelChannels(image);
1803  }
1804  if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1805  status=MagickFalse;
1806  if (image->progress_monitor != (MagickProgressMonitor) NULL)
1807  {
1808  MagickBooleanType
1809  proceed;
1810 
1811 #if defined(MAGICKCORE_OPENMP_SUPPORT)
1812  #pragma omp atomic
1813 #endif
1814  progress++;
1815  proceed=SetImageProgress(image,ContrastStretchImageTag,progress,
1816  image->rows);
1817  if (proceed == MagickFalse)
1818  status=MagickFalse;
1819  }
1820  }
1821  image_view=DestroyCacheView(image_view);
1822  (void) FormatLocaleString(property,MagickPathExtent,"%gx%g%%",100.0*
1823  QuantumScale*GetPixelIntensity(image,black),100.0*QuantumScale*
1824  GetPixelIntensity(image,white));
1825  (void) SetImageProperty(image,"histogram:contrast-stretch",property,
1826  exception);
1827  white=(Quantum *) RelinquishMagickMemory(white);
1828  black=(Quantum *) RelinquishMagickMemory(black);
1829  stretch_map=(Quantum *) RelinquishMagickMemory(stretch_map);
1830  return(status);
1831 }
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 {
1860 #define EnhanceImageTag "Enhance/Image"
1861 #define EnhancePixel(weight) \
1862  mean=QuantumScale*((double) GetPixelRed(image,r)+pixel.red)/2.0; \
1863  distance=QuantumScale*((double) GetPixelRed(image,r)-pixel.red); \
1864  distance_squared=(4.0+mean)*distance*distance; \
1865  mean=QuantumScale*((double) GetPixelGreen(image,r)+pixel.green)/2.0; \
1866  distance=QuantumScale*((double) GetPixelGreen(image,r)-pixel.green); \
1867  distance_squared+=(7.0-mean)*distance*distance; \
1868  mean=QuantumScale*((double) GetPixelBlue(image,r)+pixel.blue)/2.0; \
1869  distance=QuantumScale*((double) GetPixelBlue(image,r)-pixel.blue); \
1870  distance_squared+=(5.0-mean)*distance*distance; \
1871  mean=QuantumScale*((double) GetPixelBlack(image,r)+pixel.black)/2.0; \
1872  distance=QuantumScale*((double) GetPixelBlack(image,r)-pixel.black); \
1873  distance_squared+=(5.0-mean)*distance*distance; \
1874  mean=QuantumScale*((double) GetPixelAlpha(image,r)+pixel.alpha)/2.0; \
1875  distance=QuantumScale*((double) GetPixelAlpha(image,r)-pixel.alpha); \
1876  distance_squared+=(5.0-mean)*distance*distance; \
1877  if (distance_squared < 0.069) \
1878  { \
1879  aggregate.red+=(weight)*(double) GetPixelRed(image,r); \
1880  aggregate.green+=(weight)*(double) GetPixelGreen(image,r); \
1881  aggregate.blue+=(weight)*(double) GetPixelBlue(image,r); \
1882  aggregate.black+=(weight)*(double) GetPixelBlack(image,r); \
1883  aggregate.alpha+=(weight)*(double) GetPixelAlpha(image,r); \
1884  total_weight+=(weight); \
1885  } \
1886  r+=(ptrdiff_t) GetPixelChannels(image);
1887 
1888  CacheView
1889  *enhance_view,
1890  *image_view;
1891 
1892  Image
1893  *enhance_image;
1894 
1895  MagickBooleanType
1896  status;
1897 
1898  MagickOffsetType
1899  progress;
1900 
1901  ssize_t
1902  y;
1903 
1904  /*
1905  Initialize enhanced image attributes.
1906  */
1907  assert(image != (const Image *) NULL);
1908  assert(image->signature == MagickCoreSignature);
1909  if (IsEventLogging() != MagickFalse)
1910  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1911  assert(exception != (ExceptionInfo *) NULL);
1912  assert(exception->signature == MagickCoreSignature);
1913  enhance_image=CloneImage(image,0,0,MagickTrue,
1914  exception);
1915  if (enhance_image == (Image *) NULL)
1916  return((Image *) NULL);
1917  if (SetImageStorageClass(enhance_image,DirectClass,exception) == MagickFalse)
1918  {
1919  enhance_image=DestroyImage(enhance_image);
1920  return((Image *) NULL);
1921  }
1922  /*
1923  Enhance image.
1924  */
1925  status=MagickTrue;
1926  progress=0;
1927  image_view=AcquireVirtualCacheView(image,exception);
1928  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  for (y=0; y < (ssize_t) image->rows; y++)
1934  {
1935  PixelInfo
1936  pixel;
1937 
1938  const Quantum
1939  *magick_restrict p;
1940 
1941  Quantum
1942  *magick_restrict q;
1943 
1944  ssize_t
1945  x;
1946 
1947  ssize_t
1948  center;
1949 
1950  if (status == MagickFalse)
1951  continue;
1952  p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1953  q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1954  exception);
1955  if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
1956  {
1957  status=MagickFalse;
1958  continue;
1959  }
1960  center=(ssize_t) GetPixelChannels(image)*(2*((ssize_t) image->columns+4)+2);
1961  GetPixelInfo(image,&pixel);
1962  for (x=0; x < (ssize_t) image->columns; x++)
1963  {
1964  double
1965  distance,
1966  distance_squared,
1967  mean,
1968  total_weight;
1969 
1970  PixelInfo
1971  aggregate;
1972 
1973  const Quantum
1974  *magick_restrict r;
1975 
1976  GetPixelInfo(image,&aggregate);
1977  total_weight=0.0;
1978  GetPixelInfoPixel(image,p+center,&pixel);
1979  r=p;
1980  EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1981  EnhancePixel(8.0); EnhancePixel(5.0);
1982  r=p+GetPixelChannels(image)*(image->columns+4);
1983  EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1984  EnhancePixel(20.0); EnhancePixel(8.0);
1985  r=p+2*GetPixelChannels(image)*(image->columns+4);
1986  EnhancePixel(10.0); EnhancePixel(40.0); EnhancePixel(80.0);
1987  EnhancePixel(40.0); EnhancePixel(10.0);
1988  r=p+3*GetPixelChannels(image)*(image->columns+4);
1989  EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1990  EnhancePixel(20.0); EnhancePixel(8.0);
1991  r=p+4*GetPixelChannels(image)*(image->columns+4);
1992  EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1993  EnhancePixel(8.0); EnhancePixel(5.0);
1994  if (total_weight > MagickEpsilon)
1995  {
1996  pixel.red=((aggregate.red+total_weight/2.0)/total_weight);
1997  pixel.green=((aggregate.green+total_weight/2.0)/total_weight);
1998  pixel.blue=((aggregate.blue+total_weight/2.0)/total_weight);
1999  pixel.black=((aggregate.black+total_weight/2.0)/total_weight);
2000  pixel.alpha=((aggregate.alpha+total_weight/2.0)/total_weight);
2001  }
2002  SetPixelViaPixelInfo(enhance_image,&pixel,q);
2003  p+=(ptrdiff_t) GetPixelChannels(image);
2004  q+=(ptrdiff_t) GetPixelChannels(enhance_image);
2005  }
2006  if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
2007  status=MagickFalse;
2008  if (image->progress_monitor != (MagickProgressMonitor) NULL)
2009  {
2010  MagickBooleanType
2011  proceed;
2012 
2013 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2014  #pragma omp atomic
2015 #endif
2016  progress++;
2017  proceed=SetImageProgress(image,EnhanceImageTag,progress,image->rows);
2018  if (proceed == MagickFalse)
2019  status=MagickFalse;
2020  }
2021  }
2022  enhance_view=DestroyCacheView(enhance_view);
2023  image_view=DestroyCacheView(image_view);
2024  if (status == MagickFalse)
2025  enhance_image=DestroyImage(enhance_image);
2026  return(enhance_image);
2027 }
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 {
2056 #define EqualizeImageTag "Equalize/Image"
2057 
2058  CacheView
2059  *image_view;
2060 
2061  double
2062  black[2*CompositePixelChannel+1],
2063  *equalize_map,
2064  *histogram,
2065  *map,
2066  white[2*CompositePixelChannel+1];
2067 
2068  MagickBooleanType
2069  status;
2070 
2071  MagickOffsetType
2072  progress;
2073 
2074  ssize_t
2075  i;
2076 
2077  ssize_t
2078  y;
2079 
2080  /*
2081  Allocate and initialize histogram arrays.
2082  */
2083  assert(image != (Image *) NULL);
2084  assert(image->signature == MagickCoreSignature);
2085 #if defined(MAGICKCORE_OPENCL_SUPPORT)
2086  if (AccelerateEqualizeImage(image,exception) != MagickFalse)
2087  return(MagickTrue);
2088 #endif
2089  if (IsEventLogging() != MagickFalse)
2090  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2091  equalize_map=(double *) AcquireQuantumMemory(MaxMap+1UL,MaxPixelChannels*
2092  sizeof(*equalize_map));
2093  histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,MaxPixelChannels*
2094  sizeof(*histogram));
2095  map=(double *) AcquireQuantumMemory(MaxMap+1UL,MaxPixelChannels*sizeof(*map));
2096  if ((equalize_map == (double *) NULL) || (histogram == (double *) NULL) ||
2097  (map == (double *) NULL))
2098  {
2099  if (map != (double *) NULL)
2100  map=(double *) RelinquishMagickMemory(map);
2101  if (histogram != (double *) NULL)
2102  histogram=(double *) RelinquishMagickMemory(histogram);
2103  if (equalize_map != (double *) NULL)
2104  equalize_map=(double *) RelinquishMagickMemory(equalize_map);
2105  ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2106  image->filename);
2107  }
2108  /*
2109  Form histogram.
2110  */
2111  status=MagickTrue;
2112  (void) memset(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
2113  sizeof(*histogram));
2114  image_view=AcquireVirtualCacheView(image,exception);
2115  for (y=0; y < (ssize_t) image->rows; y++)
2116  {
2117  const Quantum
2118  *magick_restrict p;
2119 
2120  ssize_t
2121  x;
2122 
2123  if (status == MagickFalse)
2124  continue;
2125  p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
2126  if (p == (const Quantum *) NULL)
2127  {
2128  status=MagickFalse;
2129  continue;
2130  }
2131  for (x=0; x < (ssize_t) image->columns; x++)
2132  {
2133  for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2134  {
2135  double
2136  intensity;
2137 
2138  intensity=(double) p[i];
2139  if ((image->channel_mask & SyncChannels) != 0)
2140  intensity=GetPixelIntensity(image,p);
2141  histogram[GetPixelChannels(image)*ScaleQuantumToMap(
2142  ClampToQuantum(intensity))+(size_t) i]++;
2143  }
2144  p+=(ptrdiff_t) GetPixelChannels(image);
2145  }
2146  }
2147  image_view=DestroyCacheView(image_view);
2148  /*
2149  Integrate the histogram to get the equalization map.
2150  */
2151  for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2152  {
2153  double
2154  intensity;
2155 
2156  ssize_t
2157  j;
2158 
2159  intensity=0.0;
2160  for (j=0; j <= (ssize_t) MaxMap; j++)
2161  {
2162  intensity+=histogram[(ssize_t) GetPixelChannels(image)*j+i];
2163  map[(ssize_t) GetPixelChannels(image)*j+i]=intensity;
2164  }
2165  }
2166  (void) memset(equalize_map,0,(MaxMap+1)*GetPixelChannels(image)*
2167  sizeof(*equalize_map));
2168  (void) memset(black,0,sizeof(*black));
2169  (void) memset(white,0,sizeof(*white));
2170  for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2171  {
2172  ssize_t
2173  j;
2174 
2175  black[i]=map[i];
2176  white[i]=map[GetPixelChannels(image)*MaxMap+(size_t) i];
2177  if (black[i] != white[i])
2178  for (j=0; j <= (ssize_t) MaxMap; j++)
2179  equalize_map[GetPixelChannels(image)*(size_t) j+(size_t) i]=(double)
2180  ScaleMapToQuantum((double) ((MaxMap*(map[GetPixelChannels(image)*
2181  (size_t) j+(size_t) i]-black[i]))/(white[i]-black[i])));
2182  }
2183  histogram=(double *) RelinquishMagickMemory(histogram);
2184  map=(double *) RelinquishMagickMemory(map);
2185  if (image->storage_class == PseudoClass)
2186  {
2187  ssize_t
2188  j;
2189 
2190  /*
2191  Equalize colormap.
2192  */
2193  for (j=0; j < (ssize_t) image->colors; j++)
2194  {
2195  if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2196  {
2197  PixelChannel channel = GetPixelChannelChannel(image,
2198  RedPixelChannel);
2199  if (black[channel] != white[channel])
2200  image->colormap[j].red=equalize_map[(ssize_t)
2201  GetPixelChannels(image)*ScaleQuantumToMap(
2202  ClampToQuantum(image->colormap[j].red))+channel];
2203  }
2204  if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2205  {
2206  PixelChannel channel = GetPixelChannelChannel(image,
2207  GreenPixelChannel);
2208  if (black[channel] != white[channel])
2209  image->colormap[j].green=equalize_map[(ssize_t)
2210  GetPixelChannels(image)*ScaleQuantumToMap(
2211  ClampToQuantum(image->colormap[j].green))+channel];
2212  }
2213  if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2214  {
2215  PixelChannel channel = GetPixelChannelChannel(image,
2216  BluePixelChannel);
2217  if (black[channel] != white[channel])
2218  image->colormap[j].blue=equalize_map[(ssize_t)
2219  GetPixelChannels(image)*ScaleQuantumToMap(
2220  ClampToQuantum(image->colormap[j].blue))+channel];
2221  }
2222  if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2223  {
2224  PixelChannel channel = GetPixelChannelChannel(image,
2225  AlphaPixelChannel);
2226  if (black[channel] != white[channel])
2227  image->colormap[j].alpha=equalize_map[(ssize_t)
2228  GetPixelChannels(image)*ScaleQuantumToMap(
2229  ClampToQuantum(image->colormap[j].alpha))+channel];
2230  }
2231  }
2232  }
2233  /*
2234  Equalize image.
2235  */
2236  progress=0;
2237  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  for (y=0; y < (ssize_t) image->rows; y++)
2243  {
2244  Quantum
2245  *magick_restrict q;
2246 
2247  ssize_t
2248  x;
2249 
2250  if (status == MagickFalse)
2251  continue;
2252  q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2253  if (q == (Quantum *) NULL)
2254  {
2255  status=MagickFalse;
2256  continue;
2257  }
2258  for (x=0; x < (ssize_t) image->columns; x++)
2259  {
2260  ssize_t
2261  j;
2262 
2263  for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
2264  {
2265  PixelChannel channel = GetPixelChannelChannel(image,j);
2266  PixelTrait traits = GetPixelChannelTraits(image,channel);
2267  if (((traits & UpdatePixelTrait) == 0) || (black[j] == white[j]))
2268  continue;
2269  q[j]=ClampToQuantum(equalize_map[GetPixelChannels(image)*
2270  ScaleQuantumToMap(q[j])+(size_t) j]);
2271  }
2272  q+=(ptrdiff_t) GetPixelChannels(image);
2273  }
2274  if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2275  status=MagickFalse;
2276  if (image->progress_monitor != (MagickProgressMonitor) NULL)
2277  {
2278  MagickBooleanType
2279  proceed;
2280 
2281 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2282  #pragma omp atomic
2283 #endif
2284  progress++;
2285  proceed=SetImageProgress(image,EqualizeImageTag,progress,image->rows);
2286  if (proceed == MagickFalse)
2287  status=MagickFalse;
2288  }
2289  }
2290  image_view=DestroyCacheView(image_view);
2291  equalize_map=(double *) RelinquishMagickMemory(equalize_map);
2292  return(status);
2293 }
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 {
2332  return(value < 0.0 ? value : pow(value,gamma));
2333 }
2334 
2335 MagickExport MagickBooleanType GammaImage(Image *image,const double gamma,
2336  ExceptionInfo *exception)
2337 {
2338 #define GammaImageTag "Gamma/Image"
2339 
2340  CacheView
2341  *image_view;
2342 
2343  MagickBooleanType
2344  status;
2345 
2346  MagickOffsetType
2347  progress;
2348 
2349  Quantum
2350  *gamma_map;
2351 
2352  ssize_t
2353  i;
2354 
2355  ssize_t
2356  y;
2357 
2358  /*
2359  Allocate and initialize gamma maps.
2360  */
2361  assert(image != (Image *) NULL);
2362  assert(image->signature == MagickCoreSignature);
2363  if (IsEventLogging() != MagickFalse)
2364  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2365  if (gamma == 1.0)
2366  return(MagickTrue);
2367  gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
2368  if (gamma_map == (Quantum *) NULL)
2369  ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2370  image->filename);
2371  (void) memset(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
2372  if (gamma != 0.0)
2373  for (i=0; i <= (ssize_t) MaxMap; i++)
2374  gamma_map[i]=ScaleMapToQuantum((double) (MaxMap*pow((double) i/
2375  MaxMap,MagickSafeReciprocal(gamma))));
2376  if (image->storage_class == PseudoClass)
2377  for (i=0; i < (ssize_t) image->colors; i++)
2378  {
2379  /*
2380  Gamma-correct colormap.
2381  */
2382  if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2383  image->colormap[i].red=(double) gamma_map[ScaleQuantumToMap(
2384  ClampToQuantum(image->colormap[i].red))];
2385  if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2386  image->colormap[i].green=(double) gamma_map[ScaleQuantumToMap(
2387  ClampToQuantum(image->colormap[i].green))];
2388  if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2389  image->colormap[i].blue=(double) gamma_map[ScaleQuantumToMap(
2390  ClampToQuantum(image->colormap[i].blue))];
2391  if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2392  image->colormap[i].alpha=(double) gamma_map[ScaleQuantumToMap(
2393  ClampToQuantum(image->colormap[i].alpha))];
2394  }
2395  /*
2396  Gamma-correct image.
2397  */
2398  status=MagickTrue;
2399  progress=0;
2400  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  for (y=0; y < (ssize_t) image->rows; y++)
2406  {
2407  Quantum
2408  *magick_restrict q;
2409 
2410  ssize_t
2411  x;
2412 
2413  if (status == MagickFalse)
2414  continue;
2415  q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2416  if (q == (Quantum *) NULL)
2417  {
2418  status=MagickFalse;
2419  continue;
2420  }
2421  for (x=0; x < (ssize_t) image->columns; x++)
2422  {
2423  ssize_t
2424  j;
2425 
2426  for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
2427  {
2428  PixelChannel channel = GetPixelChannelChannel(image,j);
2429  PixelTrait traits = GetPixelChannelTraits(image,channel);
2430  if ((traits & UpdatePixelTrait) == 0)
2431  continue;
2432  q[j]=gamma_map[ScaleQuantumToMap(ClampToQuantum((MagickRealType)
2433  q[j]))];
2434  }
2435  q+=(ptrdiff_t) GetPixelChannels(image);
2436  }
2437  if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2438  status=MagickFalse;
2439  if (image->progress_monitor != (MagickProgressMonitor) NULL)
2440  {
2441  MagickBooleanType
2442  proceed;
2443 
2444 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2445  #pragma omp atomic
2446 #endif
2447  progress++;
2448  proceed=SetImageProgress(image,GammaImageTag,progress,image->rows);
2449  if (proceed == MagickFalse)
2450  status=MagickFalse;
2451  }
2452  }
2453  image_view=DestroyCacheView(image_view);
2454  gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
2455  if (image->gamma != 0.0)
2456  image->gamma*=gamma;
2457  return(status);
2458 }
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 {
2490 #define GrayscaleImageTag "Grayscale/Image"
2491 
2492  CacheView
2493  *image_view;
2494 
2495  MagickBooleanType
2496  status;
2497 
2498  MagickOffsetType
2499  progress;
2500 
2501  ssize_t
2502  y;
2503 
2504  assert(image != (Image *) NULL);
2505  assert(image->signature == MagickCoreSignature);
2506  if (IsEventLogging() != MagickFalse)
2507  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2508  if (image->storage_class == PseudoClass)
2509  {
2510  if (SyncImage(image,exception) == MagickFalse)
2511  return(MagickFalse);
2512  if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
2513  return(MagickFalse);
2514  }
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  status=MagickTrue;
2530  progress=0;
2531  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  for (y=0; y < (ssize_t) image->rows; y++)
2537  {
2538  Quantum
2539  *magick_restrict q;
2540 
2541  ssize_t
2542  x;
2543 
2544  if (status == MagickFalse)
2545  continue;
2546  q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2547  if (q == (Quantum *) NULL)
2548  {
2549  status=MagickFalse;
2550  continue;
2551  }
2552  for (x=0; x < (ssize_t) image->columns; x++)
2553  {
2554  MagickRealType
2555  blue,
2556  green,
2557  red,
2558  intensity;
2559 
2560  red=(MagickRealType) GetPixelRed(image,q);
2561  green=(MagickRealType) GetPixelGreen(image,q);
2562  blue=(MagickRealType) GetPixelBlue(image,q);
2563  intensity=0.0;
2564  switch (method)
2565  {
2566  case AveragePixelIntensityMethod:
2567  {
2568  intensity=(red+green+blue)/3.0;
2569  break;
2570  }
2571  case BrightnessPixelIntensityMethod:
2572  {
2573  intensity=MagickMax(MagickMax(red,green),blue);
2574  break;
2575  }
2576  case LightnessPixelIntensityMethod:
2577  {
2578  intensity=(MagickMin(MagickMin(red,green),blue)+
2579  MagickMax(MagickMax(red,green),blue))/2.0;
2580  break;
2581  }
2582  case MSPixelIntensityMethod:
2583  {
2584  intensity=(MagickRealType) (((double) red*red+green*green+
2585  blue*blue)/3.0);
2586  break;
2587  }
2588  case Rec601LumaPixelIntensityMethod:
2589  {
2590  if (image->colorspace == RGBColorspace)
2591  {
2592  red=EncodePixelGamma(red);
2593  green=EncodePixelGamma(green);
2594  blue=EncodePixelGamma(blue);
2595  }
2596  intensity=0.298839*red+0.586811*green+0.114350*blue;
2597  break;
2598  }
2599  case Rec601LuminancePixelIntensityMethod:
2600  {
2601  if (image->colorspace == sRGBColorspace)
2602  {
2603  red=DecodePixelGamma(red);
2604  green=DecodePixelGamma(green);
2605  blue=DecodePixelGamma(blue);
2606  }
2607  intensity=0.298839*red+0.586811*green+0.114350*blue;
2608  break;
2609  }
2610  case Rec709LumaPixelIntensityMethod:
2611  default:
2612  {
2613  if (image->colorspace == RGBColorspace)
2614  {
2615  red=EncodePixelGamma(red);
2616  green=EncodePixelGamma(green);
2617  blue=EncodePixelGamma(blue);
2618  }
2619  intensity=0.212656*red+0.715158*green+0.072186*blue;
2620  break;
2621  }
2622  case Rec709LuminancePixelIntensityMethod:
2623  {
2624  if (image->colorspace == sRGBColorspace)
2625  {
2626  red=DecodePixelGamma(red);
2627  green=DecodePixelGamma(green);
2628  blue=DecodePixelGamma(blue);
2629  }
2630  intensity=0.212656*red+0.715158*green+0.072186*blue;
2631  break;
2632  }
2633  case RMSPixelIntensityMethod:
2634  {
2635  intensity=(MagickRealType) (sqrt((double) red*red+green*green+
2636  blue*blue)/sqrt(3.0));
2637  break;
2638  }
2639  }
2640  SetPixelGray(image,ClampToQuantum(intensity),q);
2641  q+=(ptrdiff_t) GetPixelChannels(image);
2642  }
2643  if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2644  status=MagickFalse;
2645  if (image->progress_monitor != (MagickProgressMonitor) NULL)
2646  {
2647  MagickBooleanType
2648  proceed;
2649 
2650 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2651  #pragma omp atomic
2652 #endif
2653  progress++;
2654  proceed=SetImageProgress(image,GrayscaleImageTag,progress,image->rows);
2655  if (proceed == MagickFalse)
2656  status=MagickFalse;
2657  }
2658  }
2659  image_view=DestroyCacheView(image_view);
2660  image->intensity=method;
2661  image->type=GrayscaleType;
2662  if ((method == Rec601LuminancePixelIntensityMethod) ||
2663  (method == Rec709LuminancePixelIntensityMethod))
2664  return(SetImageColorspace(image,LinearGRAYColorspace,exception));
2665  return(SetImageColorspace(image,GRAYColorspace,exception));
2666 }
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 {
2702 #define HaldClutImageTag "Clut/Image"
2703 
2704  typedef struct _HaldInfo
2705  {
2706  double
2707  x,
2708  y,
2709  z;
2710  } HaldInfo;
2711 
2712  CacheView
2713  *hald_view,
2714  *image_view;
2715 
2716  double
2717  width;
2718 
2719  MagickBooleanType
2720  status;
2721 
2722  MagickOffsetType
2723  progress;
2724 
2725  PixelInfo
2726  zero;
2727 
2728  size_t
2729  cube_size,
2730  length,
2731  level;
2732 
2733  ssize_t
2734  y;
2735 
2736  assert(image != (Image *) NULL);
2737  assert(image->signature == MagickCoreSignature);
2738  if (IsEventLogging() != MagickFalse)
2739  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2740  assert(hald_image != (Image *) NULL);
2741  assert(hald_image->signature == MagickCoreSignature);
2742  if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
2743  return(MagickFalse);
2744  if ((image->alpha_trait & BlendPixelTrait) == 0)
2745  (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
2746  if (image->colorspace != hald_image->colorspace)
2747  (void) SetImageColorspace(image,hald_image->colorspace,exception);
2748  /*
2749  Hald clut image.
2750  */
2751  status=MagickTrue;
2752  progress=0;
2753  length=(size_t) MagickMin((MagickRealType) hald_image->columns,
2754  (MagickRealType) hald_image->rows);
2755  for (level=2; (level*level*level) < length; level++) ;
2756  level*=level;
2757  cube_size=level*level;
2758  width=(double) hald_image->columns;
2759  GetPixelInfo(hald_image,&zero);
2760  hald_view=AcquireVirtualCacheView(hald_image,exception);
2761  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  for (y=0; y < (ssize_t) image->rows; y++)
2767  {
2768  Quantum
2769  *magick_restrict q;
2770 
2771  ssize_t
2772  x;
2773 
2774  if (status == MagickFalse)
2775  continue;
2776  q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2777  if (q == (Quantum *) NULL)
2778  {
2779  status=MagickFalse;
2780  continue;
2781  }
2782  for (x=0; x < (ssize_t) image->columns; x++)
2783  {
2784  double
2785  area = 0.0,
2786  offset = 0.0;
2787 
2788  HaldInfo
2789  point = { 0, 0, 0 };
2790 
2791  PixelInfo
2792  pixel = zero,
2793  pixel1 = zero,
2794  pixel2 = zero,
2795  pixel3 = zero,
2796  pixel4 = zero;
2797 
2798  point.x=QuantumScale*(level-1.0)*(double) GetPixelRed(image,q);
2799  point.y=QuantumScale*(level-1.0)*(double) GetPixelGreen(image,q);
2800  point.z=QuantumScale*(level-1.0)*(double) GetPixelBlue(image,q);
2801  offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2802  point.x-=floor(point.x);
2803  point.y-=floor(point.y);
2804  point.z-=floor(point.z);
2805  status=InterpolatePixelInfo(hald_image,hald_view,hald_image->interpolate,
2806  fmod(offset,width),floor(offset/width),&pixel1,exception);
2807  if (status == MagickFalse)
2808  break;
2809  status=InterpolatePixelInfo(hald_image,hald_view,hald_image->interpolate,
2810  fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2811  if (status == MagickFalse)
2812  break;
2813  area=point.y;
2814  if (hald_image->interpolate == NearestInterpolatePixel)
2815  area=(point.y < 0.5) ? 0.0 : 1.0;
2816  CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2817  area,&pixel3);
2818  offset+=cube_size;
2819  status=InterpolatePixelInfo(hald_image,hald_view,hald_image->interpolate,
2820  fmod(offset,width),floor(offset/width),&pixel1,exception);
2821  if (status == MagickFalse)
2822  break;
2823  status=InterpolatePixelInfo(hald_image,hald_view,hald_image->interpolate,
2824  fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2825  if (status == MagickFalse)
2826  break;
2827  CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2828  area,&pixel4);
2829  area=point.z;
2830  if (hald_image->interpolate == NearestInterpolatePixel)
2831  area=(point.z < 0.5)? 0.0 : 1.0;
2832  CompositePixelInfoAreaBlend(&pixel3,pixel3.alpha,&pixel4,pixel4.alpha,
2833  area,&pixel);
2834  if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2835  SetPixelRed(image,ClampToQuantum(pixel.red),q);
2836  if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2837  SetPixelGreen(image,ClampToQuantum(pixel.green),q);
2838  if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2839  SetPixelBlue(image,ClampToQuantum(pixel.blue),q);
2840  if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
2841  (image->colorspace == CMYKColorspace))
2842  SetPixelBlack(image,ClampToQuantum(pixel.black),q);
2843  if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2844  (image->alpha_trait != UndefinedPixelTrait))
2845  SetPixelAlpha(image,ClampToQuantum(pixel.alpha),q);
2846  q+=(ptrdiff_t) GetPixelChannels(image);
2847  }
2848  if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2849  status=MagickFalse;
2850  if (image->progress_monitor != (MagickProgressMonitor) NULL)
2851  {
2852  MagickBooleanType
2853  proceed;
2854 
2855 #if defined(MAGICKCORE_OPENMP_SUPPORT)
2856  #pragma omp atomic
2857 #endif
2858  progress++;
2859  proceed=SetImageProgress(image,HaldClutImageTag,progress,image->rows);
2860  if (proceed == MagickFalse)
2861  status=MagickFalse;
2862  }
2863  }
2864  hald_view=DestroyCacheView(hald_view);
2865  image_view=DestroyCacheView(image_view);
2866  return(status);
2867 }
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 {
2916  double
2917  level_pixel,
2918  scale;
2919 
2920  scale=MagickSafeReciprocal(white_point-black_point);
2921  level_pixel=(double) QuantumRange*gamma_pow(scale*((double) pixel-(double)
2922  black_point),MagickSafeReciprocal(gamma));
2923  return(level_pixel);
2924 }
2925 
2926 MagickExport MagickBooleanType LevelImage(Image *image,const double black_point,
2927  const double white_point,const double gamma,ExceptionInfo *exception)
2928 {
2929 #define LevelImageTag "Level/Image"
2930 
2931  CacheView
2932  *image_view;
2933 
2934  MagickBooleanType
2935  status;
2936 
2937  MagickOffsetType
2938  progress;
2939 
2940  ssize_t
2941  i,
2942  y;
2943 
2944  /*
2945  Allocate and initialize levels map.
2946  */
2947  assert(image != (Image *) NULL);
2948  assert(image->signature == MagickCoreSignature);
2949  if (IsEventLogging() != MagickFalse)
2950  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2951  if (image->storage_class == PseudoClass)
2952  for (i=0; i < (ssize_t) image->colors; i++)
2953  {
2954  /*
2955  Level colormap.
2956  */
2957  if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
2958  image->colormap[i].red=(double) ClampToQuantum(LevelPixel(black_point,
2959  white_point,gamma,image->colormap[i].red));
2960  if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
2961  image->colormap[i].green=(double) ClampToQuantum(LevelPixel(black_point,
2962  white_point,gamma,image->colormap[i].green));
2963  if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
2964  image->colormap[i].blue=(double) ClampToQuantum(LevelPixel(black_point,
2965  white_point,gamma,image->colormap[i].blue));
2966  if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
2967  image->colormap[i].alpha=(double) ClampToQuantum(LevelPixel(black_point,
2968  white_point,gamma,image->colormap[i].alpha));
2969  }
2970  /*
2971  Level image.
2972  */
2973  status=MagickTrue;
2974  progress=0;
2975  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  for (y=0; y < (ssize_t) image->rows; y++)
2981  {
2982  Quantum
2983  *magick_restrict q;
2984 
2985  ssize_t
2986  x;
2987 
2988  if (status == MagickFalse)
2989  continue;
2990  q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2991  if (q == (Quantum *) NULL)
2992  {
2993  status=MagickFalse;
2994  continue;
2995  }
2996  for (x=0; x < (ssize_t) image->columns; x++)
2997  {
2998  ssize_t
2999  j;
3000 
3001  for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
3002  {
3003  PixelChannel channel = GetPixelChannelChannel(image,j);
3004  PixelTrait traits = GetPixelChannelTraits(image,channel);
3005  if ((traits & UpdatePixelTrait) == 0)
3006  continue;
3007  q[j]=ClampToQuantum(LevelPixel(black_point,white_point,gamma,
3008  (double) q[j]));
3009  }
3010  q+=(ptrdiff_t) GetPixelChannels(image);
3011  }
3012  if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3013  status=MagickFalse;
3014  if (image->progress_monitor != (MagickProgressMonitor) NULL)
3015  {
3016  MagickBooleanType
3017  proceed;
3018 
3019 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3020  #pragma omp atomic
3021 #endif
3022  progress++;
3023  proceed=SetImageProgress(image,LevelImageTag,progress,image->rows);
3024  if (proceed == MagickFalse)
3025  status=MagickFalse;
3026  }
3027  }
3028  image_view=DestroyCacheView(image_view);
3029  (void) ClampImage(image,exception);
3030  return(status);
3031 }
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 {
3079 #define LevelizeImageTag "Levelize/Image"
3080 #define LevelizeValue(x) ClampToQuantum(((MagickRealType) gamma_pow((double) \
3081  (QuantumScale*((double) x)),gamma))*(white_point-black_point)+black_point)
3082 
3083  CacheView
3084  *image_view;
3085 
3086  MagickBooleanType
3087  status;
3088 
3089  MagickOffsetType
3090  progress;
3091 
3092  ssize_t
3093  i;
3094 
3095  ssize_t
3096  y;
3097 
3098  /*
3099  Allocate and initialize levels map.
3100  */
3101  assert(image != (Image *) NULL);
3102  assert(image->signature == MagickCoreSignature);
3103  if (IsEventLogging() != MagickFalse)
3104  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3105  if (image->storage_class == PseudoClass)
3106  for (i=0; i < (ssize_t) image->colors; i++)
3107  {
3108  /*
3109  Level colormap.
3110  */
3111  if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3112  image->colormap[i].red=(double) LevelizeValue(image->colormap[i].red);
3113  if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3114  image->colormap[i].green=(double) LevelizeValue(
3115  image->colormap[i].green);
3116  if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3117  image->colormap[i].blue=(double) LevelizeValue(image->colormap[i].blue);
3118  if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
3119  image->colormap[i].alpha=(double) LevelizeValue(
3120  image->colormap[i].alpha);
3121  }
3122  /*
3123  Level image.
3124  */
3125  status=MagickTrue;
3126  progress=0;
3127  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  for (y=0; y < (ssize_t) image->rows; y++)
3133  {
3134  Quantum
3135  *magick_restrict q;
3136 
3137  ssize_t
3138  x;
3139 
3140  if (status == MagickFalse)
3141  continue;
3142  q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3143  if (q == (Quantum *) NULL)
3144  {
3145  status=MagickFalse;
3146  continue;
3147  }
3148  for (x=0; x < (ssize_t) image->columns; x++)
3149  {
3150  ssize_t
3151  j;
3152 
3153  for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
3154  {
3155  PixelChannel channel = GetPixelChannelChannel(image,j);
3156  PixelTrait traits = GetPixelChannelTraits(image,channel);
3157  if ((traits & UpdatePixelTrait) == 0)
3158  continue;
3159  q[j]=LevelizeValue(q[j]);
3160  }
3161  q+=(ptrdiff_t) GetPixelChannels(image);
3162  }
3163  if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3164  status=MagickFalse;
3165  if (image->progress_monitor != (MagickProgressMonitor) NULL)
3166  {
3167  MagickBooleanType
3168  proceed;
3169 
3170 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3171  #pragma omp atomic
3172 #endif
3173  progress++;
3174  proceed=SetImageProgress(image,LevelizeImageTag,progress,image->rows);
3175  if (proceed == MagickFalse)
3176  status=MagickFalse;
3177  }
3178  }
3179  image_view=DestroyCacheView(image_view);
3180  return(status);
3181 }
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 {
3228  ChannelType
3229  channel_mask;
3230 
3231  MagickStatusType
3232  status;
3233 
3234  /*
3235  Allocate and initialize levels map.
3236  */
3237  assert(image != (Image *) NULL);
3238  assert(image->signature == MagickCoreSignature);
3239  if (IsEventLogging() != MagickFalse)
3240  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3241  if ((IsGrayColorspace(image->colorspace) != MagickFalse) &&
3242  ((IsGrayColorspace(black_color->colorspace) == MagickFalse) ||
3243  (IsGrayColorspace(white_color->colorspace) == MagickFalse)))
3244  (void) SetImageColorspace(image,sRGBColorspace,exception);
3245  status=MagickTrue;
3246  if (invert == MagickFalse)
3247  {
3248  if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3249  {
3250  channel_mask=SetImageChannelMask(image,RedChannel);
3251  status&=(MagickStatusType) LevelImage(image,black_color->red,
3252  white_color->red,1.0,exception);
3253  (void) SetImageChannelMask(image,channel_mask);
3254  }
3255  if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3256  {
3257  channel_mask=SetImageChannelMask(image,GreenChannel);
3258  status&=(MagickStatusType) LevelImage(image,black_color->green,
3259  white_color->green,1.0,exception);
3260  (void) SetImageChannelMask(image,channel_mask);
3261  }
3262  if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3263  {
3264  channel_mask=SetImageChannelMask(image,BlueChannel);
3265  status&=(MagickStatusType) LevelImage(image,black_color->blue,
3266  white_color->blue,1.0,exception);
3267  (void) SetImageChannelMask(image,channel_mask);
3268  }
3269  if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
3270  (image->colorspace == CMYKColorspace))
3271  {
3272  channel_mask=SetImageChannelMask(image,BlackChannel);
3273  status&=(MagickStatusType) LevelImage(image,black_color->black,
3274  white_color->black,1.0,exception);
3275  (void) SetImageChannelMask(image,channel_mask);
3276  }
3277  if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
3278  (image->alpha_trait != UndefinedPixelTrait))
3279  {
3280  channel_mask=SetImageChannelMask(image,AlphaChannel);
3281  status&=(MagickStatusType) LevelImage(image,black_color->alpha,
3282  white_color->alpha,1.0,exception);
3283  (void) SetImageChannelMask(image,channel_mask);
3284  }
3285  }
3286  else
3287  {
3288  if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3289  {
3290  channel_mask=SetImageChannelMask(image,RedChannel);
3291  status&=(MagickStatusType) LevelizeImage(image,black_color->red,
3292  white_color->red,1.0,exception);
3293  (void) SetImageChannelMask(image,channel_mask);
3294  }
3295  if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3296  {
3297  channel_mask=SetImageChannelMask(image,GreenChannel);
3298  status&=(MagickStatusType) LevelizeImage(image,black_color->green,
3299  white_color->green,1.0,exception);
3300  (void) SetImageChannelMask(image,channel_mask);
3301  }
3302  if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3303  {
3304  channel_mask=SetImageChannelMask(image,BlueChannel);
3305  status&=(MagickStatusType) LevelizeImage(image,black_color->blue,
3306  white_color->blue,1.0,exception);
3307  (void) SetImageChannelMask(image,channel_mask);
3308  }
3309  if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
3310  (image->colorspace == CMYKColorspace))
3311  {
3312  channel_mask=SetImageChannelMask(image,BlackChannel);
3313  status&=(MagickStatusType) LevelizeImage(image,black_color->black,
3314  white_color->black,1.0,exception);
3315  (void) SetImageChannelMask(image,channel_mask);
3316  }
3317  if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
3318  (image->alpha_trait != UndefinedPixelTrait))
3319  {
3320  channel_mask=SetImageChannelMask(image,AlphaChannel);
3321  status&=(MagickStatusType) LevelizeImage(image,black_color->alpha,
3322  white_color->alpha,1.0,exception);
3323  (void) SetImageChannelMask(image,channel_mask);
3324  }
3325  }
3326  return(status != 0 ? MagickTrue : MagickFalse);
3327 }
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 {
3363 #define LinearStretchImageTag "LinearStretch/Image"
3364 
3365  CacheView
3366  *image_view;
3367 
3368  char
3369  property[MagickPathExtent];
3370 
3371  double
3372  *histogram,
3373  intensity;
3374 
3375  MagickBooleanType
3376  status;
3377 
3378  ssize_t
3379  black,
3380  white,
3381  y;
3382 
3383  /*
3384  Allocate histogram and linear map.
3385  */
3386  assert(image != (Image *) NULL);
3387  assert(image->signature == MagickCoreSignature);
3388  histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*histogram));
3389  if (histogram == (double *) NULL)
3390  ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3391  image->filename);
3392  /*
3393  Form histogram.
3394  */
3395  (void) memset(histogram,0,(MaxMap+1)*sizeof(*histogram));
3396  image_view=AcquireVirtualCacheView(image,exception);
3397  for (y=0; y < (ssize_t) image->rows; y++)
3398  {
3399  const Quantum
3400  *magick_restrict p;
3401 
3402  ssize_t
3403  x;
3404 
3405  p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
3406  if (p == (const Quantum *) NULL)
3407  break;
3408  for (x=0; x < (ssize_t) image->columns; x++)
3409  {
3410  intensity=GetPixelIntensity(image,p);
3411  histogram[ScaleQuantumToMap(ClampToQuantum(intensity))]++;
3412  p+=(ptrdiff_t) GetPixelChannels(image);
3413  }
3414  }
3415  image_view=DestroyCacheView(image_view);
3416  /*
3417  Find the histogram boundaries by locating the black and white point levels.
3418  */
3419  intensity=0.0;
3420  for (black=0; black < (ssize_t) MaxMap; black++)
3421  {
3422  intensity+=histogram[black];
3423  if (intensity >= black_point)
3424  break;
3425  }
3426  intensity=0.0;
3427  for (white=(ssize_t) MaxMap; white != 0; white--)
3428  {
3429  intensity+=histogram[white];
3430  if (intensity >= white_point)
3431  break;
3432  }
3433  histogram=(double *) RelinquishMagickMemory(histogram);
3434  status=LevelImage(image,(double) ScaleMapToQuantum((MagickRealType) black),
3435  (double) ScaleMapToQuantum((MagickRealType) white),1.0,exception);
3436  (void) FormatLocaleString(property,MagickPathExtent,"%gx%g%%",100.0*black/
3437  MaxMap,100.0*white/MaxMap);
3438  (void) SetImageProperty(image,"histogram:linear-stretch",property,exception);
3439  return(status);
3440 }
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 {
3478  double
3479  hue,
3480  luma,
3481  chroma;
3482 
3483  /*
3484  Increase or decrease color luma, chroma, or hue.
3485  */
3486  ConvertRGBToHCL(*red,*green,*blue,&hue,&chroma,&luma);
3487  hue+=fmod((percent_hue-100.0),200.0)/200.0;
3488  chroma*=0.01*percent_chroma;
3489  luma*=0.01*percent_luma;
3490  ConvertHCLToRGB(hue,chroma,luma,red,green,blue);
3491 }
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 {
3497  double
3498  hue,
3499  luma,
3500  chroma;
3501 
3502  /*
3503  Increase or decrease color luma, chroma, or hue.
3504  */
3505  ConvertRGBToHCLp(*red,*green,*blue,&hue,&chroma,&luma);
3506  hue+=fmod((percent_hue-100.0),200.0)/200.0;
3507  chroma*=0.01*percent_chroma;
3508  luma*=0.01*percent_luma;
3509  ConvertHCLpToRGB(hue,chroma,luma,red,green,blue);
3510 }
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 {
3516  double
3517  brightness,
3518  hue,
3519  saturation;
3520 
3521  /*
3522  Increase or decrease color brightness, saturation, or hue.
3523  */
3524  ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
3525  hue+=fmod((percent_hue-100.0),200.0)/200.0;
3526  saturation*=0.01*percent_saturation;
3527  brightness*=0.01*percent_brightness;
3528  ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
3529 }
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 {
3535  double
3536  intensity,
3537  hue,
3538  saturation;
3539 
3540  /*
3541  Increase or decrease color intensity, saturation, or hue.
3542  */
3543  ConvertRGBToHSI(*red,*green,*blue,&hue,&saturation,&intensity);
3544  hue+=fmod((percent_hue-100.0),200.0)/200.0;
3545  saturation*=0.01*percent_saturation;
3546  intensity*=0.01*percent_intensity;
3547  ConvertHSIToRGB(hue,saturation,intensity,red,green,blue);
3548 }
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 {
3554  double
3555  hue,
3556  lightness,
3557  saturation;
3558 
3559  /*
3560  Increase or decrease color lightness, saturation, or hue.
3561  */
3562  ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
3563  hue+=fmod((percent_hue-100.0),200.0)/200.0;
3564  saturation*=0.01*percent_saturation;
3565  lightness*=0.01*percent_lightness;
3566  ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
3567 }
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 {
3573  double
3574  hue,
3575  saturation,
3576  value;
3577 
3578  /*
3579  Increase or decrease color value, saturation, or hue.
3580  */
3581  ConvertRGBToHSV(*red,*green,*blue,&hue,&saturation,&value);
3582  hue+=fmod((percent_hue-100.0),200.0)/200.0;
3583  saturation*=0.01*percent_saturation;
3584  value*=0.01*percent_value;
3585  ConvertHSVToRGB(hue,saturation,value,red,green,blue);
3586 }
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 {
3592  double
3593  blackness,
3594  hue,
3595  whiteness;
3596 
3597  /*
3598  Increase or decrease color blackness, whiteness, or hue.
3599  */
3600  ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
3601  hue+=fmod((percent_hue-100.0),200.0)/200.0;
3602  blackness*=0.01*percent_blackness;
3603  whiteness*=0.01*percent_whiteness;
3604  ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
3605 }
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 {
3611  double
3612  hue,
3613  luma,
3614  chroma;
3615 
3616  /*
3617  Increase or decrease color luma, chroma, or hue.
3618  */
3619  ConvertRGBToLCHab(*red,*green,*blue,illuminant,&luma,&chroma,&hue);
3620  luma*=0.01*percent_luma;
3621  chroma*=0.01*percent_chroma;
3622  hue+=fmod((percent_hue-100.0),200.0)/200.0;
3623  ConvertLCHabToRGB(luma,chroma,hue,illuminant,red,green,blue);
3624 }
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 {
3630  double
3631  hue,
3632  luma,
3633  chroma;
3634 
3635  /*
3636  Increase or decrease color luma, chroma, or hue.
3637  */
3638  ConvertRGBToLCHuv(*red,*green,*blue,illuminant,&luma,&chroma,&hue);
3639  luma*=0.01*percent_luma;
3640  chroma*=0.01*percent_chroma;
3641  hue+=fmod((percent_hue-100.0),200.0)/200.0;
3642  ConvertLCHuvToRGB(luma,chroma,hue,illuminant,red,green,blue);
3643 }
3644 
3645 MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate,
3646  ExceptionInfo *exception)
3647 {
3648 #define ModulateImageTag "Modulate/Image"
3649 
3650  CacheView
3651  *image_view;
3652 
3653  ColorspaceType
3654  colorspace = UndefinedColorspace;
3655 
3656  const char
3657  *artifact;
3658 
3659  double
3660  percent_brightness = 100.0,
3661  percent_hue = 100.0,
3662  percent_saturation = 100.0;
3663 
3664  GeometryInfo
3665  geometry_info;
3666 
3667  IlluminantType
3668  illuminant = D65Illuminant;
3669 
3670  MagickBooleanType
3671  status;
3672 
3673  MagickOffsetType
3674  progress;
3675 
3676  MagickStatusType
3677  flags;
3678 
3679  ssize_t
3680  i;
3681 
3682  ssize_t
3683  y;
3684 
3685  /*
3686  Initialize modulate table.
3687  */
3688  assert(image != (Image *) NULL);
3689  assert(image->signature == MagickCoreSignature);
3690  if (IsEventLogging() != MagickFalse)
3691  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3692  if (modulate == (char *) NULL)
3693  return(MagickFalse);
3694  if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
3695  (void) SetImageColorspace(image,sRGBColorspace,exception);
3696  flags=ParseGeometry(modulate,&geometry_info);
3697  if ((flags & RhoValue) != 0)
3698  percent_brightness=geometry_info.rho;
3699  if ((flags & SigmaValue) != 0)
3700  percent_saturation=geometry_info.sigma;
3701  if ((flags & XiValue) != 0)
3702  percent_hue=geometry_info.xi;
3703  artifact=GetImageArtifact(image,"modulate:colorspace");
3704  if (artifact != (const char *) NULL)
3705  colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions,
3706  MagickFalse,artifact);
3707  artifact=GetImageArtifact(image,"color:illuminant");
3708  if (artifact != (const char *) NULL)
3709  {
3710  ssize_t
3711  illuminant_type;
3712 
3713  illuminant_type=ParseCommandOption(MagickIlluminantOptions,MagickFalse,
3714  artifact);
3715  if (illuminant_type < 0)
3716  {
3717  illuminant=UndefinedIlluminant;
3718  colorspace=UndefinedColorspace;
3719  }
3720  else
3721  illuminant=(IlluminantType) illuminant_type;
3722  }
3723  if (image->storage_class == PseudoClass)
3724  for (i=0; i < (ssize_t) image->colors; i++)
3725  {
3726  double
3727  blue,
3728  green,
3729  red;
3730 
3731  /*
3732  Modulate image colormap.
3733  */
3734  red=(double) image->colormap[i].red;
3735  green=(double) image->colormap[i].green;
3736  blue=(double) image->colormap[i].blue;
3737  switch (colorspace)
3738  {
3739  case HCLColorspace:
3740  {
3741  ModulateHCL(percent_hue,percent_saturation,percent_brightness,
3742  &red,&green,&blue);
3743  break;
3744  }
3745  case HCLpColorspace:
3746  {
3747  ModulateHCLp(percent_hue,percent_saturation,percent_brightness,
3748  &red,&green,&blue);
3749  break;
3750  }
3751  case HSBColorspace:
3752  {
3753  ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3754  &red,&green,&blue);
3755  break;
3756  }
3757  case HSIColorspace:
3758  {
3759  ModulateHSI(percent_hue,percent_saturation,percent_brightness,
3760  &red,&green,&blue);
3761  break;
3762  }
3763  case HSLColorspace:
3764  default:
3765  {
3766  ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3767  &red,&green,&blue);
3768  break;
3769  }
3770  case HSVColorspace:
3771  {
3772  ModulateHSV(percent_hue,percent_saturation,percent_brightness,
3773  &red,&green,&blue);
3774  break;
3775  }
3776  case HWBColorspace:
3777  {
3778  ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3779  &red,&green,&blue);
3780  break;
3781  }
3782  case LCHColorspace:
3783  case LCHabColorspace:
3784  {
3785  ModulateLCHab(percent_brightness,percent_saturation,percent_hue,
3786  illuminant,&red,&green,&blue);
3787  break;
3788  }
3789  case LCHuvColorspace:
3790  {
3791  ModulateLCHuv(percent_brightness,percent_saturation,percent_hue,
3792  illuminant,&red,&green,&blue);
3793  break;
3794  }
3795  }
3796  image->colormap[i].red=red;
3797  image->colormap[i].green=green;
3798  image->colormap[i].blue=blue;
3799  }
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  status=MagickTrue;
3809  progress=0;
3810  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  for (y=0; y < (ssize_t) image->rows; y++)
3816  {
3817  Quantum
3818  *magick_restrict q;
3819 
3820  ssize_t
3821  x;
3822 
3823  if (status == MagickFalse)
3824  continue;
3825  q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
3826  if (q == (Quantum *) NULL)
3827  {
3828  status=MagickFalse;
3829  continue;
3830  }
3831  for (x=0; x < (ssize_t) image->columns; x++)
3832  {
3833  double
3834  blue,
3835  green,
3836  red;
3837 
3838  red=(double) GetPixelRed(image,q);
3839  green=(double) GetPixelGreen(image,q);
3840  blue=(double) GetPixelBlue(image,q);
3841  switch (colorspace)
3842  {
3843  case HCLColorspace:
3844  {
3845  ModulateHCL(percent_hue,percent_saturation,percent_brightness,
3846  &red,&green,&blue);
3847  break;
3848  }
3849  case HCLpColorspace:
3850  {
3851  ModulateHCLp(percent_hue,percent_saturation,percent_brightness,
3852  &red,&green,&blue);
3853  break;
3854  }
3855  case HSBColorspace:
3856  {
3857  ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3858  &red,&green,&blue);
3859  break;
3860  }
3861  case HSIColorspace:
3862  {
3863  ModulateHSI(percent_hue,percent_saturation,percent_brightness,
3864  &red,&green,&blue);
3865  break;
3866  }
3867  case HSLColorspace:
3868  default:
3869  {
3870  ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3871  &red,&green,&blue);
3872  break;
3873  }
3874  case HSVColorspace:
3875  {
3876  ModulateHSV(percent_hue,percent_saturation,percent_brightness,
3877  &red,&green,&blue);
3878  break;
3879  }
3880  case HWBColorspace:
3881  {
3882  ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3883  &red,&green,&blue);
3884  break;
3885  }
3886  case LCHColorspace:
3887  case LCHabColorspace:
3888  {
3889  ModulateLCHab(percent_brightness,percent_saturation,percent_hue,
3890  illuminant,&red,&green,&blue);
3891  break;
3892  }
3893  case LCHuvColorspace:
3894  {
3895  ModulateLCHuv(percent_brightness,percent_saturation,percent_hue,
3896  illuminant,&red,&green,&blue);
3897  break;
3898  }
3899  }
3900  SetPixelRed(image,ClampToQuantum(red),q);
3901  SetPixelGreen(image,ClampToQuantum(green),q);
3902  SetPixelBlue(image,ClampToQuantum(blue),q);
3903  q+=(ptrdiff_t) GetPixelChannels(image);
3904  }
3905  if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3906  status=MagickFalse;
3907  if (image->progress_monitor != (MagickProgressMonitor) NULL)
3908  {
3909  MagickBooleanType
3910  proceed;
3911 
3912 #if defined(MAGICKCORE_OPENMP_SUPPORT)
3913  #pragma omp atomic
3914 #endif
3915  progress++;
3916  proceed=SetImageProgress(image,ModulateImageTag,progress,image->rows);
3917  if (proceed == MagickFalse)
3918  status=MagickFalse;
3919  }
3920  }
3921  image_view=DestroyCacheView(image_view);
3922  return(status);
3923 }
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 {
3956 #define NegateImageTag "Negate/Image"
3957 
3958  CacheView
3959  *image_view;
3960 
3961  MagickBooleanType
3962  status;
3963 
3964  MagickOffsetType
3965  progress;
3966 
3967  ssize_t
3968  i;
3969 
3970  ssize_t
3971  y;
3972 
3973  assert(image != (Image *) NULL);
3974  assert(image->signature == MagickCoreSignature);
3975  if (IsEventLogging() != MagickFalse)
3976  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3977  if (image->storage_class == PseudoClass)
3978  for (i=0; i < (ssize_t) image->colors; i++)
3979  {
3980  /*
3981  Negate colormap.
3982  */
3983  if (grayscale != MagickFalse)
3984  if ((image->colormap[i].red != image->colormap[i].green) ||
3985  (image->colormap[i].green != image->colormap[i].blue))
3986  continue;
3987  if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3988  image->colormap[i].red=(double) QuantumRange-image->colormap[i].red;
3989  if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3990  image->colormap[i].green=(double) QuantumRange-image->colormap[i].green;
3991  if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3992  image->colormap[i].blue=(double) QuantumRange-image->colormap[i].blue;
3993  }
3994  /*
3995  Negate image.
3996  */
3997  status=MagickTrue;
3998  progress=0;
3999  image_view=AcquireAuthenticCacheView(image,exception);
4000  if( grayscale != MagickFalse )
4001  {
4002  for (y=0; y < (ssize_t) image->rows; y++)
4003  {
4004  MagickBooleanType
4005  sync;
4006 
4007  Quantum
4008  *magick_restrict q;
4009 
4010  ssize_t
4011  x;
4012 
4013  if (status == MagickFalse)
4014  continue;
4015  q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
4016  exception);
4017  if (q == (Quantum *) NULL)
4018  {
4019  status=MagickFalse;
4020  continue;
4021  }
4022  for (x=0; x < (ssize_t) image->columns; x++)
4023  {
4024  ssize_t
4025  j;
4026 
4027  if (IsPixelGray(image,q) == MagickFalse)
4028  {
4029  q+=(ptrdiff_t) GetPixelChannels(image);
4030  continue;
4031  }
4032  for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
4033  {
4034  PixelChannel channel = GetPixelChannelChannel(image,j);
4035  PixelTrait traits = GetPixelChannelTraits(image,channel);
4036  if ((traits & UpdatePixelTrait) == 0)
4037  continue;
4038  q[j]=QuantumRange-q[j];
4039  }
4040  q+=(ptrdiff_t) GetPixelChannels(image);
4041  }
4042  sync=SyncCacheViewAuthenticPixels(image_view,exception);
4043  if (sync == MagickFalse)
4044  status=MagickFalse;
4045  if (image->progress_monitor != (MagickProgressMonitor) NULL)
4046  {
4047  MagickBooleanType
4048  proceed;
4049 
4050  progress++;
4051  proceed=SetImageProgress(image,NegateImageTag,progress,image->rows);
4052  if (proceed == MagickFalse)
4053  status=MagickFalse;
4054  }
4055  }
4056  image_view=DestroyCacheView(image_view);
4057  return(MagickTrue);
4058  }
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  for (y=0; y < (ssize_t) image->rows; y++)
4067  {
4068  Quantum
4069  *magick_restrict q;
4070 
4071  ssize_t
4072  x;
4073 
4074  if (status == MagickFalse)
4075  continue;
4076  q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
4077  if (q == (Quantum *) NULL)
4078  {
4079  status=MagickFalse;
4080  continue;
4081  }
4082  for (x=0; x < (ssize_t) image->columns; x++)
4083  {
4084  ssize_t
4085  j;
4086 
4087  for (j=0; j < (ssize_t) GetPixelChannels(image); j++)
4088  {
4089  PixelChannel channel = GetPixelChannelChannel(image,j);
4090  PixelTrait traits = GetPixelChannelTraits(image,channel);
4091  if ((traits & UpdatePixelTrait) == 0)
4092  continue;
4093  q[j]=QuantumRange-q[j];
4094  }
4095  q+=(ptrdiff_t) GetPixelChannels(image);
4096  }
4097  if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
4098  status=MagickFalse;
4099  if (image->progress_monitor != (MagickProgressMonitor) NULL)
4100  {
4101  MagickBooleanType
4102  proceed;
4103 
4104 #if defined(MAGICKCORE_OPENMP_SUPPORT)
4105  #pragma omp atomic
4106 #endif
4107  progress++;
4108  proceed=SetImageProgress(image,NegateImageTag,progress,image->rows);
4109  if (proceed == MagickFalse)
4110  status=MagickFalse;
4111  }
4112  }
4113  image_view=DestroyCacheView(image_view);
4114  return(status);
4115 }
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 {
4146  double
4147  black_point,
4148  white_point;
4149 
4150  black_point=0.02*image->columns*image->rows;
4151  white_point=0.99*image->columns*image->rows;
4152  return(ContrastStretchImage(image,black_point,white_point,exception));
4153 }
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 #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 #define ScaledSigmoidal(a,b,x) ( \
4242  (Sigmoidal((a),(b),(x))-Sigmoidal((a),(b),0.0)) / \
4243  (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 {
4256  const double sig0=Sigmoidal(a,b,0.0);
4257  const double sig1=Sigmoidal(a,b,1.0);
4258  const double argument=(sig1-sig0)*x+sig0;
4259  const double clamped=
4260  (
4261 #if defined(MAGICKCORE_HAVE_ATANH)
4262  argument < -1+MagickEpsilon
4263  ?
4264  -1+MagickEpsilon
4265  :
4266  ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
4267  );
4268  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 }
4279 
4280 MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
4281  const MagickBooleanType sharpen,const double contrast,const double midpoint,
4282  ExceptionInfo *exception)
4283 {
4284 #define SigmoidalContrastImageTag "SigmoidalContrast/Image"
4285 #define ScaledSig(x) (ClampToQuantum((double) QuantumRange* \
4286  ScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale*((double) x))) )
4287 #define InverseScaledSig(x) (ClampToQuantum((double) QuantumRange* \
4288  InverseScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale* \
4289  ((double) x))) )
4290 
4291  CacheView
4292  *image_view;
4293 
4294  MagickBooleanType
4295  status;
4296 
4297  MagickOffsetType
4298  progress;
4299 
4300  ssize_t
4301  y;
4302 
4303  /*
4304  Convenience macros.
4305  */
4306  assert(image != (Image *) NULL);
4307  assert(image->signature == MagickCoreSignature);
4308  if (IsEventLogging() != MagickFalse)
4309  (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  if (contrast < MagickEpsilon)
4315  return(MagickTrue);
4316  /*
4317  Sigmoidal-contrast enhance colormap.
4318  */
4319  if (image->storage_class == PseudoClass)
4320  {
4321  ssize_t
4322  i;
4323 
4324  if( sharpen != MagickFalse )
4325  for (i=0; i < (ssize_t) image->colors; i++)
4326  {
4327  if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
4328  image->colormap[i].red=(MagickRealType) ScaledSig(
4329  image->colormap[i].red);
4330  if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
4331  image->colormap[i].green=(MagickRealType) ScaledSig(
4332  image->colormap[i].green);
4333  if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
4334  image->colormap[i].blue=(MagickRealType) ScaledSig(
4335  image->colormap[i].blue);
4336  if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
4337  image->colormap[i].alpha=(MagickRealType) ScaledSig(
4338  image->colormap[i].alpha);
4339  }
4340  else
4341  for (i=0; i < (ssize_t) image->colors; i++)
4342  {
4343  if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
4344  image->colormap[i].red=(MagickRealType) InverseScaledSig(
4345  image->colormap[i].red);
4346  if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
4347  image->colormap[i].green=(MagickRealType) InverseScaledSig(
4348  image->colormap[i].green);
4349  if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
4350  image->colormap[i].blue=(MagickRealType) InverseScaledSig(
4351  image->colormap[i].blue);
4352  if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
4353  image->colormap[i].alpha=(MagickRealType) InverseScaledSig(
4354  image->colormap[i].alpha);
4355  }
4356  }
4357  /*
4358  Sigmoidal-contrast enhance image.
4359  */
4360  status=MagickTrue;
4361  progress=0;
4362  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  for (y=0; y < (ssize_t) image->rows; y++)
4368  {
4369  Quantum
4370  *magick_restrict q;
4371 
4372  ssize_t
4373  x;
4374 
4375  if (status == MagickFalse)
4376  continue;
4377  q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
4378  if (q == (Quantum *) NULL)
4379  {
4380  status=MagickFalse;
4381  continue;
4382  }
4383  for (x=0; x < (ssize_t) image->columns; x++)
4384  {
4385  ssize_t
4386  i;
4387 
4388  for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
4389  {
4390  PixelChannel channel = GetPixelChannelChannel(image,i);
4391  PixelTrait traits = GetPixelChannelTraits(image,channel);
4392  if ((traits & UpdatePixelTrait) == 0)
4393  continue;
4394  if( sharpen != MagickFalse )
4395  q[i]=ScaledSig(q[i]);
4396  else
4397  q[i]=InverseScaledSig(q[i]);
4398  }
4399  q+=(ptrdiff_t) GetPixelChannels(image);
4400  }
4401  if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
4402  status=MagickFalse;
4403  if (image->progress_monitor != (MagickProgressMonitor) NULL)
4404  {
4405  MagickBooleanType
4406  proceed;
4407 
4408 #if defined(MAGICKCORE_OPENMP_SUPPORT)
4409  #pragma omp atomic
4410 #endif
4411  progress++;
4412  proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress,
4413  image->rows);
4414  if (proceed == MagickFalse)
4415  status=MagickFalse;
4416  }
4417  }
4418  image_view=DestroyCacheView(image_view);
4419  return(status);
4420 }
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 {
4451 #define WhiteBalanceImageTag "WhiteBalance/Image"
4452 
4453  CacheView
4454  *image_view;
4455 
4456  const char
4457  *artifact;
4458 
4459  double
4460  a_mean,
4461  b_mean;
4462 
4463  MagickOffsetType
4464  progress;
4465 
4466  MagickStatusType
4467  status;
4468 
4469  ssize_t
4470  y;
4471 
4472  /*
4473  White balance image.
4474  */
4475  assert(image != (Image *) NULL);
4476  assert(image->signature == MagickCoreSignature);
4477  if (IsEventLogging() != MagickFalse)
4478  (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
4479  if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
4480  return(MagickFalse);
4481  status=TransformImageColorspace(image,LabColorspace,exception);
4482  a_mean=0.0;
4483  b_mean=0.0;
4484  image_view=AcquireAuthenticCacheView(image,exception);
4485  for (y=0; y < (ssize_t) image->rows; y++)
4486  {
4487  const Quantum
4488  *magick_restrict p;
4489 
4490  ssize_t
4491  x;
4492 
4493  if (status == MagickFalse)
4494  continue;
4495  p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
4496  if (p == (Quantum *) NULL)
4497  {
4498  status=MagickFalse;
4499  continue;
4500  }
4501  for (x=0; x < (ssize_t) image->columns; x++)
4502  {
4503  a_mean+=QuantumScale*(double) GetPixela(image,p)-0.5;
4504  b_mean+=QuantumScale*(double) GetPixelb(image,p)-0.5;
4505  p+=(ptrdiff_t) GetPixelChannels(image);
4506  }
4507  }
4508  a_mean/=((double) image->columns*image->rows);
4509  b_mean/=((double) image->columns*image->rows);
4510  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  for (y=0; y < (ssize_t) image->rows; y++)
4516  {
4517  Quantum
4518  *magick_restrict q;
4519 
4520  ssize_t
4521  x;
4522 
4523  if (status == MagickFalse)
4524  continue;
4525  q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
4526  if (q == (Quantum *) NULL)
4527  {
4528  status=MagickFalse;
4529  continue;
4530  }
4531  for (x=0; x < (ssize_t) image->columns; x++)
4532  {
4533  double
4534  a,
4535  b;
4536 
4537  /*
4538  Scale the chroma distance shifted according to amount of luminance.
4539  */
4540  a=(double) GetPixela(image,q)-1.1*(double) GetPixelL(image,q)*a_mean;
4541  b=(double) GetPixelb(image,q)-1.1*(double) GetPixelL(image,q)*b_mean;
4542  SetPixela(image,ClampToQuantum(a),q);
4543  SetPixelb(image,ClampToQuantum(b),q);
4544  q+=(ptrdiff_t) GetPixelChannels(image);
4545  }
4546  if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
4547  status=MagickFalse;
4548  if (image->progress_monitor != (MagickProgressMonitor) NULL)
4549  {
4550  MagickBooleanType
4551  proceed;
4552 
4553 #if defined(MAGICKCORE_OPENMP_SUPPORT)
4554  #pragma omp atomic
4555 #endif
4556  progress++;
4557  proceed=SetImageProgress(image,WhiteBalanceImageTag,progress,
4558  image->rows);
4559  if (proceed == MagickFalse)
4560  status=MagickFalse;
4561  }
4562  }
4563  image_view=DestroyCacheView(image_view);
4564  artifact=GetImageArtifact(image,"white-balance:vibrance");
4565  if (artifact != (const char *) NULL)
4566  {
4567  ChannelType
4568  channel_mask;
4569 
4570  double
4571  black_point = 0.0;
4572 
4573  GeometryInfo
4574  geometry_info;
4575 
4576  MagickStatusType
4577  flags;
4578 
4579  /*
4580  Level the a & b channels.
4581  */
4582  flags=ParseGeometry(artifact,&geometry_info);
4583  if ((flags & RhoValue) != 0)
4584  black_point=geometry_info.rho;
4585  if ((flags & PercentValue) != 0)
4586  black_point*=((double) QuantumRange/100.0);
4587  channel_mask=SetImageChannelMask(image,(ChannelType) (aChannel |
4588  bChannel));
4589  status&=(MagickStatusType) LevelImage(image,black_point,(double)
4590  QuantumRange-black_point,1.0,exception);
4591  (void) SetImageChannelMask(image,channel_mask);
4592  }
4593  status&=(MagickStatusType) TransformImageColorspace(image,sRGBColorspace,
4594  exception);
4595  return(status != 0 ? MagickTrue : MagickFalse);
4596 }
Definition: image.h:132