| /* |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % % |
| % % |
| % % |
| % EEEEE N N H H AAA N N CCCC EEEEE % |
| % E NN N H H A A NN N C E % |
| % EEE N N N HHHHH AAAAA N N N C EEE % |
| % E N NN H H A A N NN C E % |
| % EEEEE N N H H A A N N CCCC EEEEE % |
| % % |
| % % |
| % MagickCore Image Enhancement Methods % |
| % % |
| % Software Design % |
| % Cristy % |
| % July 1992 % |
| % % |
| % % |
| % Copyright 1999-2021 ImageMagick Studio LLC, a non-profit organization % |
| % dedicated to making software imaging solutions freely available. % |
| % % |
| % You may not use this file except in compliance with the License. You may % |
| % obtain a copy of the License at % |
| % % |
| % https://imagemagick.org/script/license.php % |
| % % |
| % Unless required by applicable law or agreed to in writing, software % |
| % distributed under the License is distributed on an "AS IS" BASIS, % |
| % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. % |
| % See the License for the specific language governing permissions and % |
| % limitations under the License. % |
| % % |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % |
| % |
| % |
| */ |
| |
| /* |
| Include declarations. |
| */ |
| #include "MagickCore/studio.h" |
| #include "MagickCore/accelerate-private.h" |
| #include "MagickCore/artifact.h" |
| #include "MagickCore/attribute.h" |
| #include "MagickCore/cache.h" |
| #include "MagickCore/cache-private.h" |
| #include "MagickCore/cache-view.h" |
| #include "MagickCore/channel.h" |
| #include "MagickCore/color.h" |
| #include "MagickCore/color-private.h" |
| #include "MagickCore/colorspace.h" |
| #include "MagickCore/colorspace-private.h" |
| #include "MagickCore/composite-private.h" |
| #include "MagickCore/enhance.h" |
| #include "MagickCore/exception.h" |
| #include "MagickCore/exception-private.h" |
| #include "MagickCore/fx.h" |
| #include "MagickCore/gem.h" |
| #include "MagickCore/gem-private.h" |
| #include "MagickCore/geometry.h" |
| #include "MagickCore/histogram.h" |
| #include "MagickCore/image.h" |
| #include "MagickCore/image-private.h" |
| #include "MagickCore/memory_.h" |
| #include "MagickCore/monitor.h" |
| #include "MagickCore/monitor-private.h" |
| #include "MagickCore/option.h" |
| #include "MagickCore/pixel.h" |
| #include "MagickCore/pixel-accessor.h" |
| #include "MagickCore/quantum.h" |
| #include "MagickCore/quantum-private.h" |
| #include "MagickCore/resample.h" |
| #include "MagickCore/resample-private.h" |
| #include "MagickCore/resource_.h" |
| #include "MagickCore/statistic.h" |
| #include "MagickCore/string_.h" |
| #include "MagickCore/string-private.h" |
| #include "MagickCore/thread-private.h" |
| #include "MagickCore/threshold.h" |
| #include "MagickCore/token.h" |
| #include "MagickCore/xml-tree.h" |
| #include "MagickCore/xml-tree-private.h" |
| |
| /* |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % % |
| % % |
| % % |
| % A u t o G a m m a I m a g e % |
| % % |
| % % |
| % % |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % |
| % AutoGammaImage() extract the 'mean' from the image and adjust the image |
| % to try make set its gamma appropriately. |
| % |
| % The format of the AutoGammaImage method is: |
| % |
| % MagickBooleanType AutoGammaImage(Image *image,ExceptionInfo *exception) |
| % |
| % A description of each parameter follows: |
| % |
| % o image: The image to auto-level |
| % |
| % o exception: return any errors or warnings in this structure. |
| % |
| */ |
| MagickExport MagickBooleanType AutoGammaImage(Image *image, |
| ExceptionInfo *exception) |
| { |
| double |
| gamma, |
| log_mean, |
| mean, |
| sans; |
| |
| MagickStatusType |
| status; |
| |
| ssize_t |
| i; |
| |
| log_mean=log(0.5); |
| if (image->channel_mask == DefaultChannels) |
| { |
| /* |
| Apply gamma correction equally across all given channels. |
| */ |
| (void) GetImageMean(image,&mean,&sans,exception); |
| gamma=log(mean*QuantumScale)/log_mean; |
| return(LevelImage(image,0.0,(double) QuantumRange,gamma,exception)); |
| } |
| /* |
| Auto-gamma each channel separately. |
| */ |
| status=MagickTrue; |
| for (i=0; i < (ssize_t) GetPixelChannels(image); i++) |
| { |
| ChannelType |
| channel_mask; |
| |
| PixelChannel channel = GetPixelChannelChannel(image,i); |
| PixelTrait traits = GetPixelChannelTraits(image,channel); |
| if ((traits & UpdatePixelTrait) == 0) |
| continue; |
| channel_mask=SetImageChannelMask(image,(ChannelType) (1UL << i)); |
| status=GetImageMean(image,&mean,&sans,exception); |
| gamma=log(mean*QuantumScale)/log_mean; |
| status&=LevelImage(image,0.0,(double) QuantumRange,gamma,exception); |
| (void) SetImageChannelMask(image,channel_mask); |
| if (status == MagickFalse) |
| break; |
| } |
| return(status != 0 ? MagickTrue : MagickFalse); |
| } |
| |
| /* |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % % |
| % % |
| % % |
| % A u t o L e v e l I m a g e % |
| % % |
| % % |
| % % |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % |
| % AutoLevelImage() adjusts the levels of a particular image channel by |
| % scaling the minimum and maximum values to the full quantum range. |
| % |
| % The format of the LevelImage method is: |
| % |
| % MagickBooleanType AutoLevelImage(Image *image,ExceptionInfo *exception) |
| % |
| % A description of each parameter follows: |
| % |
| % o image: The image to auto-level |
| % |
| % o exception: return any errors or warnings in this structure. |
| % |
| */ |
| MagickExport MagickBooleanType AutoLevelImage(Image *image, |
| ExceptionInfo *exception) |
| { |
| return(MinMaxStretchImage(image,0.0,0.0,1.0,exception)); |
| } |
| |
| /* |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % % |
| % % |
| % % |
| % B r i g h t n e s s C o n t r a s t I m a g e % |
| % % |
| % % |
| % % |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % |
| % BrightnessContrastImage() changes the brightness and/or contrast of an |
| % image. It converts the brightness and contrast parameters into slope and |
| % intercept and calls a polynomical function to apply to the image. |
| % |
| % The format of the BrightnessContrastImage method is: |
| % |
| % MagickBooleanType BrightnessContrastImage(Image *image, |
| % const double brightness,const double contrast,ExceptionInfo *exception) |
| % |
| % A description of each parameter follows: |
| % |
| % o image: the image. |
| % |
| % o brightness: the brightness percent (-100 .. 100). |
| % |
| % o contrast: the contrast percent (-100 .. 100). |
| % |
| % o exception: return any errors or warnings in this structure. |
| % |
| */ |
| MagickExport MagickBooleanType BrightnessContrastImage(Image *image, |
| const double brightness,const double contrast,ExceptionInfo *exception) |
| { |
| #define BrightnessContastImageTag "BrightnessContast/Image" |
| |
| double |
| alpha, |
| coefficients[2], |
| intercept, |
| slope; |
| |
| MagickBooleanType |
| status; |
| |
| /* |
| Compute slope and intercept. |
| */ |
| assert(image != (Image *) NULL); |
| assert(image->signature == MagickCoreSignature); |
| if (image->debug != MagickFalse) |
| (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
| alpha=contrast; |
| slope=tan((double) (MagickPI*(alpha/100.0+1.0)/4.0)); |
| if (slope < 0.0) |
| slope=0.0; |
| intercept=brightness/100.0+((100-brightness)/200.0)*(1.0-slope); |
| coefficients[0]=slope; |
| coefficients[1]=intercept; |
| status=FunctionImage(image,PolynomialFunction,2,coefficients,exception); |
| return(status); |
| } |
| |
| /* |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % % |
| % % |
| % % |
| % C L A H E I m a g e % |
| % % |
| % % |
| % % |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % |
| % CLAHEImage() is a variant of adaptive histogram equalization in which the |
| % contrast amplification is limited, so as to reduce this problem of noise |
| % amplification. |
| % |
| % Adapted from implementation by Karel Zuiderveld, [email protected] in |
| % "Graphics Gems IV", Academic Press, 1994. |
| % |
| % The format of the CLAHEImage method is: |
| % |
| % MagickBooleanType CLAHEImage(Image *image,const size_t width, |
| % const size_t height,const size_t number_bins,const double clip_limit, |
| % ExceptionInfo *exception) |
| % |
| % A description of each parameter follows: |
| % |
| % o image: the image. |
| % |
| % o width: the width of the tile divisions to use in horizontal direction. |
| % |
| % o height: the height of the tile divisions to use in vertical direction. |
| % |
| % o number_bins: number of bins for histogram ("dynamic range"). |
| % |
| % o clip_limit: contrast limit for localised changes in contrast. A limit |
| % less than 1 results in standard non-contrast limited AHE. |
| % |
| % o exception: return any errors or warnings in this structure. |
| % |
| */ |
| |
| typedef struct _RangeInfo |
| { |
| unsigned short |
| min, |
| max; |
| } RangeInfo; |
| |
| static void ClipCLAHEHistogram(const double clip_limit,const size_t number_bins, |
| size_t *histogram) |
| { |
| #define NumberCLAHEGrays (65536) |
| |
| ssize_t |
| i; |
| |
| size_t |
| cumulative_excess, |
| previous_excess, |
| step; |
| |
| ssize_t |
| excess; |
| |
| /* |
| Compute total number of excess pixels. |
| */ |
| cumulative_excess=0; |
| for (i=0; i < (ssize_t) number_bins; i++) |
| { |
| excess=(ssize_t) histogram[i]-(ssize_t) clip_limit; |
| if (excess > 0) |
| cumulative_excess+=excess; |
| } |
| /* |
| Clip histogram and redistribute excess pixels across all bins. |
| */ |
| step=cumulative_excess/number_bins; |
| excess=(ssize_t) (clip_limit-step); |
| for (i=0; i < (ssize_t) number_bins; i++) |
| { |
| if ((double) histogram[i] > clip_limit) |
| histogram[i]=(size_t) clip_limit; |
| else |
| if ((ssize_t) histogram[i] > excess) |
| { |
| cumulative_excess-=histogram[i]-excess; |
| histogram[i]=(size_t) clip_limit; |
| } |
| else |
| { |
| cumulative_excess-=step; |
| histogram[i]+=step; |
| } |
| } |
| /* |
| Redistribute remaining excess. |
| */ |
| do |
| { |
| size_t |
| *p; |
| |
| size_t |
| *q; |
| |
| previous_excess=cumulative_excess; |
| p=histogram; |
| q=histogram+number_bins; |
| while ((cumulative_excess != 0) && (p < q)) |
| { |
| step=number_bins/cumulative_excess; |
| if (step < 1) |
| step=1; |
| for (p=histogram; (p < q) && (cumulative_excess != 0); p+=step) |
| if ((double) *p < clip_limit) |
| { |
| (*p)++; |
| cumulative_excess--; |
| } |
| p++; |
| } |
| } while ((cumulative_excess != 0) && (cumulative_excess < previous_excess)); |
| } |
| |
| static void GenerateCLAHEHistogram(const RectangleInfo *clahe_info, |
| const RectangleInfo *tile_info,const size_t number_bins, |
| const unsigned short *lut,const unsigned short *pixels,size_t *histogram) |
| { |
| const unsigned short |
| *p; |
| |
| ssize_t |
| i; |
| |
| /* |
| Classify the pixels into a gray histogram. |
| */ |
| for (i=0; i < (ssize_t) number_bins; i++) |
| histogram[i]=0L; |
| p=pixels; |
| for (i=0; i < (ssize_t) tile_info->height; i++) |
| { |
| const unsigned short |
| *q; |
| |
| q=p+tile_info->width; |
| while (p < q) |
| histogram[lut[*p++]]++; |
| q+=clahe_info->width; |
| p=q-tile_info->width; |
| } |
| } |
| |
| static void InterpolateCLAHE(const RectangleInfo *clahe_info,const size_t *Q12, |
| const size_t *Q22,const size_t *Q11,const size_t *Q21, |
| const RectangleInfo *tile,const unsigned short *lut,unsigned short *pixels) |
| { |
| ssize_t |
| y; |
| |
| unsigned short |
| intensity; |
| |
| /* |
| Bilinear interpolate four tiles to eliminate boundary artifacts. |
| */ |
| for (y=(ssize_t) tile->height; y > 0; y--) |
| { |
| ssize_t |
| x; |
| |
| for (x=(ssize_t) tile->width; x > 0; x--) |
| { |
| intensity=lut[*pixels]; |
| *pixels++=(unsigned short) (PerceptibleReciprocal((double) tile->width* |
| tile->height)*(y*((double) x*Q12[intensity]+(tile->width-x)* |
| Q22[intensity])+(tile->height-y)*((double) x*Q11[intensity]+ |
| (tile->width-x)*Q21[intensity]))); |
| } |
| pixels+=(clahe_info->width-tile->width); |
| } |
| } |
| |
| static void GenerateCLAHELut(const RangeInfo *range_info, |
| const size_t number_bins,unsigned short *lut) |
| { |
| ssize_t |
| i; |
| |
| unsigned short |
| delta; |
| |
| /* |
| Scale input image [intensity min,max] to [0,number_bins-1]. |
| */ |
| delta=(unsigned short) ((range_info->max-range_info->min)/number_bins+1); |
| for (i=(ssize_t) range_info->min; i <= (ssize_t) range_info->max; i++) |
| lut[i]=(unsigned short) ((i-range_info->min)/delta); |
| } |
| |
| static void MapCLAHEHistogram(const RangeInfo *range_info, |
| const size_t number_bins,const size_t number_pixels,size_t *histogram) |
| { |
| double |
| scale, |
| sum; |
| |
| ssize_t |
| i; |
| |
| /* |
| Rescale histogram to range [min-intensity .. max-intensity]. |
| */ |
| scale=(double) (range_info->max-range_info->min)/number_pixels; |
| sum=0.0; |
| for (i=0; i < (ssize_t) number_bins; i++) |
| { |
| sum+=histogram[i]; |
| histogram[i]=(size_t) (range_info->min+scale*sum); |
| if (histogram[i] > range_info->max) |
| histogram[i]=range_info->max; |
| } |
| } |
| |
| static MagickBooleanType CLAHE(const RectangleInfo *clahe_info, |
| const RectangleInfo *tile_info,const RangeInfo *range_info, |
| const size_t number_bins,const double clip_limit,unsigned short *pixels) |
| { |
| MemoryInfo |
| *tile_cache; |
| |
| unsigned short |
| *p; |
| |
| size_t |
| limit, |
| *tiles; |
| |
| ssize_t |
| y; |
| |
| unsigned short |
| *lut; |
| |
| /* |
| Constrast limited adapted histogram equalization. |
| */ |
| if (clip_limit == 1.0) |
| return(MagickTrue); |
| tile_cache=AcquireVirtualMemory((size_t) clahe_info->x*number_bins, |
| clahe_info->y*sizeof(*tiles)); |
| if (tile_cache == (MemoryInfo *) NULL) |
| return(MagickFalse); |
| lut=(unsigned short *) AcquireQuantumMemory(NumberCLAHEGrays,sizeof(*lut)); |
| if (lut == (unsigned short *) NULL) |
| { |
| tile_cache=RelinquishVirtualMemory(tile_cache); |
| return(MagickFalse); |
| } |
| tiles=(size_t *) GetVirtualMemoryBlob(tile_cache); |
| limit=(size_t) (clip_limit*(tile_info->width*tile_info->height)/number_bins); |
| if (limit < 1UL) |
| limit=1UL; |
| /* |
| Generate greylevel mappings for each tile. |
| */ |
| GenerateCLAHELut(range_info,number_bins,lut); |
| p=pixels; |
| for (y=0; y < (ssize_t) clahe_info->y; y++) |
| { |
| ssize_t |
| x; |
| |
| for (x=0; x < (ssize_t) clahe_info->x; x++) |
| { |
| size_t |
| *histogram; |
| |
| histogram=tiles+(number_bins*(y*clahe_info->x+x)); |
| GenerateCLAHEHistogram(clahe_info,tile_info,number_bins,lut,p,histogram); |
| ClipCLAHEHistogram((double) limit,number_bins,histogram); |
| MapCLAHEHistogram(range_info,number_bins,tile_info->width* |
| tile_info->height,histogram); |
| p+=tile_info->width; |
| } |
| p+=clahe_info->width*(tile_info->height-1); |
| } |
| /* |
| Interpolate greylevel mappings to get CLAHE image. |
| */ |
| p=pixels; |
| for (y=0; y <= (ssize_t) clahe_info->y; y++) |
| { |
| OffsetInfo |
| offset; |
| |
| RectangleInfo |
| tile; |
| |
| ssize_t |
| x; |
| |
| tile.height=tile_info->height; |
| tile.y=y-1; |
| offset.y=tile.y+1; |
| if (y == 0) |
| { |
| /* |
| Top row. |
| */ |
| tile.height=tile_info->height >> 1; |
| tile.y=0; |
| offset.y=0; |
| } |
| else |
| if (y == (ssize_t) clahe_info->y) |
| { |
| /* |
| Bottom row. |
| */ |
| tile.height=(tile_info->height+1) >> 1; |
| tile.y=clahe_info->y-1; |
| offset.y=tile.y; |
| } |
| for (x=0; x <= (ssize_t) clahe_info->x; x++) |
| { |
| tile.width=tile_info->width; |
| tile.x=x-1; |
| offset.x=tile.x+1; |
| if (x == 0) |
| { |
| /* |
| Left column. |
| */ |
| tile.width=tile_info->width >> 1; |
| tile.x=0; |
| offset.x=0; |
| } |
| else |
| if (x == (ssize_t) clahe_info->x) |
| { |
| /* |
| Right column. |
| */ |
| tile.width=(tile_info->width+1) >> 1; |
| tile.x=clahe_info->x-1; |
| offset.x=tile.x; |
| } |
| InterpolateCLAHE(clahe_info, |
| tiles+(number_bins*(tile.y*clahe_info->x+tile.x)), /* Q12 */ |
| tiles+(number_bins*(tile.y*clahe_info->x+offset.x)), /* Q22 */ |
| tiles+(number_bins*(offset.y*clahe_info->x+tile.x)), /* Q11 */ |
| tiles+(number_bins*(offset.y*clahe_info->x+offset.x)), /* Q21 */ |
| &tile,lut,p); |
| p+=tile.width; |
| } |
| p+=clahe_info->width*(tile.height-1); |
| } |
| lut=(unsigned short *) RelinquishMagickMemory(lut); |
| tile_cache=RelinquishVirtualMemory(tile_cache); |
| return(MagickTrue); |
| } |
| |
| MagickExport MagickBooleanType CLAHEImage(Image *image,const size_t width, |
| const size_t height,const size_t number_bins,const double clip_limit, |
| ExceptionInfo *exception) |
| { |
| #define CLAHEImageTag "CLAHE/Image" |
| |
| CacheView |
| *image_view; |
| |
| ColorspaceType |
| colorspace; |
| |
| MagickBooleanType |
| status; |
| |
| MagickOffsetType |
| progress; |
| |
| MemoryInfo |
| *pixel_cache; |
| |
| RangeInfo |
| range_info; |
| |
| RectangleInfo |
| clahe_info, |
| tile_info; |
| |
| size_t |
| n; |
| |
| ssize_t |
| y; |
| |
| unsigned short |
| *pixels; |
| |
| /* |
| Configure CLAHE parameters. |
| */ |
| assert(image != (Image *) NULL); |
| assert(image->signature == MagickCoreSignature); |
| if (image->debug != MagickFalse) |
| (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
| range_info.min=0; |
| range_info.max=NumberCLAHEGrays-1; |
| tile_info.width=width; |
| if (tile_info.width == 0) |
| tile_info.width=image->columns >> 3; |
| tile_info.height=height; |
| if (tile_info.height == 0) |
| tile_info.height=image->rows >> 3; |
| tile_info.x=0; |
| if ((image->columns % tile_info.width) != 0) |
| tile_info.x=(ssize_t) tile_info.width-(image->columns % tile_info.width); |
| tile_info.y=0; |
| if ((image->rows % tile_info.height) != 0) |
| tile_info.y=(ssize_t) tile_info.height-(image->rows % tile_info.height); |
| clahe_info.width=image->columns+tile_info.x; |
| clahe_info.height=image->rows+tile_info.y; |
| clahe_info.x=(ssize_t) clahe_info.width/tile_info.width; |
| clahe_info.y=(ssize_t) clahe_info.height/tile_info.height; |
| pixel_cache=AcquireVirtualMemory(clahe_info.width,clahe_info.height* |
| sizeof(*pixels)); |
| if (pixel_cache == (MemoryInfo *) NULL) |
| ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed", |
| image->filename); |
| pixels=(unsigned short *) GetVirtualMemoryBlob(pixel_cache); |
| colorspace=image->colorspace; |
| if (TransformImageColorspace(image,LabColorspace,exception) == MagickFalse) |
| { |
| pixel_cache=RelinquishVirtualMemory(pixel_cache); |
| return(MagickFalse); |
| } |
| /* |
| Initialize CLAHE pixels. |
| */ |
| image_view=AcquireVirtualCacheView(image,exception); |
| progress=0; |
| status=MagickTrue; |
| n=0; |
| for (y=0; y < (ssize_t) clahe_info.height; y++) |
| { |
| const Quantum |
| *magick_restrict p; |
| |
| ssize_t |
| x; |
| |
| if (status == MagickFalse) |
| continue; |
| p=GetCacheViewVirtualPixels(image_view,-(tile_info.x >> 1),y- |
| (tile_info.y >> 1),clahe_info.width,1,exception); |
| if (p == (const Quantum *) NULL) |
| { |
| status=MagickFalse; |
| continue; |
| } |
| for (x=0; x < (ssize_t) clahe_info.width; x++) |
| { |
| pixels[n++]=ScaleQuantumToShort(p[0]); |
| p+=GetPixelChannels(image); |
| } |
| if (image->progress_monitor != (MagickProgressMonitor) NULL) |
| { |
| MagickBooleanType |
| proceed; |
| |
| progress++; |
| proceed=SetImageProgress(image,CLAHEImageTag,progress,2* |
| GetPixelChannels(image)); |
| if (proceed == MagickFalse) |
| status=MagickFalse; |
| } |
| } |
| image_view=DestroyCacheView(image_view); |
| status=CLAHE(&clahe_info,&tile_info,&range_info,number_bins == 0 ? |
| (size_t) 128 : MagickMin(number_bins,256),clip_limit,pixels); |
| if (status == MagickFalse) |
| (void) ThrowMagickException(exception,GetMagickModule(), |
| ResourceLimitError,"MemoryAllocationFailed","`%s'",image->filename); |
| /* |
| Push CLAHE pixels to CLAHE image. |
| */ |
| image_view=AcquireAuthenticCacheView(image,exception); |
| n=clahe_info.width*(tile_info.y >> 1); |
| for (y=0; y < (ssize_t) image->rows; y++) |
| { |
| Quantum |
| *magick_restrict q; |
| |
| ssize_t |
| x; |
| |
| if (status == MagickFalse) |
| continue; |
| q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception); |
| if (q == (Quantum *) NULL) |
| { |
| status=MagickFalse; |
| continue; |
| } |
| n+=tile_info.x >> 1; |
| for (x=0; x < (ssize_t) image->columns; x++) |
| { |
| q[0]=ScaleShortToQuantum(pixels[n++]); |
| q+=GetPixelChannels(image); |
| } |
| n+=(clahe_info.width-image->columns-(tile_info.x >> 1)); |
| if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse) |
| status=MagickFalse; |
| if (image->progress_monitor != (MagickProgressMonitor) NULL) |
| { |
| MagickBooleanType |
| proceed; |
| |
| progress++; |
| proceed=SetImageProgress(image,CLAHEImageTag,progress,2* |
| GetPixelChannels(image)); |
| if (proceed == MagickFalse) |
| status=MagickFalse; |
| } |
| } |
| image_view=DestroyCacheView(image_view); |
| pixel_cache=RelinquishVirtualMemory(pixel_cache); |
| if (TransformImageColorspace(image,colorspace,exception) == MagickFalse) |
| status=MagickFalse; |
| return(status); |
| } |
| |
| /* |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % % |
| % % |
| % % |
| % C l u t I m a g e % |
| % % |
| % % |
| % % |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % |
| % ClutImage() replaces each color value in the given image, by using it as an |
| % index to lookup a replacement color value in a Color Look UP Table in the |
| % form of an image. The values are extracted along a diagonal of the CLUT |
| % image so either a horizontal or vertial gradient image can be used. |
| % |
| % Typically this is used to either re-color a gray-scale image according to a |
| % color gradient in the CLUT image, or to perform a freeform histogram |
| % (level) adjustment according to the (typically gray-scale) gradient in the |
| % CLUT image. |
| % |
| % When the 'channel' mask includes the matte/alpha transparency channel but |
| % one image has no such channel it is assumed that that image is a simple |
| % gray-scale image that will effect the alpha channel values, either for |
| % gray-scale coloring (with transparent or semi-transparent colors), or |
| % a histogram adjustment of existing alpha channel values. If both images |
| % have matte channels, direct and normal indexing is applied, which is rarely |
| % used. |
| % |
| % The format of the ClutImage method is: |
| % |
| % MagickBooleanType ClutImage(Image *image,Image *clut_image, |
| % const PixelInterpolateMethod method,ExceptionInfo *exception) |
| % |
| % A description of each parameter follows: |
| % |
| % o image: the image, which is replaced by indexed CLUT values |
| % |
| % o clut_image: the color lookup table image for replacement color values. |
| % |
| % o method: the pixel interpolation method. |
| % |
| % o exception: return any errors or warnings in this structure. |
| % |
| */ |
| MagickExport MagickBooleanType ClutImage(Image *image,const Image *clut_image, |
| const PixelInterpolateMethod method,ExceptionInfo *exception) |
| { |
| #define ClutImageTag "Clut/Image" |
| |
| CacheView |
| *clut_view, |
| *image_view; |
| |
| MagickBooleanType |
| status; |
| |
| MagickOffsetType |
| progress; |
| |
| PixelInfo |
| *clut_map; |
| |
| ssize_t |
| i; |
| |
| ssize_t adjust, |
| y; |
| |
| assert(image != (Image *) NULL); |
| assert(image->signature == MagickCoreSignature); |
| if (image->debug != MagickFalse) |
| (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
| assert(clut_image != (Image *) NULL); |
| assert(clut_image->signature == MagickCoreSignature); |
| if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse) |
| return(MagickFalse); |
| if ((IsGrayColorspace(image->colorspace) != MagickFalse) && |
| (IsGrayColorspace(clut_image->colorspace) == MagickFalse)) |
| (void) SetImageColorspace(image,sRGBColorspace,exception); |
| clut_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*clut_map)); |
| if (clut_map == (PixelInfo *) NULL) |
| ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed", |
| image->filename); |
| /* |
| Clut image. |
| */ |
| status=MagickTrue; |
| progress=0; |
| adjust=(ssize_t) (clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1); |
| clut_view=AcquireVirtualCacheView(clut_image,exception); |
| for (i=0; i <= (ssize_t) MaxMap; i++) |
| { |
| GetPixelInfo(clut_image,clut_map+i); |
| status=InterpolatePixelInfo(clut_image,clut_view,method, |
| (double) i*(clut_image->columns-adjust)/MaxMap,(double) i* |
| (clut_image->rows-adjust)/MaxMap,clut_map+i,exception); |
| if (status == MagickFalse) |
| break; |
| } |
| clut_view=DestroyCacheView(clut_view); |
| image_view=AcquireAuthenticCacheView(image,exception); |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp parallel for schedule(static) shared(progress,status) \ |
| magick_number_threads(image,image,image->rows,1) |
| #endif |
| for (y=0; y < (ssize_t) image->rows; y++) |
| { |
| PixelInfo |
| pixel; |
| |
| Quantum |
| *magick_restrict q; |
| |
| ssize_t |
| x; |
| |
| if (status == MagickFalse) |
| continue; |
| q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception); |
| if (q == (Quantum *) NULL) |
| { |
| status=MagickFalse; |
| continue; |
| } |
| GetPixelInfo(image,&pixel); |
| for (x=0; x < (ssize_t) image->columns; x++) |
| { |
| PixelTrait |
| traits; |
| |
| GetPixelInfoPixel(image,q,&pixel); |
| traits=GetPixelChannelTraits(image,RedPixelChannel); |
| if ((traits & UpdatePixelTrait) != 0) |
| pixel.red=clut_map[ScaleQuantumToMap(ClampToQuantum( |
| pixel.red))].red; |
| traits=GetPixelChannelTraits(image,GreenPixelChannel); |
| if ((traits & UpdatePixelTrait) != 0) |
| pixel.green=clut_map[ScaleQuantumToMap(ClampToQuantum( |
| pixel.green))].green; |
| traits=GetPixelChannelTraits(image,BluePixelChannel); |
| if ((traits & UpdatePixelTrait) != 0) |
| pixel.blue=clut_map[ScaleQuantumToMap(ClampToQuantum( |
| pixel.blue))].blue; |
| traits=GetPixelChannelTraits(image,BlackPixelChannel); |
| if ((traits & UpdatePixelTrait) != 0) |
| pixel.black=clut_map[ScaleQuantumToMap(ClampToQuantum( |
| pixel.black))].black; |
| traits=GetPixelChannelTraits(image,AlphaPixelChannel); |
| if ((traits & UpdatePixelTrait) != 0) |
| pixel.alpha=clut_map[ScaleQuantumToMap(ClampToQuantum( |
| pixel.alpha))].alpha; |
| SetPixelViaPixelInfo(image,&pixel,q); |
| q+=GetPixelChannels(image); |
| } |
| if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse) |
| status=MagickFalse; |
| if (image->progress_monitor != (MagickProgressMonitor) NULL) |
| { |
| MagickBooleanType |
| proceed; |
| |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp atomic |
| #endif |
| progress++; |
| proceed=SetImageProgress(image,ClutImageTag,progress,image->rows); |
| if (proceed == MagickFalse) |
| status=MagickFalse; |
| } |
| } |
| image_view=DestroyCacheView(image_view); |
| clut_map=(PixelInfo *) RelinquishMagickMemory(clut_map); |
| if ((clut_image->alpha_trait != UndefinedPixelTrait) && |
| ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)) |
| (void) SetImageAlphaChannel(image,ActivateAlphaChannel,exception); |
| return(status); |
| } |
| |
| /* |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % % |
| % % |
| % % |
| % C o l o r D e c i s i o n L i s t I m a g e % |
| % % |
| % % |
| % % |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % |
| % ColorDecisionListImage() accepts a lightweight Color Correction Collection |
| % (CCC) file which solely contains one or more color corrections and applies |
| % the correction to the image. Here is a sample CCC file: |
| % |
| % <ColorCorrectionCollection xmlns="urn:ASC:CDL:v1.2"> |
| % <ColorCorrection id="cc03345"> |
| % <SOPNode> |
| % <Slope> 0.9 1.2 0.5 </Slope> |
| % <Offset> 0.4 -0.5 0.6 </Offset> |
| % <Power> 1.0 0.8 1.5 </Power> |
| % </SOPNode> |
| % <SATNode> |
| % <Saturation> 0.85 </Saturation> |
| % </SATNode> |
| % </ColorCorrection> |
| % </ColorCorrectionCollection> |
| % |
| % which includes the slop, offset, and power for each of the RGB channels |
| % as well as the saturation. |
| % |
| % The format of the ColorDecisionListImage method is: |
| % |
| % MagickBooleanType ColorDecisionListImage(Image *image, |
| % const char *color_correction_collection,ExceptionInfo *exception) |
| % |
| % A description of each parameter follows: |
| % |
| % o image: the image. |
| % |
| % o color_correction_collection: the color correction collection in XML. |
| % |
| % o exception: return any errors or warnings in this structure. |
| % |
| */ |
| MagickExport MagickBooleanType ColorDecisionListImage(Image *image, |
| const char *color_correction_collection,ExceptionInfo *exception) |
| { |
| #define ColorDecisionListCorrectImageTag "ColorDecisionList/Image" |
| |
| typedef struct _Correction |
| { |
| double |
| slope, |
| offset, |
| power; |
| } Correction; |
| |
| typedef struct _ColorCorrection |
| { |
| Correction |
| red, |
| green, |
| blue; |
| |
| double |
| saturation; |
| } ColorCorrection; |
| |
| CacheView |
| *image_view; |
| |
| char |
| token[MagickPathExtent]; |
| |
| ColorCorrection |
| color_correction; |
| |
| const char |
| *content, |
| *p; |
| |
| MagickBooleanType |
| status; |
| |
| MagickOffsetType |
| progress; |
| |
| PixelInfo |
| *cdl_map; |
| |
| ssize_t |
| i; |
| |
| ssize_t |
| y; |
| |
| XMLTreeInfo |
| *cc, |
| *ccc, |
| *sat, |
| *sop; |
| |
| /* |
| Allocate and initialize cdl maps. |
| */ |
| assert(image != (Image *) NULL); |
| assert(image->signature == MagickCoreSignature); |
| if (image->debug != MagickFalse) |
| (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
| if (color_correction_collection == (const char *) NULL) |
| return(MagickFalse); |
| ccc=NewXMLTree((const char *) color_correction_collection,exception); |
| if (ccc == (XMLTreeInfo *) NULL) |
| return(MagickFalse); |
| cc=GetXMLTreeChild(ccc,"ColorCorrection"); |
| if (cc == (XMLTreeInfo *) NULL) |
| { |
| ccc=DestroyXMLTree(ccc); |
| return(MagickFalse); |
| } |
| color_correction.red.slope=1.0; |
| color_correction.red.offset=0.0; |
| color_correction.red.power=1.0; |
| color_correction.green.slope=1.0; |
| color_correction.green.offset=0.0; |
| color_correction.green.power=1.0; |
| color_correction.blue.slope=1.0; |
| color_correction.blue.offset=0.0; |
| color_correction.blue.power=1.0; |
| color_correction.saturation=0.0; |
| sop=GetXMLTreeChild(cc,"SOPNode"); |
| if (sop != (XMLTreeInfo *) NULL) |
| { |
| XMLTreeInfo |
| *offset, |
| *power, |
| *slope; |
| |
| slope=GetXMLTreeChild(sop,"Slope"); |
| if (slope != (XMLTreeInfo *) NULL) |
| { |
| content=GetXMLTreeContent(slope); |
| p=(const char *) content; |
| for (i=0; (*p != '\0') && (i < 3); i++) |
| { |
| (void) GetNextToken(p,&p,MagickPathExtent,token); |
| if (*token == ',') |
| (void) GetNextToken(p,&p,MagickPathExtent,token); |
| switch (i) |
| { |
| case 0: |
| { |
| color_correction.red.slope=StringToDouble(token,(char **) NULL); |
| break; |
| } |
| case 1: |
| { |
| color_correction.green.slope=StringToDouble(token, |
| (char **) NULL); |
| break; |
| } |
| case 2: |
| { |
| color_correction.blue.slope=StringToDouble(token, |
| (char **) NULL); |
| break; |
| } |
| } |
| } |
| } |
| offset=GetXMLTreeChild(sop,"Offset"); |
| if (offset != (XMLTreeInfo *) NULL) |
| { |
| content=GetXMLTreeContent(offset); |
| p=(const char *) content; |
| for (i=0; (*p != '\0') && (i < 3); i++) |
| { |
| (void) GetNextToken(p,&p,MagickPathExtent,token); |
| if (*token == ',') |
| (void) GetNextToken(p,&p,MagickPathExtent,token); |
| switch (i) |
| { |
| case 0: |
| { |
| color_correction.red.offset=StringToDouble(token, |
| (char **) NULL); |
| break; |
| } |
| case 1: |
| { |
| color_correction.green.offset=StringToDouble(token, |
| (char **) NULL); |
| break; |
| } |
| case 2: |
| { |
| color_correction.blue.offset=StringToDouble(token, |
| (char **) NULL); |
| break; |
| } |
| } |
| } |
| } |
| power=GetXMLTreeChild(sop,"Power"); |
| if (power != (XMLTreeInfo *) NULL) |
| { |
| content=GetXMLTreeContent(power); |
| p=(const char *) content; |
| for (i=0; (*p != '\0') && (i < 3); i++) |
| { |
| (void) GetNextToken(p,&p,MagickPathExtent,token); |
| if (*token == ',') |
| (void) GetNextToken(p,&p,MagickPathExtent,token); |
| switch (i) |
| { |
| case 0: |
| { |
| color_correction.red.power=StringToDouble(token,(char **) NULL); |
| break; |
| } |
| case 1: |
| { |
| color_correction.green.power=StringToDouble(token, |
| (char **) NULL); |
| break; |
| } |
| case 2: |
| { |
| color_correction.blue.power=StringToDouble(token, |
| (char **) NULL); |
| break; |
| } |
| } |
| } |
| } |
| } |
| sat=GetXMLTreeChild(cc,"SATNode"); |
| if (sat != (XMLTreeInfo *) NULL) |
| { |
| XMLTreeInfo |
| *saturation; |
| |
| saturation=GetXMLTreeChild(sat,"Saturation"); |
| if (saturation != (XMLTreeInfo *) NULL) |
| { |
| content=GetXMLTreeContent(saturation); |
| p=(const char *) content; |
| (void) GetNextToken(p,&p,MagickPathExtent,token); |
| color_correction.saturation=StringToDouble(token,(char **) NULL); |
| } |
| } |
| ccc=DestroyXMLTree(ccc); |
| if (image->debug != MagickFalse) |
| { |
| (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
| " Color Correction Collection:"); |
| (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
| " color_correction.red.slope: %g",color_correction.red.slope); |
| (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
| " color_correction.red.offset: %g",color_correction.red.offset); |
| (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
| " color_correction.red.power: %g",color_correction.red.power); |
| (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
| " color_correction.green.slope: %g",color_correction.green.slope); |
| (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
| " color_correction.green.offset: %g",color_correction.green.offset); |
| (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
| " color_correction.green.power: %g",color_correction.green.power); |
| (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
| " color_correction.blue.slope: %g",color_correction.blue.slope); |
| (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
| " color_correction.blue.offset: %g",color_correction.blue.offset); |
| (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
| " color_correction.blue.power: %g",color_correction.blue.power); |
| (void) LogMagickEvent(TransformEvent,GetMagickModule(), |
| " color_correction.saturation: %g",color_correction.saturation); |
| } |
| cdl_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*cdl_map)); |
| if (cdl_map == (PixelInfo *) NULL) |
| ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed", |
| image->filename); |
| for (i=0; i <= (ssize_t) MaxMap; i++) |
| { |
| cdl_map[i].red=(double) ScaleMapToQuantum((double) |
| (MaxMap*(pow(color_correction.red.slope*i/MaxMap+ |
| color_correction.red.offset,color_correction.red.power)))); |
| cdl_map[i].green=(double) ScaleMapToQuantum((double) |
| (MaxMap*(pow(color_correction.green.slope*i/MaxMap+ |
| color_correction.green.offset,color_correction.green.power)))); |
| cdl_map[i].blue=(double) ScaleMapToQuantum((double) |
| (MaxMap*(pow(color_correction.blue.slope*i/MaxMap+ |
| color_correction.blue.offset,color_correction.blue.power)))); |
| } |
| if (image->storage_class == PseudoClass) |
| for (i=0; i < (ssize_t) image->colors; i++) |
| { |
| /* |
| Apply transfer function to colormap. |
| */ |
| double |
| luma; |
| |
| luma=0.21267f*image->colormap[i].red+0.71526*image->colormap[i].green+ |
| 0.07217f*image->colormap[i].blue; |
| image->colormap[i].red=luma+color_correction.saturation*cdl_map[ |
| ScaleQuantumToMap(ClampToQuantum(image->colormap[i].red))].red-luma; |
| image->colormap[i].green=luma+color_correction.saturation*cdl_map[ |
| ScaleQuantumToMap(ClampToQuantum(image->colormap[i].green))].green-luma; |
| image->colormap[i].blue=luma+color_correction.saturation*cdl_map[ |
| ScaleQuantumToMap(ClampToQuantum(image->colormap[i].blue))].blue-luma; |
| } |
| /* |
| Apply transfer function to image. |
| */ |
| status=MagickTrue; |
| progress=0; |
| image_view=AcquireAuthenticCacheView(image,exception); |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp parallel for schedule(static) shared(progress,status) \ |
| magick_number_threads(image,image,image->rows,1) |
| #endif |
| for (y=0; y < (ssize_t) image->rows; y++) |
| { |
| double |
| luma; |
| |
| Quantum |
| *magick_restrict q; |
| |
| ssize_t |
| x; |
| |
| if (status == MagickFalse) |
| continue; |
| q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception); |
| if (q == (Quantum *) NULL) |
| { |
| status=MagickFalse; |
| continue; |
| } |
| for (x=0; x < (ssize_t) image->columns; x++) |
| { |
| luma=0.21267f*GetPixelRed(image,q)+0.71526*GetPixelGreen(image,q)+ |
| 0.07217f*GetPixelBlue(image,q); |
| SetPixelRed(image,ClampToQuantum(luma+color_correction.saturation* |
| (cdl_map[ScaleQuantumToMap(GetPixelRed(image,q))].red-luma)),q); |
| SetPixelGreen(image,ClampToQuantum(luma+color_correction.saturation* |
| (cdl_map[ScaleQuantumToMap(GetPixelGreen(image,q))].green-luma)),q); |
| SetPixelBlue(image,ClampToQuantum(luma+color_correction.saturation* |
| (cdl_map[ScaleQuantumToMap(GetPixelBlue(image,q))].blue-luma)),q); |
| q+=GetPixelChannels(image); |
| } |
| if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse) |
| status=MagickFalse; |
| if (image->progress_monitor != (MagickProgressMonitor) NULL) |
| { |
| MagickBooleanType |
| proceed; |
| |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp atomic |
| #endif |
| progress++; |
| proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag, |
| progress,image->rows); |
| if (proceed == MagickFalse) |
| status=MagickFalse; |
| } |
| } |
| image_view=DestroyCacheView(image_view); |
| cdl_map=(PixelInfo *) RelinquishMagickMemory(cdl_map); |
| return(status); |
| } |
| |
| /* |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % % |
| % % |
| % % |
| % C o n t r a s t I m a g e % |
| % % |
| % % |
| % % |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % |
| % ContrastImage() enhances the intensity differences between the lighter and |
| % darker elements of the image. Set sharpen to a MagickTrue to increase the |
| % image contrast otherwise the contrast is reduced. |
| % |
| % The format of the ContrastImage method is: |
| % |
| % MagickBooleanType ContrastImage(Image *image, |
| % const MagickBooleanType sharpen,ExceptionInfo *exception) |
| % |
| % A description of each parameter follows: |
| % |
| % o image: the image. |
| % |
| % o sharpen: Increase or decrease image contrast. |
| % |
| % o exception: return any errors or warnings in this structure. |
| % |
| */ |
| |
| static void Contrast(const int sign,double *red,double *green,double *blue) |
| { |
| double |
| brightness, |
| hue, |
| saturation; |
| |
| /* |
| Enhance contrast: dark color become darker, light color become lighter. |
| */ |
| assert(red != (double *) NULL); |
| assert(green != (double *) NULL); |
| assert(blue != (double *) NULL); |
| hue=0.0; |
| saturation=0.0; |
| brightness=0.0; |
| ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness); |
| brightness+=0.5*sign*(0.5*(sin((double) (MagickPI*(brightness-0.5)))+1.0)- |
| brightness); |
| if (brightness > 1.0) |
| brightness=1.0; |
| else |
| if (brightness < 0.0) |
| brightness=0.0; |
| ConvertHSBToRGB(hue,saturation,brightness,red,green,blue); |
| } |
| |
| MagickExport MagickBooleanType ContrastImage(Image *image, |
| const MagickBooleanType sharpen,ExceptionInfo *exception) |
| { |
| #define ContrastImageTag "Contrast/Image" |
| |
| CacheView |
| *image_view; |
| |
| int |
| sign; |
| |
| MagickBooleanType |
| status; |
| |
| MagickOffsetType |
| progress; |
| |
| ssize_t |
| i; |
| |
| ssize_t |
| y; |
| |
| assert(image != (Image *) NULL); |
| assert(image->signature == MagickCoreSignature); |
| #if defined(MAGICKCORE_OPENCL_SUPPORT) |
| if (AccelerateContrastImage(image,sharpen,exception) != MagickFalse) |
| return(MagickTrue); |
| #endif |
| if (image->debug != MagickFalse) |
| (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
| sign=sharpen != MagickFalse ? 1 : -1; |
| if (image->storage_class == PseudoClass) |
| { |
| /* |
| Contrast enhance colormap. |
| */ |
| for (i=0; i < (ssize_t) image->colors; i++) |
| { |
| double |
| blue, |
| green, |
| red; |
| |
| red=(double) image->colormap[i].red; |
| green=(double) image->colormap[i].green; |
| blue=(double) image->colormap[i].blue; |
| Contrast(sign,&red,&green,&blue); |
| image->colormap[i].red=(MagickRealType) red; |
| image->colormap[i].green=(MagickRealType) green; |
| image->colormap[i].blue=(MagickRealType) blue; |
| } |
| } |
| /* |
| Contrast enhance image. |
| */ |
| status=MagickTrue; |
| progress=0; |
| image_view=AcquireAuthenticCacheView(image,exception); |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp parallel for schedule(static) shared(progress,status) \ |
| magick_number_threads(image,image,image->rows,1) |
| #endif |
| for (y=0; y < (ssize_t) image->rows; y++) |
| { |
| double |
| blue, |
| green, |
| red; |
| |
| Quantum |
| *magick_restrict q; |
| |
| ssize_t |
| x; |
| |
| if (status == MagickFalse) |
| continue; |
| q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception); |
| if (q == (Quantum *) NULL) |
| { |
| status=MagickFalse; |
| continue; |
| } |
| for (x=0; x < (ssize_t) image->columns; x++) |
| { |
| red=(double) GetPixelRed(image,q); |
| green=(double) GetPixelGreen(image,q); |
| blue=(double) GetPixelBlue(image,q); |
| Contrast(sign,&red,&green,&blue); |
| SetPixelRed(image,ClampToQuantum(red),q); |
| SetPixelGreen(image,ClampToQuantum(green),q); |
| SetPixelBlue(image,ClampToQuantum(blue),q); |
| q+=GetPixelChannels(image); |
| } |
| if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse) |
| status=MagickFalse; |
| if (image->progress_monitor != (MagickProgressMonitor) NULL) |
| { |
| MagickBooleanType |
| proceed; |
| |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp atomic |
| #endif |
| progress++; |
| proceed=SetImageProgress(image,ContrastImageTag,progress,image->rows); |
| if (proceed == MagickFalse) |
| status=MagickFalse; |
| } |
| } |
| image_view=DestroyCacheView(image_view); |
| return(status); |
| } |
| |
| /* |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % % |
| % % |
| % % |
| % C o n t r a s t S t r e t c h I m a g e % |
| % % |
| % % |
| % % |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % |
| % ContrastStretchImage() is a simple image enhancement technique that attempts |
| % to improve the contrast in an image by 'stretching' the range of intensity |
| % values it contains to span a desired range of values. It differs from the |
| % more sophisticated histogram equalization in that it can only apply a |
| % linear scaling function to the image pixel values. As a result the |
| % 'enhancement' is less harsh. |
| % |
| % The format of the ContrastStretchImage method is: |
| % |
| % MagickBooleanType ContrastStretchImage(Image *image, |
| % const char *levels,ExceptionInfo *exception) |
| % |
| % A description of each parameter follows: |
| % |
| % o image: the image. |
| % |
| % o black_point: the black point. |
| % |
| % o white_point: the white point. |
| % |
| % o levels: Specify the levels where the black and white points have the |
| % range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.). |
| % |
| % o exception: return any errors or warnings in this structure. |
| % |
| */ |
| MagickExport MagickBooleanType ContrastStretchImage(Image *image, |
| const double black_point,const double white_point,ExceptionInfo *exception) |
| { |
| #define MaxRange(color) ((double) ScaleQuantumToMap((Quantum) (color))) |
| #define ContrastStretchImageTag "ContrastStretch/Image" |
| |
| CacheView |
| *image_view; |
| |
| double |
| *black, |
| *histogram, |
| *stretch_map, |
| *white; |
| |
| MagickBooleanType |
| status; |
| |
| MagickOffsetType |
| progress; |
| |
| ssize_t |
| i; |
| |
| ssize_t |
| y; |
| |
| /* |
| Allocate histogram and stretch map. |
| */ |
| assert(image != (Image *) NULL); |
| assert(image->signature == MagickCoreSignature); |
| if (image->debug != MagickFalse) |
| (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
| if (SetImageGray(image,exception) != MagickFalse) |
| (void) SetImageColorspace(image,GRAYColorspace,exception); |
| black=(double *) AcquireQuantumMemory(MaxPixelChannels,sizeof(*black)); |
| white=(double *) AcquireQuantumMemory(MaxPixelChannels,sizeof(*white)); |
| histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,MaxPixelChannels* |
| sizeof(*histogram)); |
| stretch_map=(double *) AcquireQuantumMemory(MaxMap+1UL,MaxPixelChannels* |
| sizeof(*stretch_map)); |
| if ((black == (double *) NULL) || (white == (double *) NULL) || |
| (histogram == (double *) NULL) || (stretch_map == (double *) NULL)) |
| { |
| if (stretch_map != (double *) NULL) |
| stretch_map=(double *) RelinquishMagickMemory(stretch_map); |
| if (histogram != (double *) NULL) |
| histogram=(double *) RelinquishMagickMemory(histogram); |
| if (white != (double *) NULL) |
| white=(double *) RelinquishMagickMemory(white); |
| if (black != (double *) NULL) |
| black=(double *) RelinquishMagickMemory(black); |
| ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed", |
| image->filename); |
| } |
| /* |
| Form histogram. |
| */ |
| status=MagickTrue; |
| (void) memset(histogram,0,(MaxMap+1)*GetPixelChannels(image)* |
| sizeof(*histogram)); |
| image_view=AcquireVirtualCacheView(image,exception); |
| for (y=0; y < (ssize_t) image->rows; y++) |
| { |
| const Quantum |
| *magick_restrict p; |
| |
| ssize_t |
| x; |
| |
| if (status == MagickFalse) |
| continue; |
| p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); |
| if (p == (const Quantum *) NULL) |
| { |
| status=MagickFalse; |
| continue; |
| } |
| for (x=0; x < (ssize_t) image->columns; x++) |
| { |
| double |
| pixel; |
| |
| pixel=GetPixelIntensity(image,p); |
| for (i=0; i < (ssize_t) GetPixelChannels(image); i++) |
| { |
| if (image->channel_mask != DefaultChannels) |
| pixel=(double) p[i]; |
| histogram[GetPixelChannels(image)*ScaleQuantumToMap( |
| ClampToQuantum(pixel))+i]++; |
| } |
| p+=GetPixelChannels(image); |
| } |
| } |
| image_view=DestroyCacheView(image_view); |
| /* |
| Find the histogram boundaries by locating the black/white levels. |
| */ |
| for (i=0; i < (ssize_t) GetPixelChannels(image); i++) |
| { |
| double |
| intensity; |
| |
| ssize_t |
| j; |
| |
| black[i]=0.0; |
| white[i]=MaxRange(QuantumRange); |
| intensity=0.0; |
| for (j=0; j <= (ssize_t) MaxMap; j++) |
| { |
| intensity+=histogram[GetPixelChannels(image)*j+i]; |
| if (intensity > black_point) |
| break; |
| } |
| black[i]=(double) j; |
| intensity=0.0; |
| for (j=(ssize_t) MaxMap; j != 0; j--) |
| { |
| intensity+=histogram[GetPixelChannels(image)*j+i]; |
| if (intensity > ((double) image->columns*image->rows-white_point)) |
| break; |
| } |
| white[i]=(double) j; |
| } |
| histogram=(double *) RelinquishMagickMemory(histogram); |
| /* |
| Stretch the histogram to create the stretched image mapping. |
| */ |
| (void) memset(stretch_map,0,(MaxMap+1)*GetPixelChannels(image)* |
| sizeof(*stretch_map)); |
| for (i=0; i < (ssize_t) GetPixelChannels(image); i++) |
| { |
| ssize_t |
| j; |
| |
| for (j=0; j <= (ssize_t) MaxMap; j++) |
| { |
| double |
| gamma; |
| |
| gamma=PerceptibleReciprocal(white[i]-black[i]); |
| if (j < (ssize_t) black[i]) |
| stretch_map[GetPixelChannels(image)*j+i]=0.0; |
| else |
| if (j > (ssize_t) white[i]) |
| stretch_map[GetPixelChannels(image)*j+i]=(double) QuantumRange; |
| else |
| if (black[i] != white[i]) |
| stretch_map[GetPixelChannels(image)*j+i]=(double) ScaleMapToQuantum( |
| (double) (MaxMap*gamma*(j-black[i]))); |
| } |
| } |
| if (image->storage_class == PseudoClass) |
| { |
| ssize_t |
| j; |
| |
| /* |
| Stretch-contrast colormap. |
| */ |
| for (j=0; j < (ssize_t) image->colors; j++) |
| { |
| if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0) |
| { |
| i=GetPixelChannelOffset(image,RedPixelChannel); |
| image->colormap[j].red=stretch_map[GetPixelChannels(image)* |
| ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))+i]; |
| } |
| if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0) |
| { |
| i=GetPixelChannelOffset(image,GreenPixelChannel); |
| image->colormap[j].green=stretch_map[GetPixelChannels(image)* |
| ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))+i]; |
| } |
| if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0) |
| { |
| i=GetPixelChannelOffset(image,BluePixelChannel); |
| image->colormap[j].blue=stretch_map[GetPixelChannels(image)* |
| ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))+i]; |
| } |
| if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) |
| { |
| i=GetPixelChannelOffset(image,AlphaPixelChannel); |
| image->colormap[j].alpha=stretch_map[GetPixelChannels(image)* |
| ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))+i]; |
| } |
| } |
| } |
| /* |
| Stretch-contrast image. |
| */ |
| status=MagickTrue; |
| progress=0; |
| image_view=AcquireAuthenticCacheView(image,exception); |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp parallel for schedule(static) shared(progress,status) \ |
| magick_number_threads(image,image,image->rows,1) |
| #endif |
| for (y=0; y < (ssize_t) image->rows; y++) |
| { |
| Quantum |
| *magick_restrict q; |
| |
| ssize_t |
| x; |
| |
| if (status == MagickFalse) |
| continue; |
| q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception); |
| if (q == (Quantum *) NULL) |
| { |
| status=MagickFalse; |
| continue; |
| } |
| for (x=0; x < (ssize_t) image->columns; x++) |
| { |
| ssize_t |
| j; |
| |
| for (j=0; j < (ssize_t) GetPixelChannels(image); j++) |
| { |
| PixelChannel channel = GetPixelChannelChannel(image,j); |
| PixelTrait traits = GetPixelChannelTraits(image,channel); |
| if ((traits & UpdatePixelTrait) == 0) |
| continue; |
| if (black[j] == white[j]) |
| continue; |
| q[j]=ClampToQuantum(stretch_map[GetPixelChannels(image)* |
| ScaleQuantumToMap(q[j])+j]); |
| } |
| q+=GetPixelChannels(image); |
| } |
| if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse) |
| status=MagickFalse; |
| if (image->progress_monitor != (MagickProgressMonitor) NULL) |
| { |
| MagickBooleanType |
| proceed; |
| |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp atomic |
| #endif |
| progress++; |
| proceed=SetImageProgress(image,ContrastStretchImageTag,progress, |
| image->rows); |
| if (proceed == MagickFalse) |
| status=MagickFalse; |
| } |
| } |
| image_view=DestroyCacheView(image_view); |
| stretch_map=(double *) RelinquishMagickMemory(stretch_map); |
| white=(double *) RelinquishMagickMemory(white); |
| black=(double *) RelinquishMagickMemory(black); |
| return(status); |
| } |
| |
| /* |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % % |
| % % |
| % % |
| % E n h a n c e I m a g e % |
| % % |
| % % |
| % % |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % |
| % EnhanceImage() applies a digital filter that improves the quality of a |
| % noisy image. |
| % |
| % The format of the EnhanceImage method is: |
| % |
| % Image *EnhanceImage(const Image *image,ExceptionInfo *exception) |
| % |
| % A description of each parameter follows: |
| % |
| % o image: the image. |
| % |
| % o exception: return any errors or warnings in this structure. |
| % |
| */ |
| MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception) |
| { |
| #define EnhanceImageTag "Enhance/Image" |
| #define EnhancePixel(weight) \ |
| mean=QuantumScale*((double) GetPixelRed(image,r)+pixel.red)/2.0; \ |
| distance=QuantumScale*((double) GetPixelRed(image,r)-pixel.red); \ |
| distance_squared=(4.0+mean)*distance*distance; \ |
| mean=QuantumScale*((double) GetPixelGreen(image,r)+pixel.green)/2.0; \ |
| distance=QuantumScale*((double) GetPixelGreen(image,r)-pixel.green); \ |
| distance_squared+=(7.0-mean)*distance*distance; \ |
| mean=QuantumScale*((double) GetPixelBlue(image,r)+pixel.blue)/2.0; \ |
| distance=QuantumScale*((double) GetPixelBlue(image,r)-pixel.blue); \ |
| distance_squared+=(5.0-mean)*distance*distance; \ |
| mean=QuantumScale*((double) GetPixelBlack(image,r)+pixel.black)/2.0; \ |
| distance=QuantumScale*((double) GetPixelBlack(image,r)-pixel.black); \ |
| distance_squared+=(5.0-mean)*distance*distance; \ |
| mean=QuantumScale*((double) GetPixelAlpha(image,r)+pixel.alpha)/2.0; \ |
| distance=QuantumScale*((double) GetPixelAlpha(image,r)-pixel.alpha); \ |
| distance_squared+=(5.0-mean)*distance*distance; \ |
| if (distance_squared < 0.069) \ |
| { \ |
| aggregate.red+=(weight)*GetPixelRed(image,r); \ |
| aggregate.green+=(weight)*GetPixelGreen(image,r); \ |
| aggregate.blue+=(weight)*GetPixelBlue(image,r); \ |
| aggregate.black+=(weight)*GetPixelBlack(image,r); \ |
| aggregate.alpha+=(weight)*GetPixelAlpha(image,r); \ |
| total_weight+=(weight); \ |
| } \ |
| r+=GetPixelChannels(image); |
| |
| CacheView |
| *enhance_view, |
| *image_view; |
| |
| Image |
| *enhance_image; |
| |
| MagickBooleanType |
| status; |
| |
| MagickOffsetType |
| progress; |
| |
| ssize_t |
| y; |
| |
| /* |
| Initialize enhanced image attributes. |
| */ |
| assert(image != (const Image *) NULL); |
| assert(image->signature == MagickCoreSignature); |
| if (image->debug != MagickFalse) |
| (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
| assert(exception != (ExceptionInfo *) NULL); |
| assert(exception->signature == MagickCoreSignature); |
| enhance_image=CloneImage(image,0,0,MagickTrue, |
| exception); |
| if (enhance_image == (Image *) NULL) |
| return((Image *) NULL); |
| if (SetImageStorageClass(enhance_image,DirectClass,exception) == MagickFalse) |
| { |
| enhance_image=DestroyImage(enhance_image); |
| return((Image *) NULL); |
| } |
| /* |
| Enhance image. |
| */ |
| status=MagickTrue; |
| progress=0; |
| image_view=AcquireVirtualCacheView(image,exception); |
| enhance_view=AcquireAuthenticCacheView(enhance_image,exception); |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp parallel for schedule(static) shared(progress,status) \ |
| magick_number_threads(image,enhance_image,image->rows,1) |
| #endif |
| for (y=0; y < (ssize_t) image->rows; y++) |
| { |
| PixelInfo |
| pixel; |
| |
| const Quantum |
| *magick_restrict p; |
| |
| Quantum |
| *magick_restrict q; |
| |
| ssize_t |
| x; |
| |
| ssize_t |
| center; |
| |
| if (status == MagickFalse) |
| continue; |
| p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception); |
| q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1, |
| exception); |
| if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL)) |
| { |
| status=MagickFalse; |
| continue; |
| } |
| center=(ssize_t) GetPixelChannels(image)*(2*(image->columns+4)+2); |
| GetPixelInfo(image,&pixel); |
| for (x=0; x < (ssize_t) image->columns; x++) |
| { |
| double |
| distance, |
| distance_squared, |
| mean, |
| total_weight; |
| |
| PixelInfo |
| aggregate; |
| |
| const Quantum |
| *magick_restrict r; |
| |
| GetPixelInfo(image,&aggregate); |
| total_weight=0.0; |
| GetPixelInfoPixel(image,p+center,&pixel); |
| r=p; |
| EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0); |
| EnhancePixel(8.0); EnhancePixel(5.0); |
| r=p+GetPixelChannels(image)*(image->columns+4); |
| EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0); |
| EnhancePixel(20.0); EnhancePixel(8.0); |
| r=p+2*GetPixelChannels(image)*(image->columns+4); |
| EnhancePixel(10.0); EnhancePixel(40.0); EnhancePixel(80.0); |
| EnhancePixel(40.0); EnhancePixel(10.0); |
| r=p+3*GetPixelChannels(image)*(image->columns+4); |
| EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0); |
| EnhancePixel(20.0); EnhancePixel(8.0); |
| r=p+4*GetPixelChannels(image)*(image->columns+4); |
| EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0); |
| EnhancePixel(8.0); EnhancePixel(5.0); |
| if (total_weight > MagickEpsilon) |
| { |
| pixel.red=((aggregate.red+total_weight/2.0)/total_weight); |
| pixel.green=((aggregate.green+total_weight/2.0)/total_weight); |
| pixel.blue=((aggregate.blue+total_weight/2.0)/total_weight); |
| pixel.black=((aggregate.black+total_weight/2.0)/total_weight); |
| pixel.alpha=((aggregate.alpha+total_weight/2.0)/total_weight); |
| } |
| SetPixelViaPixelInfo(enhance_image,&pixel,q); |
| p+=GetPixelChannels(image); |
| q+=GetPixelChannels(enhance_image); |
| } |
| if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse) |
| status=MagickFalse; |
| if (image->progress_monitor != (MagickProgressMonitor) NULL) |
| { |
| MagickBooleanType |
| proceed; |
| |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp atomic |
| #endif |
| progress++; |
| proceed=SetImageProgress(image,EnhanceImageTag,progress,image->rows); |
| if (proceed == MagickFalse) |
| status=MagickFalse; |
| } |
| } |
| enhance_view=DestroyCacheView(enhance_view); |
| image_view=DestroyCacheView(image_view); |
| if (status == MagickFalse) |
| enhance_image=DestroyImage(enhance_image); |
| return(enhance_image); |
| } |
| |
| /* |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % % |
| % % |
| % % |
| % E q u a l i z e I m a g e % |
| % % |
| % % |
| % % |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % |
| % EqualizeImage() applies a histogram equalization to the image. |
| % |
| % The format of the EqualizeImage method is: |
| % |
| % MagickBooleanType EqualizeImage(Image *image,ExceptionInfo *exception) |
| % |
| % A description of each parameter follows: |
| % |
| % o image: the image. |
| % |
| % o exception: return any errors or warnings in this structure. |
| % |
| */ |
| MagickExport MagickBooleanType EqualizeImage(Image *image, |
| ExceptionInfo *exception) |
| { |
| #define EqualizeImageTag "Equalize/Image" |
| |
| CacheView |
| *image_view; |
| |
| double |
| black[CompositePixelChannel+1], |
| *equalize_map, |
| *histogram, |
| *map, |
| white[CompositePixelChannel+1]; |
| |
| MagickBooleanType |
| status; |
| |
| MagickOffsetType |
| progress; |
| |
| ssize_t |
| i; |
| |
| ssize_t |
| y; |
| |
| /* |
| Allocate and initialize histogram arrays. |
| */ |
| assert(image != (Image *) NULL); |
| assert(image->signature == MagickCoreSignature); |
| #if defined(MAGICKCORE_OPENCL_SUPPORT) |
| if (AccelerateEqualizeImage(image,exception) != MagickFalse) |
| return(MagickTrue); |
| #endif |
| if (image->debug != MagickFalse) |
| (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
| equalize_map=(double *) AcquireQuantumMemory(MaxMap+1UL,MaxPixelChannels* |
| sizeof(*equalize_map)); |
| histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,MaxPixelChannels* |
| sizeof(*histogram)); |
| map=(double *) AcquireQuantumMemory(MaxMap+1UL,MaxPixelChannels*sizeof(*map)); |
| if ((equalize_map == (double *) NULL) || (histogram == (double *) NULL) || |
| (map == (double *) NULL)) |
| { |
| if (map != (double *) NULL) |
| map=(double *) RelinquishMagickMemory(map); |
| if (histogram != (double *) NULL) |
| histogram=(double *) RelinquishMagickMemory(histogram); |
| if (equalize_map != (double *) NULL) |
| equalize_map=(double *) RelinquishMagickMemory(equalize_map); |
| ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed", |
| image->filename); |
| } |
| /* |
| Form histogram. |
| */ |
| status=MagickTrue; |
| (void) memset(histogram,0,(MaxMap+1)*GetPixelChannels(image)* |
| sizeof(*histogram)); |
| image_view=AcquireVirtualCacheView(image,exception); |
| for (y=0; y < (ssize_t) image->rows; y++) |
| { |
| const Quantum |
| *magick_restrict p; |
| |
| ssize_t |
| x; |
| |
| if (status == MagickFalse) |
| continue; |
| p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); |
| if (p == (const Quantum *) NULL) |
| { |
| status=MagickFalse; |
| continue; |
| } |
| for (x=0; x < (ssize_t) image->columns; x++) |
| { |
| for (i=0; i < (ssize_t) GetPixelChannels(image); i++) |
| { |
| double |
| intensity; |
| |
| intensity=(double) p[i]; |
| if ((image->channel_mask & SyncChannels) != 0) |
| intensity=GetPixelIntensity(image,p); |
| histogram[GetPixelChannels(image)*ScaleQuantumToMap( |
| ClampToQuantum(intensity))+i]++; |
| } |
| p+=GetPixelChannels(image); |
| } |
| } |
| image_view=DestroyCacheView(image_view); |
| /* |
| Integrate the histogram to get the equalization map. |
| */ |
| for (i=0; i < (ssize_t) GetPixelChannels(image); i++) |
| { |
| double |
| intensity; |
| |
| ssize_t |
| j; |
| |
| intensity=0.0; |
| for (j=0; j <= (ssize_t) MaxMap; j++) |
| { |
| intensity+=histogram[GetPixelChannels(image)*j+i]; |
| map[GetPixelChannels(image)*j+i]=intensity; |
| } |
| } |
| (void) memset(equalize_map,0,(MaxMap+1)*GetPixelChannels(image)* |
| sizeof(*equalize_map)); |
| (void) memset(black,0,sizeof(*black)); |
| (void) memset(white,0,sizeof(*white)); |
| for (i=0; i < (ssize_t) GetPixelChannels(image); i++) |
| { |
| ssize_t |
| j; |
| |
| black[i]=map[i]; |
| white[i]=map[GetPixelChannels(image)*MaxMap+i]; |
| if (black[i] != white[i]) |
| for (j=0; j <= (ssize_t) MaxMap; j++) |
| equalize_map[GetPixelChannels(image)*j+i]=(double) |
| ScaleMapToQuantum((double) ((MaxMap*(map[ |
| GetPixelChannels(image)*j+i]-black[i]))/(white[i]-black[i]))); |
| } |
| histogram=(double *) RelinquishMagickMemory(histogram); |
| map=(double *) RelinquishMagickMemory(map); |
| if (image->storage_class == PseudoClass) |
| { |
| ssize_t |
| j; |
| |
| /* |
| Equalize colormap. |
| */ |
| for (j=0; j < (ssize_t) image->colors; j++) |
| { |
| if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0) |
| { |
| PixelChannel channel = GetPixelChannelChannel(image, |
| RedPixelChannel); |
| if (black[channel] != white[channel]) |
| image->colormap[j].red=equalize_map[GetPixelChannels(image)* |
| ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))+ |
| channel]; |
| } |
| if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0) |
| { |
| PixelChannel channel = GetPixelChannelChannel(image, |
| GreenPixelChannel); |
| if (black[channel] != white[channel]) |
| image->colormap[j].green=equalize_map[GetPixelChannels(image)* |
| ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))+ |
| channel]; |
| } |
| if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0) |
| { |
| PixelChannel channel = GetPixelChannelChannel(image, |
| BluePixelChannel); |
| if (black[channel] != white[channel]) |
| image->colormap[j].blue=equalize_map[GetPixelChannels(image)* |
| ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))+ |
| channel]; |
| } |
| if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) |
| { |
| PixelChannel channel = GetPixelChannelChannel(image, |
| AlphaPixelChannel); |
| if (black[channel] != white[channel]) |
| image->colormap[j].alpha=equalize_map[GetPixelChannels(image)* |
| ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))+ |
| channel]; |
| } |
| } |
| } |
| /* |
| Equalize image. |
| */ |
| progress=0; |
| image_view=AcquireAuthenticCacheView(image,exception); |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp parallel for schedule(static) shared(progress,status) \ |
| magick_number_threads(image,image,image->rows,1) |
| #endif |
| for (y=0; y < (ssize_t) image->rows; y++) |
| { |
| Quantum |
| *magick_restrict q; |
| |
| ssize_t |
| x; |
| |
| if (status == MagickFalse) |
| continue; |
| q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception); |
| if (q == (Quantum *) NULL) |
| { |
| status=MagickFalse; |
| continue; |
| } |
| for (x=0; x < (ssize_t) image->columns; x++) |
| { |
| ssize_t |
| j; |
| |
| for (j=0; j < (ssize_t) GetPixelChannels(image); j++) |
| { |
| PixelChannel channel = GetPixelChannelChannel(image,j); |
| PixelTrait traits = GetPixelChannelTraits(image,channel); |
| if (((traits & UpdatePixelTrait) == 0) || (black[j] == white[j])) |
| continue; |
| q[j]=ClampToQuantum(equalize_map[GetPixelChannels(image)* |
| ScaleQuantumToMap(q[j])+j]); |
| } |
| q+=GetPixelChannels(image); |
| } |
| if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse) |
| status=MagickFalse; |
| if (image->progress_monitor != (MagickProgressMonitor) NULL) |
| { |
| MagickBooleanType |
| proceed; |
| |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp atomic |
| #endif |
| progress++; |
| proceed=SetImageProgress(image,EqualizeImageTag,progress,image->rows); |
| if (proceed == MagickFalse) |
| status=MagickFalse; |
| } |
| } |
| image_view=DestroyCacheView(image_view); |
| equalize_map=(double *) RelinquishMagickMemory(equalize_map); |
| return(status); |
| } |
| |
| /* |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % % |
| % % |
| % % |
| % G a m m a I m a g e % |
| % % |
| % % |
| % % |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % |
| % GammaImage() gamma-corrects a particular image channel. The same |
| % image viewed on different devices will have perceptual differences in the |
| % way the image's intensities are represented on the screen. Specify |
| % individual gamma levels for the red, green, and blue channels, or adjust |
| % all three with the gamma parameter. Values typically range from 0.8 to 2.3. |
| % |
| % You can also reduce the influence of a particular channel with a gamma |
| % value of 0. |
| % |
| % The format of the GammaImage method is: |
| % |
| % MagickBooleanType GammaImage(Image *image,const double gamma, |
| % ExceptionInfo *exception) |
| % |
| % A description of each parameter follows: |
| % |
| % o image: the image. |
| % |
| % o level: the image gamma as a string (e.g. 1.6,1.2,1.0). |
| % |
| % o gamma: the image gamma. |
| % |
| */ |
| |
| static inline double gamma_pow(const double value,const double gamma) |
| { |
| return(value < 0.0 ? value : pow(value,gamma)); |
| } |
| |
| MagickExport MagickBooleanType GammaImage(Image *image,const double gamma, |
| ExceptionInfo *exception) |
| { |
| #define GammaImageTag "Gamma/Image" |
| |
| CacheView |
| *image_view; |
| |
| MagickBooleanType |
| status; |
| |
| MagickOffsetType |
| progress; |
| |
| Quantum |
| *gamma_map; |
| |
| ssize_t |
| i; |
| |
| ssize_t |
| y; |
| |
| /* |
| Allocate and initialize gamma maps. |
| */ |
| assert(image != (Image *) NULL); |
| assert(image->signature == MagickCoreSignature); |
| if (image->debug != MagickFalse) |
| (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
| if (gamma == 1.0) |
| return(MagickTrue); |
| gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map)); |
| if (gamma_map == (Quantum *) NULL) |
| ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed", |
| image->filename); |
| (void) memset(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map)); |
| if (gamma != 0.0) |
| for (i=0; i <= (ssize_t) MaxMap; i++) |
| gamma_map[i]=ScaleMapToQuantum((double) (MaxMap*pow((double) i/ |
| MaxMap,PerceptibleReciprocal(gamma)))); |
| if (image->storage_class == PseudoClass) |
| for (i=0; i < (ssize_t) image->colors; i++) |
| { |
| /* |
| Gamma-correct colormap. |
| */ |
| if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0) |
| image->colormap[i].red=(double) gamma_map[ScaleQuantumToMap( |
| ClampToQuantum(image->colormap[i].red))]; |
| if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0) |
| image->colormap[i].green=(double) gamma_map[ScaleQuantumToMap( |
| ClampToQuantum(image->colormap[i].green))]; |
| if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0) |
| image->colormap[i].blue=(double) gamma_map[ScaleQuantumToMap( |
| ClampToQuantum(image->colormap[i].blue))]; |
| if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) |
| image->colormap[i].alpha=(double) gamma_map[ScaleQuantumToMap( |
| ClampToQuantum(image->colormap[i].alpha))]; |
| } |
| /* |
| Gamma-correct image. |
| */ |
| status=MagickTrue; |
| progress=0; |
| image_view=AcquireAuthenticCacheView(image,exception); |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp parallel for schedule(static) shared(progress,status) \ |
| magick_number_threads(image,image,image->rows,1) |
| #endif |
| for (y=0; y < (ssize_t) image->rows; y++) |
| { |
| Quantum |
| *magick_restrict q; |
| |
| ssize_t |
| x; |
| |
| if (status == MagickFalse) |
| continue; |
| q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception); |
| if (q == (Quantum *) NULL) |
| { |
| status=MagickFalse; |
| continue; |
| } |
| for (x=0; x < (ssize_t) image->columns; x++) |
| { |
| ssize_t |
| j; |
| |
| for (j=0; j < (ssize_t) GetPixelChannels(image); j++) |
| { |
| PixelChannel channel = GetPixelChannelChannel(image,j); |
| PixelTrait traits = GetPixelChannelTraits(image,channel); |
| if ((traits & UpdatePixelTrait) == 0) |
| continue; |
| q[j]=gamma_map[ScaleQuantumToMap(ClampToQuantum((MagickRealType) |
| q[j]))]; |
| } |
| q+=GetPixelChannels(image); |
| } |
| if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse) |
| status=MagickFalse; |
| if (image->progress_monitor != (MagickProgressMonitor) NULL) |
| { |
| MagickBooleanType |
| proceed; |
| |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp atomic |
| #endif |
| progress++; |
| proceed=SetImageProgress(image,GammaImageTag,progress,image->rows); |
| if (proceed == MagickFalse) |
| status=MagickFalse; |
| } |
| } |
| image_view=DestroyCacheView(image_view); |
| gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map); |
| if (image->gamma != 0.0) |
| image->gamma*=gamma; |
| return(status); |
| } |
| |
| /* |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % % |
| % % |
| % % |
| % G r a y s c a l e I m a g e % |
| % % |
| % % |
| % % |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % |
| % GrayscaleImage() converts the image to grayscale. |
| % |
| % The format of the GrayscaleImage method is: |
| % |
| % MagickBooleanType GrayscaleImage(Image *image, |
| % const PixelIntensityMethod method ,ExceptionInfo *exception) |
| % |
| % A description of each parameter follows: |
| % |
| % o image: the image. |
| % |
| % o method: the pixel intensity method. |
| % |
| % o exception: return any errors or warnings in this structure. |
| % |
| */ |
| MagickExport MagickBooleanType GrayscaleImage(Image *image, |
| const PixelIntensityMethod method,ExceptionInfo *exception) |
| { |
| #define GrayscaleImageTag "Grayscale/Image" |
| |
| CacheView |
| *image_view; |
| |
| MagickBooleanType |
| status; |
| |
| MagickOffsetType |
| progress; |
| |
| ssize_t |
| y; |
| |
| assert(image != (Image *) NULL); |
| assert(image->signature == MagickCoreSignature); |
| if (image->debug != MagickFalse) |
| (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
| if (image->storage_class == PseudoClass) |
| { |
| if (SyncImage(image,exception) == MagickFalse) |
| return(MagickFalse); |
| if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse) |
| return(MagickFalse); |
| } |
| #if defined(MAGICKCORE_OPENCL_SUPPORT) |
| if (AccelerateGrayscaleImage(image,method,exception) != MagickFalse) |
| { |
| image->intensity=method; |
| image->type=GrayscaleType; |
| if ((method == Rec601LuminancePixelIntensityMethod) || |
| (method == Rec709LuminancePixelIntensityMethod)) |
| return(SetImageColorspace(image,LinearGRAYColorspace,exception)); |
| return(SetImageColorspace(image,GRAYColorspace,exception)); |
| } |
| #endif |
| /* |
| Grayscale image. |
| */ |
| status=MagickTrue; |
| progress=0; |
| image_view=AcquireAuthenticCacheView(image,exception); |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp parallel for schedule(static) shared(progress,status) \ |
| magick_number_threads(image,image,image->rows,1) |
| #endif |
| for (y=0; y < (ssize_t) image->rows; y++) |
| { |
| Quantum |
| *magick_restrict q; |
| |
| ssize_t |
| x; |
| |
| if (status == MagickFalse) |
| continue; |
| q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception); |
| if (q == (Quantum *) NULL) |
| { |
| status=MagickFalse; |
| continue; |
| } |
| for (x=0; x < (ssize_t) image->columns; x++) |
| { |
| MagickRealType |
| blue, |
| green, |
| red, |
| intensity; |
| |
| red=(MagickRealType) GetPixelRed(image,q); |
| green=(MagickRealType) GetPixelGreen(image,q); |
| blue=(MagickRealType) GetPixelBlue(image,q); |
| intensity=0.0; |
| switch (method) |
| { |
| case AveragePixelIntensityMethod: |
| { |
| intensity=(red+green+blue)/3.0; |
| break; |
| } |
| case BrightnessPixelIntensityMethod: |
| { |
| intensity=MagickMax(MagickMax(red,green),blue); |
| break; |
| } |
| case LightnessPixelIntensityMethod: |
| { |
| intensity=(MagickMin(MagickMin(red,green),blue)+ |
| MagickMax(MagickMax(red,green),blue))/2.0; |
| break; |
| } |
| case MSPixelIntensityMethod: |
| { |
| intensity=(MagickRealType) (((double) red*red+green*green+ |
| blue*blue)/3.0); |
| break; |
| } |
| case Rec601LumaPixelIntensityMethod: |
| { |
| if (image->colorspace == RGBColorspace) |
| { |
| red=EncodePixelGamma(red); |
| green=EncodePixelGamma(green); |
| blue=EncodePixelGamma(blue); |
| } |
| intensity=0.298839*red+0.586811*green+0.114350*blue; |
| break; |
| } |
| case Rec601LuminancePixelIntensityMethod: |
| { |
| if (image->colorspace == sRGBColorspace) |
| { |
| red=DecodePixelGamma(red); |
| green=DecodePixelGamma(green); |
| blue=DecodePixelGamma(blue); |
| } |
| intensity=0.298839*red+0.586811*green+0.114350*blue; |
| break; |
| } |
| case Rec709LumaPixelIntensityMethod: |
| default: |
| { |
| if (image->colorspace == RGBColorspace) |
| { |
| red=EncodePixelGamma(red); |
| green=EncodePixelGamma(green); |
| blue=EncodePixelGamma(blue); |
| } |
| intensity=0.212656*red+0.715158*green+0.072186*blue; |
| break; |
| } |
| case Rec709LuminancePixelIntensityMethod: |
| { |
| if (image->colorspace == sRGBColorspace) |
| { |
| red=DecodePixelGamma(red); |
| green=DecodePixelGamma(green); |
| blue=DecodePixelGamma(blue); |
| } |
| intensity=0.212656*red+0.715158*green+0.072186*blue; |
| break; |
| } |
| case RMSPixelIntensityMethod: |
| { |
| intensity=(MagickRealType) (sqrt((double) red*red+green*green+ |
| blue*blue)/sqrt(3.0)); |
| break; |
| } |
| } |
| SetPixelGray(image,ClampToQuantum(intensity),q); |
| q+=GetPixelChannels(image); |
| } |
| if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse) |
| status=MagickFalse; |
| if (image->progress_monitor != (MagickProgressMonitor) NULL) |
| { |
| MagickBooleanType |
| proceed; |
| |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp atomic |
| #endif |
| progress++; |
| proceed=SetImageProgress(image,GrayscaleImageTag,progress,image->rows); |
| if (proceed == MagickFalse) |
| status=MagickFalse; |
| } |
| } |
| image_view=DestroyCacheView(image_view); |
| image->intensity=method; |
| image->type=GrayscaleType; |
| if ((method == Rec601LuminancePixelIntensityMethod) || |
| (method == Rec709LuminancePixelIntensityMethod)) |
| return(SetImageColorspace(image,LinearGRAYColorspace,exception)); |
| return(SetImageColorspace(image,GRAYColorspace,exception)); |
| } |
| |
| /* |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % % |
| % % |
| % % |
| % H a l d C l u t I m a g e % |
| % % |
| % % |
| % % |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % |
| % HaldClutImage() applies a Hald color lookup table to the image. A Hald |
| % color lookup table is a 3-dimensional color cube mapped to 2 dimensions. |
| % Create it with the HALD coder. You can apply any color transformation to |
| % the Hald image and then use this method to apply the transform to the |
| % image. |
| % |
| % The format of the HaldClutImage method is: |
| % |
| % MagickBooleanType HaldClutImage(Image *image,Image *hald_image, |
| % ExceptionInfo *exception) |
| % |
| % A description of each parameter follows: |
| % |
| % o image: the image, which is replaced by indexed CLUT values |
| % |
| % o hald_image: the color lookup table image for replacement color values. |
| % |
| % o exception: return any errors or warnings in this structure. |
| % |
| */ |
| MagickExport MagickBooleanType HaldClutImage(Image *image, |
| const Image *hald_image,ExceptionInfo *exception) |
| { |
| #define HaldClutImageTag "Clut/Image" |
| |
| typedef struct _HaldInfo |
| { |
| double |
| x, |
| y, |
| z; |
| } HaldInfo; |
| |
| CacheView |
| *hald_view, |
| *image_view; |
| |
| double |
| width; |
| |
| MagickBooleanType |
| status; |
| |
| MagickOffsetType |
| progress; |
| |
| PixelInfo |
| zero; |
| |
| size_t |
| cube_size, |
| length, |
| level; |
| |
| ssize_t |
| y; |
| |
| assert(image != (Image *) NULL); |
| assert(image->signature == MagickCoreSignature); |
| if (image->debug != MagickFalse) |
| (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
| assert(hald_image != (Image *) NULL); |
| assert(hald_image->signature == MagickCoreSignature); |
| if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse) |
| return(MagickFalse); |
| if (image->alpha_trait == UndefinedPixelTrait) |
| (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception); |
| /* |
| Hald clut image. |
| */ |
| status=MagickTrue; |
| progress=0; |
| length=(size_t) MagickMin((MagickRealType) hald_image->columns, |
| (MagickRealType) hald_image->rows); |
| for (level=2; (level*level*level) < length; level++) ; |
| level*=level; |
| cube_size=level*level; |
| width=(double) hald_image->columns; |
| GetPixelInfo(hald_image,&zero); |
| hald_view=AcquireVirtualCacheView(hald_image,exception); |
| image_view=AcquireAuthenticCacheView(image,exception); |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp parallel for schedule(static) shared(progress,status) \ |
| magick_number_threads(image,image,image->rows,1) |
| #endif |
| for (y=0; y < (ssize_t) image->rows; y++) |
| { |
| Quantum |
| *magick_restrict q; |
| |
| ssize_t |
| x; |
| |
| if (status == MagickFalse) |
| continue; |
| q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception); |
| if (q == (Quantum *) NULL) |
| { |
| status=MagickFalse; |
| continue; |
| } |
| for (x=0; x < (ssize_t) image->columns; x++) |
| { |
| double |
| area, |
| offset; |
| |
| HaldInfo |
| point; |
| |
| PixelInfo |
| pixel, |
| pixel1, |
| pixel2, |
| pixel3, |
| pixel4; |
| |
| point.x=QuantumScale*(level-1.0)*GetPixelRed(image,q); |
| point.y=QuantumScale*(level-1.0)*GetPixelGreen(image,q); |
| point.z=QuantumScale*(level-1.0)*GetPixelBlue(image,q); |
| offset=point.x+level*floor(point.y)+cube_size*floor(point.z); |
| point.x-=floor(point.x); |
| point.y-=floor(point.y); |
| point.z-=floor(point.z); |
| pixel1=zero; |
| status=InterpolatePixelInfo(hald_image,hald_view,hald_image->interpolate, |
| fmod(offset,width),floor(offset/width),&pixel1,exception); |
| if (status == MagickFalse) |
| break; |
| pixel2=zero; |
| status=InterpolatePixelInfo(hald_image,hald_view,hald_image->interpolate, |
| fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception); |
| if (status == MagickFalse) |
| break; |
| pixel3=zero; |
| area=point.y; |
| if (hald_image->interpolate == NearestInterpolatePixel) |
| area=(point.y < 0.5) ? 0.0 : 1.0; |
| CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha, |
| area,&pixel3); |
| offset+=cube_size; |
| status=InterpolatePixelInfo(hald_image,hald_view,hald_image->interpolate, |
| fmod(offset,width),floor(offset/width),&pixel1,exception); |
| if (status == MagickFalse) |
| break; |
| status=InterpolatePixelInfo(hald_image,hald_view,hald_image->interpolate, |
| fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception); |
| if (status == MagickFalse) |
| break; |
| pixel4=zero; |
| CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha, |
| area,&pixel4); |
| pixel=zero; |
| area=point.z; |
| if (hald_image->interpolate == NearestInterpolatePixel) |
| area=(point.z < 0.5)? 0.0 : 1.0; |
| CompositePixelInfoAreaBlend(&pixel3,pixel3.alpha,&pixel4,pixel4.alpha, |
| area,&pixel); |
| if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0) |
| SetPixelRed(image,ClampToQuantum(pixel.red),q); |
| if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0) |
| SetPixelGreen(image,ClampToQuantum(pixel.green),q); |
| if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0) |
| SetPixelBlue(image,ClampToQuantum(pixel.blue),q); |
| if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) && |
| (image->colorspace == CMYKColorspace)) |
| SetPixelBlack(image,ClampToQuantum(pixel.black),q); |
| if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) && |
| (image->alpha_trait != UndefinedPixelTrait)) |
| SetPixelAlpha(image,ClampToQuantum(pixel.alpha),q); |
| q+=GetPixelChannels(image); |
| } |
| if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse) |
| status=MagickFalse; |
| if (image->progress_monitor != (MagickProgressMonitor) NULL) |
| { |
| MagickBooleanType |
| proceed; |
| |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp atomic |
| #endif |
| progress++; |
| proceed=SetImageProgress(image,HaldClutImageTag,progress,image->rows); |
| if (proceed == MagickFalse) |
| status=MagickFalse; |
| } |
| } |
| hald_view=DestroyCacheView(hald_view); |
| image_view=DestroyCacheView(image_view); |
| return(status); |
| } |
| |
| /* |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % % |
| % % |
| % % |
| % L e v e l I m a g e % |
| % % |
| % % |
| % % |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % |
| % LevelImage() adjusts the levels of a particular image channel by |
| % scaling the colors falling between specified white and black points to |
| % the full available quantum range. |
| % |
| % The parameters provided represent the black, and white points. The black |
| % point specifies the darkest color in the image. Colors darker than the |
| % black point are set to zero. White point specifies the lightest color in |
| % the image. Colors brighter than the white point are set to the maximum |
| % quantum value. |
| % |
| % If a '!' flag is given, map black and white colors to the given levels |
| % rather than mapping those levels to black and white. See |
| % LevelizeImage() below. |
| % |
| % Gamma specifies a gamma correction to apply to the image. |
| % |
| % The format of the LevelImage method is: |
| % |
| % MagickBooleanType LevelImage(Image *image,const double black_point, |
| % const double white_point,const double gamma,ExceptionInfo *exception) |
| % |
| % A description of each parameter follows: |
| % |
| % o image: the image. |
| % |
| % o black_point: The level to map zero (black) to. |
| % |
| % o white_point: The level to map QuantumRange (white) to. |
| % |
| % o exception: return any errors or warnings in this structure. |
| % |
| */ |
| |
| static inline double LevelPixel(const double black_point, |
| const double white_point,const double gamma,const double pixel) |
| { |
| double |
| level_pixel, |
| scale; |
| |
| scale=PerceptibleReciprocal(white_point-black_point); |
| level_pixel=QuantumRange*gamma_pow(scale*((double) pixel-black_point), |
| PerceptibleReciprocal(gamma)); |
| return(level_pixel); |
| } |
| |
| MagickExport MagickBooleanType LevelImage(Image *image,const double black_point, |
| const double white_point,const double gamma,ExceptionInfo *exception) |
| { |
| #define LevelImageTag "Level/Image" |
| |
| CacheView |
| *image_view; |
| |
| MagickBooleanType |
| status; |
| |
| MagickOffsetType |
| progress; |
| |
| ssize_t |
| i; |
| |
| ssize_t |
| y; |
| |
| /* |
| Allocate and initialize levels map. |
| */ |
| assert(image != (Image *) NULL); |
| assert(image->signature == MagickCoreSignature); |
| if (image->debug != MagickFalse) |
| (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
| if (image->storage_class == PseudoClass) |
| for (i=0; i < (ssize_t) image->colors; i++) |
| { |
| /* |
| Level colormap. |
| */ |
| if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0) |
| image->colormap[i].red=(double) ClampToQuantum(LevelPixel(black_point, |
| white_point,gamma,image->colormap[i].red)); |
| if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0) |
| image->colormap[i].green=(double) ClampToQuantum(LevelPixel(black_point, |
| white_point,gamma,image->colormap[i].green)); |
| if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0) |
| image->colormap[i].blue=(double) ClampToQuantum(LevelPixel(black_point, |
| white_point,gamma,image->colormap[i].blue)); |
| if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) |
| image->colormap[i].alpha=(double) ClampToQuantum(LevelPixel(black_point, |
| white_point,gamma,image->colormap[i].alpha)); |
| } |
| /* |
| Level image. |
| */ |
| status=MagickTrue; |
| progress=0; |
| image_view=AcquireAuthenticCacheView(image,exception); |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp parallel for schedule(static) shared(progress,status) \ |
| magick_number_threads(image,image,image->rows,1) |
| #endif |
| for (y=0; y < (ssize_t) image->rows; y++) |
| { |
| Quantum |
| *magick_restrict q; |
| |
| ssize_t |
| x; |
| |
| if (status == MagickFalse) |
| continue; |
| q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception); |
| if (q == (Quantum *) NULL) |
| { |
| status=MagickFalse; |
| continue; |
| } |
| for (x=0; x < (ssize_t) image->columns; x++) |
| { |
| ssize_t |
| j; |
| |
| for (j=0; j < (ssize_t) GetPixelChannels(image); j++) |
| { |
| PixelChannel channel = GetPixelChannelChannel(image,j); |
| PixelTrait traits = GetPixelChannelTraits(image,channel); |
| if ((traits & UpdatePixelTrait) == 0) |
| continue; |
| q[j]=ClampToQuantum(LevelPixel(black_point,white_point,gamma, |
| (double) q[j])); |
| } |
| q+=GetPixelChannels(image); |
| } |
| if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse) |
| status=MagickFalse; |
| if (image->progress_monitor != (MagickProgressMonitor) NULL) |
| { |
| MagickBooleanType |
| proceed; |
| |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp atomic |
| #endif |
| progress++; |
| proceed=SetImageProgress(image,LevelImageTag,progress,image->rows); |
| if (proceed == MagickFalse) |
| status=MagickFalse; |
| } |
| } |
| image_view=DestroyCacheView(image_view); |
| (void) ClampImage(image,exception); |
| return(status); |
| } |
| |
| /* |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % % |
| % % |
| % % |
| % L e v e l i z e I m a g e % |
| % % |
| % % |
| % % |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % |
| % LevelizeImage() applies the reversed LevelImage() operation to just |
| % the specific channels specified. It compresses the full range of color |
| % values, so that they lie between the given black and white points. Gamma is |
| % applied before the values are mapped. |
| % |
| % LevelizeImage() can be called with by using a +level command line |
| % API option, or using a '!' on a -level or LevelImage() geometry string. |
| % |
| % It can be used to de-contrast a greyscale image to the exact levels |
| % specified. Or by using specific levels for each channel of an image you |
| % can convert a gray-scale image to any linear color gradient, according to |
| % those levels. |
| % |
| % The format of the LevelizeImage method is: |
| % |
| % MagickBooleanType LevelizeImage(Image *image,const double black_point, |
| % const double white_point,const double gamma,ExceptionInfo *exception) |
| % |
| % A description of each parameter follows: |
| % |
| % o image: the image. |
| % |
| % o black_point: The level to map zero (black) to. |
| % |
| % o white_point: The level to map QuantumRange (white) to. |
| % |
| % o gamma: adjust gamma by this factor before mapping values. |
| % |
| % o exception: return any errors or warnings in this structure. |
| % |
| */ |
| MagickExport MagickBooleanType LevelizeImage(Image *image, |
| const double black_point,const double white_point,const double gamma, |
| ExceptionInfo *exception) |
| { |
| #define LevelizeImageTag "Levelize/Image" |
| #define LevelizeValue(x) ClampToQuantum(((MagickRealType) gamma_pow((double) \ |
| (QuantumScale*(x)),gamma))*(white_point-black_point)+black_point) |
| |
| CacheView |
| *image_view; |
| |
| MagickBooleanType |
| status; |
| |
| MagickOffsetType |
| progress; |
| |
| ssize_t |
| i; |
| |
| ssize_t |
| y; |
| |
| /* |
| Allocate and initialize levels map. |
| */ |
| assert(image != (Image *) NULL); |
| assert(image->signature == MagickCoreSignature); |
| if (image->debug != MagickFalse) |
| (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
| if (image->storage_class == PseudoClass) |
| for (i=0; i < (ssize_t) image->colors; i++) |
| { |
| /* |
| Level colormap. |
| */ |
| if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0) |
| image->colormap[i].red=(double) LevelizeValue(image->colormap[i].red); |
| if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0) |
| image->colormap[i].green=(double) LevelizeValue( |
| image->colormap[i].green); |
| if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0) |
| image->colormap[i].blue=(double) LevelizeValue(image->colormap[i].blue); |
| if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) |
| image->colormap[i].alpha=(double) LevelizeValue( |
| image->colormap[i].alpha); |
| } |
| /* |
| Level image. |
| */ |
| status=MagickTrue; |
| progress=0; |
| image_view=AcquireAuthenticCacheView(image,exception); |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp parallel for schedule(static) shared(progress,status) \ |
| magick_number_threads(image,image,image->rows,1) |
| #endif |
| for (y=0; y < (ssize_t) image->rows; y++) |
| { |
| Quantum |
| *magick_restrict q; |
| |
| ssize_t |
| x; |
| |
| if (status == MagickFalse) |
| continue; |
| q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception); |
| if (q == (Quantum *) NULL) |
| { |
| status=MagickFalse; |
| continue; |
| } |
| for (x=0; x < (ssize_t) image->columns; x++) |
| { |
| ssize_t |
| j; |
| |
| for (j=0; j < (ssize_t) GetPixelChannels(image); j++) |
| { |
| PixelChannel channel = GetPixelChannelChannel(image,j); |
| PixelTrait traits = GetPixelChannelTraits(image,channel); |
| if ((traits & UpdatePixelTrait) == 0) |
| continue; |
| q[j]=LevelizeValue(q[j]); |
| } |
| q+=GetPixelChannels(image); |
| } |
| if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse) |
| status=MagickFalse; |
| if (image->progress_monitor != (MagickProgressMonitor) NULL) |
| { |
| MagickBooleanType |
| proceed; |
| |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp atomic |
| #endif |
| progress++; |
| proceed=SetImageProgress(image,LevelizeImageTag,progress,image->rows); |
| if (proceed == MagickFalse) |
| status=MagickFalse; |
| } |
| } |
| image_view=DestroyCacheView(image_view); |
| return(status); |
| } |
| |
| /* |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % % |
| % % |
| % % |
| % L e v e l I m a g e C o l o r s % |
| % % |
| % % |
| % % |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % |
| % LevelImageColors() maps the given color to "black" and "white" values, |
| % linearly spreading out the colors, and level values on a channel by channel |
| % bases, as per LevelImage(). The given colors allows you to specify |
| % different level ranges for each of the color channels separately. |
| % |
| % If the boolean 'invert' is set true the image values will modifyed in the |
| % reverse direction. That is any existing "black" and "white" colors in the |
| % image will become the color values given, with all other values compressed |
| % appropriately. This effectivally maps a greyscale gradient into the given |
| % color gradient. |
| % |
| % The format of the LevelImageColors method is: |
| % |
| % MagickBooleanType LevelImageColors(Image *image, |
| % const PixelInfo *black_color,const PixelInfo *white_color, |
| % const MagickBooleanType invert,ExceptionInfo *exception) |
| % |
| % A description of each parameter follows: |
| % |
| % o image: the image. |
| % |
| % o black_color: The color to map black to/from |
| % |
| % o white_point: The color to map white to/from |
| % |
| % o invert: if true map the colors (levelize), rather than from (level) |
| % |
| % o exception: return any errors or warnings in this structure. |
| % |
| */ |
| MagickExport MagickBooleanType LevelImageColors(Image *image, |
| const PixelInfo *black_color,const PixelInfo *white_color, |
| const MagickBooleanType invert,ExceptionInfo *exception) |
| { |
| ChannelType |
| channel_mask; |
| |
| MagickStatusType |
| status; |
| |
| /* |
| Allocate and initialize levels map. |
| */ |
| assert(image != (Image *) NULL); |
| assert(image->signature == MagickCoreSignature); |
| if (image->debug != MagickFalse) |
| (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
| if ((IsGrayColorspace(image->colorspace) != MagickFalse) && |
| ((IsGrayColorspace(black_color->colorspace) == MagickFalse) || |
| (IsGrayColorspace(white_color->colorspace) == MagickFalse))) |
| (void) SetImageColorspace(image,sRGBColorspace,exception); |
| status=MagickTrue; |
| if (invert == MagickFalse) |
| { |
| if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0) |
| { |
| channel_mask=SetImageChannelMask(image,RedChannel); |
| status&=LevelImage(image,black_color->red,white_color->red,1.0, |
| exception); |
| (void) SetImageChannelMask(image,channel_mask); |
| } |
| if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0) |
| { |
| channel_mask=SetImageChannelMask(image,GreenChannel); |
| status&=LevelImage(image,black_color->green,white_color->green,1.0, |
| exception); |
| (void) SetImageChannelMask(image,channel_mask); |
| } |
| if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0) |
| { |
| channel_mask=SetImageChannelMask(image,BlueChannel); |
| status&=LevelImage(image,black_color->blue,white_color->blue,1.0, |
| exception); |
| (void) SetImageChannelMask(image,channel_mask); |
| } |
| if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) && |
| (image->colorspace == CMYKColorspace)) |
| { |
| channel_mask=SetImageChannelMask(image,BlackChannel); |
| status&=LevelImage(image,black_color->black,white_color->black,1.0, |
| exception); |
| (void) SetImageChannelMask(image,channel_mask); |
| } |
| if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) && |
| (image->alpha_trait != UndefinedPixelTrait)) |
| { |
| channel_mask=SetImageChannelMask(image,AlphaChannel); |
| status&=LevelImage(image,black_color->alpha,white_color->alpha,1.0, |
| exception); |
| (void) SetImageChannelMask(image,channel_mask); |
| } |
| } |
| else |
| { |
| if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0) |
| { |
| channel_mask=SetImageChannelMask(image,RedChannel); |
| status&=LevelizeImage(image,black_color->red,white_color->red,1.0, |
| exception); |
| (void) SetImageChannelMask(image,channel_mask); |
| } |
| if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0) |
| { |
| channel_mask=SetImageChannelMask(image,GreenChannel); |
| status&=LevelizeImage(image,black_color->green,white_color->green,1.0, |
| exception); |
| (void) SetImageChannelMask(image,channel_mask); |
| } |
| if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0) |
| { |
| channel_mask=SetImageChannelMask(image,BlueChannel); |
| status&=LevelizeImage(image,black_color->blue,white_color->blue,1.0, |
| exception); |
| (void) SetImageChannelMask(image,channel_mask); |
| } |
| if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) && |
| (image->colorspace == CMYKColorspace)) |
| { |
| channel_mask=SetImageChannelMask(image,BlackChannel); |
| status&=LevelizeImage(image,black_color->black,white_color->black,1.0, |
| exception); |
| (void) SetImageChannelMask(image,channel_mask); |
| } |
| if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) && |
| (image->alpha_trait != UndefinedPixelTrait)) |
| { |
| channel_mask=SetImageChannelMask(image,AlphaChannel); |
| status&=LevelizeImage(image,black_color->alpha,white_color->alpha,1.0, |
| exception); |
| (void) SetImageChannelMask(image,channel_mask); |
| } |
| } |
| return(status != 0 ? MagickTrue : MagickFalse); |
| } |
| |
| /* |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % % |
| % % |
| % % |
| % L i n e a r S t r e t c h I m a g e % |
| % % |
| % % |
| % % |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % |
| % LinearStretchImage() discards any pixels below the black point and above |
| % the white point and levels the remaining pixels. |
| % |
| % The format of the LinearStretchImage method is: |
| % |
| % MagickBooleanType LinearStretchImage(Image *image, |
| % const double black_point,const double white_point, |
| % ExceptionInfo *exception) |
| % |
| % A description of each parameter follows: |
| % |
| % o image: the image. |
| % |
| % o black_point: the black point. |
| % |
| % o white_point: the white point. |
| % |
| % o exception: return any errors or warnings in this structure. |
| % |
| */ |
| MagickExport MagickBooleanType LinearStretchImage(Image *image, |
| const double black_point,const double white_point,ExceptionInfo *exception) |
| { |
| #define LinearStretchImageTag "LinearStretch/Image" |
| |
| CacheView |
| *image_view; |
| |
| double |
| *histogram, |
| intensity; |
| |
| MagickBooleanType |
| status; |
| |
| ssize_t |
| black, |
| white, |
| y; |
| |
| /* |
| Allocate histogram and linear map. |
| */ |
| assert(image != (Image *) NULL); |
| assert(image->signature == MagickCoreSignature); |
| histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*histogram)); |
| if (histogram == (double *) NULL) |
| ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed", |
| image->filename); |
| /* |
| Form histogram. |
| */ |
| (void) memset(histogram,0,(MaxMap+1)*sizeof(*histogram)); |
| image_view=AcquireVirtualCacheView(image,exception); |
| for (y=0; y < (ssize_t) image->rows; y++) |
| { |
| const Quantum |
| *magick_restrict p; |
| |
| ssize_t |
| x; |
| |
| p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); |
| if (p == (const Quantum *) NULL) |
| break; |
| for (x=0; x < (ssize_t) image->columns; x++) |
| { |
| intensity=GetPixelIntensity(image,p); |
| histogram[ScaleQuantumToMap(ClampToQuantum(intensity))]++; |
| p+=GetPixelChannels(image); |
| } |
| } |
| image_view=DestroyCacheView(image_view); |
| /* |
| Find the histogram boundaries by locating the black and white point levels. |
| */ |
| intensity=0.0; |
| for (black=0; black < (ssize_t) MaxMap; black++) |
| { |
| intensity+=histogram[black]; |
| if (intensity >= black_point) |
| break; |
| } |
| intensity=0.0; |
| for (white=(ssize_t) MaxMap; white != 0; white--) |
| { |
| intensity+=histogram[white]; |
| if (intensity >= white_point) |
| break; |
| } |
| histogram=(double *) RelinquishMagickMemory(histogram); |
| status=LevelImage(image,(double) ScaleMapToQuantum((MagickRealType) black), |
| (double) ScaleMapToQuantum((MagickRealType) white),1.0,exception); |
| return(status); |
| } |
| |
| |
| /* |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % % |
| % % |
| % % |
| % M o d u l a t e I m a g e % |
| % % |
| % % |
| % % |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % |
| % ModulateImage() lets you control the brightness, saturation, and hue |
| % of an image. Modulate represents the brightness, saturation, and hue |
| % as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the |
| % modulation is lightness, saturation, and hue. For HWB, use blackness, |
| % whiteness, and hue. And for HCL, use chrome, luma, and hue. |
| % |
| % The format of the ModulateImage method is: |
| % |
| % MagickBooleanType ModulateImage(Image *image,const char *modulate, |
| % ExceptionInfo *exception) |
| % |
| % A description of each parameter follows: |
| % |
| % o image: the image. |
| % |
| % o modulate: Define the percent change in brightness, saturation, and hue. |
| % |
| % o exception: return any errors or warnings in this structure. |
| % |
| */ |
| |
| static inline void ModulateHCL(const double percent_hue, |
| const double percent_chroma,const double percent_luma,double *red, |
| double *green,double *blue) |
| { |
| double |
| hue, |
| luma, |
| chroma; |
| |
| /* |
| Increase or decrease color luma, chroma, or hue. |
| */ |
| ConvertRGBToHCL(*red,*green,*blue,&hue,&chroma,&luma); |
| hue+=fmod((percent_hue-100.0),200.0)/200.0; |
| chroma*=0.01*percent_chroma; |
| luma*=0.01*percent_luma; |
| ConvertHCLToRGB(hue,chroma,luma,red,green,blue); |
| } |
| |
| static inline void ModulateHCLp(const double percent_hue, |
| const double percent_chroma,const double percent_luma,double *red, |
| double *green,double *blue) |
| { |
| double |
| hue, |
| luma, |
| chroma; |
| |
| /* |
| Increase or decrease color luma, chroma, or hue. |
| */ |
| ConvertRGBToHCLp(*red,*green,*blue,&hue,&chroma,&luma); |
| hue+=fmod((percent_hue-100.0),200.0)/200.0; |
| chroma*=0.01*percent_chroma; |
| luma*=0.01*percent_luma; |
| ConvertHCLpToRGB(hue,chroma,luma,red,green,blue); |
| } |
| |
| static inline void ModulateHSB(const double percent_hue, |
| const double percent_saturation,const double percent_brightness,double *red, |
| double *green,double *blue) |
| { |
| double |
| brightness, |
| hue, |
| saturation; |
| |
| /* |
| Increase or decrease color brightness, saturation, or hue. |
| */ |
| ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness); |
| hue+=fmod((percent_hue-100.0),200.0)/200.0; |
| saturation*=0.01*percent_saturation; |
| brightness*=0.01*percent_brightness; |
| ConvertHSBToRGB(hue,saturation,brightness,red,green,blue); |
| } |
| |
| static inline void ModulateHSI(const double percent_hue, |
| const double percent_saturation,const double percent_intensity,double *red, |
| double *green,double *blue) |
| { |
| double |
| intensity, |
| hue, |
| saturation; |
| |
| /* |
| Increase or decrease color intensity, saturation, or hue. |
| */ |
| ConvertRGBToHSI(*red,*green,*blue,&hue,&saturation,&intensity); |
| hue+=fmod((percent_hue-100.0),200.0)/200.0; |
| saturation*=0.01*percent_saturation; |
| intensity*=0.01*percent_intensity; |
| ConvertHSIToRGB(hue,saturation,intensity,red,green,blue); |
| } |
| |
| static inline void ModulateHSL(const double percent_hue, |
| const double percent_saturation,const double percent_lightness,double *red, |
| double *green,double *blue) |
| { |
| double |
| hue, |
| lightness, |
| saturation; |
| |
| /* |
| Increase or decrease color lightness, saturation, or hue. |
| */ |
| ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness); |
| hue+=fmod((percent_hue-100.0),200.0)/200.0; |
| saturation*=0.01*percent_saturation; |
| lightness*=0.01*percent_lightness; |
| ConvertHSLToRGB(hue,saturation,lightness,red,green,blue); |
| } |
| |
| static inline void ModulateHSV(const double percent_hue, |
| const double percent_saturation,const double percent_value,double *red, |
| double *green,double *blue) |
| { |
| double |
| hue, |
| saturation, |
| value; |
| |
| /* |
| Increase or decrease color value, saturation, or hue. |
| */ |
| ConvertRGBToHSV(*red,*green,*blue,&hue,&saturation,&value); |
| hue+=fmod((percent_hue-100.0),200.0)/200.0; |
| saturation*=0.01*percent_saturation; |
| value*=0.01*percent_value; |
| ConvertHSVToRGB(hue,saturation,value,red,green,blue); |
| } |
| |
| static inline void ModulateHWB(const double percent_hue, |
| const double percent_whiteness,const double percent_blackness,double *red, |
| double *green,double *blue) |
| { |
| double |
| blackness, |
| hue, |
| whiteness; |
| |
| /* |
| Increase or decrease color blackness, whiteness, or hue. |
| */ |
| ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness); |
| hue+=fmod((percent_hue-100.0),200.0)/200.0; |
| blackness*=0.01*percent_blackness; |
| whiteness*=0.01*percent_whiteness; |
| ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue); |
| } |
| |
| static inline void ModulateLCHab(const double percent_luma, |
| const double percent_chroma,const double percent_hue, |
| const IlluminantType illuminant,double *red,double *green,double *blue) |
| { |
| double |
| hue, |
| luma, |
| chroma; |
| |
| /* |
| Increase or decrease color luma, chroma, or hue. |
| */ |
| ConvertRGBToLCHab(*red,*green,*blue,illuminant,&luma,&chroma,&hue); |
| luma*=0.01*percent_luma; |
| chroma*=0.01*percent_chroma; |
| hue+=fmod((percent_hue-100.0),200.0)/200.0; |
| ConvertLCHabToRGB(luma,chroma,hue,illuminant,red,green,blue); |
| } |
| |
| static inline void ModulateLCHuv(const double percent_luma, |
| const double percent_chroma,const double percent_hue, |
| const IlluminantType illuminant,double *red,double *green,double *blue) |
| { |
| double |
| hue, |
| luma, |
| chroma; |
| |
| /* |
| Increase or decrease color luma, chroma, or hue. |
| */ |
| ConvertRGBToLCHuv(*red,*green,*blue,illuminant,&luma,&chroma,&hue); |
| luma*=0.01*percent_luma; |
| chroma*=0.01*percent_chroma; |
| hue+=fmod((percent_hue-100.0),200.0)/200.0; |
| ConvertLCHuvToRGB(luma,chroma,hue,illuminant,red,green,blue); |
| } |
| |
| MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate, |
| ExceptionInfo *exception) |
| { |
| #define ModulateImageTag "Modulate/Image" |
| |
| CacheView |
| *image_view; |
| |
| ColorspaceType |
| colorspace = UndefinedColorspace; |
| |
| const char |
| *artifact; |
| |
| double |
| percent_brightness, |
| percent_hue, |
| percent_saturation; |
| |
| GeometryInfo |
| geometry_info; |
| |
| IlluminantType |
| illuminant = D65Illuminant; |
| |
| MagickBooleanType |
| status; |
| |
| MagickOffsetType |
| progress; |
| |
| MagickStatusType |
| flags; |
| |
| ssize_t |
| i; |
| |
| ssize_t |
| y; |
| |
| /* |
| Initialize modulate table. |
| */ |
| assert(image != (Image *) NULL); |
| assert(image->signature == MagickCoreSignature); |
| if (image->debug != MagickFalse) |
| (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
| if (modulate == (char *) NULL) |
| return(MagickFalse); |
| if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse) |
| (void) SetImageColorspace(image,sRGBColorspace,exception); |
| flags=ParseGeometry(modulate,&geometry_info); |
| percent_brightness=geometry_info.rho; |
| percent_saturation=geometry_info.sigma; |
| if ((flags & SigmaValue) == 0) |
| percent_saturation=100.0; |
| percent_hue=geometry_info.xi; |
| if ((flags & XiValue) == 0) |
| percent_hue=100.0; |
| artifact=GetImageArtifact(image,"modulate:colorspace"); |
| if (artifact != (const char *) NULL) |
| { |
| colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions, |
| MagickFalse,artifact); |
| if ((ssize_t) illuminant < 0) |
| colorspace=UndefinedColorspace; |
| } |
| artifact=GetImageArtifact(image,"color:illuminant"); |
| if (artifact != (const char *) NULL) |
| { |
| illuminant=(IlluminantType) ParseCommandOption(MagickIlluminantOptions, |
| MagickFalse,artifact); |
| if ((ssize_t) illuminant < 0) |
| illuminant=UndefinedIlluminant; |
| } |
| if (image->storage_class == PseudoClass) |
| for (i=0; i < (ssize_t) image->colors; i++) |
| { |
| double |
| blue, |
| green, |
| red; |
| |
| /* |
| Modulate image colormap. |
| */ |
| red=(double) image->colormap[i].red; |
| green=(double) image->colormap[i].green; |
| blue=(double) image->colormap[i].blue; |
| switch (colorspace) |
| { |
| case HCLColorspace: |
| { |
| ModulateHCL(percent_hue,percent_saturation,percent_brightness, |
| &red,&green,&blue); |
| break; |
| } |
| case HCLpColorspace: |
| { |
| ModulateHCLp(percent_hue,percent_saturation,percent_brightness, |
| &red,&green,&blue); |
| break; |
| } |
| case HSBColorspace: |
| { |
| ModulateHSB(percent_hue,percent_saturation,percent_brightness, |
| &red,&green,&blue); |
| break; |
| } |
| case HSIColorspace: |
| { |
| ModulateHSI(percent_hue,percent_saturation,percent_brightness, |
| &red,&green,&blue); |
| break; |
| } |
| case HSLColorspace: |
| default: |
| { |
| ModulateHSL(percent_hue,percent_saturation,percent_brightness, |
| &red,&green,&blue); |
| break; |
| } |
| case HSVColorspace: |
| { |
| ModulateHSV(percent_hue,percent_saturation,percent_brightness, |
| &red,&green,&blue); |
| break; |
| } |
| case HWBColorspace: |
| { |
| ModulateHWB(percent_hue,percent_saturation,percent_brightness, |
| &red,&green,&blue); |
| break; |
| } |
| case LCHColorspace: |
| case LCHabColorspace: |
| { |
| ModulateLCHab(percent_brightness,percent_saturation,percent_hue, |
| illuminant,&red,&green,&blue); |
| break; |
| } |
| case LCHuvColorspace: |
| { |
| ModulateLCHuv(percent_brightness,percent_saturation,percent_hue, |
| illuminant,&red,&green,&blue); |
| break; |
| } |
| } |
| image->colormap[i].red=red; |
| image->colormap[i].green=green; |
| image->colormap[i].blue=blue; |
| } |
| /* |
| Modulate image. |
| */ |
| #if defined(MAGICKCORE_OPENCL_SUPPORT) |
| if (AccelerateModulateImage(image,percent_brightness,percent_hue, |
| percent_saturation,colorspace,exception) != MagickFalse) |
| return(MagickTrue); |
| #endif |
| status=MagickTrue; |
| progress=0; |
| image_view=AcquireAuthenticCacheView(image,exception); |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp parallel for schedule(static) shared(progress,status) \ |
| magick_number_threads(image,image,image->rows,1) |
| #endif |
| for (y=0; y < (ssize_t) image->rows; y++) |
| { |
| Quantum |
| *magick_restrict q; |
| |
| ssize_t |
| x; |
| |
| if (status == MagickFalse) |
| continue; |
| q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception); |
| if (q == (Quantum *) NULL) |
| { |
| status=MagickFalse; |
| continue; |
| } |
| for (x=0; x < (ssize_t) image->columns; x++) |
| { |
| double |
| blue, |
| green, |
| red; |
| |
| red=(double) GetPixelRed(image,q); |
| green=(double) GetPixelGreen(image,q); |
| blue=(double) GetPixelBlue(image,q); |
| switch (colorspace) |
| { |
| case HCLColorspace: |
| { |
| ModulateHCL(percent_hue,percent_saturation,percent_brightness, |
| &red,&green,&blue); |
| break; |
| } |
| case HCLpColorspace: |
| { |
| ModulateHCLp(percent_hue,percent_saturation,percent_brightness, |
| &red,&green,&blue); |
| break; |
| } |
| case HSBColorspace: |
| { |
| ModulateHSB(percent_hue,percent_saturation,percent_brightness, |
| &red,&green,&blue); |
| break; |
| } |
| case HSLColorspace: |
| default: |
| { |
| ModulateHSL(percent_hue,percent_saturation,percent_brightness, |
| &red,&green,&blue); |
| break; |
| } |
| case HSVColorspace: |
| { |
| ModulateHSV(percent_hue,percent_saturation,percent_brightness, |
| &red,&green,&blue); |
| break; |
| } |
| case HWBColorspace: |
| { |
| ModulateHWB(percent_hue,percent_saturation,percent_brightness, |
| &red,&green,&blue); |
| break; |
| } |
| case LCHabColorspace: |
| { |
| ModulateLCHab(percent_brightness,percent_saturation,percent_hue, |
| illuminant,&red,&green,&blue); |
| break; |
| } |
| case LCHColorspace: |
| case LCHuvColorspace: |
| { |
| ModulateLCHuv(percent_brightness,percent_saturation,percent_hue, |
| illuminant,&red,&green,&blue); |
| break; |
| } |
| } |
| SetPixelRed(image,ClampToQuantum(red),q); |
| SetPixelGreen(image,ClampToQuantum(green),q); |
| SetPixelBlue(image,ClampToQuantum(blue),q); |
| q+=GetPixelChannels(image); |
| } |
| if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse) |
| status=MagickFalse; |
| if (image->progress_monitor != (MagickProgressMonitor) NULL) |
| { |
| MagickBooleanType |
| proceed; |
| |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp atomic |
| #endif |
| progress++; |
| proceed=SetImageProgress(image,ModulateImageTag,progress,image->rows); |
| if (proceed == MagickFalse) |
| status=MagickFalse; |
| } |
| } |
| image_view=DestroyCacheView(image_view); |
| return(status); |
| } |
| |
| /* |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % % |
| % % |
| % % |
| % N e g a t e I m a g e % |
| % % |
| % % |
| % % |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % |
| % NegateImage() negates the colors in the reference image. The grayscale |
| % option means that only grayscale values within the image are negated. |
| % |
| % The format of the NegateImage method is: |
| % |
| % MagickBooleanType NegateImage(Image *image, |
| % const MagickBooleanType grayscale,ExceptionInfo *exception) |
| % |
| % A description of each parameter follows: |
| % |
| % o image: the image. |
| % |
| % o grayscale: If MagickTrue, only negate grayscale pixels within the image. |
| % |
| % o exception: return any errors or warnings in this structure. |
| % |
| */ |
| MagickExport MagickBooleanType NegateImage(Image *image, |
| const MagickBooleanType grayscale,ExceptionInfo *exception) |
| { |
| #define NegateImageTag "Negate/Image" |
| |
| CacheView |
| *image_view; |
| |
| MagickBooleanType |
| status; |
| |
| MagickOffsetType |
| progress; |
| |
| ssize_t |
| i; |
| |
| ssize_t |
| y; |
| |
| assert(image != (Image *) NULL); |
| assert(image->signature == MagickCoreSignature); |
| if (image->debug != MagickFalse) |
| (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
| if (image->storage_class == PseudoClass) |
| for (i=0; i < (ssize_t) image->colors; i++) |
| { |
| /* |
| Negate colormap. |
| */ |
| if (grayscale != MagickFalse) |
| if ((image->colormap[i].red != image->colormap[i].green) || |
| (image->colormap[i].green != image->colormap[i].blue)) |
| continue; |
| if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0) |
| image->colormap[i].red=QuantumRange-image->colormap[i].red; |
| if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0) |
| image->colormap[i].green=QuantumRange-image->colormap[i].green; |
| if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0) |
| image->colormap[i].blue=QuantumRange-image->colormap[i].blue; |
| } |
| /* |
| Negate image. |
| */ |
| status=MagickTrue; |
| progress=0; |
| image_view=AcquireAuthenticCacheView(image,exception); |
| if( grayscale != MagickFalse ) |
| { |
| for (y=0; y < (ssize_t) image->rows; y++) |
| { |
| MagickBooleanType |
| sync; |
| |
| Quantum |
| *magick_restrict q; |
| |
| ssize_t |
| x; |
| |
| if (status == MagickFalse) |
| continue; |
| q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1, |
| exception); |
| if (q == (Quantum *) NULL) |
| { |
| status=MagickFalse; |
| continue; |
| } |
| for (x=0; x < (ssize_t) image->columns; x++) |
| { |
| ssize_t |
| j; |
| |
| if (IsPixelGray(image,q) == MagickFalse) |
| { |
| q+=GetPixelChannels(image); |
| continue; |
| } |
| for (j=0; j < (ssize_t) GetPixelChannels(image); j++) |
| { |
| PixelChannel channel = GetPixelChannelChannel(image,j); |
| PixelTrait traits = GetPixelChannelTraits(image,channel); |
| if ((traits & UpdatePixelTrait) == 0) |
| continue; |
| q[j]=QuantumRange-q[j]; |
| } |
| q+=GetPixelChannels(image); |
| } |
| sync=SyncCacheViewAuthenticPixels(image_view,exception); |
| if (sync == MagickFalse) |
| status=MagickFalse; |
| if (image->progress_monitor != (MagickProgressMonitor) NULL) |
| { |
| MagickBooleanType |
| proceed; |
| |
| progress++; |
| proceed=SetImageProgress(image,NegateImageTag,progress,image->rows); |
| if (proceed == MagickFalse) |
| status=MagickFalse; |
| } |
| } |
| image_view=DestroyCacheView(image_view); |
| return(MagickTrue); |
| } |
| /* |
| Negate image. |
| */ |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp parallel for schedule(static) shared(progress,status) \ |
| magick_number_threads(image,image,image->rows,1) |
| #endif |
| for (y=0; y < (ssize_t) image->rows; y++) |
| { |
| Quantum |
| *magick_restrict q; |
| |
| ssize_t |
| x; |
| |
| if (status == MagickFalse) |
| continue; |
| q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception); |
| if (q == (Quantum *) NULL) |
| { |
| status=MagickFalse; |
| continue; |
| } |
| for (x=0; x < (ssize_t) image->columns; x++) |
| { |
| ssize_t |
| j; |
| |
| for (j=0; j < (ssize_t) GetPixelChannels(image); j++) |
| { |
| PixelChannel channel = GetPixelChannelChannel(image,j); |
| PixelTrait traits = GetPixelChannelTraits(image,channel); |
| if ((traits & UpdatePixelTrait) == 0) |
| continue; |
| q[j]=QuantumRange-q[j]; |
| } |
| q+=GetPixelChannels(image); |
| } |
| if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse) |
| status=MagickFalse; |
| if (image->progress_monitor != (MagickProgressMonitor) NULL) |
| { |
| MagickBooleanType |
| proceed; |
| |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp atomic |
| #endif |
| progress++; |
| proceed=SetImageProgress(image,NegateImageTag,progress,image->rows); |
| if (proceed == MagickFalse) |
| status=MagickFalse; |
| } |
| } |
| image_view=DestroyCacheView(image_view); |
| return(status); |
| } |
| |
| /* |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % % |
| % % |
| % % |
| % N o r m a l i z e I m a g e % |
| % % |
| % % |
| % % |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % |
| % The NormalizeImage() method enhances the contrast of a color image by |
| % mapping the darkest 2 percent of all pixel to black and the brightest |
| % 1 percent to white. |
| % |
| % The format of the NormalizeImage method is: |
| % |
| % MagickBooleanType NormalizeImage(Image *image,ExceptionInfo *exception) |
| % |
| % A description of each parameter follows: |
| % |
| % o image: the image. |
| % |
| % o exception: return any errors or warnings in this structure. |
| % |
| */ |
| MagickExport MagickBooleanType NormalizeImage(Image *image, |
| ExceptionInfo *exception) |
| { |
| double |
| black_point, |
| white_point; |
| |
| black_point=(double) image->columns*image->rows*0.0015; |
| white_point=(double) image->columns*image->rows*0.9995; |
| return(ContrastStretchImage(image,black_point,white_point,exception)); |
| } |
| |
| /* |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % % |
| % % |
| % % |
| % S i g m o i d a l C o n t r a s t I m a g e % |
| % % |
| % % |
| % % |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % |
| % SigmoidalContrastImage() adjusts the contrast of an image with a non-linear |
| % sigmoidal contrast algorithm. Increase the contrast of the image using a |
| % sigmoidal transfer function without saturating highlights or shadows. |
| % Contrast indicates how much to increase the contrast (0 is none; 3 is |
| % typical; 20 is pushing it); mid-point indicates where midtones fall in the |
| % resultant image (0 is white; 50% is middle-gray; 100% is black). Set |
| % sharpen to MagickTrue to increase the image contrast otherwise the contrast |
| % is reduced. |
| % |
| % The format of the SigmoidalContrastImage method is: |
| % |
| % MagickBooleanType SigmoidalContrastImage(Image *image, |
| % const MagickBooleanType sharpen,const char *levels, |
| % ExceptionInfo *exception) |
| % |
| % A description of each parameter follows: |
| % |
| % o image: the image. |
| % |
| % o sharpen: Increase or decrease image contrast. |
| % |
| % o contrast: strength of the contrast, the larger the number the more |
| % 'threshold-like' it becomes. |
| % |
| % o midpoint: midpoint of the function as a color value 0 to QuantumRange. |
| % |
| % o exception: return any errors or warnings in this structure. |
| % |
| */ |
| |
| /* |
| ImageMagick 6 has a version of this function which uses LUTs. |
| */ |
| |
| /* |
| Sigmoidal function Sigmoidal with inflexion point moved to b and "slope |
| constant" set to a. |
| |
| The first version, based on the hyperbolic tangent tanh, when combined with |
| the scaling step, is an exact arithmetic clone of the sigmoid function |
| based on the logistic curve. The equivalence is based on the identity |
| |
| 1/(1+exp(-t)) = (1+tanh(t/2))/2 |
| |
| (http://de.wikipedia.org/wiki/Sigmoidfunktion) and the fact that the |
| scaled sigmoidal derivation is invariant under affine transformations of |
| the ordinate. |
| |
| The tanh version is almost certainly more accurate and cheaper. The 0.5 |
| factor in the argument is to clone the legacy ImageMagick behavior. The |
| reason for making the define depend on atanh even though it only uses tanh |
| has to do with the construction of the inverse of the scaled sigmoidal. |
| */ |
| #if defined(MAGICKCORE_HAVE_ATANH) |
| #define Sigmoidal(a,b,x) ( tanh((0.5*(a))*((x)-(b))) ) |
| #else |
| #define Sigmoidal(a,b,x) ( 1.0/(1.0+exp((a)*((b)-(x)))) ) |
| #endif |
| /* |
| Scaled sigmoidal function: |
| |
| ( Sigmoidal(a,b,x) - Sigmoidal(a,b,0) ) / |
| ( Sigmoidal(a,b,1) - Sigmoidal(a,b,0) ) |
| |
| See http://osdir.com/ml/video.image-magick.devel/2005-04/msg00006.html and |
| http://www.cs.dartmouth.edu/farid/downloads/tutorials/fip.pdf. The limit |
| of ScaledSigmoidal as a->0 is the identity, but a=0 gives a division by |
| zero. This is fixed below by exiting immediately when contrast is small, |
| leaving the image (or colormap) unmodified. This appears to be safe because |
| the series expansion of the logistic sigmoidal function around x=b is |
| |
| 1/2-a*(b-x)/4+... |
| |
| so that the key denominator s(1)-s(0) is about a/4 (a/2 with tanh). |
| */ |
| #define ScaledSigmoidal(a,b,x) ( \ |
| (Sigmoidal((a),(b),(x))-Sigmoidal((a),(b),0.0)) / \ |
| (Sigmoidal((a),(b),1.0)-Sigmoidal((a),(b),0.0)) ) |
| /* |
| Inverse of ScaledSigmoidal, used for +sigmoidal-contrast. Because b |
| may be 0 or 1, the argument of the hyperbolic tangent (resp. logistic |
| sigmoidal) may be outside of the interval (-1,1) (resp. (0,1)), even |
| when creating a LUT from in gamut values, hence the branching. In |
| addition, HDRI may have out of gamut values. |
| InverseScaledSigmoidal is not a two-sided inverse of ScaledSigmoidal: |
| It is only a right inverse. This is unavoidable. |
| */ |
| static inline double InverseScaledSigmoidal(const double a,const double b, |
| const double x) |
| { |
| const double sig0=Sigmoidal(a,b,0.0); |
| const double sig1=Sigmoidal(a,b,1.0); |
| const double argument=(sig1-sig0)*x+sig0; |
| const double clamped= |
| ( |
| #if defined(MAGICKCORE_HAVE_ATANH) |
| argument < -1+MagickEpsilon |
| ? |
| -1+MagickEpsilon |
| : |
| ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument ) |
| ); |
| return(b+(2.0/a)*atanh(clamped)); |
| #else |
| argument < MagickEpsilon |
| ? |
| MagickEpsilon |
| : |
| ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument ) |
| ); |
| return(b-log(1.0/clamped-1.0)/a); |
| #endif |
| } |
| |
| MagickExport MagickBooleanType SigmoidalContrastImage(Image *image, |
| const MagickBooleanType sharpen,const double contrast,const double midpoint, |
| ExceptionInfo *exception) |
| { |
| #define SigmoidalContrastImageTag "SigmoidalContrast/Image" |
| #define ScaledSig(x) ( ClampToQuantum(QuantumRange* \ |
| ScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale*(x))) ) |
| #define InverseScaledSig(x) ( ClampToQuantum(QuantumRange* \ |
| InverseScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale*(x))) ) |
| |
| CacheView |
| *image_view; |
| |
| MagickBooleanType |
| status; |
| |
| MagickOffsetType |
| progress; |
| |
| ssize_t |
| y; |
| |
| /* |
| Convenience macros. |
| */ |
| assert(image != (Image *) NULL); |
| assert(image->signature == MagickCoreSignature); |
| if (image->debug != MagickFalse) |
| (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
| /* |
| Side effect: may clamp values unless contrast<MagickEpsilon, in which |
| case nothing is done. |
| */ |
| if (contrast < MagickEpsilon) |
| return(MagickTrue); |
| /* |
| Sigmoidal-contrast enhance colormap. |
| */ |
| if (image->storage_class == PseudoClass) |
| { |
| ssize_t |
| i; |
| |
| if( sharpen != MagickFalse ) |
| for (i=0; i < (ssize_t) image->colors; i++) |
| { |
| if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0) |
| image->colormap[i].red=(MagickRealType) ScaledSig( |
| image->colormap[i].red); |
| if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0) |
| image->colormap[i].green=(MagickRealType) ScaledSig( |
| image->colormap[i].green); |
| if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0) |
| image->colormap[i].blue=(MagickRealType) ScaledSig( |
| image->colormap[i].blue); |
| if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) |
| image->colormap[i].alpha=(MagickRealType) ScaledSig( |
| image->colormap[i].alpha); |
| } |
| else |
| for (i=0; i < (ssize_t) image->colors; i++) |
| { |
| if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0) |
| image->colormap[i].red=(MagickRealType) InverseScaledSig( |
| image->colormap[i].red); |
| if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0) |
| image->colormap[i].green=(MagickRealType) InverseScaledSig( |
| image->colormap[i].green); |
| if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0) |
| image->colormap[i].blue=(MagickRealType) InverseScaledSig( |
| image->colormap[i].blue); |
| if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) |
| image->colormap[i].alpha=(MagickRealType) InverseScaledSig( |
| image->colormap[i].alpha); |
| } |
| } |
| /* |
| Sigmoidal-contrast enhance image. |
| */ |
| status=MagickTrue; |
| progress=0; |
| image_view=AcquireAuthenticCacheView(image,exception); |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp parallel for schedule(static) shared(progress,status) \ |
| magick_number_threads(image,image,image->rows,1) |
| #endif |
| for (y=0; y < (ssize_t) image->rows; y++) |
| { |
| Quantum |
| *magick_restrict q; |
| |
| ssize_t |
| x; |
| |
| if (status == MagickFalse) |
| continue; |
| q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception); |
| if (q == (Quantum *) NULL) |
| { |
| status=MagickFalse; |
| continue; |
| } |
| for (x=0; x < (ssize_t) image->columns; x++) |
| { |
| ssize_t |
| i; |
| |
| for (i=0; i < (ssize_t) GetPixelChannels(image); i++) |
| { |
| PixelChannel channel = GetPixelChannelChannel(image,i); |
| PixelTrait traits = GetPixelChannelTraits(image,channel); |
| if ((traits & UpdatePixelTrait) == 0) |
| continue; |
| if( sharpen != MagickFalse ) |
| q[i]=ScaledSig(q[i]); |
| else |
| q[i]=InverseScaledSig(q[i]); |
| } |
| q+=GetPixelChannels(image); |
| } |
| if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse) |
| status=MagickFalse; |
| if (image->progress_monitor != (MagickProgressMonitor) NULL) |
| { |
| MagickBooleanType |
| proceed; |
| |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp atomic |
| #endif |
| progress++; |
| proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress, |
| image->rows); |
| if (proceed == MagickFalse) |
| status=MagickFalse; |
| } |
| } |
| image_view=DestroyCacheView(image_view); |
| return(status); |
| } |
| |
| /* |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % % |
| % % |
| % % |
| % W h i t e B a l a n c e I m a g e % |
| % % |
| % % |
| % % |
| %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% |
| % |
| % WhiteBalanceImage() applies white balancing to an image according to a |
| % grayworld assumption in the LAB colorspace. |
| % |
| % The format of the WhiteBalanceImage method is: |
| % |
| % MagickBooleanType WhiteBalanceImage(Image *image, |
| % ExceptionInfo *exception) |
| % |
| % A description of each parameter follows: |
| % |
| % o image: The image to auto-level |
| % |
| % o exception: return any errors or warnings in this structure. |
| % |
| */ |
| MagickExport MagickBooleanType WhiteBalanceImage(Image *image, |
| ExceptionInfo *exception) |
| { |
| #define WhiteBalanceImageTag "WhiteBalance/Image" |
| |
| CacheView |
| *image_view; |
| |
| const char |
| *artifact; |
| |
| double |
| a_mean, |
| b_mean; |
| |
| MagickOffsetType |
| progress; |
| |
| MagickStatusType |
| status; |
| |
| ssize_t |
| y; |
| |
| /* |
| White balance image. |
| */ |
| assert(image != (Image *) NULL); |
| assert(image->signature == MagickCoreSignature); |
| if (image->debug != MagickFalse) |
| (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename); |
| if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse) |
| return(MagickFalse); |
| status=TransformImageColorspace(image,LabColorspace,exception); |
| a_mean=0.0; |
| b_mean=0.0; |
| image_view=AcquireAuthenticCacheView(image,exception); |
| for (y=0; y < (ssize_t) image->rows; y++) |
| { |
| const Quantum |
| *magick_restrict p; |
| |
| ssize_t |
| x; |
| |
| if (status == MagickFalse) |
| continue; |
| p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception); |
| if (p == (Quantum *) NULL) |
| { |
| status=MagickFalse; |
| continue; |
| } |
| for (x=0; x < (ssize_t) image->columns; x++) |
| { |
| a_mean+=QuantumScale*GetPixela(image,p)-0.5; |
| b_mean+=QuantumScale*GetPixelb(image,p)-0.5; |
| p+=GetPixelChannels(image); |
| } |
| } |
| a_mean/=((double) image->columns*image->rows); |
| b_mean/=((double) image->columns*image->rows); |
| progress=0; |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp parallel for schedule(static) shared(progress,status) \ |
| magick_number_threads(image,image,image->rows,1) |
| #endif |
| for (y=0; y < (ssize_t) image->rows; y++) |
| { |
| Quantum |
| *magick_restrict q; |
| |
| ssize_t |
| x; |
| |
| if (status == MagickFalse) |
| continue; |
| q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception); |
| if (q == (Quantum *) NULL) |
| { |
| status=MagickFalse; |
| continue; |
| } |
| for (x=0; x < (ssize_t) image->columns; x++) |
| { |
| double |
| a, |
| b; |
| |
| /* |
| Scale the chroma distance shifted according to amount of luminance. |
| */ |
| a=(double) GetPixela(image,q)-1.1*GetPixelL(image,q)*a_mean; |
| b=(double) GetPixelb(image,q)-1.1*GetPixelL(image,q)*b_mean; |
| SetPixela(image,ClampToQuantum(a),q); |
| SetPixelb(image,ClampToQuantum(b),q); |
| q+=GetPixelChannels(image); |
| } |
| if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse) |
| status=MagickFalse; |
| if (image->progress_monitor != (MagickProgressMonitor) NULL) |
| { |
| MagickBooleanType |
| proceed; |
| |
| #if defined(MAGICKCORE_OPENMP_SUPPORT) |
| #pragma omp atomic |
| #endif |
| progress++; |
| proceed=SetImageProgress(image,WhiteBalanceImageTag,progress,image->rows); |
| if (proceed == MagickFalse) |
| status=MagickFalse; |
| } |
| } |
| image_view=DestroyCacheView(image_view); |
| artifact=GetImageArtifact(image,"white-balance:vibrance"); |
| if (artifact != (const char *) NULL) |
| { |
| ChannelType |
| channel_mask; |
| |
| double |
| black_point; |
| |
| GeometryInfo |
| geometry_info; |
| |
| MagickStatusType |
| flags; |
| |
| /* |
| Level the a & b channels. |
| */ |
| flags=ParseGeometry(artifact,&geometry_info); |
| black_point=geometry_info.rho; |
| if ((flags & PercentValue) != 0) |
| black_point*=(double) (QuantumRange/100.0); |
| channel_mask=SetImageChannelMask(image,(ChannelType) (aChannel | |
| bChannel)); |
| status&=LevelImage(image,black_point,(double) QuantumRange-black_point, |
| 1.0,exception); |
| (void) SetImageChannelMask(image,channel_mask); |
| } |
| status&=TransformImageColorspace(image,sRGBColorspace,exception); |
| return(status != 0 ? MagickTrue : MagickFalse); |
| } |