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