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