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