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