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