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