enhance.c

Go to the documentation of this file.
00001 /*
00002 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00003 %                                                                             %
00004 %                                                                             %
00005 %                                                                             %
00006 %              EEEEE  N   N  H   H   AAA   N   N   CCCC  EEEEE                %
00007 %              E      NN  N  H   H  A   A  NN  N  C      E                    %
00008 %              EEE    N N N  HHHHH  AAAAA  N N N  C      EEE                  %
00009 %              E      N  NN  H   H  A   A  N  NN  C      E                    %
00010 %              EEEEE  N   N  H   H  A   A  N   N   CCCC  EEEEE                %
00011 %                                                                             %
00012 %                                                                             %
00013 %                    MagickCore Image Enhancement Methods                     %
00014 %                                                                             %
00015 %                              Software Design                                %
00016 %                                John Cristy                                  %
00017 %                                 July 1992                                   %
00018 %                                                                             %
00019 %                                                                             %
00020 %  Copyright 1999-2009 ImageMagick Studio LLC, a non-profit organization      %
00021 %  dedicated to making software imaging solutions freely available.           %
00022 %                                                                             %
00023 %  You may not use this file except in compliance with the License.  You may  %
00024 %  obtain a copy of the License at                                            %
00025 %                                                                             %
00026 %    http://www.imagemagick.org/script/license.php                            %
00027 %                                                                             %
00028 %  Unless required by applicable law or agreed to in writing, software        %
00029 %  distributed under the License is distributed on an "AS IS" BASIS,          %
00030 %  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   %
00031 %  See the License for the specific language governing permissions and        %
00032 %  limitations under the License.                                             %
00033 %                                                                             %
00034 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00035 %
00036 %
00037 %
00038 */
00039 
00040 /*
00041   Include declarations.
00042 */
00043 #include "magick/studio.h"
00044 #include "magick/artifact.h"
00045 #include "magick/cache.h"
00046 #include "magick/cache-view.h"
00047 #include "magick/color.h"
00048 #include "magick/color-private.h"
00049 #include "magick/colorspace.h"
00050 #include "magick/composite-private.h"
00051 #include "magick/enhance.h"
00052 #include "magick/exception.h"
00053 #include "magick/exception-private.h"
00054 #include "magick/gem.h"
00055 #include "magick/geometry.h"
00056 #include "magick/histogram.h"
00057 #include "magick/image.h"
00058 #include "magick/image-private.h"
00059 #include "magick/memory_.h"
00060 #include "magick/monitor.h"
00061 #include "magick/monitor-private.h"
00062 #include "magick/option.h"
00063 #include "magick/quantum.h"
00064 #include "magick/quantum-private.h"
00065 #include "magick/resample.h"
00066 #include "magick/resample-private.h"
00067 #include "magick/statistic.h"
00068 #include "magick/string_.h"
00069 #include "magick/thread-private.h"
00070 #include "magick/token.h"
00071 #include "magick/xml-tree.h"
00072 
00073 /*
00074 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00075 %                                                                             %
00076 %                                                                             %
00077 %                                                                             %
00078 %     A u t o G a m m a I m a g e                                             %
00079 %                                                                             %
00080 %                                                                             %
00081 %                                                                             %
00082 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00083 %
00084 %  AutoGammaImage() extract the 'mean' from the image and adjust the image
00085 %  to try make set its gamma appropriatally.
00086 %
00087 %  The format of the AutoGammaImage method is:
00088 %
00089 %      MagickBooleanType AutoGammaImage(Image *image)
00090 %      MagickBooleanType AutoGammaImageChannel(Image *image,
00091 %        const ChannelType channel)
00092 %
00093 %  A description of each parameter follows:
00094 %
00095 %    o image: The image to auto-level
00096 %
00097 %    o channel: The channels to auto-level.  If the special 'SyncChannels'
00098 %      flag is set all given channels is adjusted in the same way using the
00099 %      mean average of those channels.
00100 %
00101 */
00102 
00103 MagickExport MagickBooleanType AutoGammaImage(Image *image)
00104 {
00105   return(AutoGammaImageChannel(image,DefaultChannels));
00106 }
00107 
00108 MagickExport MagickBooleanType AutoGammaImageChannel(Image *image,
00109   const ChannelType channel)
00110 {
00111   MagickStatusType
00112     status;
00113 
00114   double
00115     mean,junk,gamma,logmean;
00116 
00117   logmean=log(0.5);
00118 
00119   if ((channel & SyncChannels) != 0 )
00120     {
00121       /*
00122         Apply gamma correction equally accross all given channels
00123       */
00124       GetImageChannelMean(image, channel, &mean, &junk, &image->exception);
00125       gamma = log(mean*QuantumScale)/logmean;
00126       //return GammaImageChannel(image, channel, gamma);
00127       return LevelImageChannel(image, channel,
00128                                0.0, (double)QuantumRange, gamma);
00129     }
00130 
00131   /*
00132     auto-gamma each channel separateally
00133   */
00134   status = MagickTrue;
00135   if ((channel & RedChannel) != 0)
00136     {
00137       GetImageChannelMean(image, RedChannel, &mean, &junk, &image->exception);
00138       gamma = log(mean*QuantumScale)/logmean;
00139       //status = status && GammaImageChannel(image, RedChannel, gamma);
00140       status = status && LevelImageChannel(image, RedChannel,
00141                                0.0, (double)QuantumRange, gamma);
00142     }
00143   if ((channel & GreenChannel) != 0)
00144     {
00145       GetImageChannelMean(image, GreenChannel, &mean, &junk, &image->exception);
00146       gamma = log(mean*QuantumScale)/logmean;
00147       //status = status && GammaImageChannel(image, GreenChannel, gamma);
00148       status = status && LevelImageChannel(image, GreenChannel,
00149                                0.0, (double)QuantumRange, gamma);
00150     }
00151   if ((channel & BlueChannel) != 0)
00152     {
00153       GetImageChannelMean(image, BlueChannel, &mean, &junk, &image->exception);
00154       gamma = log(mean*QuantumScale)/logmean;
00155       //status = status && GammaImageChannel(image, BlueChannel, gamma);
00156       status = status && LevelImageChannel(image, BlueChannel,
00157                                0.0, (double)QuantumRange, gamma);
00158     }
00159   if (((channel & OpacityChannel) != 0) &&
00160       (image->matte == MagickTrue))
00161     {
00162       GetImageChannelMean(image, OpacityChannel, &mean, &junk, &image->exception);
00163       gamma = log(mean*QuantumScale)/logmean;
00164       //status = status && GammaImageChannel(image, OpacityChannel, gamma);
00165       status = status && LevelImageChannel(image, OpacityChannel,
00166                                0.0, (double)QuantumRange, gamma);
00167     }
00168   if (((channel & IndexChannel) != 0) &&
00169       (image->colorspace == CMYKColorspace))
00170     {
00171       GetImageChannelMean(image, IndexChannel, &mean, &junk, &image->exception);
00172       gamma = log(mean*QuantumScale)/logmean;
00173       //status = status && GammaImageChannel(image, IndexChannel, gamma);
00174       status = status && LevelImageChannel(image, IndexChannel,
00175                                0.0, (double)QuantumRange, gamma);
00176     }
00177   return(status != 0 ? MagickTrue : MagickFalse);
00178 }
00179 
00180 /*
00181 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00182 %                                                                             %
00183 %                                                                             %
00184 %                                                                             %
00185 %     A u t o L e v e l I m a g e                                             %
00186 %                                                                             %
00187 %                                                                             %
00188 %                                                                             %
00189 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00190 %
00191 %  AutoLevelImage() adjusts the levels of a particular image channel by
00192 %  scaling the minimum and maximum values to the full quantum range.
00193 %
00194 %  The format of the LevelImage method is:
00195 %
00196 %      MagickBooleanType AutoLevelImage(Image *image)
00197 %      MagickBooleanType AutoLevelImageChannel(Image *image,
00198 %        const ChannelType channel)
00199 %
00200 %  A description of each parameter follows:
00201 %
00202 %    o image: The image to auto-level
00203 %
00204 %    o channel: The channels to auto-level.  If the special 'SyncChannels'
00205 %      flag is set the min/max/mean value of all given channels is used for
00206 %      all given channels, to all channels in the same way.
00207 %
00208 */
00209 
00210 MagickExport MagickBooleanType AutoLevelImage(Image *image)
00211 {
00212   return(AutoLevelImageChannel(image,DefaultChannels));
00213 }
00214 
00215 MagickExport MagickBooleanType AutoLevelImageChannel(Image *image,
00216   const ChannelType channel)
00217 {
00218   /*
00219     This is simply a convenience function around a Min/Max Histogram Stretch
00220   */
00221   return MinMaxStretchImage(image, channel, 0.0, 0.0);
00222 }
00223 
00224 /*
00225 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00226 %                                                                             %
00227 %                                                                             %
00228 %                                                                             %
00229 %     C o l o r D e c i s i o n L i s t I m a g e                             %
00230 %                                                                             %
00231 %                                                                             %
00232 %                                                                             %
00233 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00234 %
00235 %  ColorDecisionListImage() accepts a lightweight Color Correction Collection
00236 %  (CCC) file which solely contains one or more color corrections and applies
00237 %  the correction to the image.  Here is a sample CCC file:
00238 %
00239 %    <ColorCorrectionCollection xmlns="urn:ASC:CDL:v1.2">
00240 %          <ColorCorrection id="cc03345">
00241 %                <SOPNode>
00242 %                     <Slope> 0.9 1.2 0.5 </Slope>
00243 %                     <Offset> 0.4 -0.5 0.6 </Offset>
00244 %                     <Power> 1.0 0.8 1.5 </Power>
00245 %                </SOPNode>
00246 %                <SATNode>
00247 %                     <Saturation> 0.85 </Saturation>
00248 %                </SATNode>
00249 %          </ColorCorrection>
00250 %    </ColorCorrectionCollection>
00251 %
00252 %  which includes the slop, offset, and power for each of the RGB channels
00253 %  as well as the saturation.
00254 %
00255 %  The format of the ColorDecisionListImage method is:
00256 %
00257 %      MagickBooleanType ColorDecisionListImage(Image *image,
00258 %        const char *color_correction_collection)
00259 %
00260 %  A description of each parameter follows:
00261 %
00262 %    o image: the image.
00263 %
00264 %    o color_correction_collection: the color correction collection in XML.
00265 %
00266 */
00267 MagickExport MagickBooleanType ColorDecisionListImage(Image *image,
00268   const char *color_correction_collection)
00269 {
00270 #define ColorDecisionListCorrectImageTag  "ColorDecisionList/Image"
00271 
00272   typedef struct _Correction
00273   {
00274     double
00275       slope,
00276       offset,
00277       power;
00278   } Correction;
00279 
00280   typedef struct _ColorCorrection
00281   {
00282     Correction
00283       red,
00284       green,
00285       blue;
00286 
00287     double
00288       saturation;
00289   } ColorCorrection;
00290 
00291   char
00292     token[MaxTextExtent];
00293 
00294   ColorCorrection
00295     color_correction;
00296 
00297   const char
00298     *content,
00299     *p;
00300 
00301   ExceptionInfo
00302     *exception;
00303 
00304   long
00305     progress,
00306     y;
00307 
00308   MagickBooleanType
00309     status;
00310 
00311   PixelPacket
00312     *cdl_map;
00313 
00314   register long
00315     i;
00316 
00317   XMLTreeInfo
00318     *cc,
00319     *ccc,
00320     *sat,
00321     *sop;
00322 
00323   CacheView
00324     *image_view;
00325 
00326   /*
00327     Allocate and initialize cdl maps.
00328   */
00329   assert(image != (Image *) NULL);
00330   assert(image->signature == MagickSignature);
00331   if (image->debug != MagickFalse)
00332     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
00333   if (color_correction_collection == (const char *) NULL)
00334     return(MagickFalse);
00335   ccc=NewXMLTree((const char *) color_correction_collection,&image->exception);
00336   if (ccc == (XMLTreeInfo *) NULL)
00337     return(MagickFalse);
00338   cc=GetXMLTreeChild(ccc,"ColorCorrection");
00339   if (cc == (XMLTreeInfo *) NULL)
00340     {
00341       ccc=DestroyXMLTree(ccc);
00342       return(MagickFalse);
00343     }
00344   color_correction.red.slope=1.0;
00345   color_correction.red.offset=0.0;
00346   color_correction.red.power=1.0;
00347   color_correction.green.slope=1.0;
00348   color_correction.green.offset=0.0;
00349   color_correction.green.power=1.0;
00350   color_correction.blue.slope=1.0;
00351   color_correction.blue.offset=0.0;
00352   color_correction.blue.power=1.0;
00353   color_correction.saturation=0.0;
00354   sop=GetXMLTreeChild(cc,"SOPNode");
00355   if (sop != (XMLTreeInfo *) NULL)
00356     {
00357       XMLTreeInfo
00358         *offset,
00359         *power,
00360         *slope;
00361 
00362       slope=GetXMLTreeChild(sop,"Slope");
00363       if (slope != (XMLTreeInfo *) NULL)
00364         {
00365           content=GetXMLTreeContent(slope);
00366           p=(const char *) content;
00367           for (i=0; (*p != '\0') && (i < 3); i++)
00368           {
00369             GetMagickToken(p,&p,token);
00370             if (*token == ',')
00371               GetMagickToken(p,&p,token);
00372             switch (i)
00373             {
00374               case 0: color_correction.red.slope=atof(token); break;
00375               case 1: color_correction.green.slope=atof(token); break;
00376               case 2: color_correction.blue.slope=atof(token); break;
00377             }
00378           }
00379         }
00380       offset=GetXMLTreeChild(sop,"Offset");
00381       if (offset != (XMLTreeInfo *) NULL)
00382         {
00383           content=GetXMLTreeContent(offset);
00384           p=(const char *) content;
00385           for (i=0; (*p != '\0') && (i < 3); i++)
00386           {
00387             GetMagickToken(p,&p,token);
00388             if (*token == ',')
00389               GetMagickToken(p,&p,token);
00390             switch (i)
00391             {
00392               case 0: color_correction.red.offset=atof(token); break;
00393               case 1: color_correction.green.offset=atof(token); break;
00394               case 2: color_correction.blue.offset=atof(token); break;
00395             }
00396           }
00397         }
00398       power=GetXMLTreeChild(sop,"Power");
00399       if (power != (XMLTreeInfo *) NULL)
00400         {
00401           content=GetXMLTreeContent(power);
00402           p=(const char *) content;
00403           for (i=0; (*p != '\0') && (i < 3); i++)
00404           {
00405             GetMagickToken(p,&p,token);
00406             if (*token == ',')
00407               GetMagickToken(p,&p,token);
00408             switch (i)
00409             {
00410               case 0: color_correction.red.power=atof(token); break;
00411               case 1: color_correction.green.power=atof(token); break;
00412               case 2: color_correction.blue.power=atof(token); break;
00413             }
00414           }
00415         }
00416     }
00417   sat=GetXMLTreeChild(cc,"SATNode");
00418   if (sat != (XMLTreeInfo *) NULL)
00419     {
00420       XMLTreeInfo
00421         *saturation;
00422 
00423       saturation=GetXMLTreeChild(sat,"Saturation");
00424       if (saturation != (XMLTreeInfo *) NULL)
00425         {
00426           content=GetXMLTreeContent(saturation);
00427           p=(const char *) content;
00428           GetMagickToken(p,&p,token);
00429           color_correction.saturation=atof(token);
00430         }
00431     }
00432   ccc=DestroyXMLTree(ccc);
00433   if (image->debug != MagickFalse)
00434     {
00435       (void) LogMagickEvent(TransformEvent,GetMagickModule(),
00436         "  Color Correction Collection:");
00437       (void) LogMagickEvent(TransformEvent,GetMagickModule(),
00438         "  color_correction.red.slope: %g",color_correction.red.slope);
00439       (void) LogMagickEvent(TransformEvent,GetMagickModule(),
00440         "  color_correction.red.offset: %g",color_correction.red.offset);
00441       (void) LogMagickEvent(TransformEvent,GetMagickModule(),
00442         "  color_correction.red.power: %g",color_correction.red.power);
00443       (void) LogMagickEvent(TransformEvent,GetMagickModule(),
00444         "  color_correction.green.slope: %g",color_correction.green.slope);
00445       (void) LogMagickEvent(TransformEvent,GetMagickModule(),
00446         "  color_correction.green.offset: %g",color_correction.green.offset);
00447       (void) LogMagickEvent(TransformEvent,GetMagickModule(),
00448         "  color_correction.green.power: %g",color_correction.green.power);
00449       (void) LogMagickEvent(TransformEvent,GetMagickModule(),
00450         "  color_correction.blue.slope: %g",color_correction.blue.slope);
00451       (void) LogMagickEvent(TransformEvent,GetMagickModule(),
00452         "  color_correction.blue.offset: %g",color_correction.blue.offset);
00453       (void) LogMagickEvent(TransformEvent,GetMagickModule(),
00454         "  color_correction.blue.power: %g",color_correction.blue.power);
00455       (void) LogMagickEvent(TransformEvent,GetMagickModule(),
00456         "  color_correction.saturation: %g",color_correction.saturation);
00457     }
00458   cdl_map=(PixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*cdl_map));
00459   if (cdl_map == (PixelPacket *) NULL)
00460     ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
00461       image->filename);
00462 #if defined(MAGICKCORE_OPENMP_SUPPORT)
00463   #pragma omp parallel for schedule(dynamic,4)
00464 #endif
00465   for (i=0; i <= (long) MaxMap; i++)
00466   {
00467     cdl_map[i].red=RoundToQuantum((MagickRealType) ScaleMapToQuantum((
00468       MagickRealType) (MaxMap*(pow(color_correction.red.slope*i/MaxMap+
00469       color_correction.red.offset,color_correction.red.power)))));
00470     cdl_map[i].green=RoundToQuantum((MagickRealType) ScaleMapToQuantum((
00471       MagickRealType) (MaxMap*(pow(color_correction.green.slope*i/MaxMap+
00472       color_correction.green.offset,color_correction.green.power)))));
00473     cdl_map[i].blue=RoundToQuantum((MagickRealType) ScaleMapToQuantum((
00474       MagickRealType) (MaxMap*(pow(color_correction.blue.slope*i/MaxMap+
00475       color_correction.blue.offset,color_correction.blue.power)))));
00476   }
00477   if (image->storage_class == PseudoClass)
00478     {
00479       /*
00480         Apply transfer function to colormap.
00481       */
00482 #if defined(MAGICKCORE_OPENMP_SUPPORT)
00483   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
00484 #endif
00485       for (i=0; i < (long) image->colors; i++)
00486       {
00487         double
00488           luma;
00489 
00490         luma=0.2126*image->colormap[i].red+0.7152*image->colormap[i].green+
00491           0.0722*image->colormap[i].blue;
00492         image->colormap[i].red=RoundToQuantum(luma+color_correction.saturation*
00493           cdl_map[ScaleQuantumToMap(image->colormap[i].red)].red-luma);
00494         image->colormap[i].green=RoundToQuantum(luma+
00495           color_correction.saturation*cdl_map[ScaleQuantumToMap(
00496           image->colormap[i].green)].green-luma);
00497         image->colormap[i].blue=RoundToQuantum(luma+color_correction.saturation*
00498           cdl_map[ScaleQuantumToMap(image->colormap[i].blue)].blue-luma);
00499       }
00500     }
00501   /*
00502     Apply transfer function to image.
00503   */
00504   status=MagickTrue;
00505   progress=0;
00506   exception=(&image->exception);
00507   image_view=AcquireCacheView(image);
00508 #if defined(MAGICKCORE_OPENMP_SUPPORT)
00509   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
00510 #endif
00511   for (y=0; y < (long) image->rows; y++)
00512   {
00513     double
00514       luma;
00515 
00516     register long
00517       x;
00518 
00519     register PixelPacket
00520       *__restrict q;
00521 
00522     if (status == MagickFalse)
00523       continue;
00524     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
00525     if (q == (PixelPacket *) NULL)
00526       {
00527         status=MagickFalse;
00528         continue;
00529       }
00530     for (x=0; x < (long) image->columns; x++)
00531     {
00532       luma=0.2126*q->red+0.7152*q->green+0.0722*q->blue;
00533       q->red=RoundToQuantum(luma+color_correction.saturation*
00534         (cdl_map[ScaleQuantumToMap(q->red)].red-luma));
00535       q->green=RoundToQuantum(luma+color_correction.saturation*
00536         (cdl_map[ScaleQuantumToMap(q->green)].green-luma));
00537       q->blue=RoundToQuantum(luma+color_correction.saturation*
00538         (cdl_map[ScaleQuantumToMap(q->blue)].blue-luma));
00539       q++;
00540     }
00541     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
00542       status=MagickFalse;
00543     if (image->progress_monitor != (MagickProgressMonitor) NULL)
00544       {
00545         MagickBooleanType
00546           proceed;
00547 
00548 #if defined(MAGICKCORE_OPENMP_SUPPORT)
00549   #pragma omp critical (MagickCore_ColorDecisionListImageChannel)
00550 #endif
00551         proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
00552           progress++,image->rows);
00553         if (proceed == MagickFalse)
00554           status=MagickFalse;
00555       }
00556   }
00557   image_view=DestroyCacheView(image_view);
00558   cdl_map=(PixelPacket *) RelinquishMagickMemory(cdl_map);
00559   return(status);
00560 }
00561 
00562 /*
00563 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00564 %                                                                             %
00565 %                                                                             %
00566 %                                                                             %
00567 %     C l u t I m a g e                                                       %
00568 %                                                                             %
00569 %                                                                             %
00570 %                                                                             %
00571 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00572 %
00573 %  ClutImage() replaces each color value in the given image, by using it as an
00574 %  index to lookup a replacement color value in a Color Look UP Table in the
00575 %  form of an image.  The values are extracted along a diagonal of the CLUT
00576 %  image so either a horizontal or vertial gradient image can be used.
00577 %
00578 %  Typically this is used to either re-color a gray-scale image according to a
00579 %  color gradient in the CLUT image, or to perform a freeform histogram
00580 %  (level) adjustment according to the (typically gray-scale) gradient in the
00581 %  CLUT image.
00582 %
00583 %  When the 'channel' mask includes the matte/alpha transparency channel but
00584 %  one image has no such channel it is assumed that that image is a simple
00585 %  gray-scale image that will effect the alpha channel values, either for
00586 %  gray-scale coloring (with transparent or semi-transparent colors), or
00587 %  a histogram adjustment of existing alpha channel values.   If both images
00588 %  have matte channels, direct and normal indexing is applied, which is rarely
00589 %  used.
00590 %
00591 %  The format of the ClutImage method is:
00592 %
00593 %      MagickBooleanType ClutImage(Image *image,Image *clut_image)
00594 %      MagickBooleanType ClutImageChannel(Image *image,
00595 %        const ChannelType channel,Image *clut_image)
00596 %
00597 %  A description of each parameter follows:
00598 %
00599 %    o image: the image, which is replaced by indexed CLUT values
00600 %
00601 %    o clut_image: the color lookup table image for replacement color values.
00602 %
00603 %    o channel: the channel.
00604 %
00605 */
00606 
00607 MagickExport MagickBooleanType ClutImage(Image *image,const Image *clut_image)
00608 {
00609   return(ClutImageChannel(image,DefaultChannels,clut_image));
00610 }
00611 
00612 MagickExport MagickBooleanType ClutImageChannel(Image *image,
00613   const ChannelType channel,const Image *clut_image)
00614 {
00615 #define ClutImageTag  "Clut/Image"
00616 
00617   ExceptionInfo
00618     *exception;
00619 
00620   long
00621     adjust,
00622     progress,
00623     y;
00624 
00625   MagickBooleanType
00626     status;
00627 
00628   MagickPixelPacket
00629     zero;
00630 
00631   ResampleFilter
00632     **resample_filter;
00633 
00634   CacheView
00635     *image_view;
00636 
00637   assert(image != (Image *) NULL);
00638   assert(image->signature == MagickSignature);
00639   if (image->debug != MagickFalse)
00640     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
00641   assert(clut_image != (Image *) NULL);
00642   assert(clut_image->signature == MagickSignature);
00643   if (SetImageStorageClass(image,DirectClass) == MagickFalse)
00644     return(MagickFalse);
00645   /*
00646     Clut image.
00647   */
00648   status=MagickTrue;
00649   progress=0;
00650   GetMagickPixelPacket(clut_image,&zero);
00651   adjust=clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1;
00652   exception=(&image->exception);
00653   resample_filter=AcquireResampleFilterThreadSet(clut_image,MagickTrue,
00654     exception);
00655   image_view=AcquireCacheView(image);
00656 #if defined(MAGICKCORE_OPENMP_SUPPORT)
00657   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
00658 #endif
00659   for (y=0; y < (long) image->rows; y++)
00660   {
00661     MagickPixelPacket
00662       pixel;
00663 
00664     register IndexPacket
00665       *__restrict indexes;
00666 
00667     register long
00668       id,
00669       x;
00670 
00671     register PixelPacket
00672       *__restrict q;
00673 
00674     if (status == MagickFalse)
00675       continue;
00676     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
00677     if (q == (PixelPacket *) NULL)
00678       {
00679         status=MagickFalse;
00680         continue;
00681       }
00682     indexes=GetCacheViewAuthenticIndexQueue(image_view);
00683     pixel=zero;
00684     id=GetOpenMPThreadId();
00685     for (x=0; x < (long) image->columns; x++)
00686     {
00687       /*
00688         PROGRAMMERS WARNING:
00689 
00690         Apply OpacityChannel BEFORE the color channels.  Do not re-order.
00691 
00692         The handling special case 2 (coloring gray-scale), requires access to
00693         the unmodified colors of the original image to determine the index
00694         value.  As such alpha/matte channel handling must be performed BEFORE,
00695         any of the color channels are modified.
00696 
00697       */
00698       if ((channel & OpacityChannel) != 0)
00699         {
00700           if (clut_image->matte == MagickFalse)
00701             {
00702               /*
00703                 A gray-scale LUT replacement for an image alpha channel.
00704               */
00705               (void) ResamplePixelColor(resample_filter[id],QuantumScale*
00706                 (QuantumRange-q->opacity)*(clut_image->columns+adjust),
00707                 QuantumScale*(QuantumRange-q->opacity)*(clut_image->rows+
00708                 adjust),&pixel);
00709               q->opacity=(Quantum) (QuantumRange-MagickPixelIntensityToQuantum(
00710                 &pixel));
00711             }
00712           else
00713             if (image->matte == MagickFalse)
00714               {
00715                 /*
00716                   A greyscale image being colored by a LUT with transparency.
00717                 */
00718                 (void) ResamplePixelColor(resample_filter[id],QuantumScale*
00719                   PixelIntensity(q)*(clut_image->columns-adjust),QuantumScale*
00720                   PixelIntensity(q)*(clut_image->rows-adjust),&pixel);
00721                 q->opacity=RoundToQuantum(pixel.opacity);
00722               }
00723             else
00724               {
00725                 /*
00726                   Direct alpha channel lookup.
00727                 */
00728                 (void) ResamplePixelColor(resample_filter[id],QuantumScale*
00729                   q->opacity*(clut_image->columns-adjust),QuantumScale*
00730                   q->opacity* (clut_image->rows-adjust),&pixel);
00731                 q->opacity=RoundToQuantum(pixel.opacity);
00732               }
00733         }
00734       if ((channel & RedChannel) != 0)
00735         {
00736           (void) ResamplePixelColor(resample_filter[id],QuantumScale*q->red*
00737             (clut_image->columns-adjust),QuantumScale*q->red*
00738             (clut_image->rows-adjust),&pixel);
00739           q->red=RoundToQuantum(pixel.red);
00740         }
00741       if ((channel & GreenChannel) != 0)
00742         {
00743           (void) ResamplePixelColor(resample_filter[id],QuantumScale*q->green*
00744             (clut_image->columns-adjust),QuantumScale*q->green*
00745             (clut_image->rows-adjust),&pixel);
00746           q->green=RoundToQuantum(pixel.green);
00747         }
00748       if ((channel & BlueChannel) != 0)
00749         {
00750           (void) ResamplePixelColor(resample_filter[id],QuantumScale*q->blue*
00751             (clut_image->columns-adjust),QuantumScale*q->blue*
00752             (clut_image->rows-adjust),&pixel);
00753           q->blue=RoundToQuantum(pixel.blue);
00754         }
00755       if (((channel & IndexChannel) != 0) &&
00756           (image->colorspace == CMYKColorspace))
00757         {
00758           (void) ResamplePixelColor(resample_filter[id],QuantumScale*indexes[x]*
00759             (clut_image->columns-adjust),QuantumScale*indexes[x]*
00760             (clut_image->rows-adjust),&pixel);
00761           indexes[x]=RoundToQuantum(pixel.index);
00762         }
00763       q++;
00764     }
00765     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
00766       status=MagickFalse;
00767     if (image->progress_monitor != (MagickProgressMonitor) NULL)
00768       {
00769         MagickBooleanType
00770           proceed;
00771 
00772 #if defined(MAGICKCORE_OPENMP_SUPPORT)
00773   #pragma omp critical (MagickCore_ClutImageChannel)
00774 #endif
00775         proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
00776         if (proceed == MagickFalse)
00777           status=MagickFalse;
00778       }
00779   }
00780   image_view=DestroyCacheView(image_view);
00781   resample_filter=DestroyResampleFilterThreadSet(resample_filter);
00782   /*
00783     Enable alpha channel if CLUT image could enable it.
00784   */
00785   if ((clut_image->matte != MagickFalse) && ((channel & OpacityChannel) != 0))
00786     (void) SetImageAlphaChannel(image,ActivateAlphaChannel);
00787   return(status);
00788 }
00789 
00790 /*
00791 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00792 %                                                                             %
00793 %                                                                             %
00794 %                                                                             %
00795 %     C o n t r a s t I m a g e                                               %
00796 %                                                                             %
00797 %                                                                             %
00798 %                                                                             %
00799 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00800 %
00801 %  ContrastImage() enhances the intensity differences between the lighter and
00802 %  darker elements of the image.  Set sharpen to a MagickTrue to increase the
00803 %  image contrast otherwise the contrast is reduced.
00804 %
00805 %  The format of the ContrastImage method is:
00806 %
00807 %      MagickBooleanType ContrastImage(Image *image,
00808 %        const MagickBooleanType sharpen)
00809 %
00810 %  A description of each parameter follows:
00811 %
00812 %    o image: the image.
00813 %
00814 %    o sharpen: Increase or decrease image contrast.
00815 %
00816 */
00817 
00818 static void Contrast(const int sign,Quantum *red,Quantum *green,Quantum *blue)
00819 {
00820   double
00821     brightness,
00822     hue,
00823     saturation;
00824 
00825   /*
00826     Enhance contrast: dark color become darker, light color become lighter.
00827   */
00828   assert(red != (Quantum *) NULL);
00829   assert(green != (Quantum *) NULL);
00830   assert(blue != (Quantum *) NULL);
00831   hue=0.0;
00832   saturation=0.0;
00833   brightness=0.0;
00834   ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
00835   brightness+=0.5*sign*(0.5*(sin(MagickPI*(brightness-0.5))+1.0)-brightness);
00836   if (brightness > 1.0)
00837     brightness=1.0;
00838   else
00839     if (brightness < 0.0)
00840       brightness=0.0;
00841   ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
00842 }
00843 
00844 MagickExport MagickBooleanType ContrastImage(Image *image,
00845   const MagickBooleanType sharpen)
00846 {
00847 #define ContrastImageTag  "Contrast/Image"
00848 
00849   ExceptionInfo
00850     *exception;
00851 
00852   int
00853     sign;
00854 
00855   long
00856     progress,
00857     y;
00858 
00859   MagickBooleanType
00860     status;
00861 
00862   register long
00863     i;
00864 
00865   CacheView
00866     *image_view;
00867 
00868   assert(image != (Image *) NULL);
00869   assert(image->signature == MagickSignature);
00870   if (image->debug != MagickFalse)
00871     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
00872   sign=sharpen != MagickFalse ? 1 : -1;
00873   if (image->storage_class == PseudoClass)
00874     {
00875       /*
00876         Contrast enhance colormap.
00877       */
00878       for (i=0; i < (long) image->colors; i++)
00879         Contrast(sign,&image->colormap[i].red,&image->colormap[i].green,
00880           &image->colormap[i].blue);
00881     }
00882   /*
00883     Contrast enhance image.
00884   */
00885   status=MagickTrue;
00886   progress=0;
00887   exception=(&image->exception);
00888   image_view=AcquireCacheView(image);
00889 #if defined(MAGICKCORE_OPENMP_SUPPORT)
00890   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
00891 #endif
00892   for (y=0; y < (long) image->rows; y++)
00893   {
00894     register long
00895       x;
00896 
00897     register PixelPacket
00898       *__restrict q;
00899 
00900     if (status == MagickFalse)
00901       continue;
00902     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
00903     if (q == (PixelPacket *) NULL)
00904       {
00905         status=MagickFalse;
00906         continue;
00907       }
00908     for (x=0; x < (long) image->columns; x++)
00909     {
00910       Contrast(sign,&q->red,&q->green,&q->blue);
00911       q++;
00912     }
00913     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
00914       status=MagickFalse;
00915     if (image->progress_monitor != (MagickProgressMonitor) NULL)
00916       {
00917         MagickBooleanType
00918           proceed;
00919 
00920 #if defined(MAGICKCORE_OPENMP_SUPPORT)
00921   #pragma omp critical (MagickCore_ContrastImage)
00922 #endif
00923         proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
00924         if (proceed == MagickFalse)
00925           status=MagickFalse;
00926       }
00927   }
00928   image_view=DestroyCacheView(image_view);
00929   return(status);
00930 }
00931 
00932 /*
00933 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00934 %                                                                             %
00935 %                                                                             %
00936 %                                                                             %
00937 %     C o n t r a s t S t r e t c h I m a g e                                 %
00938 %                                                                             %
00939 %                                                                             %
00940 %                                                                             %
00941 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
00942 %
00943 %  The ContrastStretchImage() is a simple image enhancement technique that
00944 %  attempts to improve the contrast in an image by `stretching' the range of
00945 %  intensity values it contains to span a desired range of values. It differs
00946 %  from the more sophisticated histogram equalization in that it can only
00947 %  apply %  a linear scaling function to the image pixel values.  As a result
00948 %  the `enhancement' is less harsh.
00949 %
00950 %  The format of the ContrastStretchImage method is:
00951 %
00952 %      MagickBooleanType ContrastStretchImage(Image *image,
00953 %        const char *levels)
00954 %      MagickBooleanType ContrastStretchImageChannel(Image *image,
00955 %        const unsigned long channel,const double black_point,
00956 %        const double white_point)
00957 %
00958 %  A description of each parameter follows:
00959 %
00960 %    o image: the image.
00961 %
00962 %    o channel: the channel.
00963 %
00964 %    o black_point: the black point.
00965 %
00966 %    o white_point: the white point.
00967 %
00968 %    o levels: Specify the levels where the black and white points have the
00969 %      range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
00970 %
00971 */
00972 
00973 MagickExport MagickBooleanType ContrastStretchImage(Image *image,
00974   const char *levels)
00975 {
00976   double
00977     black_point,
00978     white_point;
00979 
00980   GeometryInfo
00981     geometry_info;
00982 
00983   MagickBooleanType
00984     status;
00985 
00986   MagickStatusType
00987     flags;
00988 
00989   /*
00990     Parse levels.
00991   */
00992   if (levels == (char *) NULL)
00993     return(MagickFalse);
00994   flags=ParseGeometry(levels,&geometry_info);
00995   black_point=geometry_info.rho;
00996   white_point=(double) image->columns*image->rows;
00997   if ((flags & SigmaValue) != 0)
00998     white_point=geometry_info.sigma;
00999   if ((flags & PercentValue) != 0)
01000     {
01001       black_point*=(double) QuantumRange/100.0;
01002       white_point*=(double) QuantumRange/100.0;
01003     }
01004   if ((flags & SigmaValue) == 0)
01005     white_point=(double) image->columns*image->rows-black_point;
01006   status=ContrastStretchImageChannel(image,DefaultChannels,black_point,
01007     white_point);
01008   return(status);
01009 }
01010 
01011 MagickExport MagickBooleanType ContrastStretchImageChannel(Image *image,
01012   const ChannelType channel,const double black_point,const double white_point)
01013 {
01014 #define MaxRange(color)  ((MagickRealType) ScaleQuantumToMap((Quantum) (color)))
01015 #define ContrastStretchImageTag  "ContrastStretch/Image"
01016 
01017   double
01018     intensity;
01019 
01020   ExceptionInfo
01021     *exception;
01022 
01023   long
01024     progress,
01025     y;
01026 
01027   MagickBooleanType
01028     status;
01029 
01030   MagickPixelPacket
01031     black,
01032     *histogram,
01033     *stretch_map,
01034     white;
01035 
01036   register long
01037     i;
01038 
01039   CacheView
01040     *image_view;
01041 
01042   /*
01043     Allocate histogram and stretch map.
01044   */
01045   assert(image != (Image *) NULL);
01046   assert(image->signature == MagickSignature);
01047   if (image->debug != MagickFalse)
01048     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
01049   histogram=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
01050     sizeof(*histogram));
01051   stretch_map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
01052     sizeof(*stretch_map));
01053   if ((histogram == (MagickPixelPacket *) NULL) ||
01054       (stretch_map == (MagickPixelPacket *) NULL))
01055     ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
01056       image->filename);
01057   /*
01058     Form histogram.
01059   */
01060   status=MagickTrue;
01061   exception=(&image->exception);
01062   (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
01063   image_view=AcquireCacheView(image);
01064   for (y=0; y < (long) image->rows; y++)
01065   {
01066     register const PixelPacket
01067       *__restrict p;
01068 
01069     register IndexPacket
01070       *__restrict indexes;
01071 
01072     register long
01073       x;
01074 
01075     if (status == MagickFalse)
01076       continue;
01077     p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
01078     if (p == (const PixelPacket *) NULL)
01079       {
01080         status=MagickFalse;
01081         continue;
01082       }
01083     indexes=GetCacheViewAuthenticIndexQueue(image_view);
01084     if (channel == DefaultChannels)
01085       for (x=0; x < (long) image->columns; x++)
01086       {
01087         Quantum
01088           intensity;
01089 
01090         intensity=PixelIntensityToQuantum(p);
01091         histogram[ScaleQuantumToMap(intensity)].red++;
01092         histogram[ScaleQuantumToMap(intensity)].green++;
01093         histogram[ScaleQuantumToMap(intensity)].blue++;
01094         histogram[ScaleQuantumToMap(intensity)].index++;
01095         p++;
01096       }
01097     else
01098       for (x=0; x < (long) image->columns; x++)
01099       {
01100         if ((channel & RedChannel) != 0)
01101           histogram[ScaleQuantumToMap(p->red)].red++;
01102         if ((channel & GreenChannel) != 0)
01103           histogram[ScaleQuantumToMap(p->green)].green++;
01104         if ((channel & BlueChannel) != 0)
01105           histogram[ScaleQuantumToMap(p->blue)].blue++;
01106         if ((channel & OpacityChannel) != 0)
01107           histogram[ScaleQuantumToMap(p->opacity)].opacity++;
01108         if (((channel & IndexChannel) != 0) &&
01109             (image->colorspace == CMYKColorspace))
01110           histogram[ScaleQuantumToMap(indexes[x])].index++;
01111         p++;
01112       }
01113   }
01114   /*
01115     Find the histogram boundaries by locating the black/white levels.
01116   */
01117   black.red=0.0;
01118   white.red=MaxRange(QuantumRange);
01119   if ((channel & RedChannel) != 0)
01120     {
01121       intensity=0.0;
01122       for (i=0; i <= (long) MaxMap; i++)
01123       {
01124         intensity+=histogram[i].red;
01125         if (intensity > black_point)
01126           break;
01127       }
01128       black.red=(MagickRealType) i;
01129       intensity=0.0;
01130       for (i=(long) MaxMap; i != 0; i--)
01131       {
01132         intensity+=histogram[i].red;
01133         if (intensity > ((double) image->columns*image->rows-white_point))
01134           break;
01135       }
01136       white.red=(MagickRealType) i;
01137     }
01138   black.green=0.0;
01139   white.green=MaxRange(QuantumRange);
01140   if ((channel & GreenChannel) != 0)
01141     {
01142       intensity=0.0;
01143       for (i=0; i <= (long) MaxMap; i++)
01144       {
01145         intensity+=histogram[i].green;
01146         if (intensity > black_point)
01147           break;
01148       }
01149       black.green=(MagickRealType) i;
01150       intensity=0.0;
01151       for (i=(long) MaxMap; i != 0; i--)
01152       {
01153         intensity+=histogram[i].green;
01154         if (intensity > ((double) image->columns*image->rows-white_point))
01155           break;
01156       }
01157       white.green=(MagickRealType) i;
01158     }
01159   black.blue=0.0;
01160   white.blue=MaxRange(QuantumRange);
01161   if ((channel & BlueChannel) != 0)
01162     {
01163       intensity=0.0;
01164       for (i=0; i <= (long) MaxMap; i++)
01165       {
01166         intensity+=histogram[i].blue;
01167         if (intensity > black_point)
01168           break;
01169       }
01170       black.blue=(MagickRealType) i;
01171       intensity=0.0;
01172       for (i=(long) MaxMap; i != 0; i--)
01173       {
01174         intensity+=histogram[i].blue;
01175         if (intensity > ((double) image->columns*image->rows-white_point))
01176           break;
01177       }
01178       white.blue=(MagickRealType) i;
01179     }
01180   black.opacity=0.0;
01181   white.opacity=MaxRange(QuantumRange);
01182   if ((channel & OpacityChannel) != 0)
01183     {
01184       intensity=0.0;
01185       for (i=0; i <= (long) MaxMap; i++)
01186       {
01187         intensity+=histogram[i].opacity;
01188         if (intensity > black_point)
01189           break;
01190       }
01191       black.opacity=(MagickRealType) i;
01192       intensity=0.0;
01193       for (i=(long) MaxMap; i != 0; i--)
01194       {
01195         intensity+=histogram[i].opacity;
01196         if (intensity > ((double) image->columns*image->rows-white_point))
01197           break;
01198       }
01199       white.opacity=(MagickRealType) i;
01200     }
01201   black.index=0.0;
01202   white.index=MaxRange(QuantumRange);
01203   if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
01204     {
01205       intensity=0.0;
01206       for (i=0; i <= (long) MaxMap; i++)
01207       {
01208         intensity+=histogram[i].index;
01209         if (intensity > black_point)
01210           break;
01211       }
01212       black.index=(MagickRealType) i;
01213       intensity=0.0;
01214       for (i=(long) MaxMap; i != 0; i--)
01215       {
01216         intensity+=histogram[i].index;
01217         if (intensity > ((double) image->columns*image->rows-white_point))
01218           break;
01219       }
01220       white.index=(MagickRealType) i;
01221     }
01222   histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
01223   /*
01224     Stretch the histogram to create the stretched image mapping.
01225   */
01226   (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*sizeof(*stretch_map));
01227 #if defined(MAGICKCORE_OPENMP_SUPPORT)
01228   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
01229 #endif
01230   for (i=0; i <= (long) MaxMap; i++)
01231   {
01232     if ((channel & RedChannel) != 0)
01233       {
01234         if (i < (long) black.red)
01235           stretch_map[i].red=0.0;
01236         else
01237           if (i > (long) white.red)
01238             stretch_map[i].red=(MagickRealType) QuantumRange;
01239           else
01240             if (black.red != white.red)
01241               stretch_map[i].red=(MagickRealType) ScaleMapToQuantum(
01242                 (MagickRealType) (MaxMap*(i-black.red)/(white.red-black.red)));
01243       }
01244     if ((channel & GreenChannel) != 0)
01245       {
01246         if (i < (long) black.green)
01247           stretch_map[i].green=0.0;
01248         else
01249           if (i > (long) white.green)
01250             stretch_map[i].green=(MagickRealType) QuantumRange;
01251           else
01252             if (black.green != white.green)
01253               stretch_map[i].green=(MagickRealType) ScaleMapToQuantum(
01254                 (MagickRealType) (MaxMap*(i-black.green)/(white.green-
01255                 black.green)));
01256       }
01257     if ((channel & BlueChannel) != 0)
01258       {
01259         if (i < (long) black.blue)
01260           stretch_map[i].blue=0.0;
01261         else
01262           if (i > (long) white.blue)
01263             stretch_map[i].blue=(MagickRealType) QuantumRange;
01264           else
01265             if (black.blue != white.blue)
01266               stretch_map[i].blue=(MagickRealType) ScaleMapToQuantum(
01267                 (MagickRealType) (MaxMap*(i-black.blue)/(white.blue-
01268                 black.blue)));
01269       }
01270     if ((channel & OpacityChannel) != 0)
01271       {
01272         if (i < (long) black.opacity)
01273           stretch_map[i].opacity=0.0;
01274         else
01275           if (i > (long) white.opacity)
01276             stretch_map[i].opacity=(MagickRealType) QuantumRange;
01277           else
01278             if (black.opacity != white.opacity)
01279               stretch_map[i].opacity=(MagickRealType) ScaleMapToQuantum(
01280                 (MagickRealType) (MaxMap*(i-black.opacity)/(white.opacity-
01281                 black.opacity)));
01282       }
01283     if (((channel & IndexChannel) != 0) &&
01284         (image->colorspace == CMYKColorspace))
01285       {
01286         if (i < (long) black.index)
01287           stretch_map[i].index=0.0;
01288         else
01289           if (i > (long) white.index)
01290             stretch_map[i].index=(MagickRealType) QuantumRange;
01291           else
01292             if (black.index != white.index)
01293               stretch_map[i].index=(MagickRealType) ScaleMapToQuantum(
01294                 (MagickRealType) (MaxMap*(i-black.index)/(white.index-
01295                 black.index)));
01296       }
01297   }
01298   /*
01299     Stretch the image.
01300   */
01301   if (((channel & OpacityChannel) != 0) || (((channel & IndexChannel) != 0) &&
01302       (image->colorspace == CMYKColorspace)))
01303     image->storage_class=DirectClass;
01304   if (image->storage_class == PseudoClass)
01305     {
01306       /*
01307         Stretch colormap.
01308       */
01309 #if defined(MAGICKCORE_OPENMP_SUPPORT)
01310   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
01311 #endif
01312       for (i=0; i < (long) image->colors; i++)
01313       {
01314         if ((channel & RedChannel) != 0)
01315           {
01316             if (black.red != white.red)
01317               image->colormap[i].red=RoundToQuantum(stretch_map[
01318                 ScaleQuantumToMap(image->colormap[i].red)].red);
01319           }
01320         if ((channel & GreenChannel) != 0)
01321           {
01322             if (black.green != white.green)
01323               image->colormap[i].green=RoundToQuantum(stretch_map[
01324                 ScaleQuantumToMap(image->colormap[i].green)].green);
01325           }
01326         if ((channel & BlueChannel) != 0)
01327           {
01328             if (black.blue != white.blue)
01329               image->colormap[i].blue=RoundToQuantum(stretch_map[
01330                 ScaleQuantumToMap(image->colormap[i].blue)].blue);
01331           }
01332         if ((channel & OpacityChannel) != 0)
01333           {
01334             if (black.opacity != white.opacity)
01335               image->colormap[i].opacity=RoundToQuantum(stretch_map[
01336                 ScaleQuantumToMap(image->colormap[i].opacity)].opacity);
01337           }
01338       }
01339     }
01340   /*
01341     Stretch image.
01342   */
01343   status=MagickTrue;
01344   progress=0;
01345 #if defined(MAGICKCORE_OPENMP_SUPPORT)
01346   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
01347 #endif
01348   for (y=0; y < (long) image->rows; y++)
01349   {
01350     register IndexPacket
01351       *__restrict indexes;
01352 
01353     register long
01354       x;
01355 
01356     register PixelPacket
01357       *__restrict q;
01358 
01359     if (status == MagickFalse)
01360       continue;
01361     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
01362     if (q == (PixelPacket *) NULL)
01363       {
01364         status=MagickFalse;
01365         continue;
01366       }
01367     indexes=GetCacheViewAuthenticIndexQueue(image_view);
01368     for (x=0; x < (long) image->columns; x++)
01369     {
01370       if ((channel & RedChannel) != 0)
01371         {
01372           if (black.red != white.red)
01373             q->red=RoundToQuantum(stretch_map[ScaleQuantumToMap(q->red)].red);
01374         }
01375       if ((channel & GreenChannel) != 0)
01376         {
01377           if (black.green != white.green)
01378             q->green=RoundToQuantum(stretch_map[ScaleQuantumToMap(
01379               q->green)].green);
01380         }
01381       if ((channel & BlueChannel) != 0)
01382         {
01383           if (black.blue != white.blue)
01384             q->blue=RoundToQuantum(stretch_map[ScaleQuantumToMap(
01385               q->blue)].blue);
01386         }
01387       if ((channel & OpacityChannel) != 0)
01388         {
01389           if (black.opacity != white.opacity)
01390             q->opacity=RoundToQuantum(stretch_map[ScaleQuantumToMap(
01391               q->opacity)].opacity);
01392         }
01393       if (((channel & IndexChannel) != 0) &&
01394           (image->colorspace == CMYKColorspace))
01395         {
01396           if (black.index != white.index)
01397             indexes[x]=(IndexPacket) RoundToQuantum(stretch_map[
01398               ScaleQuantumToMap(indexes[x])].index);
01399         }
01400       q++;
01401     }
01402     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
01403       status=MagickFalse;
01404     if (image->progress_monitor != (MagickProgressMonitor) NULL)
01405       {
01406         MagickBooleanType
01407           proceed;
01408 
01409 #if defined(MAGICKCORE_OPENMP_SUPPORT)
01410   #pragma omp critical (MagickCore_ContrastStretchImageChannel)
01411 #endif
01412         proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
01413           image->rows);
01414         if (proceed == MagickFalse)
01415           status=MagickFalse;
01416       }
01417   }
01418   image_view=DestroyCacheView(image_view);
01419   stretch_map=(MagickPixelPacket *) RelinquishMagickMemory(stretch_map);
01420   return(status);
01421 }
01422 
01423 /*
01424 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
01425 %                                                                             %
01426 %                                                                             %
01427 %                                                                             %
01428 %     E n h a n c e I m a g e                                                 %
01429 %                                                                             %
01430 %                                                                             %
01431 %                                                                             %
01432 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
01433 %
01434 %  EnhanceImage() applies a digital filter that improves the quality of a
01435 %  noisy image.
01436 %
01437 %  The format of the EnhanceImage method is:
01438 %
01439 %      Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
01440 %
01441 %  A description of each parameter follows:
01442 %
01443 %    o image: the image.
01444 %
01445 %    o exception: return any errors or warnings in this structure.
01446 %
01447 */
01448 MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
01449 {
01450 #define Enhance(weight) \
01451   mean=((MagickRealType) r->red+pixel.red)/2; \
01452   distance=(MagickRealType) r->red-(MagickRealType) pixel.red; \
01453   distance_squared=QuantumScale*(2.0*((MagickRealType) QuantumRange+1.0)+ \
01454      mean)*distance*distance; \
01455   mean=((MagickRealType) r->green+pixel.green)/2; \
01456   distance=(MagickRealType) r->green-(MagickRealType) pixel.green; \
01457   distance_squared+=4.0*distance*distance; \
01458   mean=((MagickRealType) r->blue+pixel.blue)/2; \
01459   distance=(MagickRealType) r->blue-(MagickRealType) pixel.blue; \
01460   distance_squared+=QuantumScale*(3.0*((MagickRealType) \
01461     QuantumRange+1.0)-1.0-mean)*distance*distance; \
01462   mean=((MagickRealType) r->opacity+pixel.opacity)/2; \
01463   distance=(MagickRealType) r->opacity-(MagickRealType) pixel.opacity; \
01464   distance_squared+=QuantumScale*(3.0*((MagickRealType) \
01465     QuantumRange+1.0)-1.0-mean)*distance*distance; \
01466   if (distance_squared < ((MagickRealType) QuantumRange*(MagickRealType) \
01467       QuantumRange/25.0f)) \
01468     { \
01469       aggregate.red+=(weight)*r->red; \
01470       aggregate.green+=(weight)*r->green; \
01471       aggregate.blue+=(weight)*r->blue; \
01472       aggregate.opacity+=(weight)*r->opacity; \
01473       total_weight+=(weight); \
01474     } \
01475   r++;
01476 #define EnhanceImageTag  "Enhance/Image"
01477 
01478   Image
01479     *enhance_image;
01480 
01481   long
01482     progress,
01483     y;
01484 
01485   MagickBooleanType
01486     status;
01487 
01488   MagickPixelPacket
01489     zero;
01490 
01491   CacheView
01492     *enhance_view,
01493     *image_view;
01494 
01495   /*
01496     Initialize enhanced image attributes.
01497   */
01498   assert(image != (const Image *) NULL);
01499   assert(image->signature == MagickSignature);
01500   if (image->debug != MagickFalse)
01501     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
01502   assert(exception != (ExceptionInfo *) NULL);
01503   assert(exception->signature == MagickSignature);
01504   if ((image->columns < 5) || (image->rows < 5))
01505     return((Image *) NULL);
01506   enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
01507     exception);
01508   if (enhance_image == (Image *) NULL)
01509     return((Image *) NULL);
01510   if (SetImageStorageClass(enhance_image,DirectClass) == MagickFalse)
01511     {
01512       InheritException(exception,&enhance_image->exception);
01513       enhance_image=DestroyImage(enhance_image);
01514       return((Image *) NULL);
01515     }
01516   /*
01517     Enhance image.
01518   */
01519   status=MagickTrue;
01520   progress=0;
01521   (void) ResetMagickMemory(&zero,0,sizeof(zero));
01522   image_view=AcquireCacheView(image);
01523   enhance_view=AcquireCacheView(enhance_image);
01524 #if defined(MAGICKCORE_OPENMP_SUPPORT)
01525   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
01526 #endif
01527   for (y=0; y < (long) image->rows; y++)
01528   {
01529     register const PixelPacket
01530       *__restrict p;
01531 
01532     register long
01533       x;
01534 
01535     register PixelPacket
01536       *__restrict q;
01537 
01538     /*
01539       Read another scan line.
01540     */
01541     if (status == MagickFalse)
01542       continue;
01543     p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
01544     q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
01545       exception);
01546     if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
01547       {
01548         status=MagickFalse;
01549         continue;
01550       }
01551     for (x=0; x < (long) image->columns; x++)
01552     {
01553       MagickPixelPacket
01554         aggregate;
01555 
01556       MagickRealType
01557         distance,
01558         distance_squared,
01559         mean,
01560         total_weight;
01561 
01562       PixelPacket
01563         pixel;
01564 
01565       register const PixelPacket
01566         *__restrict r;
01567 
01568       /*
01569         Compute weighted average of target pixel color components.
01570       */
01571       aggregate=zero;
01572       total_weight=0.0;
01573       r=p+2*(image->columns+4)+2;
01574       pixel=(*r);
01575       r=p;
01576       Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
01577       r=p+(image->columns+4);
01578       Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
01579       r=p+2*(image->columns+4);
01580       Enhance(10.0); Enhance(40.0); Enhance(80.0); Enhance(40.0); Enhance(10.0);
01581       r=p+3*(image->columns+4);
01582       Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);
01583       r=p+4*(image->columns+4);
01584       Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
01585       q->red=(Quantum) ((aggregate.red+(total_weight/2)-1)/total_weight);
01586       q->green=(Quantum) ((aggregate.green+(total_weight/2)-1)/total_weight);
01587       q->blue=(Quantum) ((aggregate.blue+(total_weight/2)-1)/total_weight);
01588       q->opacity=(Quantum) ((aggregate.opacity+(total_weight/2)-1)/
01589         total_weight);
01590       p++;
01591       q++;
01592     }
01593     if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
01594       status=MagickFalse;
01595     if (image->progress_monitor != (MagickProgressMonitor) NULL)
01596       {
01597         MagickBooleanType
01598           proceed;
01599 
01600 #if defined(MAGICKCORE_OPENMP_SUPPORT)
01601   #pragma omp critical (MagickCore_EnhanceImage)
01602 #endif
01603         proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
01604         if (proceed == MagickFalse)
01605           status=MagickFalse;
01606       }
01607   }
01608   enhance_view=DestroyCacheView(enhance_view);
01609   image_view=DestroyCacheView(image_view);
01610   return(enhance_image);
01611 }
01612 
01613 /*
01614 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
01615 %                                                                             %
01616 %                                                                             %
01617 %                                                                             %
01618 %     E q u a l i z e I m a g e                                               %
01619 %                                                                             %
01620 %                                                                             %
01621 %                                                                             %
01622 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
01623 %
01624 %  EqualizeImage() applies a histogram equalization to the image.
01625 %
01626 %  The format of the EqualizeImage method is:
01627 %
01628 %      MagickBooleanType EqualizeImage(Image *image)
01629 %      MagickBooleanType EqualizeImageChannel(Image *image,
01630 %        const ChannelType channel)
01631 %
01632 %  A description of each parameter follows:
01633 %
01634 %    o image: the image.
01635 %
01636 %    o channel: the channel.
01637 %
01638 */
01639 
01640 MagickExport MagickBooleanType EqualizeImage(Image *image)
01641 {
01642   return(EqualizeImageChannel(image,DefaultChannels));
01643 }
01644 
01645 MagickExport MagickBooleanType EqualizeImageChannel(Image *image,
01646   const ChannelType channel)
01647 {
01648 #define EqualizeImageTag  "Equalize/Image"
01649 
01650   ExceptionInfo
01651     *exception;
01652 
01653   long
01654     progress,
01655     y;
01656 
01657   MagickBooleanType
01658     status;
01659 
01660   MagickPixelPacket
01661     black,
01662     *equalize_map,
01663     *histogram,
01664     intensity,
01665     *map,
01666     white;
01667 
01668   register long
01669     i;
01670 
01671   CacheView
01672     *image_view;
01673 
01674   /*
01675     Allocate and initialize histogram arrays.
01676   */
01677   assert(image != (Image *) NULL);
01678   assert(image->signature == MagickSignature);
01679   if (image->debug != MagickFalse)
01680     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
01681   equalize_map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
01682     sizeof(*equalize_map));
01683   histogram=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
01684     sizeof(*histogram));
01685   map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*map));
01686   if ((equalize_map == (MagickPixelPacket *) NULL) ||
01687       (histogram == (MagickPixelPacket *) NULL) ||
01688       (map == (MagickPixelPacket *) NULL))
01689     {
01690       if (map != (MagickPixelPacket *) NULL)
01691         map=(MagickPixelPacket *) RelinquishMagickMemory(map);
01692       if (histogram != (MagickPixelPacket *) NULL)
01693         histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
01694       if (equalize_map != (MagickPixelPacket *) NULL)
01695         equalize_map=(MagickPixelPacket *) RelinquishMagickMemory(equalize_map);
01696       ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
01697         image->filename);
01698     }
01699   /*
01700     Form histogram.
01701   */
01702   (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
01703   exception=(&image->exception);
01704   for (y=0; y < (long) image->rows; y++)
01705   {
01706     register const IndexPacket
01707       *__restrict indexes;
01708 
01709     register const PixelPacket
01710       *__restrict p;
01711 
01712     register long
01713       x;
01714 
01715     p=GetVirtualPixels(image,0,y,image->columns,1,exception);
01716     if (p == (const PixelPacket *) NULL)
01717       break;
01718     indexes=GetVirtualIndexQueue(image);
01719     for (x=0; x < (long) image->columns; x++)
01720     {
01721       if ((channel & RedChannel) != 0)
01722         histogram[ScaleQuantumToMap(p->red)].red++;
01723       if ((channel & GreenChannel) != 0)
01724         histogram[ScaleQuantumToMap(p->green)].green++;
01725       if ((channel & BlueChannel) != 0)
01726         histogram[ScaleQuantumToMap(p->blue)].blue++;
01727       if ((channel & OpacityChannel) != 0)
01728         histogram[ScaleQuantumToMap(p->opacity)].opacity++;
01729       if (((channel & IndexChannel) != 0) &&
01730           (image->colorspace == CMYKColorspace))
01731         histogram[ScaleQuantumToMap(indexes[x])].index++;
01732       p++;
01733     }
01734   }
01735   /*
01736     Integrate the histogram to get the equalization map.
01737   */
01738   (void) ResetMagickMemory(&intensity,0,sizeof(intensity));
01739   for (i=0; i <= (long) MaxMap; i++)
01740   {
01741     if ((channel & RedChannel) != 0)
01742       intensity.red+=histogram[i].red;
01743     if ((channel & GreenChannel) != 0)
01744       intensity.green+=histogram[i].green;
01745     if ((channel & BlueChannel) != 0)
01746       intensity.blue+=histogram[i].blue;
01747     if ((channel & OpacityChannel) != 0)
01748       intensity.opacity+=histogram[i].opacity;
01749     if (((channel & IndexChannel) != 0) &&
01750         (image->colorspace == CMYKColorspace))
01751       intensity.index+=histogram[i].index;
01752     map[i]=intensity;
01753   }
01754   black=map[0];
01755   white=map[(int) MaxMap];
01756   (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*sizeof(*equalize_map));
01757 #if defined(MAGICKCORE_OPENMP_SUPPORT)
01758   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
01759 #endif
01760   for (i=0; i <= (long) MaxMap; i++)
01761   {
01762     if (((channel & RedChannel) != 0) && (white.red != black.red))
01763       equalize_map[i].red=(MagickRealType) ScaleMapToQuantum((MagickRealType)
01764         ((MaxMap*(map[i].red-black.red))/(white.red-black.red)));
01765     if (((channel & GreenChannel) != 0) && (white.green != black.green))
01766       equalize_map[i].green=(MagickRealType) ScaleMapToQuantum((MagickRealType)
01767         ((MaxMap*(map[i].green-black.green))/(white.green-black.green)));
01768     if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
01769       equalize_map[i].blue=(MagickRealType) ScaleMapToQuantum((MagickRealType)
01770         ((MaxMap*(map[i].blue-black.blue))/(white.blue-black.blue)));
01771     if (((channel & OpacityChannel) != 0) && (white.opacity != black.opacity))
01772       equalize_map[i].opacity=(MagickRealType) ScaleMapToQuantum(
01773         (MagickRealType) ((MaxMap*(map[i].opacity-black.opacity))/
01774         (white.opacity-black.opacity)));
01775     if ((((channel & IndexChannel) != 0) &&
01776         (image->colorspace == CMYKColorspace)) &&
01777         (white.index != black.index))
01778       equalize_map[i].index=(MagickRealType) ScaleMapToQuantum((MagickRealType)
01779         ((MaxMap*(map[i].index-black.index))/(white.index-black.index)));
01780   }
01781   histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
01782   map=(MagickPixelPacket *) RelinquishMagickMemory(map);
01783   if (image->storage_class == PseudoClass)
01784     {
01785       /*
01786         Equalize colormap.
01787       */
01788 #if defined(MAGICKCORE_OPENMP_SUPPORT)
01789   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
01790 #endif
01791       for (i=0; i < (long) image->colors; i++)
01792       {
01793         if (((channel & RedChannel) != 0) && (white.red != black.red))
01794           image->colormap[i].red=RoundToQuantum(equalize_map[
01795             ScaleQuantumToMap(image->colormap[i].red)].red);
01796         if (((channel & GreenChannel) != 0) && (white.green != black.green))
01797           image->colormap[i].green=RoundToQuantum(equalize_map[
01798             ScaleQuantumToMap(image->colormap[i].green)].green);
01799         if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
01800           image->colormap[i].blue=RoundToQuantum(equalize_map[
01801             ScaleQuantumToMap(image->colormap[i].blue)].blue);
01802         if (((channel & OpacityChannel) != 0) &&
01803             (white.opacity != black.opacity))
01804           image->colormap[i].opacity=RoundToQuantum(equalize_map[
01805             ScaleQuantumToMap(image->colormap[i].opacity)].opacity);
01806       }
01807     }
01808   /*
01809     Equalize image.
01810   */
01811   status=MagickTrue;
01812   progress=0;
01813   exception=(&image->exception);
01814   image_view=AcquireCacheView(image);
01815 #if defined(MAGICKCORE_OPENMP_SUPPORT)
01816   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
01817 #endif
01818   for (y=0; y < (long) image->rows; y++)
01819   {
01820     register IndexPacket
01821       *__restrict indexes;
01822 
01823     register long
01824       x;
01825 
01826     register PixelPacket
01827       *__restrict q;
01828 
01829     if (status == MagickFalse)
01830       continue;
01831     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
01832     if (q == (PixelPacket *) NULL)
01833       {
01834         status=MagickFalse;
01835         continue;
01836       }
01837     indexes=GetCacheViewAuthenticIndexQueue(image_view);
01838     for (x=0; x < (long) image->columns; x++)
01839     {
01840       if (((channel & RedChannel) != 0) && (white.red != black.red))
01841         q->red=RoundToQuantum(equalize_map[ScaleQuantumToMap(q->red)].red);
01842       if (((channel & GreenChannel) != 0) && (white.green != black.green))
01843         q->green=RoundToQuantum(equalize_map[ScaleQuantumToMap(
01844           q->green)].green);
01845       if (((channel & BlueChannel) != 0) && (white.blue != black.blue))
01846         q->blue=RoundToQuantum(equalize_map[ScaleQuantumToMap(q->blue)].blue);
01847       if (((channel & OpacityChannel) != 0) && (white.opacity != black.opacity))
01848         q->opacity=RoundToQuantum(equalize_map[ScaleQuantumToMap(
01849           q->opacity)].opacity);
01850       if ((((channel & IndexChannel) != 0) &&
01851           (image->colorspace == CMYKColorspace)) &&
01852           (white.index != black.index))
01853         indexes[x]=RoundToQuantum(equalize_map[ScaleQuantumToMap(
01854           indexes[x])].index);
01855       q++;
01856     }
01857     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
01858       status=MagickFalse;
01859     if (image->progress_monitor != (MagickProgressMonitor) NULL)
01860       {
01861         MagickBooleanType
01862           proceed;
01863 
01864 #if defined(MAGICKCORE_OPENMP_SUPPORT)
01865   #pragma omp critical (MagickCore_EqualizeImageChannel)
01866 #endif
01867         proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
01868         if (proceed == MagickFalse)
01869           status=MagickFalse;
01870       }
01871   }
01872   image_view=DestroyCacheView(image_view);
01873   equalize_map=(MagickPixelPacket *) RelinquishMagickMemory(equalize_map);
01874   return(status);
01875 }
01876 
01877 /*
01878 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
01879 %                                                                             %
01880 %                                                                             %
01881 %                                                                             %
01882 %     G a m m a I m a g e                                                     %
01883 %                                                                             %
01884 %                                                                             %
01885 %                                                                             %
01886 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
01887 %
01888 %  GammaImage() gamma-corrects a particular image channel.  The same
01889 %  image viewed on different devices will have perceptual differences in the
01890 %  way the image's intensities are represented on the screen.  Specify
01891 %  individual gamma levels for the red, green, and blue channels, or adjust
01892 %  all three with the gamma parameter.  Values typically range from 0.8 to 2.3.
01893 %
01894 %  You can also reduce the influence of a particular channel with a gamma
01895 %  value of 0.
01896 %
01897 %  The format of the GammaImage method is:
01898 %
01899 %      MagickBooleanType GammaImage(Image *image,const double gamma)
01900 %      MagickBooleanType GammaImageChannel(Image *image,
01901 %        const ChannelType channel,const double gamma)
01902 %
01903 %  A description of each parameter follows:
01904 %
01905 %    o image: the image.
01906 %
01907 %    o channel: the channel.
01908 %
01909 %    o gamma: the image gamma.
01910 %
01911 */
01912 MagickExport MagickBooleanType GammaImage(Image *image,const char *level)
01913 {
01914   GeometryInfo
01915     geometry_info;
01916 
01917   MagickPixelPacket
01918     gamma;
01919 
01920   MagickStatusType
01921     flags,
01922     status;
01923 
01924   assert(image != (Image *) NULL);
01925   assert(image->signature == MagickSignature);
01926   if (image->debug != MagickFalse)
01927     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
01928   if (level == (char *) NULL)
01929     return(MagickFalse);
01930   flags=ParseGeometry(level,&geometry_info);
01931   gamma.red=geometry_info.rho;
01932   gamma.green=geometry_info.sigma;
01933   if ((flags & SigmaValue) == 0)
01934     gamma.green=gamma.red;
01935   gamma.blue=geometry_info.xi;
01936   if ((flags & XiValue) == 0)
01937     gamma.blue=gamma.red;
01938   if ((gamma.red == 1.0) && (gamma.green == 1.0) && (gamma.blue == 1.0))
01939     return(MagickTrue);
01940   if ((gamma.red == gamma.green) && (gamma.green == gamma.blue))
01941     status=GammaImageChannel(image,(const ChannelType) (RedChannel |
01942       GreenChannel | BlueChannel),(double) gamma.red);
01943   else
01944     {
01945       status=GammaImageChannel(image,RedChannel,(double) gamma.red);
01946       status|=GammaImageChannel(image,GreenChannel,(double) gamma.green);
01947       status|=GammaImageChannel(image,BlueChannel,(double) gamma.blue);
01948     }
01949   return(status != 0 ? MagickTrue : MagickFalse);
01950 }
01951 
01952 MagickExport MagickBooleanType GammaImageChannel(Image *image,
01953   const ChannelType channel,const double gamma)
01954 {
01955 #define GammaCorrectImageTag  "GammaCorrect/Image"
01956 
01957   ExceptionInfo
01958     *exception;
01959 
01960   long
01961     progress,
01962     y;
01963 
01964   MagickBooleanType
01965     status;
01966 
01967   Quantum
01968     *gamma_map;
01969 
01970   register long
01971     i;
01972 
01973   CacheView
01974     *image_view;
01975 
01976   /*
01977     Allocate and initialize gamma maps.
01978   */
01979   assert(image != (Image *) NULL);
01980   assert(image->signature == MagickSignature);
01981   if (image->debug != MagickFalse)
01982     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
01983   if (gamma == 1.0)
01984     return(MagickTrue);
01985   gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
01986   if (gamma_map == (Quantum *) NULL)
01987     ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
01988       image->filename);
01989   (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
01990   if (gamma != 0.0)
01991 #if defined(MAGICKCORE_OPENMP_SUPPORT)
01992   #pragma omp parallel for schedule(dynamic,4)
01993 #endif
01994     for (i=0; i <= (long) MaxMap; i++)
01995       gamma_map[i]=RoundToQuantum((MagickRealType) ScaleMapToQuantum((
01996         MagickRealType) (MaxMap*pow((double) i/MaxMap,1.0/gamma))));
01997   if (image->storage_class == PseudoClass)
01998     {
01999       /*
02000         Gamma-correct colormap.
02001       */
02002 #if defined(MAGICKCORE_OPENMP_SUPPORT)
02003   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
02004 #endif
02005       for (i=0; i < (long) image->colors; i++)
02006       {
02007         if ((channel & RedChannel) != 0)
02008           image->colormap[i].red=gamma_map[
02009             ScaleQuantumToMap(image->colormap[i].red)];
02010         if ((channel & GreenChannel) != 0)
02011           image->colormap[i].green=gamma_map[
02012             ScaleQuantumToMap(image->colormap[i].green)];
02013         if ((channel & BlueChannel) != 0)
02014           image->colormap[i].blue=gamma_map[
02015             ScaleQuantumToMap(image->colormap[i].blue)];
02016         if ((channel & OpacityChannel) != 0)
02017           {
02018             if (image->matte == MagickFalse)
02019               image->colormap[i].opacity=gamma_map[
02020                 ScaleQuantumToMap(image->colormap[i].opacity)];
02021             else
02022               image->colormap[i].opacity=(Quantum) QuantumRange-
02023                 gamma_map[ScaleQuantumToMap((Quantum) (QuantumRange-
02024                 image->colormap[i].opacity))];
02025           }
02026       }
02027     }
02028   /*
02029     Gamma-correct image.
02030   */
02031   status=MagickTrue;
02032   progress=0;
02033   exception=(&image->exception);
02034   image_view=AcquireCacheView(image);
02035 #if defined(MAGICKCORE_OPENMP_SUPPORT)
02036   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
02037 #endif
02038   for (y=0; y < (long) image->rows; y++)
02039   {
02040     register IndexPacket
02041       *__restrict indexes;
02042 
02043     register long
02044       x;
02045 
02046     register PixelPacket
02047       *__restrict q;
02048 
02049     if (status == MagickFalse)
02050       continue;
02051     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
02052     if (q == (PixelPacket *) NULL)
02053       {
02054         status=MagickFalse;
02055         continue;
02056       }
02057     indexes=GetCacheViewAuthenticIndexQueue(image_view);
02058     for (x=0; x < (long) image->columns; x++)
02059     {
02060       if (channel == DefaultChannels)
02061         {
02062           q->red=gamma_map[ScaleQuantumToMap(q->red)];
02063           q->green=gamma_map[ScaleQuantumToMap(q->green)];
02064           q->blue=gamma_map[ScaleQuantumToMap(q->blue)];
02065         }
02066       else
02067         {
02068           if ((channel & RedChannel) != 0)
02069             q->red=gamma_map[ScaleQuantumToMap(q->red)];
02070           if ((channel & GreenChannel) != 0)
02071             q->green=gamma_map[ScaleQuantumToMap(q->green)];
02072           if ((channel & BlueChannel) != 0)
02073             q->blue=gamma_map[ScaleQuantumToMap(q->blue)];
02074           if ((channel & OpacityChannel) != 0)
02075             {
02076               if (image->matte == MagickFalse)
02077                 q->opacity=gamma_map[ScaleQuantumToMap(q->opacity)];
02078               else
02079                 q->opacity=(Quantum) QuantumRange-gamma_map[
02080                   ScaleQuantumToMap((Quantum) (QuantumRange-q->opacity))];
02081             }
02082         }
02083       q++;
02084     }
02085     if (((channel & IndexChannel) != 0) &&
02086         (image->colorspace == CMYKColorspace))
02087       for (x=0; x < (long) image->columns; x++)
02088         indexes[x]=gamma_map[ScaleQuantumToMap(indexes[x])];
02089     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
02090       status=MagickFalse;
02091     if (image->progress_monitor != (MagickProgressMonitor) NULL)
02092       {
02093         MagickBooleanType
02094           proceed;
02095 
02096 #if defined(MAGICKCORE_OPENMP_SUPPORT)
02097   #pragma omp critical (MagickCore_GammaImageChannel)
02098 #endif
02099         proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
02100           image->rows);
02101         if (proceed == MagickFalse)
02102           status=MagickFalse;
02103       }
02104   }
02105   image_view=DestroyCacheView(image_view);
02106   gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
02107   if (image->gamma != 0.0)
02108     image->gamma*=gamma;
02109   return(status);
02110 }
02111 
02112 /*
02113 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
02114 %                                                                             %
02115 %                                                                             %
02116 %                                                                             %
02117 %     H a l d C l u t I m a g e                                               %
02118 %                                                                             %
02119 %                                                                             %
02120 %                                                                             %
02121 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
02122 %
02123 %  HaldClutImage() applies a Hald color lookup table to the image.  A Hald
02124 %  color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
02125 %  Create it with the HALD coder.  You can apply any color transformation to
02126 %  the Hald image and then use this method to apply the transform to the
02127 %  image.
02128 %
02129 %  The format of the HaldClutImage method is:
02130 %
02131 %      MagickBooleanType HaldClutImage(Image *image,Image *hald_image)
02132 %      MagickBooleanType HaldClutImageChannel(Image *image,
02133 %        const ChannelType channel,Image *hald_image)
02134 %
02135 %  A description of each parameter follows:
02136 %
02137 %    o image: the image, which is replaced by indexed CLUT values
02138 %
02139 %    o hald_image: the color lookup table image for replacement color values.
02140 %
02141 %    o channel: the channel.
02142 %
02143 */
02144 
02145 static inline size_t MagickMin(const size_t x,const size_t y)
02146 {
02147   if (x < y)
02148     return(x);
02149   return(y);
02150 }
02151 
02152 MagickExport MagickBooleanType HaldClutImage(Image *image,
02153   const Image *hald_image)
02154 {
02155   return(HaldClutImageChannel(image,DefaultChannels,hald_image));
02156 }
02157 
02158 MagickExport MagickBooleanType HaldClutImageChannel(Image *image,
02159   const ChannelType channel,const Image *hald_image)
02160 {
02161 #define HaldClutImageTag  "Clut/Image"
02162 
02163   typedef struct _HaldInfo
02164   {
02165     MagickRealType
02166       x,
02167       y,
02168       z;
02169   } HaldInfo;
02170 
02171   double
02172     width;
02173 
02174   ExceptionInfo
02175     *exception;
02176 
02177   long
02178     progress,
02179     y;
02180 
02181   MagickBooleanType
02182     status;
02183 
02184   MagickPixelPacket
02185     zero;
02186 
02187   ResampleFilter
02188     **resample_filter;
02189 
02190   size_t
02191     cube_size,
02192     length,
02193     level;
02194 
02195   CacheView
02196     *image_view;
02197 
02198   assert(image != (Image *) NULL);
02199   assert(image->signature == MagickSignature);
02200   if (image->debug != MagickFalse)
02201     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
02202   assert(hald_image != (Image *) NULL);
02203   assert(hald_image->signature == MagickSignature);
02204   if (SetImageStorageClass(image,DirectClass) == MagickFalse)
02205     return(MagickFalse);
02206   if (image->matte == MagickFalse)
02207     (void) SetImageAlphaChannel(image,OpaqueAlphaChannel);
02208   /*
02209     Hald clut image.
02210   */
02211   status=MagickTrue;
02212   progress=0;
02213   length=MagickMin(hald_image->columns,hald_image->rows);
02214   for (level=2; (level*level*level) < length; level++) ;
02215   level*=level;
02216   cube_size=level*level;
02217   width=(double) hald_image->columns;
02218   GetMagickPixelPacket(hald_image,&zero);
02219   exception=(&image->exception);
02220   resample_filter=AcquireResampleFilterThreadSet(hald_image,MagickTrue,
02221     exception);
02222   image_view=AcquireCacheView(image);
02223 #if defined(MAGICKCORE_OPENMP_SUPPORT)
02224   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
02225 #endif
02226   for (y=0; y < (long) image->rows; y++)
02227   {
02228     double
02229       offset;
02230 
02231     HaldInfo
02232       point;
02233 
02234     MagickPixelPacket
02235       pixel,
02236       pixel1,
02237       pixel2,
02238       pixel3,
02239       pixel4;
02240 
02241     register IndexPacket
02242       *__restrict indexes;
02243 
02244     register long
02245       id,
02246       x;
02247 
02248     register PixelPacket
02249       *__restrict q;
02250 
02251     if (status == MagickFalse)
02252       continue;
02253     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
02254     if (q == (PixelPacket *) NULL)
02255       {
02256         status=MagickFalse;
02257         continue;
02258       }
02259     indexes=GetCacheViewAuthenticIndexQueue(image_view);
02260     pixel=zero;
02261     pixel1=zero;
02262     pixel2=zero;
02263     pixel3=zero;
02264     pixel4=zero;
02265     id=GetOpenMPThreadId();
02266     for (x=0; x < (long) image->columns; x++)
02267     {
02268       point.x=QuantumScale*(level-1.0)*q->red;
02269       point.y=QuantumScale*(level-1.0)*q->green;
02270       point.z=QuantumScale*(level-1.0)*q->blue;
02271       offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
02272       point.x-=floor(point.x);
02273       point.y-=floor(point.y);
02274       point.z-=floor(point.z);
02275       (void) ResamplePixelColor(resample_filter[id],fmod(offset,width),
02276         floor(offset/width),&pixel1);
02277       (void) ResamplePixelColor(resample_filter[id],fmod(offset+level,width),
02278         floor((offset+level)/width),&pixel2);
02279       MagickPixelCompositeAreaBlend(&pixel1,pixel1.opacity,&pixel2,
02280         pixel2.opacity,point.y,&pixel3);
02281       offset+=cube_size;
02282       (void) ResamplePixelColor(resample_filter[id],fmod(offset,width),
02283         floor(offset/width),&pixel1);
02284       (void) ResamplePixelColor(resample_filter[id],fmod(offset+level,width),
02285         floor((offset+level)/width),&pixel2);
02286       MagickPixelCompositeAreaBlend(&pixel1,pixel1.opacity,&pixel2,
02287         pixel2.opacity,point.y,&pixel4);
02288       MagickPixelCompositeAreaBlend(&pixel3,pixel3.opacity,&pixel4,
02289         pixel4.opacity,point.z,&pixel);
02290       if ((channel & RedChannel) != 0)
02291         q->red=RoundToQuantum(pixel.red);
02292       if ((channel & GreenChannel) != 0)
02293         q->green=RoundToQuantum(pixel.green);
02294       if ((channel & BlueChannel) != 0)
02295         q->blue=RoundToQuantum(pixel.blue);
02296       if (((channel & OpacityChannel) != 0) && (image->matte != MagickFalse))
02297         q->opacity=RoundToQuantum(pixel.opacity);
02298       if (((channel & IndexChannel) != 0) &&
02299           (image->colorspace == CMYKColorspace))
02300         indexes[x]=RoundToQuantum(pixel.index);
02301       q++;
02302     }
02303     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
02304       status=MagickFalse;
02305     if (image->progress_monitor != (MagickProgressMonitor) NULL)
02306       {
02307         MagickBooleanType
02308           proceed;
02309 
02310 #if defined(MAGICKCORE_OPENMP_SUPPORT)
02311   #pragma omp critical (MagickCore_HaldClutImageChannel)
02312 #endif
02313         proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
02314         if (proceed == MagickFalse)
02315           status=MagickFalse;
02316       }
02317   }
02318   image_view=DestroyCacheView(image_view);
02319   resample_filter=DestroyResampleFilterThreadSet(resample_filter);
02320   return(status);
02321 }
02322 
02323 /*
02324 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
02325 %                                                                             %
02326 %                                                                             %
02327 %                                                                             %
02328 %     L e v e l I m a g e                                                     %
02329 %                                                                             %
02330 %                                                                             %
02331 %                                                                             %
02332 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
02333 %
02334 %  LevelImage() adjusts the levels of a particular image channel by
02335 %  scaling the colors falling between specified white and black points to
02336 %  the full available quantum range.
02337 %
02338 %  The parameters provided represent the black, and white points.  The black
02339 %  point specifies the darkest color in the image. Colors darker than the
02340 %  black point are set to zero.  White point specifies the lightest color in
02341 %  the image.  Colors brighter than the white point are set to the maximum
02342 %  quantum value.
02343 %
02344 %  If a '!' flag is given, map black and white colors to the given levels
02345 %  rather than mapping those levels to black and white.  See
02346 %  LevelizeImageChannel() and LevelizeImageChannel(), below.
02347 %
02348 %  Gamma specifies a gamma correction to apply to the image.
02349 %
02350 %  The format of the LevelImage method is:
02351 %
02352 %      MagickBooleanType LevelImage(Image *image,const char *levels)
02353 %
02354 %  A description of each parameter follows:
02355 %
02356 %    o image: the image.
02357 %
02358 %    o levels: Specify the levels where the black and white points have the
02359 %      range of 0-QuantumRange, and gamma has the range 0-10 (e.g. 10x90%+2).
02360 %      A '!' flag inverts the re-mapping.
02361 %
02362 */
02363 
02364 MagickExport MagickBooleanType LevelImage(Image *image,const char *levels)
02365 {
02366   double
02367     black_point,
02368     gamma,
02369     white_point;
02370 
02371   GeometryInfo
02372     geometry_info;
02373 
02374   MagickBooleanType
02375     status;
02376 
02377   MagickStatusType
02378     flags;
02379 
02380   /*
02381     Parse levels.
02382   */
02383   if (levels == (char *) NULL)
02384     return(MagickFalse);
02385   flags=ParseGeometry(levels,&geometry_info);
02386   black_point=geometry_info.rho;
02387   white_point=(double) QuantumRange;
02388   if ((flags & SigmaValue) != 0)
02389     white_point=geometry_info.sigma;
02390   gamma=1.0;
02391   if ((flags & XiValue) != 0)
02392     gamma=geometry_info.xi;
02393   if ((flags & PercentValue) != 0)
02394     {
02395       black_point*=(double) image->columns*image->rows/100.0;
02396       white_point*=(double) image->columns*image->rows/100.0;
02397     }
02398   if ((flags & SigmaValue) == 0)
02399     white_point=(double) QuantumRange-black_point;
02400   if ((flags & AspectValue ) == 0)
02401     status=LevelImageChannel(image,DefaultChannels,black_point,white_point,
02402       gamma);
02403   else
02404     status=LevelizeImage(image,black_point,white_point,gamma);
02405   return(status);
02406 }
02407 
02408 /*
02409 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
02410 %                                                                             %
02411 %                                                                             %
02412 %                                                                             %
02413 %     L e v e l i z e I m a g e                                               %
02414 %                                                                             %
02415 %                                                                             %
02416 %                                                                             %
02417 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
02418 %
02419 %  LevelizeImage() applies the normal level operation to the image, spreading
02420 %  out the values between the black and white points over the entire range of
02421 %  values.  Gamma correction is also applied after the values has been mapped.
02422 %
02423 %  It is typically used to improve image contrast, or to provide a controlled
02424 %  linear threshold for the image. If the black and white points are set to
02425 %  the minimum and maximum values found in the image, the image can be
02426 %  normalized.  or by swapping black and white values, negate the image.
02427 %
02428 %  The format of the LevelizeImage method is:
02429 %
02430 %      MagickBooleanType LevelizeImage(Image *image,const double black_point,
02431 %        const double white_point,const double gamma)
02432 %      MagickBooleanType LevelizeImageChannel(Image *image,
02433 %        const ChannelType channel,const double black_point,
02434 %        const double white_point,const double gamma)
02435 %
02436 %  A description of each parameter follows:
02437 %
02438 %    o image: the image.
02439 %
02440 %    o channel: the channel.
02441 %
02442 %    o black_point: The level which is to be mapped to zero (black)
02443 %
02444 %    o white_point: The level which is to be mapped to QuantiumRange (white)
02445 %
02446 %    o gamma: adjust gamma by this factor before mapping values.
02447 %             use 1.0 for purely linear stretching of image color values
02448 %
02449 */
02450 
02451 MagickExport MagickBooleanType LevelizeImage(Image *image,
02452   const double black_point,const double white_point,const double gamma)
02453 {
02454   MagickBooleanType
02455     status;
02456 
02457   status=LevelizeImageChannel(image,DefaultChannels,black_point,white_point,
02458     gamma);
02459   return(status);
02460 }
02461 
02462 MagickExport MagickBooleanType LevelImageChannel(Image *image,
02463   const ChannelType channel,const double black_point,const double white_point,
02464   const double gamma)
02465 {
02466 #define LevelImageTag  "Level/Image"
02467 #define LevelValue(x) (RoundToQuantum((MagickRealType) QuantumRange* \
02468   pow(((double) (x)-black_point)/(white_point-black_point),1.0/gamma)))
02469 
02470   ExceptionInfo
02471     *exception;
02472 
02473   long
02474     progress,
02475     y;
02476 
02477   MagickBooleanType
02478     status;
02479 
02480   register long
02481     i;
02482 
02483   CacheView
02484     *image_view;
02485 
02486   /*
02487     Allocate and initialize levels map.
02488   */
02489   assert(image != (Image *) NULL);
02490   assert(image->signature == MagickSignature);
02491   if (image->debug != MagickFalse)
02492     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
02493   if (image->storage_class == PseudoClass)
02494 #if defined(MAGICKCORE_OPENMP_SUPPORT)
02495   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
02496 #endif
02497     for (i=0; i < (long) image->colors; i++)
02498     {
02499       /*
02500         Level colormap.
02501       */
02502       if ((channel & RedChannel) != 0)
02503         image->colormap[i].red=LevelValue(image->colormap[i].red);
02504       if ((channel & GreenChannel) != 0)
02505         image->colormap[i].green=LevelValue(image->colormap[i].green);
02506       if ((channel & BlueChannel) != 0)
02507         image->colormap[i].blue=LevelValue(image->colormap[i].blue);
02508       if ((channel & OpacityChannel) != 0)
02509         image->colormap[i].opacity=LevelValue(image->colormap[i].opacity);
02510       }
02511   /*
02512     Level image.
02513   */
02514   status=MagickTrue;
02515   progress=0;
02516   exception=(&image->exception);
02517   image_view=AcquireCacheView(image);
02518 #if defined(MAGICKCORE_OPENMP_SUPPORT)
02519   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
02520 #endif
02521   for (y=0; y < (long) image->rows; y++)
02522   {
02523     register IndexPacket
02524       *__restrict indexes;
02525 
02526     register long
02527       x;
02528 
02529     register PixelPacket
02530       *__restrict q;
02531 
02532     if (status == MagickFalse)
02533       continue;
02534     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
02535     if (q == (PixelPacket *) NULL)
02536       {
02537         status=MagickFalse;
02538         continue;
02539       }
02540     indexes=GetCacheViewAuthenticIndexQueue(image_view);
02541     for (x=0; x < (long) image->columns; x++)
02542     {
02543       if ((channel & RedChannel) != 0)
02544         q->red=LevelValue(q->red);
02545       if ((channel & GreenChannel) != 0)
02546         q->green=LevelValue(q->green);
02547       if ((channel & BlueChannel) != 0)
02548         q->blue=LevelValue(q->blue);
02549       if (((channel & OpacityChannel) != 0) &&
02550           (image->matte == MagickTrue))
02551         q->opacity=LevelValue(q->opacity);
02552       if (((channel & IndexChannel) != 0) &&
02553           (image->colorspace == CMYKColorspace))
02554         indexes[x]=LevelValue(indexes[x]);
02555       q++;
02556     }
02557     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
02558       status=MagickFalse;
02559     if (image->progress_monitor != (MagickProgressMonitor) NULL)
02560       {
02561         MagickBooleanType
02562           proceed;
02563 
02564 #if defined(MAGICKCORE_OPENMP_SUPPORT)
02565   #pragma omp critical (MagickCore_LevelImageChannel)
02566 #endif
02567         proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
02568         if (proceed == MagickFalse)
02569           status=MagickFalse;
02570       }
02571   }
02572   image_view=DestroyCacheView(image_view);
02573   return(status);
02574 }
02575 
02576 /*
02577 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
02578 %                                                                             %
02579 %                                                                             %
02580 %                                                                             %
02581 %     L e v e l i z e I m a g e C h a n n e l                                 %
02582 %                                                                             %
02583 %                                                                             %
02584 %                                                                             %
02585 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
02586 %
02587 %  LevelizeImageChannel() applies the reversed LevelImage() operation to just
02588 %  the specific channels specified.  It compresses the full range of color
02589 %  values, so that they lie between the given black and white points. Gamma is
02590 %  applied before the values are mapped.
02591 %
02592 %  LevelizeImageChannel() can be called with by using a +level command line
02593 %  API option, or using a '!' on a -level or LevelImage() geometry string.
02594 %
02595 %  It can be used for example de-contrast a greyscale image to the exact
02596 %  levels specified.  Or by using specific levels for each channel of an image
02597 %  you can convert a gray-scale image to any linear color gradient, according
02598 %  to those levels.
02599 %
02600 %  The format of the LevelizeImageChannel method is:
02601 %
02602 %      MagickBooleanType LevelizeImageChannel(Image *image,
02603 %        const ChannelType channel,const char *levels)
02604 %
02605 %  A description of each parameter follows:
02606 %
02607 %    o image: the image.
02608 %
02609 %    o channel: the channel.
02610 %
02611 %    o black_point: The level to map zero (black) to.
02612 %
02613 %    o white_point: The level to map QuantiumRange (white) to.
02614 %
02615 %    o gamma: adjust gamma by this factor before mapping values.
02616 %
02617 */
02618 MagickExport MagickBooleanType LevelizeImageChannel(Image *image,
02619   const ChannelType channel,const double black_point,const double white_point,
02620   const double gamma)
02621 {
02622 #define LevelizeImageTag  "Levelize/Image"
02623 #define LevelizeValue(x) (RoundToQuantum(((MagickRealType) \
02624   pow((double)(QuantumScale*(x)),1.0/gamma))*(white_point-black_point)+ \
02625   black_point))
02626 
02627   ExceptionInfo
02628     *exception;
02629 
02630   long
02631     progress,
02632     y;
02633 
02634   MagickBooleanType
02635     status;
02636 
02637   register long
02638     i;
02639 
02640   CacheView
02641     *image_view;
02642 
02643   /*
02644     Allocate and initialize levels map.
02645   */
02646   assert(image != (Image *) NULL);
02647   assert(image->signature == MagickSignature);
02648   if (image->debug != MagickFalse)
02649     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
02650   if (image->storage_class == PseudoClass)
02651 #if defined(MAGICKCORE_OPENMP_SUPPORT)
02652   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
02653 #endif
02654     for (i=0; i < (long) image->colors; i++)
02655     {
02656       /*
02657         Level colormap.
02658       */
02659       if ((channel & RedChannel) != 0)
02660         image->colormap[i].red=LevelizeValue(image->colormap[i].red);
02661       if ((channel & GreenChannel) != 0)
02662         image->colormap[i].green=LevelizeValue(image->colormap[i].green);
02663       if ((channel & BlueChannel) != 0)
02664         image->colormap[i].blue=LevelizeValue(image->colormap[i].blue);
02665       if ((channel & OpacityChannel) != 0)
02666         image->colormap[i].opacity=LevelizeValue(image->colormap[i].opacity);
02667     }
02668   /*
02669     Level image.
02670   */
02671   status=MagickTrue;
02672   progress=0;
02673   exception=(&image->exception);
02674   image_view=AcquireCacheView(image);
02675 #if defined(MAGICKCORE_OPENMP_SUPPORT)
02676   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
02677 #endif
02678   for (y=0; y < (long) image->rows; y++)
02679   {
02680     register IndexPacket
02681       *__restrict indexes;
02682 
02683     register long
02684       x;
02685 
02686     register PixelPacket
02687       *__restrict q;
02688 
02689     if (status == MagickFalse)
02690       continue;
02691     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
02692     if (q == (PixelPacket *) NULL)
02693       {
02694         status=MagickFalse;
02695         continue;
02696       }
02697     indexes=GetCacheViewAuthenticIndexQueue(image_view);
02698     for (x=0; x < (long) image->columns; x++)
02699     {
02700       if ((channel & RedChannel) != 0)
02701         q->red=LevelizeValue(q->red);
02702       if ((channel & GreenChannel) != 0)
02703         q->green=LevelizeValue(q->green);
02704       if ((channel & BlueChannel) != 0)
02705         q->blue=LevelizeValue(q->blue);
02706       if (((channel & OpacityChannel) != 0) &&
02707           (image->matte == MagickTrue))
02708         q->opacity=LevelizeValue(q->opacity);
02709       if (((channel & IndexChannel) != 0) &&
02710           (image->colorspace == CMYKColorspace))
02711         indexes[x]=LevelizeValue(indexes[x]);
02712       q++;
02713     }
02714     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
02715       status=MagickFalse;
02716     if (image->progress_monitor != (MagickProgressMonitor) NULL)
02717       {
02718         MagickBooleanType
02719           proceed;
02720 
02721 #if defined(MAGICKCORE_OPENMP_SUPPORT)
02722   #pragma omp critical (MagickCore_LevelizeImageChannel)
02723 #endif
02724         proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
02725         if (proceed == MagickFalse)
02726           status=MagickFalse;
02727       }
02728   }
02729   return(status);
02730 }
02731 
02732 /*
02733 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
02734 %                                                                             %
02735 %                                                                             %
02736 %                                                                             %
02737 %     L e v e l I m a g e C o l o r s                                         %
02738 %                                                                             %
02739 %                                                                             %
02740 %                                                                             %
02741 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
02742 %
02743 %  LevelImageColor() maps the given color to "black" and "white" values,
02744 %  linearly spreading out the colors, and level values on a channel by channel
02745 %  bases, as per LevelImage().  The given colors allows you to specify
02746 %  different level ranges for each of the color channels seperatally.
02747 %
02748 %  If the boolean 'invert' is set true the image values will modifyed in the
02749 %  reverse direction. That is any existing "black" and "white" colors in the
02750 %  image will become the color values given, with all other values compressed
02751 %  appropriatally.  This effectivally maps a greyscale gradient into the given
02752 %  color gradient.
02753 %
02754 %  The format of the LevelColorsImageChannel method is:
02755 %
02756 %    MagickBooleanType LevelColorsImage(Image *image,
02757 %      const MagickPixelPacket *black_color,
02758 %      const MagickPixelPacket *white_color,const MagickBooleanType invert)
02759 %    MagickBooleanType LevelColorsImageChannel(Image *image,
02760 %      const ChannelType channel,const MagickPixelPacket *black_color,
02761 %      const MagickPixelPacket *white_color,const MagickBooleanType invert)
02762 %
02763 %  A description of each parameter follows:
02764 %
02765 %    o image: the image.
02766 %
02767 %    o channel: the channel.
02768 %
02769 %    o black_color: The color to map black to/from
02770 %
02771 %    o white_point: The color to map white to/from
02772 %
02773 %    o invert: if true map the colors (levelize), rather than from (level)
02774 %
02775 */
02776 
02777 MagickExport MagickBooleanType LevelColorsImage(Image *image,
02778   const MagickPixelPacket *black_color,const MagickPixelPacket *white_color,
02779   const MagickBooleanType invert)
02780 {
02781   MagickBooleanType
02782     status;
02783 
02784   status=LevelColorsImageChannel(image,DefaultChannels,black_color,white_color,
02785     invert);
02786   return(status);
02787 }
02788 
02789 MagickExport MagickBooleanType LevelColorsImageChannel(Image *image,
02790   const ChannelType channel,const MagickPixelPacket *black_color,
02791   const MagickPixelPacket *white_color,const MagickBooleanType invert)
02792 {
02793   MagickStatusType
02794     status;
02795 
02796   /*
02797     Allocate and initialize levels map.
02798   */
02799   assert(image != (Image *) NULL);
02800   assert(image->signature == MagickSignature);
02801   if (image->debug != MagickFalse)
02802     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
02803   status=MagickFalse;
02804   if (invert == MagickFalse)
02805     {
02806       if ((channel & RedChannel) != 0)
02807         status|=LevelImageChannel(image,RedChannel,
02808           black_color->red,white_color->red,(double) 1.0);
02809       if ((channel & GreenChannel) != 0)
02810         status|=LevelImageChannel(image,GreenChannel,
02811           black_color->green,white_color->green,(double) 1.0);
02812       if ((channel & BlueChannel) != 0)
02813         status|=LevelImageChannel(image,BlueChannel,
02814           black_color->blue,white_color->blue,(double) 1.0);
02815       if (((channel & OpacityChannel) != 0) &&
02816           (image->matte == MagickTrue))
02817         status|=LevelImageChannel(image,OpacityChannel,
02818           black_color->opacity,white_color->opacity,(double) 1.0);
02819       if (((channel & IndexChannel) != 0) &&
02820           (image->colorspace == CMYKColorspace))
02821         status|=LevelImageChannel(image,IndexChannel,
02822           black_color->index,white_color->index,(double) 1.0);
02823     }
02824   else
02825     {
02826       if ((channel & RedChannel) != 0)
02827         status|=LevelizeImageChannel(image,RedChannel,
02828           black_color->red,white_color->red,(double) 1.0);
02829       if ((channel & GreenChannel) != 0)
02830         status|=LevelizeImageChannel(image,GreenChannel,
02831           black_color->green,white_color->green,(double) 1.0);
02832       if ((channel & BlueChannel) != 0)
02833         status|=LevelizeImageChannel(image,BlueChannel,
02834           black_color->blue,white_color->blue,(double) 1.0);
02835       if (((channel & OpacityChannel) != 0) &&
02836           (image->matte == MagickTrue))
02837         status|=LevelizeImageChannel(image,OpacityChannel,
02838           black_color->opacity,white_color->opacity,(double) 1.0);
02839       if (((channel & IndexChannel) != 0) &&
02840           (image->colorspace == CMYKColorspace))
02841         status|=LevelizeImageChannel(image,IndexChannel,
02842           black_color->index,white_color->index,(double) 1.0);
02843     }
02844   return(status == 0 ? MagickFalse : MagickTrue);
02845 }
02846 
02847 /*
02848 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
02849 %                                                                             %
02850 %                                                                             %
02851 %                                                                             %
02852 %     L i n e a r S t r e t c h I m a g e                                     %
02853 %                                                                             %
02854 %                                                                             %
02855 %                                                                             %
02856 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
02857 %
02858 %  The LinearStretchImage() discards any pixels below the black point and
02859 %  above the white point and levels the remaining pixels.
02860 %
02861 %  The format of the LinearStretchImage method is:
02862 %
02863 %      MagickBooleanType LinearStretchImage(Image *image,
02864 %        const double black_point,const double white_point)
02865 %
02866 %  A description of each parameter follows:
02867 %
02868 %    o image: the image.
02869 %
02870 %    o black_point: the black point.
02871 %
02872 %    o white_point: the white point.
02873 %
02874 */
02875 MagickExport MagickBooleanType LinearStretchImage(Image *image,
02876   const double black_point,const double white_point)
02877 {
02878 #define LinearStretchImageTag  "LinearStretch/Image"
02879 
02880   ExceptionInfo
02881     *exception;
02882 
02883   long
02884     black,
02885     white,
02886     y;
02887 
02888   MagickBooleanType
02889     status;
02890 
02891   MagickRealType
02892     *histogram,
02893     intensity;
02894 
02895   MagickSizeType
02896     number_pixels;
02897 
02898   /*
02899     Allocate histogram and linear map.
02900   */
02901   assert(image != (Image *) NULL);
02902   assert(image->signature == MagickSignature);
02903   histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
02904     sizeof(*histogram));
02905   if (histogram == (MagickRealType *) NULL)
02906     ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
02907       image->filename);
02908   /*
02909     Form histogram.
02910   */
02911   (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
02912   exception=(&image->exception);
02913   for (y=0; y < (long) image->rows; y++)
02914   {
02915     register const PixelPacket
02916       *__restrict p;
02917 
02918     register long
02919       x;
02920 
02921     p=GetVirtualPixels(image,0,y,image->columns,1,exception);
02922     if (p == (const PixelPacket *) NULL)
02923       break;
02924     for (x=(long) image->columns-1; x >= 0; x--)
02925     {
02926       histogram[ScaleQuantumToMap(PixelIntensityToQuantum(p))]++;
02927       p++;
02928     }
02929   }
02930   /*
02931     Find the histogram boundaries by locating the black and white point levels.
02932   */
02933   number_pixels=(MagickSizeType) image->columns*image->rows;
02934   intensity=0.0;
02935   for (black=0; black < (long) MaxMap; black++)
02936   {
02937     intensity+=histogram[black];
02938     if (intensity >= black_point)
02939       break;
02940   }
02941   intensity=0.0;
02942   for (white=(long) MaxMap; white != 0; white--)
02943   {
02944     intensity+=histogram[white];
02945     if (intensity >= white_point)
02946       break;
02947   }
02948   histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
02949   status=LevelImageChannel(image,DefaultChannels,(double) black,(double) white,
02950     1.0);
02951   return(status);
02952 }
02953 
02954 /*
02955 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
02956 %                                                                             %
02957 %                                                                             %
02958 %                                                                             %
02959 %     M o d u l a t e I m a g e                                               %
02960 %                                                                             %
02961 %                                                                             %
02962 %                                                                             %
02963 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
02964 %
02965 %  ModulateImage() lets you control the brightness, saturation, and hue
02966 %  of an image.  Modulate represents the brightness, saturation, and hue
02967 %  as one parameter (e.g. 90,150,100).  If the image colorspace is HSL, the
02968 %  modulation is lightness, saturation, and hue.  And if the colorspace is
02969 %  HWB, use blackness, whiteness, and hue.
02970 %
02971 %  The format of the ModulateImage method is:
02972 %
02973 %      MagickBooleanType ModulateImage(Image *image,const char *modulate)
02974 %
02975 %  A description of each parameter follows:
02976 %
02977 %    o image: the image.
02978 %
02979 %    o modulate: Define the percent change in brightness, saturation, and
02980 %      hue.
02981 %
02982 */
02983 
02984 static void ModulateHSB(const double percent_hue,
02985   const double percent_saturation,const double percent_brightness,
02986   Quantum *red,Quantum *green,Quantum *blue)
02987 {
02988   double
02989     brightness,
02990     hue,
02991     saturation;
02992 
02993   /*
02994     Increase or decrease color brightness, saturation, or hue.
02995   */
02996   assert(red != (Quantum *) NULL);
02997   assert(green != (Quantum *) NULL);
02998   assert(blue != (Quantum *) NULL);
02999   ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
03000   hue+=0.5*(0.01*percent_hue-1.0);
03001   while (hue < 0.0)
03002     hue+=1.0;
03003   while (hue > 1.0)
03004     hue-=1.0;
03005   saturation*=0.01*percent_saturation;
03006   brightness*=0.01*percent_brightness;
03007   ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
03008 }
03009 
03010 static void ModulateHSL(const double percent_hue,
03011   const double percent_saturation,const double percent_lightness,
03012   Quantum *red,Quantum *green,Quantum *blue)
03013 {
03014   double
03015     hue,
03016     lightness,
03017     saturation;
03018 
03019   /*
03020     Increase or decrease color lightness, saturation, or hue.
03021   */
03022   assert(red != (Quantum *) NULL);
03023   assert(green != (Quantum *) NULL);
03024   assert(blue != (Quantum *) NULL);
03025   ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
03026   hue+=0.5*(0.01*percent_hue-1.0);
03027   while (hue < 0.0)
03028     hue+=1.0;
03029   while (hue > 1.0)
03030     hue-=1.0;
03031   saturation*=0.01*percent_saturation;
03032   lightness*=0.01*percent_lightness;
03033   ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
03034 }
03035 
03036 static void ModulateHWB(const double percent_hue,const double percent_whiteness,  const double percent_blackness,Quantum *red,Quantum *green,Quantum *blue)
03037 {
03038   double
03039     blackness,
03040     hue,
03041     whiteness;
03042 
03043   /*
03044     Increase or decrease color blackness, whiteness, or hue.
03045   */
03046   assert(red != (Quantum *) NULL);
03047   assert(green != (Quantum *) NULL);
03048   assert(blue != (Quantum *) NULL);
03049   ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
03050   hue+=0.5*(0.01*percent_hue-1.0);
03051   while (hue < 0.0)
03052     hue+=1.0;
03053   while (hue > 1.0)
03054     hue-=1.0;
03055   blackness*=0.01*percent_blackness;
03056   whiteness*=0.01*percent_whiteness;
03057   ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
03058 }
03059 
03060 MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate)
03061 {
03062 #define ModulateImageTag  "Modulate/Image"
03063 
03064   ColorspaceType
03065     colorspace;
03066 
03067   const char
03068     *artifact;
03069 
03070   double
03071     percent_brightness,
03072     percent_hue,
03073     percent_saturation;
03074 
03075   ExceptionInfo
03076     *exception;
03077 
03078   GeometryInfo
03079     geometry_info;
03080 
03081   long
03082     progress,
03083     y;
03084 
03085   MagickBooleanType
03086     status;
03087 
03088   MagickStatusType
03089     flags;
03090 
03091   register long
03092     i;
03093 
03094   CacheView
03095     *image_view;
03096 
03097   /*
03098     Initialize gamma table.
03099   */
03100   assert(image != (Image *) NULL);
03101   assert(image->signature == MagickSignature);
03102   if (image->debug != MagickFalse)
03103     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
03104   if (modulate == (char *) NULL)
03105     return(MagickFalse);
03106   flags=ParseGeometry(modulate,&geometry_info);
03107   percent_brightness=geometry_info.rho;
03108   percent_saturation=geometry_info.sigma;
03109   if ((flags & SigmaValue) == 0)
03110     percent_saturation=100.0;
03111   percent_hue=geometry_info.xi;
03112   if ((flags & XiValue) == 0)
03113     percent_hue=100.0;
03114   colorspace=UndefinedColorspace;
03115   artifact=GetImageArtifact(image,"modulate:colorspace");
03116   if (artifact != (const char *) NULL)
03117     colorspace=(ColorspaceType) ParseMagickOption(MagickColorspaceOptions,
03118       MagickFalse,artifact);
03119   if (image->storage_class == PseudoClass)
03120     {
03121       /*
03122         Modulate colormap.
03123       */
03124 #if defined(MAGICKCORE_OPENMP_SUPPORT)
03125   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
03126 #endif
03127       for (i=0; i < (long) image->colors; i++)
03128         switch (colorspace)
03129         {
03130           case HSBColorspace:
03131           {
03132             ModulateHSB(percent_hue,percent_saturation,percent_brightness,
03133               &image->colormap[i].red,&image->colormap[i].green,
03134               &image->colormap[i].blue);
03135             break;
03136           }
03137           case HSLColorspace:
03138           default:
03139           {
03140             ModulateHSL(percent_hue,percent_saturation,percent_brightness,
03141               &image->colormap[i].red,&image->colormap[i].green,
03142               &image->colormap[i].blue);
03143             break;
03144           }
03145           case HWBColorspace:
03146           {
03147             ModulateHWB(percent_hue,percent_saturation,percent_brightness,
03148               &image->colormap[i].red,&image->colormap[i].green,
03149               &image->colormap[i].blue);
03150             break;
03151           }
03152         }
03153     }
03154   /*
03155     Modulate image.
03156   */
03157   status=MagickTrue;
03158   progress=0;
03159   exception=(&image->exception);
03160   image_view=AcquireCacheView(image);
03161 #if defined(MAGICKCORE_OPENMP_SUPPORT)
03162   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
03163 #endif
03164   for (y=0; y < (long) image->rows; y++)
03165   {
03166     register long
03167       x;
03168 
03169     register PixelPacket
03170       *__restrict q;
03171 
03172     if (status == MagickFalse)
03173       continue;
03174     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
03175     if (q == (PixelPacket *) NULL)
03176       {
03177         status=MagickFalse;
03178         continue;
03179       }
03180     for (x=0; x < (long) image->columns; x++)
03181     {
03182       switch (colorspace)
03183       {
03184         case HSBColorspace:
03185         {
03186           ModulateHSB(percent_hue,percent_saturation,percent_brightness,
03187             &q->red,&q->green,&q->blue);
03188           break;
03189         }
03190         case HSLColorspace:
03191         default:
03192         {
03193           ModulateHSL(percent_hue,percent_saturation,percent_brightness,
03194             &q->red,&q->green,&q->blue);
03195           break;
03196         }
03197         case HWBColorspace:
03198         {
03199           ModulateHWB(percent_hue,percent_saturation,percent_brightness,
03200             &q->red,&q->green,&q->blue);
03201           break;
03202         }
03203       }
03204       q++;
03205     }
03206     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
03207       status=MagickFalse;
03208     if (image->progress_monitor != (MagickProgressMonitor) NULL)
03209       {
03210         MagickBooleanType
03211           proceed;
03212 
03213 #if defined(MAGICKCORE_OPENMP_SUPPORT)
03214   #pragma omp critical (MagickCore_ModulateImage)
03215 #endif
03216         proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
03217         if (proceed == MagickFalse)
03218           status=MagickFalse;
03219       }
03220   }
03221   image_view=DestroyCacheView(image_view);
03222   return(status);
03223 }
03224 
03225 /*
03226 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
03227 %                                                                             %
03228 %                                                                             %
03229 %                                                                             %
03230 %     N e g a t e I m a g e                                                   %
03231 %                                                                             %
03232 %                                                                             %
03233 %                                                                             %
03234 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
03235 %
03236 %  NegateImage() negates the colors in the reference image.  The grayscale
03237 %  option means that only grayscale values within the image are negated.
03238 %
03239 %  The format of the NegateImageChannel method is:
03240 %
03241 %      MagickBooleanType NegateImage(Image *image,
03242 %        const MagickBooleanType grayscale)
03243 %      MagickBooleanType NegateImageChannel(Image *image,
03244 %        const ChannelType channel,const MagickBooleanType grayscale)
03245 %
03246 %  A description of each parameter follows:
03247 %
03248 %    o image: the image.
03249 %
03250 %    o channel: the channel.
03251 %
03252 %    o grayscale: If MagickTrue, only negate grayscale pixels within the image.
03253 %
03254 */
03255 
03256 MagickExport MagickBooleanType NegateImage(Image *image,
03257   const MagickBooleanType grayscale)
03258 {
03259   MagickBooleanType
03260     status;
03261 
03262   status=NegateImageChannel(image,DefaultChannels,grayscale);
03263   return(status);
03264 }
03265 
03266 MagickExport MagickBooleanType NegateImageChannel(Image *image,
03267   const ChannelType channel,const MagickBooleanType grayscale)
03268 {
03269 #define NegateImageTag  "Negate/Image"
03270 
03271   ExceptionInfo
03272     *exception;
03273 
03274   long
03275     progress,
03276     y;
03277 
03278   MagickBooleanType
03279     status;
03280 
03281   register long
03282     i;
03283 
03284   CacheView
03285     *image_view;
03286 
03287   assert(image != (Image *) NULL);
03288   assert(image->signature == MagickSignature);
03289   if (image->debug != MagickFalse)
03290     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
03291   if (image->storage_class == PseudoClass)
03292     {
03293       /*
03294         Negate colormap.
03295       */
03296 #if defined(MAGICKCORE_OPENMP_SUPPORT)
03297   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
03298 #endif
03299       for (i=0; i < (long) image->colors; i++)
03300       {
03301         if (grayscale != MagickFalse)
03302           if ((image->colormap[i].red != image->colormap[i].green) ||
03303               (image->colormap[i].green != image->colormap[i].blue))
03304             continue;
03305         if ((channel & RedChannel) != 0)
03306           image->colormap[i].red=(Quantum) QuantumRange-
03307             image->colormap[i].red;
03308         if ((channel & GreenChannel) != 0)
03309           image->colormap[i].green=(Quantum) QuantumRange-
03310             image->colormap[i].green;
03311         if ((channel & BlueChannel) != 0)
03312           image->colormap[i].blue=(Quantum) QuantumRange-
03313             image->colormap[i].blue;
03314       }
03315     }
03316   /*
03317     Negate image.
03318   */
03319   status=MagickTrue;
03320   progress=0;
03321   exception=(&image->exception);
03322   image_view=AcquireCacheView(image);
03323   if (grayscale != MagickFalse)
03324     {
03325 #if defined(MAGICKCORE_OPENMP_SUPPORT)
03326   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
03327 #endif
03328       for (y=0; y < (long) image->rows; y++)
03329       {
03330         MagickBooleanType
03331           sync;
03332 
03333         register IndexPacket
03334           *__restrict indexes;
03335 
03336         register long
03337           x;
03338 
03339         register PixelPacket
03340           *__restrict q;
03341 
03342         if (status == MagickFalse)
03343           continue;
03344         q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
03345           exception);
03346         if (q == (PixelPacket *) NULL)
03347           {
03348             status=MagickFalse;
03349             continue;
03350           }
03351         indexes=GetCacheViewAuthenticIndexQueue(image_view);
03352         for (x=0; x < (long) image->columns; x++)
03353         {
03354           if ((q->red != q->green) || (q->green != q->blue))
03355             {
03356               q++;
03357               continue;
03358             }
03359           if ((channel & RedChannel) != 0)
03360             q->red=(Quantum) QuantumRange-q->red;
03361           if ((channel & GreenChannel) != 0)
03362             q->green=(Quantum) QuantumRange-q->green;
03363           if ((channel & BlueChannel) != 0)
03364             q->blue=(Quantum) QuantumRange-q->blue;
03365           if ((channel & OpacityChannel) != 0)
03366             q->opacity=(Quantum) QuantumRange-q->opacity;
03367           if (((channel & IndexChannel) != 0) &&
03368               (image->colorspace == CMYKColorspace))
03369             indexes[x]=(IndexPacket) QuantumRange-indexes[x];
03370           q++;
03371         }
03372         sync=SyncCacheViewAuthenticPixels(image_view,exception);
03373         if (sync == MagickFalse)
03374           status=MagickFalse;
03375         if (image->progress_monitor != (MagickProgressMonitor) NULL)
03376           {
03377             MagickBooleanType
03378               proceed;
03379 
03380 #if defined(MAGICKCORE_OPENMP_SUPPORT)
03381   #pragma omp critical (MagickCore_NegateImageChannel)
03382 #endif
03383             proceed=SetImageProgress(image,NegateImageTag,progress++,
03384               image->rows);
03385             if (proceed == MagickFalse)
03386               status=MagickFalse;
03387           }
03388       }
03389       image_view=DestroyCacheView(image_view);
03390       return(MagickTrue);
03391     }
03392   /*
03393     Negate image.
03394   */
03395 #if defined(MAGICKCORE_OPENMP_SUPPORT)
03396   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
03397 #endif
03398   for (y=0; y < (long) image->rows; y++)
03399   {
03400     register IndexPacket
03401       *__restrict indexes;
03402 
03403     register long
03404       x;
03405 
03406     register PixelPacket
03407       *__restrict q;
03408 
03409     if (status == MagickFalse)
03410       continue;
03411     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
03412     if (q == (PixelPacket *) NULL)
03413       {
03414         status=MagickFalse;
03415         continue;
03416       }
03417     indexes=GetCacheViewAuthenticIndexQueue(image_view);
03418     for (x=0; x < (long) image->columns; x++)
03419     {
03420       if ((channel & RedChannel) != 0)
03421         q->red=(Quantum) QuantumRange-q->red;
03422       if ((channel & GreenChannel) != 0)
03423         q->green=(Quantum) QuantumRange-q->green;
03424       if ((channel & BlueChannel) != 0)
03425         q->blue=(Quantum) QuantumRange-q->blue;
03426       if ((channel & OpacityChannel) != 0)
03427         q->opacity=(Quantum) QuantumRange-q->opacity;
03428       if (((channel & IndexChannel) != 0) &&
03429           (image->colorspace == CMYKColorspace))
03430         indexes[x]=(IndexPacket) QuantumRange-indexes[x];
03431       q++;
03432     }
03433     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
03434       status=MagickFalse;
03435     if (image->progress_monitor != (MagickProgressMonitor) NULL)
03436       {
03437         MagickBooleanType
03438           proceed;
03439 
03440 #if defined(MAGICKCORE_OPENMP_SUPPORT)
03441   #pragma omp critical (MagickCore_NegateImageChannel)
03442 #endif
03443         proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
03444         if (proceed == MagickFalse)
03445           status=MagickFalse;
03446       }
03447   }
03448   image_view=DestroyCacheView(image_view);
03449   return(status);
03450 }
03451 
03452 /*
03453 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
03454 %                                                                             %
03455 %                                                                             %
03456 %                                                                             %
03457 %     N o r m a l i z e I m a g e                                             %
03458 %                                                                             %
03459 %                                                                             %
03460 %                                                                             %
03461 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
03462 %
03463 %  The NormalizeImage() method enhances the contrast of a color image by
03464 %  mapping the darkest 2 percent of all pixel to black and the brightest
03465 %  1 percent to white.
03466 %
03467 %  The format of the NormalizeImage method is:
03468 %
03469 %      MagickBooleanType NormalizeImage(Image *image)
03470 %      MagickBooleanType NormalizeImageChannel(Image *image,
03471 %        const ChannelType channel)
03472 %
03473 %  A description of each parameter follows:
03474 %
03475 %    o image: the image.
03476 %
03477 %    o channel: the channel.
03478 %
03479 */
03480 
03481 MagickExport MagickBooleanType NormalizeImage(Image *image)
03482 {
03483   MagickBooleanType
03484     status;
03485 
03486   status=NormalizeImageChannel(image,DefaultChannels);
03487   return(status);
03488 }
03489 
03490 MagickExport MagickBooleanType NormalizeImageChannel(Image *image,
03491   const ChannelType channel)
03492 {
03493   double
03494     black_point,
03495     white_point;
03496 
03497   black_point=(double) image->columns*image->rows*0.02;
03498   white_point=(double) image->columns*image->rows*0.99;
03499   return(ContrastStretchImageChannel(image,channel,black_point,white_point));
03500 }
03501 
03502 /*
03503 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
03504 %                                                                             %
03505 %                                                                             %
03506 %                                                                             %
03507 %     S i g m o i d a l C o n t r a s t I m a g e                             %
03508 %                                                                             %
03509 %                                                                             %
03510 %                                                                             %
03511 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
03512 %
03513 %  SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
03514 %  sigmoidal contrast algorithm.  Increase the contrast of the image using a
03515 %  sigmoidal transfer function without saturating highlights or shadows.
03516 %  Contrast indicates how much to increase the contrast (0 is none; 3 is
03517 %  typical; 20 is pushing it); mid-point indicates where midtones fall in the
03518 %  resultant image (0 is white; 50% is middle-gray; 100% is black).  Set
03519 %  sharpen to MagickTrue to increase the image contrast otherwise the contrast
03520 %  is reduced.
03521 %
03522 %  The format of the SigmoidalContrastImage method is:
03523 %
03524 %      MagickBooleanType SigmoidalContrastImage(Image *image,
03525 %        const MagickBooleanType sharpen,const char *levels)
03526 %      MagickBooleanType SigmoidalContrastImageChannel(Image *image,
03527 %        const ChannelType channel,const MagickBooleanType sharpen,
03528 %        const double contrast,const double midpoint)
03529 %
03530 %  A description of each parameter follows:
03531 %
03532 %    o image: the image.
03533 %
03534 %    o channel: the channel.
03535 %
03536 %    o sharpen: Increase or decrease image contrast.
03537 %
03538 %    o contrast: control the "shoulder" of the contast curve.
03539 %
03540 %    o midpoint: control the "toe" of the contast curve.
03541 %
03542 */
03543 
03544 MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
03545   const MagickBooleanType sharpen,const char *levels)
03546 {
03547   GeometryInfo
03548     geometry_info;
03549 
03550   MagickBooleanType
03551     status;
03552 
03553   MagickStatusType
03554     flags;
03555 
03556   flags=ParseGeometry(levels,&geometry_info);
03557   if ((flags & SigmaValue) == 0)
03558     geometry_info.sigma=1.0*QuantumRange/2.0;
03559   if ((flags & PercentValue) != 0)
03560     geometry_info.sigma=1.0*QuantumRange*geometry_info.sigma/100.0;
03561   status=SigmoidalContrastImageChannel(image,DefaultChannels,sharpen,
03562     geometry_info.rho,geometry_info.sigma);
03563   return(status);
03564 }
03565 
03566 MagickExport MagickBooleanType SigmoidalContrastImageChannel(Image *image,
03567   const ChannelType channel,const MagickBooleanType sharpen,
03568   const double contrast,const double midpoint)
03569 {
03570 #define SigmoidalContrastImageTag  "SigmoidalContrast/Image"
03571 
03572   ExceptionInfo
03573     *exception;
03574 
03575   long
03576     progress,
03577     y;
03578 
03579   MagickBooleanType
03580     status;
03581 
03582   MagickRealType
03583     *sigmoidal_map;
03584 
03585   register long
03586     i;
03587 
03588   CacheView
03589     *image_view;
03590 
03591   /*
03592     Allocate and initialize sigmoidal maps.
03593   */
03594   assert(image != (Image *) NULL);
03595   assert(image->signature == MagickSignature);
03596   if (image->debug != MagickFalse)
03597     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
03598   sigmoidal_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
03599     sizeof(*sigmoidal_map));
03600   if (sigmoidal_map == (MagickRealType *) NULL)
03601     ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
03602       image->filename);
03603   (void) ResetMagickMemory(sigmoidal_map,0,(MaxMap+1)*sizeof(*sigmoidal_map));
03604 #if defined(MAGICKCORE_OPENMP_SUPPORT)
03605   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
03606 #endif
03607   for (i=0; i <= (long) MaxMap; i++)
03608   {
03609     if (sharpen != MagickFalse)
03610       {
03611         sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
03612           (MaxMap*((1.0/(1.0+exp(contrast*(midpoint/(double) QuantumRange-
03613           (double) i/MaxMap))))-(1.0/(1.0+exp(contrast*(midpoint/
03614           (double) QuantumRange)))))/((1.0/(1.0+exp(contrast*(midpoint/
03615           (double) QuantumRange-1.0))))-(1.0/(1.0+exp(contrast*(midpoint/
03616           (double) QuantumRange)))))+0.5));
03617         continue;
03618       }
03619     sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
03620       (MaxMap*(QuantumScale*midpoint-log((1.0-(1.0/(1.0+exp(midpoint/
03621       (double) QuantumRange*contrast))+((double) i/MaxMap)*((1.0/
03622       (1.0+exp(contrast*(midpoint/(double) QuantumRange-1.0))))-(1.0/
03623       (1.0+exp(midpoint/(double) QuantumRange*contrast))))))/
03624       (1.0/(1.0+exp(midpoint/(double) QuantumRange*contrast))+
03625       ((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(midpoint/
03626       (double) QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/
03627       (double) QuantumRange*contrast))))))/contrast)));
03628   }
03629   if (image->storage_class == PseudoClass)
03630     {
03631       /*
03632         Sigmoidal-contrast enhance colormap.
03633       */
03634 #if defined(MAGICKCORE_OPENMP_SUPPORT)
03635   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
03636 #endif
03637       for (i=0; i < (long) image->colors; i++)
03638       {
03639         if ((channel & RedChannel) != 0)
03640           image->colormap[i].red=RoundToQuantum(sigmoidal_map[
03641             ScaleQuantumToMap(image->colormap[i].red)]);
03642         if ((channel & GreenChannel) != 0)
03643           image->colormap[i].green=RoundToQuantum(sigmoidal_map[
03644             ScaleQuantumToMap(image->colormap[i].green)]);
03645         if ((channel & BlueChannel) != 0)
03646           image->colormap[i].blue=RoundToQuantum(sigmoidal_map[
03647             ScaleQuantumToMap(image->colormap[i].blue)]);
03648         if ((channel & OpacityChannel) != 0)
03649           image->colormap[i].opacity=RoundToQuantum(sigmoidal_map[
03650             ScaleQuantumToMap(image->colormap[i].opacity)]);
03651       }
03652     }
03653   /*
03654     Sigmoidal-contrast enhance image.
03655   */
03656   status=MagickTrue;
03657   progress=0;
03658   exception=(&image->exception);
03659   image_view=AcquireCacheView(image);
03660 #if defined(MAGICKCORE_OPENMP_SUPPORT)
03661   #pragma omp parallel for schedule(dynamic,4) shared(progress,status)
03662 #endif
03663   for (y=0; y < (long) image->rows; y++)
03664   {
03665     register IndexPacket
03666       *__restrict indexes;
03667 
03668     register long
03669       x;
03670 
03671     register PixelPacket
03672       *__restrict q;
03673 
03674     if (status == MagickFalse)
03675       continue;
03676     q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
03677     if (q == (PixelPacket *) NULL)
03678       {
03679         status=MagickFalse;
03680         continue;
03681       }
03682     indexes=GetCacheViewAuthenticIndexQueue(image_view);
03683     for (x=0; x < (long) image->columns; x++)
03684     {
03685       if ((channel & RedChannel) != 0)
03686         q->red=RoundToQuantum(sigmoidal_map[ScaleQuantumToMap(q->red)]);
03687       if ((channel & GreenChannel) != 0)
03688         q->green=RoundToQuantum(sigmoidal_map[ScaleQuantumToMap(q->green)]);
03689       if ((channel & BlueChannel) != 0)
03690         q->blue=RoundToQuantum(sigmoidal_map[ScaleQuantumToMap(q->blue)]);
03691       if ((channel & OpacityChannel) != 0)
03692         q->opacity=RoundToQuantum(sigmoidal_map[ScaleQuantumToMap(q->opacity)]);
03693       if (((channel & IndexChannel) != 0) &&
03694           (image->colorspace == CMYKColorspace))
03695         indexes[x]=(IndexPacket) RoundToQuantum(sigmoidal_map[
03696           ScaleQuantumToMap(indexes[x])]);
03697       q++;
03698     }
03699     if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
03700       status=MagickFalse;
03701     if (image->progress_monitor != (MagickProgressMonitor) NULL)
03702       {
03703         MagickBooleanType
03704           proceed;
03705 
03706 #if defined(MAGICKCORE_OPENMP_SUPPORT)
03707   #pragma omp critical (MagickCore_SigmoidalContrastImageChannel)
03708 #endif
03709         proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
03710           image->rows);
03711         if (proceed == MagickFalse)
03712           status=MagickFalse;
03713       }
03714   }
03715   image_view=DestroyCacheView(image_view);
03716   sigmoidal_map=(MagickRealType *) RelinquishMagickMemory(sigmoidal_map);
03717   return(status);
03718 }

Generated on 19 Nov 2009 for MagickCore by  doxygen 1.6.1