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