root / ImageMagick / trunk / magick / enhance.c

Revision 11875, 91.2 kB (checked in by cristy, 5 days ago)
Line 
1/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3%                                                                             %
4%                                                                             %
5%                                                                             %
6%              EEEEE  N   N  H   H   AAA   N   N   CCCC  EEEEE                %
7%              E      NN  N  H   H  A   A  NN  N  C      E                    %
8%              EEE    N N N  HHHHH  AAAAA  N N N  C      EEE                  %
9%              E      N  NN  H   H  A   A  N  NN  C      E                    %
10%              EEEEE  N   N  H   H  A   A  N   N   CCCC  EEEEE                %
11%                                                                             %
12%                                                                             %
13%                    ImageMagick Image Enhancement Methods                    %
14%                                                                             %
15%                              Software Design                                %
16%                                John Cristy                                  %
17%                                 July 1992                                   %
18%                                                                             %
19%                                                                             %
20%  Copyright 1999-2008 ImageMagick Studio LLC, a non-profit organization      %
21%  dedicated to making software imaging solutions freely available.           %
22%                                                                             %
23%  You may not use this file except in compliance with the License.  You may  %
24%  obtain a copy of the License at                                            %
25%                                                                             %
26%    http://www.imagemagick.org/script/license.php                            %
27%                                                                             %
28%  Unless required by applicable law or agreed to in writing, software        %
29%  distributed under the License is distributed on an "AS IS" BASIS,          %
30%  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   %
31%  See the License for the specific language governing permissions and        %
32%  limitations under the License.                                             %
33%                                                                             %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
36%
37%
38*/
39
40/*
41  Include declarations.
42*/
43#include "magick/studio.h"
44#include "magick/cache-view.h"
45#include "magick/color.h"
46#include "magick/color-private.h"
47#include "magick/colorspace.h"
48#include "magick/enhance.h"
49#include "magick/exception.h"
50#include "magick/exception-private.h"
51#include "magick/gem.h"
52#include "magick/geometry.h"
53#include "magick/image.h"
54#include "magick/image-private.h"
55#include "magick/memory_.h"
56#include "magick/monitor.h"
57#include "magick/monitor-private.h"
58#include "magick/quantum.h"
59#include "magick/quantum-private.h"
60#include "magick/resample.h"
61#include "magick/string_.h"
62
63/*
64%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
65%                                                                             %
66%                                                                             %
67%                                                                             %
68%     C l u t I m a g e                                                       %
69%                                                                             %
70%                                                                             %
71%                                                                             %
72%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
73%
74%  ClutImage() replaces colors in the image from a color lookup table.
75%
76%  The format of the ClutImage method is:
77%
78%      MagickBooleanType ClutImage(Image *image,Image *clut_image)
79%      MagickBooleanType ClutImageChannel(Image *image,
80%        const ChannelType channel,Image *clut_image)
81%
82%  A description of each parameter follows:
83%
84%    o image: the image.
85%
86%    o channel: the channel.
87%
88%    o clut_image: the color lookup.
89%
90*/
91
92static ResampleFilter **DestroyResampleFilterThreadSet(ResampleFilter **filter)
93{
94  register long
95    i;
96
97  assert(filter != (ResampleFilter **) NULL);
98  for (i=0; i < (long) GetCacheViewMaximumThreads(); i++)
99    if (filter[i] != (ResampleFilter *) NULL)
100      filter[i]=DestroyResampleFilter(filter[i]);
101  return((ResampleFilter **) RelinquishMagickMemory(filter));
102}
103
104static ResampleFilter **AcquireResampleFilterThreadSet(const Image *image,
105  ExceptionInfo *exception)
106{
107  register long
108    i;
109
110  ResampleFilter
111    **filter;
112
113  filter=(ResampleFilter **) AcquireQuantumMemory(GetCacheViewMaximumThreads(),
114    sizeof(*filter));
115  if (filter == (ResampleFilter **) NULL)
116    return((ResampleFilter **) NULL);
117  (void) ResetMagickMemory(filter,0,GetCacheViewMaximumThreads()*
118    sizeof(*filter));
119  for (i=0; i < (long) GetCacheViewMaximumThreads(); i++)
120  {
121    filter[i]=AcquireResampleFilter(image,exception);
122    if (filter[i] == (ResampleFilter *) NULL)
123      return(DestroyResampleFilterThreadSet(filter));
124  }
125  return(filter);
126}
127
128MagickExport MagickBooleanType ClutImage(Image *image,const Image *clut_image)
129{
130  return(ClutImageChannel(image,DefaultChannels,clut_image));
131}
132
133MagickExport MagickBooleanType ClutImageChannel(Image *image,
134  const ChannelType channel,const Image *clut_image)
135{
136#define ClutImageTag  "Clut/Image"
137
138  long
139    adjust,
140    progress,
141    y;
142
143  MagickBooleanType
144    status;
145
146  ResampleFilter
147    **resample_filter;
148
149  ViewInfo
150    **image_view;
151
152  assert(image != (Image *) NULL);
153  assert(image->signature == MagickSignature);
154  if (image->debug != MagickFalse)
155    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
156  assert(clut_image != (Image *) NULL);
157  assert(clut_image->signature == MagickSignature);
158  if (SetImageStorageClass(image,DirectClass) == MagickFalse)
159    return(MagickFalse);
160  /*
161    Clut image.
162  */
163  status=MagickTrue;
164  progress=0;
165  adjust=clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1;
166  resample_filter=AcquireResampleFilterThreadSet(clut_image,&image->exception);
167  image_view=AcquireCacheViewThreadSet(image);
168  #pragma omp parallel for
169  for (y=0; y < (long) image->rows; y++)
170  {
171    MagickPixelPacket
172      pixel;
173
174    register IndexPacket
175      *indexes;
176
177    register long
178      id,
179      x;
180
181    register PixelPacket
182      *q;
183
184    if (status == MagickFalse)
185      continue;
186    id=GetCacheViewThreadId();
187    q=GetCacheViewPixels(image_view[id],0,y,image->columns,1);
188    if (q == (PixelPacket *) NULL)
189      {
190        status=MagickFalse;
191        continue;
192      }
193    indexes=GetCacheViewIndexes(image_view[id]);
194    GetMagickPixelPacket(clut_image,&pixel);
195    for (x=0; x < (long) image->columns; x++)
196    {
197      if ((channel & RedChannel) != 0)
198        {
199          pixel=ResamplePixelColor(resample_filter[id],QuantumScale*q->red*
200            (clut_image->columns-adjust),QuantumScale*q->red*
201            (clut_image->rows-adjust));
202          q->red=RoundToQuantum(pixel.red);
203        }
204      if ((channel & GreenChannel) != 0)
205        {
206          pixel=ResamplePixelColor(resample_filter[id],QuantumScale*q->green*
207            (clut_image->columns-adjust),QuantumScale*q->green*
208            (clut_image->rows-adjust));
209          q->green=RoundToQuantum(pixel.green);
210        }
211      if ((channel & BlueChannel) != 0)
212        {
213          pixel=ResamplePixelColor(resample_filter[id],QuantumScale*q->blue*
214            (clut_image->columns-adjust),QuantumScale*q->blue*
215            (clut_image->rows-adjust));
216          q->blue=RoundToQuantum(pixel.blue);
217        }
218      if ((channel & OpacityChannel) != 0)
219        {
220          if (clut_image->matte == MagickFalse)
221            {
222              /*
223                A gray-scale LUT replacement for an image alpha channel.
224              */
225              pixel=ResamplePixelColor(resample_filter[id],QuantumScale*
226                (QuantumRange-q->opacity)*(clut_image->columns+adjust),
227                QuantumScale*(QuantumRange-q->opacity)*(clut_image->rows+
228                adjust));
229              q->opacity=(Quantum) (QuantumRange-MagickPixelIntensityToQuantum(
230                &pixel));
231            }
232          else
233            if (image->matte == MagickFalse)
234              {
235                /*
236                  A greyscale image being colored by a LUT with transparency.
237                */
238                pixel=ResamplePixelColor(resample_filter[id],QuantumScale*
239                  PixelIntensity(q)*(clut_image->columns-adjust),QuantumScale*
240                  PixelIntensity(q)*(clut_image->rows-adjust));
241                q->opacity=RoundToQuantum(pixel.opacity);
242              }
243            else
244              {
245                /*
246                  Direct alpha channel lookup.
247                */
248                pixel=ResamplePixelColor(resample_filter[id],QuantumScale*
249                  q->opacity*(clut_image->columns-adjust),QuantumScale*
250                  q->opacity* (clut_image->rows-adjust));
251                q->opacity=RoundToQuantum(pixel.opacity);
252              }
253        }
254      if (((channel & IndexChannel) != 0) &&
255          (image->colorspace == CMYKColorspace))
256        {
257          pixel=ResamplePixelColor(resample_filter[id],QuantumScale*indexes[x]*
258            (clut_image->columns-adjust),QuantumScale*indexes[x]*
259            (clut_image->rows-adjust));
260          indexes[x]=RoundToQuantum(pixel.index);
261        }
262      q++;
263    }
264    if (SyncCacheView(image_view[id]) == MagickFalse)
265      status=MagickFalse;
266    if (image->progress_monitor != (MagickProgressMonitor) NULL)
267      {
268        MagickBooleanType
269          proceed;
270
271        #pragma omp critical
272        proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
273        if (proceed == MagickFalse)
274          status=MagickFalse;
275      }
276  }
277  image_view=DestroyCacheViewThreadSet(image_view);
278  resample_filter=DestroyResampleFilterThreadSet(resample_filter);
279  /*
280    Enable alpha channel if CLUT image could enable it.
281  */
282  if ((clut_image->matte == MagickTrue) && ((channel & OpacityChannel) != 0))
283    (void) SetImageAlphaChannel(image,ActivateAlphaChannel);
284  return(status);
285}
286
287/*
288%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
289%                                                                             %
290%                                                                             %
291%                                                                             %
292%     C o n t r a s t I m a g e                                               %
293%                                                                             %
294%                                                                             %
295%                                                                             %
296%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
297%
298%  ContrastImage() enhances the intensity differences between the lighter and
299%  darker elements of the image.  Set sharpen to a MagickTrue to increase the
300%  image contrast otherwise the contrast is reduced.
301%
302%  The format of the ContrastImage method is:
303%
304%      MagickBooleanType ContrastImage(Image *image,
305%        const MagickBooleanType sharpen)
306%
307%  A description of each parameter follows:
308%
309%    o image: the image.
310%
311%    o sharpen: Increase or decrease image contrast.
312%
313*/
314
315static void Contrast(const int sign,Quantum *red,Quantum *green,Quantum *blue)
316{
317  double
318    brightness,
319    hue,
320    saturation;
321
322  /*
323    Enhance contrast: dark color become darker, light color become lighter.
324  */
325  assert(red != (Quantum *) NULL);
326  assert(green != (Quantum *) NULL);
327  assert(blue != (Quantum *) NULL);
328  hue=0.0;
329  saturation=0.0;
330  brightness=0.0;
331  ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
332  brightness+=0.5*sign*(0.5*(sin(MagickPI*(brightness-0.5))+1.0)-brightness);
333  if (brightness > 1.0)
334    brightness=1.0;
335  else
336    if (brightness < 0.0)
337      brightness=0.0;
338  ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
339}
340
341MagickExport MagickBooleanType ContrastImage(Image *image,
342  const MagickBooleanType sharpen)
343{
344#define ContrastImageTag  "Contrast/Image"
345
346  int
347    sign;
348
349  long
350    progress,
351    y;
352
353  MagickBooleanType
354    status;
355
356  register long
357    i;
358
359  ViewInfo
360    **image_view;
361
362  assert(image != (Image *) NULL);
363  assert(image->signature == MagickSignature);
364  if (image->debug != MagickFalse)
365    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
366  sign=sharpen != MagickFalse ? 1 : -1;
367  if (image->storage_class == PseudoClass)
368    {
369      /*
370        Contrast enhance colormap.
371      */
372      for (i=0; i < (long) image->colors; i++)
373        Contrast(sign,&image->colormap[i].red,&image->colormap[i].green,
374          &image->colormap[i].blue);
375    }
376  /*
377    Contrast enhance image.
378  */
379  status=MagickTrue;
380  progress=0;
381  image_view=AcquireCacheViewThreadSet(image);
382  #pragma omp parallel for
383  for (y=0; y < (long) image->rows; y++)
384  {
385    register long
386      id,
387      x;
388
389    register PixelPacket
390      *q;
391
392    if (status == MagickFalse)
393      continue;
394    id=GetCacheViewThreadId();
395    q=GetCacheViewPixels(image_view[id],0,y,image->columns,1);
396    if (q == (PixelPacket *) NULL)
397      {
398        status=MagickFalse;
399        continue;
400      }
401    for (x=0; x < (long) image->columns; x++)
402    {
403      Contrast(sign,&q->red,&q->green,&q->blue);
404      q++;
405    }
406    if (SyncCacheView(image_view[id]) == MagickFalse)
407      status=MagickFalse;
408    if (image->progress_monitor != (MagickProgressMonitor) NULL)
409      {
410        MagickBooleanType
411          proceed;
412
413        #pragma omp critical
414        proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
415        if (proceed == MagickFalse)
416          status=MagickFalse;
417      }
418  }
419  image_view=DestroyCacheViewThreadSet(image_view);
420  return(status);
421}
422
423/*
424%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
425%                                                                             %
426%                                                                             %
427%                                                                             %
428%     C o n t r a s t S t r e t c h I m a g e                                 %
429%                                                                             %
430%                                                                             %
431%                                                                             %
432%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
433%
434%  The ContrastStretchImage() is a simple image enhancement technique that
435%  attempts to improve the contrast in an image by `stretching' the range of
436%  intensity values it contains to span a desired range of values. It differs
437%  from the more sophisticated histogram equalization in that it can only
438%  apply %  a linear scaling function to the image pixel values.  As a result
439%  the `enhancement' is less harsh.
440%
441%  The format of the ContrastStretchImage method is:
442%
443%      MagickBooleanType ContrastStretchImage(Image *image,
444%        const char *levels)
445%      MagickBooleanType ContrastStretchImageChannel(Image *image,
446%        const unsigned long channel,const double black_point,
447%        const double white_point)
448%
449%  A description of each parameter follows:
450%
451%    o image: the image.
452%
453%    o channel: the channel.
454%
455%    o black_point: the black point.
456%
457%    o white_point: the white point.
458%
459%    o levels: Specify the levels where the black and white points have the
460%      range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
461%
462*/
463
464MagickExport MagickBooleanType ContrastStretchImage(Image *image,
465  const char *levels)
466{
467  double
468    black_point,
469    white_point;
470
471  GeometryInfo
472    geometry_info;
473
474  MagickBooleanType
475    status;
476
477  MagickStatusType
478    flags;
479
480  /*
481    Parse levels.
482  */
483  if (levels == (char *) NULL)
484    return(MagickFalse);
485  flags=ParseGeometry(levels,&geometry_info);
486  black_point=geometry_info.rho;
487  white_point=(double) image->columns*image->rows;
488  if ((flags & SigmaValue) != 0)
489    white_point=geometry_info.sigma;
490  if ((flags & PercentValue) != 0)
491    {
492      black_point*=(double) QuantumRange/100.0;
493      white_point*=(double) QuantumRange/100.0;
494    }
495  if ((flags & SigmaValue) == 0)
496    white_point=(double) image->columns*image->rows-black_point;
497  status=ContrastStretchImageChannel(image,DefaultChannels,black_point,
498    white_point);
499  return(status);
500}
501
502MagickExport MagickBooleanType ContrastStretchImageChannel(Image *image,
503  const ChannelType channel,const double black_point,const double white_point)
504{
505#define MaxRange(color)  ((MagickRealType) ScaleQuantumToMap((Quantum) (color)))
506#define ContrastStretchImageTag  "ContrastStretch/Image"
507
508  double
509    intensity;
510
511  long
512    progress,
513    y;
514
515  MagickBooleanType
516    status;
517
518  MagickPixelPacket
519    black,
520    *histogram,
521    *stretch_map,
522    white;
523
524  register long
525    i;
526
527  ViewInfo
528    **image_view;
529
530  /*
531    Allocate histogram and stretch map.
532  */
533  assert(image != (Image *) NULL);
534  assert(image->signature == MagickSignature);
535  if (image->debug != MagickFalse)
536    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
537  histogram=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
538    sizeof(*histogram));
539  stretch_map=(MagickPixelPacket *) AcquireQuantumMemory(MaxMap+1UL,
540    sizeof(*stretch_map));
541  if ((histogram == (MagickPixelPacket *) NULL) ||
542      (stretch_map == (MagickPixelPacket *) NULL))
543    ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
544      image->filename);
545  /*
546    Form histogram.
547  */
548  status=MagickTrue;
549  (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
550  image_view=AcquireCacheViewThreadSet(image);
551  #pragma omp parallel for
552  for (y=0; y < (long) image->rows; y++)
553  {
554    IndexPacket
555      *indexes;
556
557    register const PixelPacket
558      *p;
559
560    register long
561      id,
562      x;
563
564    if (status == MagickFalse)
565      continue;
566    id=GetCacheViewThreadId();
567    p=AcquireCacheViewPixels(image_view[id],0,y,image->columns,1,
568      &image->exception);
569    if (p == (const PixelPacket *) NULL)
570      {
571        status=MagickFalse;
572        continue;
573      }
574    indexes=GetCacheViewIndexes(image_view[id]);
575    if (channel == DefaultChannels)
576      #pragma omp parallel for
577      for (x=0; x < (long) image->columns; x++)
578      {
579        Quantum
580          intensity;
581
582        intensity=PixelIntensityToQuantum(p+x);
583        histogram[ScaleQuantumToMap(intensity)].red++;
584        histogram[ScaleQuantumToMap(intensity)].green++;
585        histogram[ScaleQuantumToMap(intensity)].blue++;
586        histogram[ScaleQuantumToMap(intensity)].index++;
587      }
588    else
589      #pragma omp parallel for
590      for (x=0; x < (long) image->columns; x++)
591      {
592        if ((channel & RedChannel) != 0)
593          histogram[ScaleQuantumToMap((p+x)->red)].red++;
594        if ((channel & GreenChannel) != 0)
595          histogram[ScaleQuantumToMap((p+x)->green)].green++;
596        if ((channel & BlueChannel) != 0)
597          histogram[ScaleQuantumToMap((p+x)->blue)].blue++;
598        if ((channel & OpacityChannel) != 0)
599          histogram[ScaleQuantumToMap((p+x)->opacity)].opacity++;
600        if (((channel & IndexChannel) != 0) &&
601            (image->colorspace == CMYKColorspace))
602          histogram[ScaleQuantumToMap(indexes[x])].index++;
603      }
604  }
605  /*
606    Find the histogram boundaries by locating the black/white levels.
607  */
608  black.red=0.0;
609  white.red=MaxRange(QuantumRange);
610  if ((channel & RedChannel) != 0)
611    {
612      intensity=0.0;
613      for (i=0; i <= (long) MaxMap; i++)
614      {
615        intensity+=histogram[i].red;
616        if (intensity > black_point)
617          break;
618      }
619      black.red=(MagickRealType) i;
620      intensity=0.0;
621      for (i=(long) MaxMap; i != 0; i--)
622      {
623        intensity+=histogram[i].red;
624        if (intensity > ((double) image->columns*image->rows-white_point))
625          break;
626      }
627      white.red=(MagickRealType) i;
628    }
629  black.green=0.0;
630  white.green=MaxRange(QuantumRange);
631  if ((channel & GreenChannel) != 0)
632    {
633      intensity=0.0;
634      for (i=0; i <= (long) MaxMap; i++)
635      {
636        intensity+=histogram[i].green;
637        if (intensity > black_point)
638          break;
639      }
640      black.green=(MagickRealType) i;
641      intensity=0.0;
642      for (i=(long) MaxMap; i != 0; i--)
643      {
644        intensity+=histogram[i].green;
645        if (intensity > ((double) image->columns*image->rows-white_point))
646          break;
647      }
648      white.green=(MagickRealType) i;
649    }
650  black.blue=0.0;
651  white.blue=MaxRange(QuantumRange);
652  if ((channel & BlueChannel) != 0)
653    {
654      intensity=0.0;
655      for (i=0; i <= (long) MaxMap; i++)
656      {
657        intensity+=histogram[i].blue;
658        if (intensity > black_point)
659          break;
660      }
661      black.blue=(MagickRealType) i;
662      intensity=0.0;
663      for (i=(long) MaxMap; i != 0; i--)
664      {
665        intensity+=histogram[i].blue;
666        if (intensity > ((double) image->columns*image->rows-white_point))
667          break;
668      }
669      white.blue=(MagickRealType) i;
670    }
671  black.opacity=0.0;
672  white.opacity=MaxRange(QuantumRange);
673  if ((channel & OpacityChannel) != 0)
674    {
675      intensity=0.0;
676      for (i=0; i <= (long) MaxMap; i++)
677      {
678        intensity+=histogram[i].opacity;
679        if (intensity > black_point)
680          break;
681      }
682      black.opacity=(MagickRealType) i;
683      intensity=0.0;
684      for (i=(long) MaxMap; i != 0; i--)
685      {
686        intensity+=histogram[i].opacity;
687        if (intensity > ((double) image->columns*image->rows-white_point))
688          break;
689      }
690      white.opacity=(MagickRealType) i;
691    }
692  black.index=0.0;
693  white.index=MaxRange(QuantumRange);
694  if (((channel & IndexChannel) != 0) && (image->colorspace == CMYKColorspace))
695    {
696      intensity=0.0;
697      for (i=0; i <= (long) MaxMap; i++)
698      {
699        intensity+=histogram[i].index;
700        if (intensity > black_point)
701          break;
702      }
703      black.index=(MagickRealType) i;
704      intensity=0.0;
705      for (i=(long) MaxMap; i != 0; i--)
706      {
707        intensity+=histogram[i].index;
708        if (intensity > ((double) image->columns*image->rows-white_point))
709          break;
710      }
711      white.index=(MagickRealType) i;
712    }
713  histogram=(MagickPixelPacket *) RelinquishMagickMemory(histogram);
714  /*
715    Stretch the histogram to create the stretched image mapping.
716  */
717  (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*sizeof(*stretch_map));
718  #pragma omp parallel for
719  for (i=0; i <= (long) MaxMap; i++)
720  {
721    if ((channel & RedChannel) != 0)
722      {
723        if (i < (long) black.red)
724          stretch_map[i].red=0.0;
725        else
726          if (i > (long) white.red)
727            stretch_map[i].red=(MagickRealType) QuantumRange;
728          else
729            if (black.red != white.red)
730              stretch_map[i].red=(MagickRealType) ScaleMapToQuantum(
731                (MagickRealType) (MaxMap*(i-black.red)/(white.red-black.red)));
732      }
733    if ((channel & GreenChannel) != 0)
734      {
735        if (i < (long) black.green)
736          stretch_map[i].green=0.0;
737        else
738          if (i > (long) white.green)
739            stretch_map[i].green=(MagickRealType) QuantumRange;
740          else
741            if (black.green != white.green)
742              stretch_map[i].green=(MagickRealType) ScaleMapToQuantum(
743                (MagickRealType) (MaxMap*(i-black.green)/(white.green-
744                black.green)));
745      }
746    if ((channel & BlueChannel) != 0)
747      {
748        if (i < (long) black.blue)
749          stretch_map[i].blue=0.0;
750        else
751          if (i > (long) white.blue)
752            stretch_map[i].blue=(MagickRealType) QuantumRange;
753          else
754            if (black.blue != white.blue)
755              stretch_map[i].blue=(MagickRealType) ScaleMapToQuantum(
756                (MagickRealType) (MaxMap*(i-black.blue)/(white.blue-
757                black.blue)));
758      }
759    if ((channel & OpacityChannel) != 0)
760      {
761        if (i < (long) black.opacity)
762          stretch_map[i].opacity=0.0;
763        else
764          if (i > (long) white.opacity)
765            stretch_map[i].opacity=(MagickRealType) QuantumRange;
766          else
767            if (black.opacity != white.opacity)
768              stretch_map[i].opacity=(MagickRealType) ScaleMapToQuantum(
769                (MagickRealType) (MaxMap*(i-black.opacity)/(white.opacity-
770                black.opacity)));
771      }
772    if (((channel & IndexChannel) != 0) &&
773        (image->colorspace == CMYKColorspace))
774      {
775        if (i < (long) black.index)
776          stretch_map[i].index=0.0;
777        else
778          if (i > (long) white.index)
779            stretch_map[i].index=(MagickRealType) QuantumRange;
780          else
781            if (black.index != white.index)
782              stretch_map[i].index=(MagickRealType) ScaleMapToQuantum(
783                (MagickRealType) (MaxMap*(i-black.index)/(white.index-
784                black.index)));
785      }
786  }
787  /*
788    Stretch the image.
789  */
790  if (((channel & OpacityChannel) != 0) || (((channel & IndexChannel) != 0) &&
791      (image->colorspace == CMYKColorspace)))
792    image->storage_class=DirectClass;
793  if (image->storage_class == PseudoClass)
794    {
795      /*
796        Stretch colormap.
797      */
798      #pragma omp parallel for
799      for (i=0; i < (long) image->colors; i++)
800      {
801        if ((channel & RedChannel) != 0)
802          {
803            if (black.red != white.red)
804              image->colormap[i].red=RoundToQuantum(stretch_map[
805                ScaleQuantumToMap(image->colormap[i].red)].red);
806          }
807        if ((channel & GreenChannel) != 0)
808          {
809            if (black.green != white.green)
810              image->colormap[i].green=RoundToQuantum(stretch_map[
811                ScaleQuantumToMap(image->colormap[i].green)].green);
812          }
813        if ((channel & BlueChannel) != 0)
814          {
815            if (black.blue != white.blue)
816              image->colormap[i].blue=RoundToQuantum(stretch_map[
817                ScaleQuantumToMap(image->colormap[i].blue)].blue);
818          }
819        if ((channel & OpacityChannel) != 0)
820          {
821            if (black.opacity != white.opacity)
822              image->colormap[i].opacity=RoundToQuantum(stretch_map[
823                ScaleQuantumToMap(image->colormap[i].opacity)].opacity);
824          }
825      }
826    }
827  /*
828    Stretch image.
829  */
830  status=MagickTrue;
831  progress=0;
832  #pragma omp parallel for
833  for (y=0; y < (long) image->rows; y++)
834  {
835    IndexPacket
836      *indexes;
837
838    register long
839      id,
840      x;
841
842    register PixelPacket
843      *q;
844
845    if (status == MagickFalse)
846      continue;
847    id=GetCacheViewThreadId();
848    q=GetCacheViewPixels(image_view[id],0,y,image->columns,1);
849    if (q == (PixelPacket *) NULL)
850      {
851        status=MagickFalse;
852        continue;
853      }
854    indexes=GetCacheViewIndexes(image_view[id]);
855    for (x=0; x < (long) image->columns; x++)
856    {
857      if ((channel & RedChannel) != 0)
858        {
859          if (black.red != white.red)
860            q->red=RoundToQuantum(stretch_map[ScaleQuantumToMap(
861              q->red)].red);
862        }
863      if ((channel & GreenChannel) != 0)
864        {
865          if (black.green != white.green)
866            q->green=RoundToQuantum(stretch_map[ScaleQuantumToMap(
867              q->green)].green);
868        }
869      if ((channel & BlueChannel) != 0)
870        {
871          if (black.blue != white.blue)
872            q->blue=RoundToQuantum(stretch_map[ScaleQuantumToMap(
873              q->blue)].blue);
874        }
875      if ((channel & OpacityChannel) != 0)
876        {
877          if (black.opacity != white.opacity)
878            q->opacity=RoundToQuantum(stretch_map[ScaleQuantumToMap(
879              q->opacity)].opacity);
880        }
881      if (((channel & IndexChannel) != 0) &&
882          (image->colorspace == CMYKColorspace))
883        {
884          if (black.index != white.index)
885            indexes[x]=(IndexPacket) RoundToQuantum(stretch_map[
886              ScaleQuantumToMap(indexes[x])].index);
887        }
888      q++;
889    }
890    if (SyncCacheView(image_view[id]) == MagickFalse)
891      status=MagickFalse;
892    if (image->progress_monitor != (MagickProgressMonitor) NULL)
893      {
894        MagickBooleanType
895          proceed;
896
897        #pragma omp critical
898        proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
899          image->rows);
900        if (proceed == MagickFalse)
901          status=MagickFalse;
902      }
903  }
904  image_view=DestroyCacheViewThreadSet(image_view);
905  stretch_map=(MagickPixelPacket *) RelinquishMagickMemory(stretch_map);
906  return(status);
907}
908
909/*
910%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
911%                                                                             %
912%                                                                             %
913%                                                                             %
914%     E n h a n c e I m a g e                                                 %
915%                                                                             %
916%                                                                             %
917%                                                                             %
918%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
919%
920%  EnhanceImage() applies a digital filter that improves the quality of a
921%  noisy image.
922%
923%  The format of the EnhanceImage method is:
924%
925%      Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
926%
927%  A description of each parameter follows:
928%
929%    o image: the image.
930%
931%    o exception: Return any errors or warnings in this structure.
932%
933*/
934MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
935{
936#define Enhance(weight) \
937  mean=((MagickRealType) r->red+pixel.red)/2; \
938  distance=(MagickRealType) r->red-(MagickRealType) pixel.red; \
939  distance_squared=QuantumScale*(2.0*((MagickRealType) QuantumRange+1.0)+ \
940     mean)*distance*distance; \
941  mean=((MagickRealType) r->green+pixel.green)/2; \
942  distance=(MagickRealType) r->green-(MagickRealType) pixel.green; \
943  distance_squared+=4.0*distance*distance; \
944  mean=((MagickRealType) r->blue+pixel.blue)/2; \
945  distance=(MagickRealType) r->blue-(MagickRealType) pixel.blue; \
946  distance_squared+=QuantumScale*(3.0*((MagickRealType) \
947    QuantumRange+1.0)-1.0-mean)*distance*distance; \
948  mean=((MagickRealType) r->opacity+pixel.opacity)/2; \
949  distance=(MagickRealType) r->opacity-(MagickRealType) pixel.opacity; \
950  distance_squared+=QuantumScale*(3.0*((MagickRealType) \
951    QuantumRange+1.0)-1.0-mean)*distance*distance; \
952  if (distance_squared < ((MagickRealType) QuantumRange*(MagickRealType) \
953      QuantumRange/25.0f)) \
954    { \
955      aggregate.red+=(weight)*r->red; \
956      aggregate.green+=(weight)*r->green; \
957      aggregate.blue+=(weight)*r->blue; \
958      aggregate.opacity+=(weight)*r->opacity; \
959      total_weight+=(weight); \
960    } \
961  r++;
962#define EnhanceImageTag  "Enhance/Image"
963
964  Image
965    *enhance_image;
966
967  long
968    progress,
969    y;
970
971  MagickBooleanType
972    status;
973
974  MagickPixelPacket
975    zero;
976
977  ViewInfo
978    **enhance_view,
979    **image_view;
980
981  /*
982    Initialize enhanced image attributes.
983  */
984  assert(image != (const Image *) NULL);
985  assert(image->signature == MagickSignature);
986  if (image->debug != MagickFalse)
987    (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
988  assert(exception != (ExceptionInfo *) NULL);
989  assert(exception->signature == MagickSignature);
990  if ((image->columns < 5) || (image->rows < 5))
991    return((Image *) NULL);
992  enhance_image=CloneImage(image,0,0,MagickTrue,exception);
993  if (enhance_image == (Image *) NULL)
994    return((Image *) NULL);
995  if (SetImageStorageClass(enhance_image,DirectClass) == MagickFalse)
996    {
997      InheritException(exception,&enhance_image->exception);
998      enhance_image=DestroyImage(enhance_image);
999      return((Image *) NULL);
1000    }
1001  /*
1002    Enhance image.
1003  */
1004  status=MagickTrue;
1005  progress=0;
1006  (void) ResetMagickMemory(&zero,0,sizeof(zero));
1007  image_view=AcquireCacheViewThreadSet(image);
1008  enhance_view=AcquireCacheViewThreadSet(enhance_image);
1009  #pragma omp parallel for
1010  for (y=0; y < (long) image->rows; y++)
1011  {
1012    register const PixelPacket
1013      *p;
1014
1015    register long
1016      id,
1017      x;
1018
1019    register PixelPacket
1020      *q;
1021
1022    /*
1023      Read another scan line.
1024    */
1025    if (status == MagickFalse)
1026      continue;
1027    id=GetCacheViewThreadId();
1028    p=AcquireCacheViewPixels(image_view[id],-2,y-2,image->columns+4,5,
1029      exception);
1030    q=GetCacheViewPixels(enhance_view[id],0,y,enhance_image->columns,1);
1031    if ((p == (const PixelPacket *) NULL) || (q == (PixelPacket *) NULL))
1032      {
1033        status=MagickFalse;
1034        continue;
1035      }
1036    for (x=0; x < (long) image->columns; x++)
1037    {
1038      MagickPixelPacket
1039        aggregate;
1040
1041      MagickRealType
1042        distance,
1043        distance_squared,
1044        mean,
1045        total_weight;
1046
1047      PixelPacket
1048        pixel;
1049
1050      register const PixelPacket
1051        *r;
1052
1053      /*
1054        Compute weighted average of target pixel color components.
1055      */
1056      aggregate=zero;
1057      total_weight=0.0;
1058      r=p+2*(image->columns+4)+2;
1059      pixel=(*r);
1060      r=p;
1061      Enhance(5.0); Enhance(8.0); Enhance(10.0); Enhance(8.0); Enhance(5.0);
1062      r=p+(image->columns+4);
1063      Enhance(8.0); Enhance(20.0); Enhance(40.0); Enhance(20.0); Enhance(8.0);