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