blob: c5fd1945fd9e1b07bd8e89fff78826ae35931f4c [file] [log] [blame]
cristy3ed852e2009-09-05 21:47:34 +00001/*
2%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3% %
4% %
5% %
6% EEEEE N N H H AAA N N CCCC EEEEE %
7% E NN N H H A A NN N C E %
8% EEE N N N HHHHH AAAAA N N N C EEE %
9% E N NN H H A A N NN C E %
10% EEEEE N N H H A A N N CCCC EEEEE %
11% %
12% %
13% MagickCore Image Enhancement Methods %
14% %
15% Software Design %
16% John Cristy %
17% July 1992 %
18% %
19% %
cristy45ef08f2012-12-07 13:13:34 +000020% Copyright 1999-2013 ImageMagick Studio LLC, a non-profit organization %
cristy3ed852e2009-09-05 21:47:34 +000021% dedicated to making software imaging solutions freely available. %
22% %
23% You may not use this file except in compliance with the License. You may %
24% obtain a copy of the License at %
25% %
26% http://www.imagemagick.org/script/license.php %
27% %
28% Unless required by applicable law or agreed to in writing, software %
29% distributed under the License is distributed on an "AS IS" BASIS, %
30% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31% See the License for the specific language governing permissions and %
32% limitations under the License. %
33% %
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35%
36%
37%
38*/
39
40/*
41 Include declarations.
42*/
cristy4c08aed2011-07-01 19:47:50 +000043#include "MagickCore/studio.h"
44#include "MagickCore/artifact.h"
cristy5836c742013-03-27 21:10:56 +000045#include "MagickCore/attribute.h"
cristy4c08aed2011-07-01 19:47:50 +000046#include "MagickCore/cache.h"
47#include "MagickCore/cache-view.h"
cristy6a2180c2013-05-27 10:28:36 +000048#include "MagickCore/channel.h"
cristy4c08aed2011-07-01 19:47:50 +000049#include "MagickCore/color.h"
50#include "MagickCore/color-private.h"
51#include "MagickCore/colorspace.h"
cristy0898eba2012-04-09 16:38:29 +000052#include "MagickCore/colorspace-private.h"
cristy4c08aed2011-07-01 19:47:50 +000053#include "MagickCore/composite-private.h"
54#include "MagickCore/enhance.h"
55#include "MagickCore/exception.h"
56#include "MagickCore/exception-private.h"
57#include "MagickCore/fx.h"
58#include "MagickCore/gem.h"
cristyd1dd6e42011-09-04 01:46:08 +000059#include "MagickCore/gem-private.h"
cristy4c08aed2011-07-01 19:47:50 +000060#include "MagickCore/geometry.h"
61#include "MagickCore/histogram.h"
62#include "MagickCore/image.h"
63#include "MagickCore/image-private.h"
64#include "MagickCore/memory_.h"
65#include "MagickCore/monitor.h"
66#include "MagickCore/monitor-private.h"
67#include "MagickCore/option.h"
cristy1e0fe422012-04-11 18:43:52 +000068#include "MagickCore/pixel.h"
cristy4c08aed2011-07-01 19:47:50 +000069#include "MagickCore/pixel-accessor.h"
70#include "MagickCore/quantum.h"
71#include "MagickCore/quantum-private.h"
72#include "MagickCore/resample.h"
73#include "MagickCore/resample-private.h"
cristyac245f82012-05-05 17:13:57 +000074#include "MagickCore/resource_.h"
cristy4c08aed2011-07-01 19:47:50 +000075#include "MagickCore/statistic.h"
76#include "MagickCore/string_.h"
77#include "MagickCore/string-private.h"
78#include "MagickCore/thread-private.h"
cristyd1e1c222012-08-13 01:13:28 +000079#include "MagickCore/threshold.h"
cristy4c08aed2011-07-01 19:47:50 +000080#include "MagickCore/token.h"
81#include "MagickCore/xml-tree.h"
cristy433d1182011-09-04 13:38:52 +000082#include "MagickCore/xml-tree-private.h"
cristy3ed852e2009-09-05 21:47:34 +000083
84/*
85%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
86% %
87% %
88% %
89% A u t o G a m m a I m a g e %
90% %
91% %
92% %
93%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
94%
95% AutoGammaImage() extract the 'mean' from the image and adjust the image
96% to try make set its gamma appropriatally.
97%
cristy308b4e62009-09-21 14:40:44 +000098% The format of the AutoGammaImage method is:
cristy3ed852e2009-09-05 21:47:34 +000099%
cristy95111202011-08-09 19:41:42 +0000100% MagickBooleanType AutoGammaImage(Image *image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000101%
102% A description of each parameter follows:
103%
104% o image: The image to auto-level
105%
cristy95111202011-08-09 19:41:42 +0000106% o exception: return any errors or warnings in this structure.
107%
cristy3ed852e2009-09-05 21:47:34 +0000108*/
cristy95111202011-08-09 19:41:42 +0000109MagickExport MagickBooleanType AutoGammaImage(Image *image,
110 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000111{
cristy3ed852e2009-09-05 21:47:34 +0000112 double
cristy4c08aed2011-07-01 19:47:50 +0000113 gamma,
114 log_mean,
115 mean,
116 sans;
anthony4efe5972009-09-11 06:46:40 +0000117
cristy95111202011-08-09 19:41:42 +0000118 MagickStatusType
119 status;
120
cristy01e9afd2011-08-10 17:38:41 +0000121 register ssize_t
122 i;
123
cristy4c08aed2011-07-01 19:47:50 +0000124 log_mean=log(0.5);
cristy5f95f4f2011-10-23 01:01:01 +0000125 if (image->channel_mask == DefaultChannels)
cristy3ed852e2009-09-05 21:47:34 +0000126 {
127 /*
cristy01e9afd2011-08-10 17:38:41 +0000128 Apply gamma correction equally across all given channels.
cristy3ed852e2009-09-05 21:47:34 +0000129 */
cristy95111202011-08-09 19:41:42 +0000130 (void) GetImageMean(image,&mean,&sans,exception);
cristy4c08aed2011-07-01 19:47:50 +0000131 gamma=log(mean*QuantumScale)/log_mean;
cristy01e9afd2011-08-10 17:38:41 +0000132 return(LevelImage(image,0.0,(double) QuantumRange,gamma,exception));
cristy3ed852e2009-09-05 21:47:34 +0000133 }
cristy3ed852e2009-09-05 21:47:34 +0000134 /*
cristy4c08aed2011-07-01 19:47:50 +0000135 Auto-gamma each channel separately.
cristy3ed852e2009-09-05 21:47:34 +0000136 */
cristy4c08aed2011-07-01 19:47:50 +0000137 status=MagickTrue;
cristy01e9afd2011-08-10 17:38:41 +0000138 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
139 {
cristybd5a96c2011-08-21 00:04:26 +0000140 ChannelType
141 channel_mask;
142
cristy5a23c552013-02-13 14:34:28 +0000143 PixelChannel channel=GetPixelChannelChannel(image,i);
144 PixelTrait traits=GetPixelChannelTraits(image,channel);
cristy01e9afd2011-08-10 17:38:41 +0000145 if ((traits & UpdatePixelTrait) == 0)
146 continue;
cristycf1296e2012-08-26 23:40:49 +0000147 channel_mask=SetImageChannelMask(image,(ChannelType) (1 << i));
cristy01e9afd2011-08-10 17:38:41 +0000148 status=GetImageMean(image,&mean,&sans,exception);
149 gamma=log(mean*QuantumScale)/log_mean;
150 status&=LevelImage(image,0.0,(double) QuantumRange,gamma,exception);
cristycf1296e2012-08-26 23:40:49 +0000151 (void) SetImageChannelMask(image,channel_mask);
anthonya322a832013-04-27 06:28:03 +0000152 if( IfMagickFalse(status) )
cristy01e9afd2011-08-10 17:38:41 +0000153 break;
154 }
cristyee1bdaa2013-07-16 16:45:03 +0000155 return(status != 0 ? MagickTrue : MagickFalse);
cristy3ed852e2009-09-05 21:47:34 +0000156}
157
158/*
159%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
160% %
161% %
162% %
163% A u t o L e v e l I m a g e %
164% %
165% %
166% %
167%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
168%
169% AutoLevelImage() adjusts the levels of a particular image channel by
170% scaling the minimum and maximum values to the full quantum range.
171%
172% The format of the LevelImage method is:
173%
cristy95111202011-08-09 19:41:42 +0000174% MagickBooleanType AutoLevelImage(Image *image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000175%
176% A description of each parameter follows:
177%
178% o image: The image to auto-level
179%
cristy95111202011-08-09 19:41:42 +0000180% o exception: return any errors or warnings in this structure.
181%
cristy3ed852e2009-09-05 21:47:34 +0000182*/
cristy95111202011-08-09 19:41:42 +0000183MagickExport MagickBooleanType AutoLevelImage(Image *image,
184 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000185{
cristyb303c3d2011-09-09 11:24:40 +0000186 return(MinMaxStretchImage(image,0.0,0.0,1.0,exception));
cristy3ed852e2009-09-05 21:47:34 +0000187}
188
189/*
190%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
191% %
192% %
193% %
cristya28d6b82010-01-11 20:03:47 +0000194% B r i g h t n e s s C o n t r a s t I m a g e %
195% %
196% %
197% %
198%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
199%
cristyf4356f92011-08-01 15:33:48 +0000200% BrightnessContrastImage() changes the brightness and/or contrast of an
201% image. It converts the brightness and contrast parameters into slope and
202% intercept and calls a polynomical function to apply to the image.
cristya28d6b82010-01-11 20:03:47 +0000203%
204% The format of the BrightnessContrastImage method is:
205%
206% MagickBooleanType BrightnessContrastImage(Image *image,
cristy444eda62011-08-10 02:07:46 +0000207% const double brightness,const double contrast,ExceptionInfo *exception)
cristya28d6b82010-01-11 20:03:47 +0000208%
209% A description of each parameter follows:
210%
211% o image: the image.
212%
cristya28d6b82010-01-11 20:03:47 +0000213% o brightness: the brightness percent (-100 .. 100).
214%
215% o contrast: the contrast percent (-100 .. 100).
216%
cristy444eda62011-08-10 02:07:46 +0000217% o exception: return any errors or warnings in this structure.
218%
cristya28d6b82010-01-11 20:03:47 +0000219*/
cristya28d6b82010-01-11 20:03:47 +0000220MagickExport MagickBooleanType BrightnessContrastImage(Image *image,
cristy444eda62011-08-10 02:07:46 +0000221 const double brightness,const double contrast,ExceptionInfo *exception)
cristya28d6b82010-01-11 20:03:47 +0000222{
cristya28d6b82010-01-11 20:03:47 +0000223#define BrightnessContastImageTag "BrightnessContast/Image"
224
225 double
226 alpha,
cristya28d6b82010-01-11 20:03:47 +0000227 coefficients[2],
cristye23ec9d2011-08-16 18:15:40 +0000228 intercept,
cristya28d6b82010-01-11 20:03:47 +0000229 slope;
230
231 MagickBooleanType
232 status;
233
234 /*
235 Compute slope and intercept.
236 */
237 assert(image != (Image *) NULL);
238 assert(image->signature == MagickSignature);
anthonya322a832013-04-27 06:28:03 +0000239 if( IfMagickTrue(image->debug) )
cristya28d6b82010-01-11 20:03:47 +0000240 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
241 alpha=contrast;
cristy4205a3c2010-09-12 20:19:59 +0000242 slope=tan((double) (MagickPI*(alpha/100.0+1.0)/4.0));
cristya28d6b82010-01-11 20:03:47 +0000243 if (slope < 0.0)
244 slope=0.0;
245 intercept=brightness/100.0+((100-brightness)/200.0)*(1.0-slope);
246 coefficients[0]=slope;
247 coefficients[1]=intercept;
cristy444eda62011-08-10 02:07:46 +0000248 status=FunctionImage(image,PolynomialFunction,2,coefficients,exception);
249 return(status);
250}
251
252/*
253%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
254% %
255% %
256% %
257% C l u t I m a g e %
258% %
259% %
260% %
261%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
262%
263% ClutImage() replaces each color value in the given image, by using it as an
264% index to lookup a replacement color value in a Color Look UP Table in the
265% form of an image. The values are extracted along a diagonal of the CLUT
266% image so either a horizontal or vertial gradient image can be used.
267%
268% Typically this is used to either re-color a gray-scale image according to a
269% color gradient in the CLUT image, or to perform a freeform histogram
270% (level) adjustment according to the (typically gray-scale) gradient in the
271% CLUT image.
272%
273% When the 'channel' mask includes the matte/alpha transparency channel but
274% one image has no such channel it is assumed that that image is a simple
275% gray-scale image that will effect the alpha channel values, either for
276% gray-scale coloring (with transparent or semi-transparent colors), or
277% a histogram adjustment of existing alpha channel values. If both images
278% have matte channels, direct and normal indexing is applied, which is rarely
279% used.
280%
281% The format of the ClutImage method is:
282%
283% MagickBooleanType ClutImage(Image *image,Image *clut_image,
cristy5c4e2582011-09-11 19:21:03 +0000284% const PixelInterpolateMethod method,ExceptionInfo *exception)
cristy444eda62011-08-10 02:07:46 +0000285%
286% A description of each parameter follows:
287%
288% o image: the image, which is replaced by indexed CLUT values
289%
290% o clut_image: the color lookup table image for replacement color values.
291%
cristy5c4e2582011-09-11 19:21:03 +0000292% o method: the pixel interpolation method.
cristy444eda62011-08-10 02:07:46 +0000293%
294% o exception: return any errors or warnings in this structure.
295%
296*/
297MagickExport MagickBooleanType ClutImage(Image *image,const Image *clut_image,
cristy5c4e2582011-09-11 19:21:03 +0000298 const PixelInterpolateMethod method,ExceptionInfo *exception)
cristy444eda62011-08-10 02:07:46 +0000299{
cristy444eda62011-08-10 02:07:46 +0000300#define ClutImageTag "Clut/Image"
301
302 CacheView
303 *clut_view,
304 *image_view;
305
cristy444eda62011-08-10 02:07:46 +0000306 MagickBooleanType
307 status;
308
309 MagickOffsetType
310 progress;
311
cristy1e0fe422012-04-11 18:43:52 +0000312 PixelInfo
313 *clut_map;
cristy444eda62011-08-10 02:07:46 +0000314
cristy1e0fe422012-04-11 18:43:52 +0000315 register ssize_t
316 i;
317
318 ssize_t adjust,
cristy444eda62011-08-10 02:07:46 +0000319 y;
320
321 assert(image != (Image *) NULL);
322 assert(image->signature == MagickSignature);
anthonya322a832013-04-27 06:28:03 +0000323 if( IfMagickTrue(image->debug) )
cristy444eda62011-08-10 02:07:46 +0000324 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
325 assert(clut_image != (Image *) NULL);
326 assert(clut_image->signature == MagickSignature);
anthonya322a832013-04-27 06:28:03 +0000327 if( IfMagickFalse(SetImageStorageClass(image,DirectClass,exception)) )
cristy444eda62011-08-10 02:07:46 +0000328 return(MagickFalse);
anthonya322a832013-04-27 06:28:03 +0000329 if( IfMagickTrue(IsGrayColorspace(image->colorspace)) &&
330 IfMagickFalse(IsGrayColorspace(clut_image->colorspace)))
cristy0c81d062013-04-21 15:22:02 +0000331 (void) SetImageColorspace(image,sRGBColorspace,exception);
cristy1e0fe422012-04-11 18:43:52 +0000332 clut_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*clut_map));
333 if (clut_map == (PixelInfo *) NULL)
cristy444eda62011-08-10 02:07:46 +0000334 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
335 image->filename);
336 /*
337 Clut image.
338 */
339 status=MagickTrue;
340 progress=0;
341 adjust=(ssize_t) (clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1);
cristy46ff2672012-12-14 15:32:26 +0000342 clut_view=AcquireVirtualCacheView(clut_image,exception);
cristy1e0fe422012-04-11 18:43:52 +0000343 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy444eda62011-08-10 02:07:46 +0000344 {
cristy1e0fe422012-04-11 18:43:52 +0000345 GetPixelInfo(clut_image,clut_map+i);
anthony6feb7802012-04-12 06:24:29 +0000346 (void) InterpolatePixelInfo(clut_image,clut_view,method,
cristy1e0fe422012-04-11 18:43:52 +0000347 QuantumScale*i*(clut_image->columns-adjust),QuantumScale*i*
348 (clut_image->rows-adjust),clut_map+i,exception);
cristy444eda62011-08-10 02:07:46 +0000349 }
350 clut_view=DestroyCacheView(clut_view);
cristy46ff2672012-12-14 15:32:26 +0000351 image_view=AcquireAuthenticCacheView(image,exception);
cristy444eda62011-08-10 02:07:46 +0000352#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000353 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy5e6b2592012-12-19 14:08:11 +0000354 magick_threads(image,image,image->rows,1)
cristy444eda62011-08-10 02:07:46 +0000355#endif
356 for (y=0; y < (ssize_t) image->rows; y++)
357 {
cristy1e0fe422012-04-11 18:43:52 +0000358 PixelInfo
359 pixel;
360
cristy444eda62011-08-10 02:07:46 +0000361 register Quantum
362 *restrict q;
363
364 register ssize_t
365 x;
366
anthonya322a832013-04-27 06:28:03 +0000367 if( IfMagickFalse(status) )
cristy444eda62011-08-10 02:07:46 +0000368 continue;
369 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
370 if (q == (Quantum *) NULL)
371 {
372 status=MagickFalse;
373 continue;
374 }
cristy1e0fe422012-04-11 18:43:52 +0000375 GetPixelInfo(image,&pixel);
cristy444eda62011-08-10 02:07:46 +0000376 for (x=0; x < (ssize_t) image->columns; x++)
377 {
cristy883fde12013-04-08 00:50:13 +0000378 if (GetPixelReadMask(image,q) == 0)
cristy10a6c612012-01-29 21:41:05 +0000379 {
380 q+=GetPixelChannels(image);
381 continue;
382 }
cristy1e0fe422012-04-11 18:43:52 +0000383 GetPixelInfoPixel(image,q,&pixel);
cristyac245f82012-05-05 17:13:57 +0000384 pixel.red=clut_map[ScaleQuantumToMap(
385 ClampToQuantum(pixel.red))].red;
386 pixel.green=clut_map[ScaleQuantumToMap(
387 ClampToQuantum(pixel.green))].green;
388 pixel.blue=clut_map[ScaleQuantumToMap(
389 ClampToQuantum(pixel.blue))].blue;
390 pixel.black=clut_map[ScaleQuantumToMap(
391 ClampToQuantum(pixel.black))].black;
392 pixel.alpha=clut_map[ScaleQuantumToMap(
393 ClampToQuantum(pixel.alpha))].alpha;
cristy1e0fe422012-04-11 18:43:52 +0000394 SetPixelInfoPixel(image,&pixel,q);
cristy444eda62011-08-10 02:07:46 +0000395 q+=GetPixelChannels(image);
396 }
anthonya322a832013-04-27 06:28:03 +0000397 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
cristy444eda62011-08-10 02:07:46 +0000398 status=MagickFalse;
399 if (image->progress_monitor != (MagickProgressMonitor) NULL)
400 {
401 MagickBooleanType
402 proceed;
403
404#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +0000405 #pragma omp critical (MagickCore_ClutImage)
cristy444eda62011-08-10 02:07:46 +0000406#endif
407 proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
anthonya322a832013-04-27 06:28:03 +0000408 if( IfMagickFalse(proceed) )
cristy444eda62011-08-10 02:07:46 +0000409 status=MagickFalse;
410 }
411 }
412 image_view=DestroyCacheView(image_view);
cristy1e0fe422012-04-11 18:43:52 +0000413 clut_map=(PixelInfo *) RelinquishMagickMemory(clut_map);
cristy8a46d822012-08-28 23:32:39 +0000414 if ((clut_image->alpha_trait == BlendPixelTrait) &&
cristy444eda62011-08-10 02:07:46 +0000415 ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0))
416 (void) SetImageAlphaChannel(image,ActivateAlphaChannel,exception);
cristya28d6b82010-01-11 20:03:47 +0000417 return(status);
418}
419
420/*
421%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
422% %
423% %
424% %
cristy3ed852e2009-09-05 21:47:34 +0000425% C o l o r D e c i s i o n L i s t I m a g e %
426% %
427% %
428% %
429%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
430%
431% ColorDecisionListImage() accepts a lightweight Color Correction Collection
432% (CCC) file which solely contains one or more color corrections and applies
433% the correction to the image. Here is a sample CCC file:
434%
435% <ColorCorrectionCollection xmlns="urn:ASC:CDL:v1.2">
436% <ColorCorrection id="cc03345">
437% <SOPNode>
438% <Slope> 0.9 1.2 0.5 </Slope>
439% <Offset> 0.4 -0.5 0.6 </Offset>
440% <Power> 1.0 0.8 1.5 </Power>
441% </SOPNode>
442% <SATNode>
443% <Saturation> 0.85 </Saturation>
444% </SATNode>
445% </ColorCorrection>
446% </ColorCorrectionCollection>
447%
448% which includes the slop, offset, and power for each of the RGB channels
449% as well as the saturation.
450%
451% The format of the ColorDecisionListImage method is:
452%
453% MagickBooleanType ColorDecisionListImage(Image *image,
cristy1bfa9f02011-08-11 02:35:43 +0000454% const char *color_correction_collection,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000455%
456% A description of each parameter follows:
457%
458% o image: the image.
459%
460% o color_correction_collection: the color correction collection in XML.
461%
cristy1bfa9f02011-08-11 02:35:43 +0000462% o exception: return any errors or warnings in this structure.
463%
cristy3ed852e2009-09-05 21:47:34 +0000464*/
465MagickExport MagickBooleanType ColorDecisionListImage(Image *image,
cristy1bfa9f02011-08-11 02:35:43 +0000466 const char *color_correction_collection,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000467{
468#define ColorDecisionListCorrectImageTag "ColorDecisionList/Image"
469
470 typedef struct _Correction
471 {
472 double
473 slope,
474 offset,
475 power;
476 } Correction;
477
478 typedef struct _ColorCorrection
479 {
480 Correction
481 red,
482 green,
483 blue;
484
485 double
486 saturation;
487 } ColorCorrection;
488
cristyc4c8d132010-01-07 01:58:38 +0000489 CacheView
490 *image_view;
491
cristy3ed852e2009-09-05 21:47:34 +0000492 char
493 token[MaxTextExtent];
494
495 ColorCorrection
496 color_correction;
497
498 const char
499 *content,
500 *p;
501
cristy3ed852e2009-09-05 21:47:34 +0000502 MagickBooleanType
503 status;
504
cristybb503372010-05-27 20:51:26 +0000505 MagickOffsetType
506 progress;
507
cristy101ab702011-10-13 13:06:32 +0000508 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000509 *cdl_map;
510
cristybb503372010-05-27 20:51:26 +0000511 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000512 i;
513
cristybb503372010-05-27 20:51:26 +0000514 ssize_t
515 y;
516
cristy3ed852e2009-09-05 21:47:34 +0000517 XMLTreeInfo
518 *cc,
519 *ccc,
520 *sat,
521 *sop;
522
cristy3ed852e2009-09-05 21:47:34 +0000523 /*
524 Allocate and initialize cdl maps.
525 */
526 assert(image != (Image *) NULL);
527 assert(image->signature == MagickSignature);
anthonya322a832013-04-27 06:28:03 +0000528 if( IfMagickTrue(image->debug) )
cristy3ed852e2009-09-05 21:47:34 +0000529 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
530 if (color_correction_collection == (const char *) NULL)
531 return(MagickFalse);
cristy1bfa9f02011-08-11 02:35:43 +0000532 ccc=NewXMLTree((const char *) color_correction_collection,exception);
cristy3ed852e2009-09-05 21:47:34 +0000533 if (ccc == (XMLTreeInfo *) NULL)
534 return(MagickFalse);
535 cc=GetXMLTreeChild(ccc,"ColorCorrection");
536 if (cc == (XMLTreeInfo *) NULL)
537 {
538 ccc=DestroyXMLTree(ccc);
539 return(MagickFalse);
540 }
541 color_correction.red.slope=1.0;
542 color_correction.red.offset=0.0;
543 color_correction.red.power=1.0;
544 color_correction.green.slope=1.0;
545 color_correction.green.offset=0.0;
546 color_correction.green.power=1.0;
547 color_correction.blue.slope=1.0;
548 color_correction.blue.offset=0.0;
549 color_correction.blue.power=1.0;
550 color_correction.saturation=0.0;
551 sop=GetXMLTreeChild(cc,"SOPNode");
552 if (sop != (XMLTreeInfo *) NULL)
553 {
554 XMLTreeInfo
555 *offset,
556 *power,
557 *slope;
558
559 slope=GetXMLTreeChild(sop,"Slope");
560 if (slope != (XMLTreeInfo *) NULL)
561 {
562 content=GetXMLTreeContent(slope);
563 p=(const char *) content;
564 for (i=0; (*p != '\0') && (i < 3); i++)
565 {
566 GetMagickToken(p,&p,token);
567 if (*token == ',')
568 GetMagickToken(p,&p,token);
569 switch (i)
570 {
cristyc1acd842011-05-19 23:05:47 +0000571 case 0:
572 {
cristy9b34e302011-11-05 02:15:45 +0000573 color_correction.red.slope=StringToDouble(token,(char **) NULL);
cristyc1acd842011-05-19 23:05:47 +0000574 break;
575 }
576 case 1:
577 {
cristydbdd0e32011-11-04 23:29:40 +0000578 color_correction.green.slope=StringToDouble(token,
cristyc1acd842011-05-19 23:05:47 +0000579 (char **) NULL);
580 break;
581 }
582 case 2:
583 {
cristydbdd0e32011-11-04 23:29:40 +0000584 color_correction.blue.slope=StringToDouble(token,
cristyc1acd842011-05-19 23:05:47 +0000585 (char **) NULL);
586 break;
587 }
cristy3ed852e2009-09-05 21:47:34 +0000588 }
589 }
590 }
591 offset=GetXMLTreeChild(sop,"Offset");
592 if (offset != (XMLTreeInfo *) NULL)
593 {
594 content=GetXMLTreeContent(offset);
595 p=(const char *) content;
596 for (i=0; (*p != '\0') && (i < 3); i++)
597 {
598 GetMagickToken(p,&p,token);
599 if (*token == ',')
600 GetMagickToken(p,&p,token);
601 switch (i)
602 {
cristyc1acd842011-05-19 23:05:47 +0000603 case 0:
604 {
cristydbdd0e32011-11-04 23:29:40 +0000605 color_correction.red.offset=StringToDouble(token,
cristyc1acd842011-05-19 23:05:47 +0000606 (char **) NULL);
607 break;
608 }
609 case 1:
610 {
cristydbdd0e32011-11-04 23:29:40 +0000611 color_correction.green.offset=StringToDouble(token,
cristyc1acd842011-05-19 23:05:47 +0000612 (char **) NULL);
613 break;
614 }
615 case 2:
616 {
cristydbdd0e32011-11-04 23:29:40 +0000617 color_correction.blue.offset=StringToDouble(token,
cristyc1acd842011-05-19 23:05:47 +0000618 (char **) NULL);
619 break;
620 }
cristy3ed852e2009-09-05 21:47:34 +0000621 }
622 }
623 }
624 power=GetXMLTreeChild(sop,"Power");
625 if (power != (XMLTreeInfo *) NULL)
626 {
627 content=GetXMLTreeContent(power);
628 p=(const char *) content;
629 for (i=0; (*p != '\0') && (i < 3); i++)
630 {
631 GetMagickToken(p,&p,token);
632 if (*token == ',')
633 GetMagickToken(p,&p,token);
634 switch (i)
635 {
cristyc1acd842011-05-19 23:05:47 +0000636 case 0:
637 {
cristy9b34e302011-11-05 02:15:45 +0000638 color_correction.red.power=StringToDouble(token,(char **) NULL);
cristyc1acd842011-05-19 23:05:47 +0000639 break;
640 }
641 case 1:
642 {
cristydbdd0e32011-11-04 23:29:40 +0000643 color_correction.green.power=StringToDouble(token,
cristyc1acd842011-05-19 23:05:47 +0000644 (char **) NULL);
645 break;
646 }
647 case 2:
648 {
cristydbdd0e32011-11-04 23:29:40 +0000649 color_correction.blue.power=StringToDouble(token,
cristyc1acd842011-05-19 23:05:47 +0000650 (char **) NULL);
651 break;
652 }
cristy3ed852e2009-09-05 21:47:34 +0000653 }
654 }
655 }
656 }
657 sat=GetXMLTreeChild(cc,"SATNode");
658 if (sat != (XMLTreeInfo *) NULL)
659 {
660 XMLTreeInfo
661 *saturation;
662
663 saturation=GetXMLTreeChild(sat,"Saturation");
664 if (saturation != (XMLTreeInfo *) NULL)
665 {
666 content=GetXMLTreeContent(saturation);
667 p=(const char *) content;
668 GetMagickToken(p,&p,token);
cristyca826b52012-01-18 18:50:46 +0000669 color_correction.saturation=StringToDouble(token,(char **) NULL);
cristy3ed852e2009-09-05 21:47:34 +0000670 }
671 }
672 ccc=DestroyXMLTree(ccc);
anthonya322a832013-04-27 06:28:03 +0000673 if( IfMagickTrue(image->debug) )
cristy3ed852e2009-09-05 21:47:34 +0000674 {
675 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
676 " Color Correction Collection:");
677 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000678 " color_correction.red.slope: %g",color_correction.red.slope);
cristy3ed852e2009-09-05 21:47:34 +0000679 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000680 " color_correction.red.offset: %g",color_correction.red.offset);
cristy3ed852e2009-09-05 21:47:34 +0000681 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000682 " color_correction.red.power: %g",color_correction.red.power);
cristy3ed852e2009-09-05 21:47:34 +0000683 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000684 " color_correction.green.slope: %g",color_correction.green.slope);
cristy3ed852e2009-09-05 21:47:34 +0000685 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000686 " color_correction.green.offset: %g",color_correction.green.offset);
cristy3ed852e2009-09-05 21:47:34 +0000687 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000688 " color_correction.green.power: %g",color_correction.green.power);
cristy3ed852e2009-09-05 21:47:34 +0000689 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000690 " color_correction.blue.slope: %g",color_correction.blue.slope);
cristy3ed852e2009-09-05 21:47:34 +0000691 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000692 " color_correction.blue.offset: %g",color_correction.blue.offset);
cristy3ed852e2009-09-05 21:47:34 +0000693 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000694 " color_correction.blue.power: %g",color_correction.blue.power);
cristy3ed852e2009-09-05 21:47:34 +0000695 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000696 " color_correction.saturation: %g",color_correction.saturation);
cristy3ed852e2009-09-05 21:47:34 +0000697 }
cristy101ab702011-10-13 13:06:32 +0000698 cdl_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*cdl_map));
699 if (cdl_map == (PixelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000700 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
701 image->filename);
cristybb503372010-05-27 20:51:26 +0000702 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +0000703 {
cristya19f1d72012-08-07 18:24:38 +0000704 cdl_map[i].red=(double) ScaleMapToQuantum((double)
cristyda1f9c12011-10-02 21:39:49 +0000705 (MaxMap*(pow(color_correction.red.slope*i/MaxMap+
706 color_correction.red.offset,color_correction.red.power))));
cristya19f1d72012-08-07 18:24:38 +0000707 cdl_map[i].green=(double) ScaleMapToQuantum((double)
cristyda1f9c12011-10-02 21:39:49 +0000708 (MaxMap*(pow(color_correction.green.slope*i/MaxMap+
709 color_correction.green.offset,color_correction.green.power))));
cristya19f1d72012-08-07 18:24:38 +0000710 cdl_map[i].blue=(double) ScaleMapToQuantum((double)
cristyda1f9c12011-10-02 21:39:49 +0000711 (MaxMap*(pow(color_correction.blue.slope*i/MaxMap+
712 color_correction.blue.offset,color_correction.blue.power))));
cristy3ed852e2009-09-05 21:47:34 +0000713 }
714 if (image->storage_class == PseudoClass)
nicolase0aa3c32012-08-08 14:57:02 +0000715 for (i=0; i < (ssize_t) image->colors; i++)
nicolasfd74ae32012-08-08 16:00:11 +0000716 {
717 /*
cristyaeded782012-09-11 23:39:36 +0000718 Apply transfer function to colormap.
nicolasfd74ae32012-08-08 16:00:11 +0000719 */
720 double
cristyaeded782012-09-11 23:39:36 +0000721 luma;
cristy3ed852e2009-09-05 21:47:34 +0000722
cristya86a5cb2012-10-14 13:40:33 +0000723 luma=0.21267f*image->colormap[i].red+0.71526*image->colormap[i].green+
724 0.07217f*image->colormap[i].blue;
nicolasfd74ae32012-08-08 16:00:11 +0000725 image->colormap[i].red=luma+color_correction.saturation*cdl_map[
cristyaeded782012-09-11 23:39:36 +0000726 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].red))].red-luma;
nicolasfd74ae32012-08-08 16:00:11 +0000727 image->colormap[i].green=luma+color_correction.saturation*cdl_map[
728 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].green))].green-luma;
729 image->colormap[i].blue=luma+color_correction.saturation*cdl_map[
730 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].blue))].blue-luma;
731 }
cristy3ed852e2009-09-05 21:47:34 +0000732 /*
733 Apply transfer function to image.
734 */
735 status=MagickTrue;
736 progress=0;
cristy46ff2672012-12-14 15:32:26 +0000737 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000738#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000739 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy5e6b2592012-12-19 14:08:11 +0000740 magick_threads(image,image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +0000741#endif
cristybb503372010-05-27 20:51:26 +0000742 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000743 {
744 double
745 luma;
746
cristy4c08aed2011-07-01 19:47:50 +0000747 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000748 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000749
cristy8d4629b2010-08-30 17:59:46 +0000750 register ssize_t
751 x;
752
anthonya322a832013-04-27 06:28:03 +0000753 if( IfMagickFalse(status) )
cristy3ed852e2009-09-05 21:47:34 +0000754 continue;
755 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +0000756 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000757 {
758 status=MagickFalse;
759 continue;
760 }
cristybb503372010-05-27 20:51:26 +0000761 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000762 {
cristyb7113232013-02-15 00:35:19 +0000763 luma=0.21267f*GetPixelRed(image,q)+0.71526*GetPixelGreen(image,q)+
764 0.07217f*GetPixelBlue(image,q);
cristy4c08aed2011-07-01 19:47:50 +0000765 SetPixelRed(image,ClampToQuantum(luma+color_correction.saturation*
766 (cdl_map[ScaleQuantumToMap(GetPixelRed(image,q))].red-luma)),q);
767 SetPixelGreen(image,ClampToQuantum(luma+color_correction.saturation*
768 (cdl_map[ScaleQuantumToMap(GetPixelGreen(image,q))].green-luma)),q);
769 SetPixelBlue(image,ClampToQuantum(luma+color_correction.saturation*
770 (cdl_map[ScaleQuantumToMap(GetPixelBlue(image,q))].blue-luma)),q);
cristyed231572011-07-14 02:18:59 +0000771 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000772 }
anthonya322a832013-04-27 06:28:03 +0000773 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
cristy3ed852e2009-09-05 21:47:34 +0000774 status=MagickFalse;
775 if (image->progress_monitor != (MagickProgressMonitor) NULL)
776 {
777 MagickBooleanType
778 proceed;
779
cristyb5d5f722009-11-04 03:03:49 +0000780#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +0000781 #pragma omp critical (MagickCore_ColorDecisionListImageChannel)
cristy3ed852e2009-09-05 21:47:34 +0000782#endif
783 proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
784 progress++,image->rows);
anthonya322a832013-04-27 06:28:03 +0000785 if( IfMagickFalse(proceed) )
cristy3ed852e2009-09-05 21:47:34 +0000786 status=MagickFalse;
787 }
788 }
789 image_view=DestroyCacheView(image_view);
cristy101ab702011-10-13 13:06:32 +0000790 cdl_map=(PixelInfo *) RelinquishMagickMemory(cdl_map);
cristy3ed852e2009-09-05 21:47:34 +0000791 return(status);
792}
793
794/*
795%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
796% %
797% %
798% %
cristy3ed852e2009-09-05 21:47:34 +0000799% C o n t r a s t I m a g e %
800% %
801% %
802% %
803%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
804%
805% ContrastImage() enhances the intensity differences between the lighter and
806% darker elements of the image. Set sharpen to a MagickTrue to increase the
807% image contrast otherwise the contrast is reduced.
808%
809% The format of the ContrastImage method is:
810%
811% MagickBooleanType ContrastImage(Image *image,
cristye23ec9d2011-08-16 18:15:40 +0000812% const MagickBooleanType sharpen,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000813%
814% A description of each parameter follows:
815%
816% o image: the image.
817%
818% o sharpen: Increase or decrease image contrast.
819%
cristye23ec9d2011-08-16 18:15:40 +0000820% o exception: return any errors or warnings in this structure.
821%
cristy3ed852e2009-09-05 21:47:34 +0000822*/
823
cristy3094b7f2011-10-01 23:18:02 +0000824static void Contrast(const int sign,double *red,double *green,double *blue)
cristy3ed852e2009-09-05 21:47:34 +0000825{
826 double
827 brightness,
828 hue,
829 saturation;
830
831 /*
832 Enhance contrast: dark color become darker, light color become lighter.
833 */
cristy3094b7f2011-10-01 23:18:02 +0000834 assert(red != (double *) NULL);
835 assert(green != (double *) NULL);
836 assert(blue != (double *) NULL);
cristy3ed852e2009-09-05 21:47:34 +0000837 hue=0.0;
838 saturation=0.0;
839 brightness=0.0;
cristy0a39a5c2012-06-27 12:51:45 +0000840 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
cristy31ac0f02011-03-12 02:04:47 +0000841 brightness+=0.5*sign*(0.5*(sin((double) (MagickPI*(brightness-0.5)))+1.0)-
cristy4205a3c2010-09-12 20:19:59 +0000842 brightness);
cristy3ed852e2009-09-05 21:47:34 +0000843 if (brightness > 1.0)
844 brightness=1.0;
845 else
846 if (brightness < 0.0)
847 brightness=0.0;
cristy0a39a5c2012-06-27 12:51:45 +0000848 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
cristy3ed852e2009-09-05 21:47:34 +0000849}
850
851MagickExport MagickBooleanType ContrastImage(Image *image,
cristye23ec9d2011-08-16 18:15:40 +0000852 const MagickBooleanType sharpen,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000853{
854#define ContrastImageTag "Contrast/Image"
855
cristyc4c8d132010-01-07 01:58:38 +0000856 CacheView
857 *image_view;
858
cristy3ed852e2009-09-05 21:47:34 +0000859 int
860 sign;
861
cristy3ed852e2009-09-05 21:47:34 +0000862 MagickBooleanType
863 status;
864
cristybb503372010-05-27 20:51:26 +0000865 MagickOffsetType
866 progress;
867
868 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000869 i;
870
cristybb503372010-05-27 20:51:26 +0000871 ssize_t
872 y;
873
cristy3ed852e2009-09-05 21:47:34 +0000874 assert(image != (Image *) NULL);
875 assert(image->signature == MagickSignature);
anthonya322a832013-04-27 06:28:03 +0000876 if( IfMagickTrue(image->debug) )
cristy3ed852e2009-09-05 21:47:34 +0000877 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
anthonya322a832013-04-27 06:28:03 +0000878 sign=IfMagickTrue(sharpen) ? 1 : -1;
cristy3ed852e2009-09-05 21:47:34 +0000879 if (image->storage_class == PseudoClass)
880 {
881 /*
882 Contrast enhance colormap.
883 */
cristybb503372010-05-27 20:51:26 +0000884 for (i=0; i < (ssize_t) image->colors; i++)
cristybdf42e52012-10-10 23:58:20 +0000885 {
886 double
887 blue,
888 green,
889 red;
890
891 Contrast(sign,&red,&green,&blue);
892 image->colormap[i].red=(MagickRealType) red;
893 image->colormap[i].red=(MagickRealType) red;
894 image->colormap[i].red=(MagickRealType) red;
895 }
cristy3ed852e2009-09-05 21:47:34 +0000896 }
897 /*
898 Contrast enhance image.
899 */
900 status=MagickTrue;
901 progress=0;
cristy46ff2672012-12-14 15:32:26 +0000902 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000903#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000904 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy5e6b2592012-12-19 14:08:11 +0000905 magick_threads(image,image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +0000906#endif
cristybb503372010-05-27 20:51:26 +0000907 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000908 {
cristy3094b7f2011-10-01 23:18:02 +0000909 double
cristy5afeab82011-04-30 01:30:09 +0000910 blue,
911 green,
912 red;
913
cristy4c08aed2011-07-01 19:47:50 +0000914 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000915 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000916
cristy8d4629b2010-08-30 17:59:46 +0000917 register ssize_t
918 x;
919
anthonya322a832013-04-27 06:28:03 +0000920 if( IfMagickFalse(status) )
cristy3ed852e2009-09-05 21:47:34 +0000921 continue;
922 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +0000923 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000924 {
925 status=MagickFalse;
926 continue;
927 }
cristybb503372010-05-27 20:51:26 +0000928 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000929 {
cristyda1f9c12011-10-02 21:39:49 +0000930 red=(double) GetPixelRed(image,q);
931 green=(double) GetPixelGreen(image,q);
932 blue=(double) GetPixelBlue(image,q);
cristy5afeab82011-04-30 01:30:09 +0000933 Contrast(sign,&red,&green,&blue);
cristyda1f9c12011-10-02 21:39:49 +0000934 SetPixelRed(image,ClampToQuantum(red),q);
935 SetPixelGreen(image,ClampToQuantum(green),q);
936 SetPixelBlue(image,ClampToQuantum(blue),q);
cristyed231572011-07-14 02:18:59 +0000937 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000938 }
anthonya322a832013-04-27 06:28:03 +0000939 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
cristy3ed852e2009-09-05 21:47:34 +0000940 status=MagickFalse;
941 if (image->progress_monitor != (MagickProgressMonitor) NULL)
942 {
943 MagickBooleanType
944 proceed;
945
cristyb5d5f722009-11-04 03:03:49 +0000946#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +0000947 #pragma omp critical (MagickCore_ContrastImage)
cristy3ed852e2009-09-05 21:47:34 +0000948#endif
949 proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
anthonya322a832013-04-27 06:28:03 +0000950 if( IfMagickFalse(proceed) )
cristy3ed852e2009-09-05 21:47:34 +0000951 status=MagickFalse;
952 }
953 }
954 image_view=DestroyCacheView(image_view);
955 return(status);
956}
957
958/*
959%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
960% %
961% %
962% %
963% C o n t r a s t S t r e t c h I m a g e %
964% %
965% %
966% %
967%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
968%
cristyf1611782011-08-01 15:39:13 +0000969% ContrastStretchImage() is a simple image enhancement technique that attempts
anthonye5b39652012-04-21 05:37:29 +0000970% to improve the contrast in an image by 'stretching' the range of intensity
cristyf1611782011-08-01 15:39:13 +0000971% values it contains to span a desired range of values. It differs from the
972% more sophisticated histogram equalization in that it can only apply a
973% linear scaling function to the image pixel values. As a result the
anthonye5b39652012-04-21 05:37:29 +0000974% 'enhancement' is less harsh.
cristy3ed852e2009-09-05 21:47:34 +0000975%
976% The format of the ContrastStretchImage method is:
977%
978% MagickBooleanType ContrastStretchImage(Image *image,
cristye23ec9d2011-08-16 18:15:40 +0000979% const char *levels,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000980%
981% A description of each parameter follows:
982%
983% o image: the image.
984%
cristy3ed852e2009-09-05 21:47:34 +0000985% o black_point: the black point.
986%
987% o white_point: the white point.
988%
989% o levels: Specify the levels where the black and white points have the
990% range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
991%
cristye23ec9d2011-08-16 18:15:40 +0000992% o exception: return any errors or warnings in this structure.
993%
cristy3ed852e2009-09-05 21:47:34 +0000994*/
cristy3ed852e2009-09-05 21:47:34 +0000995MagickExport MagickBooleanType ContrastStretchImage(Image *image,
cristye23ec9d2011-08-16 18:15:40 +0000996 const double black_point,const double white_point,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000997{
cristya19f1d72012-08-07 18:24:38 +0000998#define MaxRange(color) ((double) ScaleQuantumToMap((Quantum) (color)))
cristy3ed852e2009-09-05 21:47:34 +0000999#define ContrastStretchImageTag "ContrastStretch/Image"
1000
cristyc4c8d132010-01-07 01:58:38 +00001001 CacheView
1002 *image_view;
1003
cristy3ed852e2009-09-05 21:47:34 +00001004 MagickBooleanType
1005 status;
1006
cristybb503372010-05-27 20:51:26 +00001007 MagickOffsetType
1008 progress;
1009
cristyf45fec72011-08-23 16:02:32 +00001010 double
1011 *black,
cristy3ed852e2009-09-05 21:47:34 +00001012 *histogram,
1013 *stretch_map,
cristyf45fec72011-08-23 16:02:32 +00001014 *white;
cristy3ed852e2009-09-05 21:47:34 +00001015
cristybb503372010-05-27 20:51:26 +00001016 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001017 i;
1018
cristy564a5692012-01-20 23:56:26 +00001019 size_t
1020 number_channels;
1021
cristybb503372010-05-27 20:51:26 +00001022 ssize_t
1023 y;
1024
cristy3ed852e2009-09-05 21:47:34 +00001025 /*
1026 Allocate histogram and stretch map.
1027 */
1028 assert(image != (Image *) NULL);
1029 assert(image->signature == MagickSignature);
anthonya322a832013-04-27 06:28:03 +00001030 if( IfMagickTrue(image->debug) )
cristy3ed852e2009-09-05 21:47:34 +00001031 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristyf45fec72011-08-23 16:02:32 +00001032 black=(double *) AcquireQuantumMemory(GetPixelChannels(image),sizeof(*black));
1033 white=(double *) AcquireQuantumMemory(GetPixelChannels(image),sizeof(*white));
cristy564a5692012-01-20 23:56:26 +00001034 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,GetPixelChannels(image)*
1035 sizeof(*histogram));
cristyf45fec72011-08-23 16:02:32 +00001036 stretch_map=(double *) AcquireQuantumMemory(MaxMap+1UL,
cristy465571b2011-08-21 20:43:15 +00001037 GetPixelChannels(image)*sizeof(*stretch_map));
cristyf45fec72011-08-23 16:02:32 +00001038 if ((black == (double *) NULL) || (white == (double *) NULL) ||
1039 (histogram == (double *) NULL) || (stretch_map == (double *) NULL))
cristy465571b2011-08-21 20:43:15 +00001040 {
cristyf45fec72011-08-23 16:02:32 +00001041 if (stretch_map != (double *) NULL)
1042 stretch_map=(double *) RelinquishMagickMemory(stretch_map);
1043 if (histogram != (double *) NULL)
1044 histogram=(double *) RelinquishMagickMemory(histogram);
1045 if (white != (double *) NULL)
1046 white=(double *) RelinquishMagickMemory(white);
1047 if (black != (double *) NULL)
1048 black=(double *) RelinquishMagickMemory(black);
cristy465571b2011-08-21 20:43:15 +00001049 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1050 image->filename);
1051 }
cristy3ed852e2009-09-05 21:47:34 +00001052 /*
1053 Form histogram.
1054 */
anthonya322a832013-04-27 06:28:03 +00001055 if( IfMagickTrue(IsImageGray(image,exception)) )
cristy5836c742013-03-27 21:10:56 +00001056 (void) SetImageColorspace(image,GRAYColorspace,exception);
cristy3ed852e2009-09-05 21:47:34 +00001057 status=MagickTrue;
cristy465571b2011-08-21 20:43:15 +00001058 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1059 sizeof(*histogram));
cristy46ff2672012-12-14 15:32:26 +00001060 image_view=AcquireVirtualCacheView(image,exception);
cristybb503372010-05-27 20:51:26 +00001061 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001062 {
cristy4c08aed2011-07-01 19:47:50 +00001063 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001064 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001065
cristybb503372010-05-27 20:51:26 +00001066 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001067 x;
1068
anthonya322a832013-04-27 06:28:03 +00001069 if( IfMagickFalse(status) )
cristy3ed852e2009-09-05 21:47:34 +00001070 continue;
1071 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001072 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001073 {
1074 status=MagickFalse;
1075 continue;
1076 }
cristy43547a52011-08-09 13:07:05 +00001077 for (x=0; x < (ssize_t) image->columns; x++)
1078 {
cristya64f4b92012-07-11 23:59:00 +00001079 double
1080 pixel;
1081
cristy465571b2011-08-21 20:43:15 +00001082 register ssize_t
1083 i;
1084
cristyf13c5942012-08-08 23:50:11 +00001085 pixel=GetPixelIntensity(image,p);
cristy465571b2011-08-21 20:43:15 +00001086 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristya64f4b92012-07-11 23:59:00 +00001087 {
1088 if (image->channel_mask != DefaultChannels)
cristy1fc77e82012-08-08 15:43:42 +00001089 pixel=(double) p[i];
1090 histogram[GetPixelChannels(image)*ScaleQuantumToMap(
1091 ClampToQuantum(pixel))+i]++;
cristya64f4b92012-07-11 23:59:00 +00001092 }
cristy43547a52011-08-09 13:07:05 +00001093 p+=GetPixelChannels(image);
1094 }
cristy3ed852e2009-09-05 21:47:34 +00001095 }
cristydb070952012-04-20 14:33:00 +00001096 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00001097 /*
1098 Find the histogram boundaries by locating the black/white levels.
1099 */
cristy564a5692012-01-20 23:56:26 +00001100 number_channels=GetPixelChannels(image);
cristyc94ba6f2012-01-29 23:19:58 +00001101 for (i=0; i < (ssize_t) number_channels; i++)
cristy3ed852e2009-09-05 21:47:34 +00001102 {
cristy465571b2011-08-21 20:43:15 +00001103 double
1104 intensity;
1105
1106 register ssize_t
1107 j;
1108
1109 black[i]=0.0;
1110 white[i]=MaxRange(QuantumRange);
1111 intensity=0.0;
1112 for (j=0; j <= (ssize_t) MaxMap; j++)
1113 {
1114 intensity+=histogram[GetPixelChannels(image)*j+i];
1115 if (intensity > black_point)
1116 break;
1117 }
cristya19f1d72012-08-07 18:24:38 +00001118 black[i]=(double) j;
cristy465571b2011-08-21 20:43:15 +00001119 intensity=0.0;
1120 for (j=(ssize_t) MaxMap; j != 0; j--)
1121 {
1122 intensity+=histogram[GetPixelChannels(image)*j+i];
1123 if (intensity > ((double) image->columns*image->rows-white_point))
1124 break;
1125 }
cristya19f1d72012-08-07 18:24:38 +00001126 white[i]=(double) j;
cristy3ed852e2009-09-05 21:47:34 +00001127 }
cristy465571b2011-08-21 20:43:15 +00001128 histogram=(double *) RelinquishMagickMemory(histogram);
cristy3ed852e2009-09-05 21:47:34 +00001129 /*
cristy465571b2011-08-21 20:43:15 +00001130 Stretch the histogram to create the stretched image mapping.
cristy3ed852e2009-09-05 21:47:34 +00001131 */
cristy465571b2011-08-21 20:43:15 +00001132 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*GetPixelChannels(image)*
1133 sizeof(*stretch_map));
cristy564a5692012-01-20 23:56:26 +00001134 number_channels=GetPixelChannels(image);
cristy564a5692012-01-20 23:56:26 +00001135 for (i=0; i < (ssize_t) number_channels; i++)
cristy465571b2011-08-21 20:43:15 +00001136 {
1137 register ssize_t
1138 j;
1139
1140 for (j=0; j <= (ssize_t) MaxMap; j++)
1141 {
1142 if (j < (ssize_t) black[i])
1143 stretch_map[GetPixelChannels(image)*j+i]=0.0;
1144 else
1145 if (j > (ssize_t) white[i])
cristyb7113232013-02-15 00:35:19 +00001146 stretch_map[GetPixelChannels(image)*j+i]=(double) QuantumRange;
cristy465571b2011-08-21 20:43:15 +00001147 else
1148 if (black[i] != white[i])
cristyb7113232013-02-15 00:35:19 +00001149 stretch_map[GetPixelChannels(image)*j+i]=(double) ScaleMapToQuantum(
1150 (double) (MaxMap*(j-black[i])/(white[i]-black[i])));
cristy465571b2011-08-21 20:43:15 +00001151 }
1152 }
cristy3ed852e2009-09-05 21:47:34 +00001153 if (image->storage_class == PseudoClass)
1154 {
cristy465571b2011-08-21 20:43:15 +00001155 register ssize_t
1156 j;
1157
cristy3ed852e2009-09-05 21:47:34 +00001158 /*
cristy465571b2011-08-21 20:43:15 +00001159 Stretch-contrast colormap.
cristy3ed852e2009-09-05 21:47:34 +00001160 */
cristy465571b2011-08-21 20:43:15 +00001161 for (j=0; j < (ssize_t) image->colors; j++)
cristy3ed852e2009-09-05 21:47:34 +00001162 {
cristyed231572011-07-14 02:18:59 +00001163 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001164 {
cristycf1296e2012-08-26 23:40:49 +00001165 i=GetPixelChannelChannel(image,RedPixelChannel);
cristy465571b2011-08-21 20:43:15 +00001166 if (black[i] != white[i])
cristyda1f9c12011-10-02 21:39:49 +00001167 image->colormap[j].red=stretch_map[GetPixelChannels(image)*
1168 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))]+i;
cristy3ed852e2009-09-05 21:47:34 +00001169 }
cristyed231572011-07-14 02:18:59 +00001170 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001171 {
cristycf1296e2012-08-26 23:40:49 +00001172 i=GetPixelChannelChannel(image,GreenPixelChannel);
cristy465571b2011-08-21 20:43:15 +00001173 if (black[i] != white[i])
cristyda1f9c12011-10-02 21:39:49 +00001174 image->colormap[j].green=stretch_map[GetPixelChannels(image)*
1175 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))]+i;
cristy3ed852e2009-09-05 21:47:34 +00001176 }
cristyed231572011-07-14 02:18:59 +00001177 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001178 {
cristycf1296e2012-08-26 23:40:49 +00001179 i=GetPixelChannelChannel(image,BluePixelChannel);
cristy465571b2011-08-21 20:43:15 +00001180 if (black[i] != white[i])
cristyda1f9c12011-10-02 21:39:49 +00001181 image->colormap[j].blue=stretch_map[GetPixelChannels(image)*
1182 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))]+i;
cristy3ed852e2009-09-05 21:47:34 +00001183 }
cristyed231572011-07-14 02:18:59 +00001184 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001185 {
cristycf1296e2012-08-26 23:40:49 +00001186 i=GetPixelChannelChannel(image,AlphaPixelChannel);
cristy465571b2011-08-21 20:43:15 +00001187 if (black[i] != white[i])
cristyda1f9c12011-10-02 21:39:49 +00001188 image->colormap[j].alpha=stretch_map[GetPixelChannels(image)*
1189 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))]+i;
cristy3ed852e2009-09-05 21:47:34 +00001190 }
1191 }
1192 }
1193 /*
cristy465571b2011-08-21 20:43:15 +00001194 Stretch-contrast image.
cristy3ed852e2009-09-05 21:47:34 +00001195 */
1196 status=MagickTrue;
1197 progress=0;
cristy46ff2672012-12-14 15:32:26 +00001198 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +00001199#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001200 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy5e6b2592012-12-19 14:08:11 +00001201 magick_threads(image,image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00001202#endif
cristybb503372010-05-27 20:51:26 +00001203 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001204 {
cristy4c08aed2011-07-01 19:47:50 +00001205 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001206 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001207
cristy8d4629b2010-08-30 17:59:46 +00001208 register ssize_t
1209 x;
1210
anthonya322a832013-04-27 06:28:03 +00001211 if( IfMagickFalse(status) )
cristy3ed852e2009-09-05 21:47:34 +00001212 continue;
1213 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001214 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001215 {
1216 status=MagickFalse;
1217 continue;
1218 }
cristybb503372010-05-27 20:51:26 +00001219 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001220 {
cristy465571b2011-08-21 20:43:15 +00001221 register ssize_t
1222 i;
1223
cristy883fde12013-04-08 00:50:13 +00001224 if (GetPixelReadMask(image,q) == 0)
cristy10a6c612012-01-29 21:41:05 +00001225 {
1226 q+=GetPixelChannels(image);
1227 continue;
1228 }
cristy465571b2011-08-21 20:43:15 +00001229 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1230 {
cristy5a23c552013-02-13 14:34:28 +00001231 PixelChannel channel=GetPixelChannelChannel(image,i);
1232 PixelTrait traits=GetPixelChannelTraits(image,channel);
cristyd09f8802012-02-04 16:44:10 +00001233 if (((traits & UpdatePixelTrait) == 0) || (black[i] == white[i]))
1234 continue;
1235 q[i]=ClampToQuantum(stretch_map[GetPixelChannels(image)*
1236 ScaleQuantumToMap(q[i])+i]);
cristy465571b2011-08-21 20:43:15 +00001237 }
cristyed231572011-07-14 02:18:59 +00001238 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001239 }
anthonya322a832013-04-27 06:28:03 +00001240 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
cristy3ed852e2009-09-05 21:47:34 +00001241 status=MagickFalse;
1242 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1243 {
1244 MagickBooleanType
1245 proceed;
1246
cristyb5d5f722009-11-04 03:03:49 +00001247#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00001248 #pragma omp critical (MagickCore_ContrastStretchImage)
cristy3ed852e2009-09-05 21:47:34 +00001249#endif
1250 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1251 image->rows);
anthonya322a832013-04-27 06:28:03 +00001252 if( IfMagickFalse(proceed) )
cristy3ed852e2009-09-05 21:47:34 +00001253 status=MagickFalse;
1254 }
1255 }
1256 image_view=DestroyCacheView(image_view);
cristyf45fec72011-08-23 16:02:32 +00001257 stretch_map=(double *) RelinquishMagickMemory(stretch_map);
1258 white=(double *) RelinquishMagickMemory(white);
1259 black=(double *) RelinquishMagickMemory(black);
cristy3ed852e2009-09-05 21:47:34 +00001260 return(status);
1261}
1262
1263/*
1264%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1265% %
1266% %
1267% %
1268% E n h a n c e I m a g e %
1269% %
1270% %
1271% %
1272%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1273%
1274% EnhanceImage() applies a digital filter that improves the quality of a
1275% noisy image.
1276%
1277% The format of the EnhanceImage method is:
1278%
1279% Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1280%
1281% A description of each parameter follows:
1282%
1283% o image: the image.
1284%
1285% o exception: return any errors or warnings in this structure.
1286%
1287*/
1288MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1289{
cristy6d8c3d72011-08-22 01:20:01 +00001290#define EnhancePixel(weight) \
cristya19f1d72012-08-07 18:24:38 +00001291 mean=((double) r[i]+GetPixelChannel(enhance_image,channel,q))/2.0; \
cristy76c670b2012-10-06 21:30:50 +00001292 distance=(double) r[i]-(double) GetPixelChannel(enhance_image,channel,q); \
1293 distance_squared=QuantumScale*(2.0*((double) QuantumRange+1.0)+mean)* \
1294 distance*distance; \
1295 if (distance_squared < ((double) QuantumRange*(double) QuantumRange/25.0f)) \
cristy3ed852e2009-09-05 21:47:34 +00001296 { \
cristy6d8c3d72011-08-22 01:20:01 +00001297 aggregate+=(weight)*r[i]; \
cristy3ed852e2009-09-05 21:47:34 +00001298 total_weight+=(weight); \
1299 } \
cristy6d8c3d72011-08-22 01:20:01 +00001300 r+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001301#define EnhanceImageTag "Enhance/Image"
1302
cristyc4c8d132010-01-07 01:58:38 +00001303 CacheView
1304 *enhance_view,
1305 *image_view;
1306
cristy3ed852e2009-09-05 21:47:34 +00001307 Image
1308 *enhance_image;
1309
cristy3ed852e2009-09-05 21:47:34 +00001310 MagickBooleanType
1311 status;
1312
cristybb503372010-05-27 20:51:26 +00001313 MagickOffsetType
1314 progress;
1315
cristybb503372010-05-27 20:51:26 +00001316 ssize_t
1317 y;
1318
cristy3ed852e2009-09-05 21:47:34 +00001319 /*
1320 Initialize enhanced image attributes.
1321 */
1322 assert(image != (const Image *) NULL);
1323 assert(image->signature == MagickSignature);
anthonya322a832013-04-27 06:28:03 +00001324 if( IfMagickTrue(image->debug) )
cristy3ed852e2009-09-05 21:47:34 +00001325 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1326 assert(exception != (ExceptionInfo *) NULL);
1327 assert(exception->signature == MagickSignature);
cristy3ed852e2009-09-05 21:47:34 +00001328 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1329 exception);
1330 if (enhance_image == (Image *) NULL)
1331 return((Image *) NULL);
anthonya322a832013-04-27 06:28:03 +00001332 if( IfMagickFalse(SetImageStorageClass(enhance_image,DirectClass,exception)) )
cristy3ed852e2009-09-05 21:47:34 +00001333 {
cristy3ed852e2009-09-05 21:47:34 +00001334 enhance_image=DestroyImage(enhance_image);
1335 return((Image *) NULL);
1336 }
1337 /*
1338 Enhance image.
1339 */
1340 status=MagickTrue;
1341 progress=0;
cristy46ff2672012-12-14 15:32:26 +00001342 image_view=AcquireVirtualCacheView(image,exception);
1343 enhance_view=AcquireAuthenticCacheView(enhance_image,exception);
cristyb5d5f722009-11-04 03:03:49 +00001344#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001345 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy5e6b2592012-12-19 14:08:11 +00001346 magick_threads(image,enhance_image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00001347#endif
cristybb503372010-05-27 20:51:26 +00001348 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001349 {
cristy4c08aed2011-07-01 19:47:50 +00001350 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001351 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001352
cristy4c08aed2011-07-01 19:47:50 +00001353 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001354 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001355
cristy8d4629b2010-08-30 17:59:46 +00001356 register ssize_t
1357 x;
1358
cristy6d8c3d72011-08-22 01:20:01 +00001359 ssize_t
1360 center;
1361
anthonya322a832013-04-27 06:28:03 +00001362 if( IfMagickFalse(status) )
cristy3ed852e2009-09-05 21:47:34 +00001363 continue;
1364 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1365 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1366 exception);
cristy4c08aed2011-07-01 19:47:50 +00001367 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001368 {
1369 status=MagickFalse;
1370 continue;
1371 }
cristyf45fec72011-08-23 16:02:32 +00001372 center=(ssize_t) GetPixelChannels(image)*(2*(image->columns+4)+2);
cristybb503372010-05-27 20:51:26 +00001373 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001374 {
cristy6d8c3d72011-08-22 01:20:01 +00001375 register ssize_t
1376 i;
cristy3ed852e2009-09-05 21:47:34 +00001377
cristy883fde12013-04-08 00:50:13 +00001378 if (GetPixelReadMask(image,p) == 0)
cristy10a6c612012-01-29 21:41:05 +00001379 {
1380 p+=GetPixelChannels(image);
1381 q+=GetPixelChannels(enhance_image);
1382 continue;
1383 }
cristy6d8c3d72011-08-22 01:20:01 +00001384 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1385 {
cristya19f1d72012-08-07 18:24:38 +00001386 double
cristy6d8c3d72011-08-22 01:20:01 +00001387 aggregate,
1388 distance,
1389 distance_squared,
1390 mean,
1391 total_weight;
cristy3ed852e2009-09-05 21:47:34 +00001392
cristy6d8c3d72011-08-22 01:20:01 +00001393 register const Quantum
1394 *restrict r;
1395
cristy5a23c552013-02-13 14:34:28 +00001396 PixelChannel channel=GetPixelChannelChannel(image,i);
1397 PixelTrait traits=GetPixelChannelTraits(image,channel);
1398 PixelTrait enhance_traits=GetPixelChannelTraits(enhance_image,channel);
cristy010d7d12011-08-31 01:02:48 +00001399 if ((traits == UndefinedPixelTrait) ||
1400 (enhance_traits == UndefinedPixelTrait))
cristy6d8c3d72011-08-22 01:20:01 +00001401 continue;
cristy0beccfa2011-09-25 20:47:53 +00001402 SetPixelChannel(enhance_image,channel,p[center+i],q);
cristy6d8c3d72011-08-22 01:20:01 +00001403 if ((enhance_traits & CopyPixelTrait) != 0)
1404 continue;
1405 /*
1406 Compute weighted average of target pixel color components.
1407 */
1408 aggregate=0.0;
1409 total_weight=0.0;
cristyf45fec72011-08-23 16:02:32 +00001410 r=p;
cristy6d8c3d72011-08-22 01:20:01 +00001411 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1412 EnhancePixel(8.0); EnhancePixel(5.0);
1413 r=p+1*GetPixelChannels(image)*(image->columns+4);
1414 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1415 EnhancePixel(20.0); EnhancePixel(8.0);
1416 r=p+2*GetPixelChannels(image)*(image->columns+4);
1417 EnhancePixel(10.0); EnhancePixel(40.0); EnhancePixel(80.0);
1418 EnhancePixel(40.0); EnhancePixel(10.0);
1419 r=p+3*GetPixelChannels(image)*(image->columns+4);
1420 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1421 EnhancePixel(20.0); EnhancePixel(8.0);
1422 r=p+4*GetPixelChannels(image)*(image->columns+4);
1423 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1424 EnhancePixel(8.0); EnhancePixel(5.0);
cristy0beccfa2011-09-25 20:47:53 +00001425 SetPixelChannel(enhance_image,channel,ClampToQuantum(aggregate/
1426 total_weight),q);
cristy6d8c3d72011-08-22 01:20:01 +00001427 }
cristyed231572011-07-14 02:18:59 +00001428 p+=GetPixelChannels(image);
1429 q+=GetPixelChannels(enhance_image);
cristy3ed852e2009-09-05 21:47:34 +00001430 }
anthonya322a832013-04-27 06:28:03 +00001431 if( IfMagickFalse(SyncCacheViewAuthenticPixels(enhance_view,exception)) )
cristy3ed852e2009-09-05 21:47:34 +00001432 status=MagickFalse;
1433 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1434 {
1435 MagickBooleanType
1436 proceed;
1437
cristyb5d5f722009-11-04 03:03:49 +00001438#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00001439 #pragma omp critical (MagickCore_EnhanceImage)
cristy3ed852e2009-09-05 21:47:34 +00001440#endif
1441 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
anthonya322a832013-04-27 06:28:03 +00001442 if( IfMagickFalse(proceed) )
cristy3ed852e2009-09-05 21:47:34 +00001443 status=MagickFalse;
1444 }
1445 }
1446 enhance_view=DestroyCacheView(enhance_view);
1447 image_view=DestroyCacheView(image_view);
anthonya322a832013-04-27 06:28:03 +00001448 if( IfMagickFalse(status) )
cristy1c2f48d2012-12-14 01:20:55 +00001449 enhance_image=DestroyImage(enhance_image);
cristy3ed852e2009-09-05 21:47:34 +00001450 return(enhance_image);
1451}
1452
1453/*
1454%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1455% %
1456% %
1457% %
1458% E q u a l i z e I m a g e %
1459% %
1460% %
1461% %
1462%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1463%
1464% EqualizeImage() applies a histogram equalization to the image.
1465%
1466% The format of the EqualizeImage method is:
1467%
cristy6d8c3d72011-08-22 01:20:01 +00001468% MagickBooleanType EqualizeImage(Image *image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001469%
1470% A description of each parameter follows:
1471%
1472% o image: the image.
1473%
cristy6d8c3d72011-08-22 01:20:01 +00001474% o exception: return any errors or warnings in this structure.
cristy3ed852e2009-09-05 21:47:34 +00001475%
1476*/
cristy6d8c3d72011-08-22 01:20:01 +00001477MagickExport MagickBooleanType EqualizeImage(Image *image,
1478 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001479{
cristy3ed852e2009-09-05 21:47:34 +00001480#define EqualizeImageTag "Equalize/Image"
1481
cristyc4c8d132010-01-07 01:58:38 +00001482 CacheView
1483 *image_view;
1484
cristy3ed852e2009-09-05 21:47:34 +00001485 MagickBooleanType
1486 status;
1487
cristybb503372010-05-27 20:51:26 +00001488 MagickOffsetType
1489 progress;
1490
cristya19f1d72012-08-07 18:24:38 +00001491 double
cristy5f95f4f2011-10-23 01:01:01 +00001492 black[CompositePixelChannel],
cristy3ed852e2009-09-05 21:47:34 +00001493 *equalize_map,
1494 *histogram,
cristy3ed852e2009-09-05 21:47:34 +00001495 *map,
cristy5f95f4f2011-10-23 01:01:01 +00001496 white[CompositePixelChannel];
cristy3ed852e2009-09-05 21:47:34 +00001497
cristybb503372010-05-27 20:51:26 +00001498 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001499 i;
1500
cristy564a5692012-01-20 23:56:26 +00001501 size_t
1502 number_channels;
1503
cristybb503372010-05-27 20:51:26 +00001504 ssize_t
1505 y;
1506
cristy3ed852e2009-09-05 21:47:34 +00001507 /*
1508 Allocate and initialize histogram arrays.
1509 */
1510 assert(image != (Image *) NULL);
1511 assert(image->signature == MagickSignature);
anthonya322a832013-04-27 06:28:03 +00001512 if( IfMagickTrue(image->debug) )
cristy3ed852e2009-09-05 21:47:34 +00001513 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristya19f1d72012-08-07 18:24:38 +00001514 equalize_map=(double *) AcquireQuantumMemory(MaxMap+1UL,
cristyf45fec72011-08-23 16:02:32 +00001515 GetPixelChannels(image)*sizeof(*equalize_map));
cristy7d4aa382013-06-30 01:59:39 +00001516 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,GetPixelChannels(image)*
1517 sizeof(*histogram));
1518 map=(double *) AcquireQuantumMemory(MaxMap+1UL,GetPixelChannels(image)*
1519 sizeof(*map));
cristy955c1772012-12-21 17:25:55 +00001520 if ((equalize_map == (double *) NULL) || (histogram == (double *) NULL) ||
cristya19f1d72012-08-07 18:24:38 +00001521 (map == (double *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001522 {
cristya19f1d72012-08-07 18:24:38 +00001523 if (map != (double *) NULL)
1524 map=(double *) RelinquishMagickMemory(map);
1525 if (histogram != (double *) NULL)
1526 histogram=(double *) RelinquishMagickMemory(histogram);
1527 if (equalize_map != (double *) NULL)
1528 equalize_map=(double *) RelinquishMagickMemory(equalize_map);
cristy3ed852e2009-09-05 21:47:34 +00001529 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1530 image->filename);
1531 }
1532 /*
1533 Form histogram.
1534 */
cristyf45fec72011-08-23 16:02:32 +00001535 status=MagickTrue;
1536 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1537 sizeof(*histogram));
cristy46ff2672012-12-14 15:32:26 +00001538 image_view=AcquireVirtualCacheView(image,exception);
cristybb503372010-05-27 20:51:26 +00001539 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001540 {
cristy4c08aed2011-07-01 19:47:50 +00001541 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001542 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001543
cristybb503372010-05-27 20:51:26 +00001544 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001545 x;
1546
anthonya322a832013-04-27 06:28:03 +00001547 if( IfMagickFalse(status) )
cristyf45fec72011-08-23 16:02:32 +00001548 continue;
1549 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001550 if (p == (const Quantum *) NULL)
cristyf45fec72011-08-23 16:02:32 +00001551 {
1552 status=MagickFalse;
1553 continue;
1554 }
cristybb503372010-05-27 20:51:26 +00001555 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001556 {
cristyf45fec72011-08-23 16:02:32 +00001557 register ssize_t
1558 i;
1559
1560 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1561 histogram[GetPixelChannels(image)*ScaleQuantumToMap(p[i])+i]++;
cristyed231572011-07-14 02:18:59 +00001562 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001563 }
1564 }
cristydb070952012-04-20 14:33:00 +00001565 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00001566 /*
1567 Integrate the histogram to get the equalization map.
1568 */
cristy564a5692012-01-20 23:56:26 +00001569 number_channels=GetPixelChannels(image);
cristyc94ba6f2012-01-29 23:19:58 +00001570 for (i=0; i < (ssize_t) number_channels; i++)
cristy3ed852e2009-09-05 21:47:34 +00001571 {
cristya19f1d72012-08-07 18:24:38 +00001572 double
cristyf45fec72011-08-23 16:02:32 +00001573 intensity;
1574
1575 register ssize_t
1576 j;
1577
1578 intensity=0.0;
1579 for (j=0; j <= (ssize_t) MaxMap; j++)
1580 {
1581 intensity+=histogram[GetPixelChannels(image)*j+i];
1582 map[GetPixelChannels(image)*j+i]=intensity;
1583 }
cristy3ed852e2009-09-05 21:47:34 +00001584 }
cristyf45fec72011-08-23 16:02:32 +00001585 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*GetPixelChannels(image)*
1586 sizeof(*equalize_map));
cristy564a5692012-01-20 23:56:26 +00001587 number_channels=GetPixelChannels(image);
cristy564a5692012-01-20 23:56:26 +00001588 for (i=0; i < (ssize_t) number_channels; i++)
cristyf45fec72011-08-23 16:02:32 +00001589 {
1590 register ssize_t
1591 j;
1592
1593 black[i]=map[i];
1594 white[i]=map[GetPixelChannels(image)*MaxMap+i];
1595 if (black[i] != white[i])
1596 for (j=0; j <= (ssize_t) MaxMap; j++)
cristya19f1d72012-08-07 18:24:38 +00001597 equalize_map[GetPixelChannels(image)*j+i]=(double)
1598 ScaleMapToQuantum((double) ((MaxMap*(map[
cristyf45fec72011-08-23 16:02:32 +00001599 GetPixelChannels(image)*j+i]-black[i]))/(white[i]-black[i])));
1600 }
cristya19f1d72012-08-07 18:24:38 +00001601 histogram=(double *) RelinquishMagickMemory(histogram);
1602 map=(double *) RelinquishMagickMemory(map);
cristy3ed852e2009-09-05 21:47:34 +00001603 if (image->storage_class == PseudoClass)
1604 {
cristyf45fec72011-08-23 16:02:32 +00001605 register ssize_t
1606 j;
1607
cristy3ed852e2009-09-05 21:47:34 +00001608 /*
1609 Equalize colormap.
1610 */
cristyf45fec72011-08-23 16:02:32 +00001611 for (j=0; j < (ssize_t) image->colors; j++)
cristy3ed852e2009-09-05 21:47:34 +00001612 {
cristyf54798b2011-11-21 18:38:23 +00001613 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00001614 {
cristy5a23c552013-02-13 14:34:28 +00001615 PixelChannel channel=GetPixelChannelChannel(image,RedPixelChannel);
cristyf54798b2011-11-21 18:38:23 +00001616 if (black[channel] != white[channel])
cristyda1f9c12011-10-02 21:39:49 +00001617 image->colormap[j].red=equalize_map[GetPixelChannels(image)*
cristyf54798b2011-11-21 18:38:23 +00001618 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))]+
1619 channel;
cristyf45fec72011-08-23 16:02:32 +00001620 }
cristyf54798b2011-11-21 18:38:23 +00001621 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00001622 {
cristy5a23c552013-02-13 14:34:28 +00001623 PixelChannel channel=GetPixelChannelChannel(image,
1624 GreenPixelChannel);
cristyf54798b2011-11-21 18:38:23 +00001625 if (black[channel] != white[channel])
cristyda1f9c12011-10-02 21:39:49 +00001626 image->colormap[j].green=equalize_map[GetPixelChannels(image)*
cristyf54798b2011-11-21 18:38:23 +00001627 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))]+
1628 channel;
cristyf45fec72011-08-23 16:02:32 +00001629 }
cristyf54798b2011-11-21 18:38:23 +00001630 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00001631 {
cristy5a23c552013-02-13 14:34:28 +00001632 PixelChannel channel=GetPixelChannelChannel(image,BluePixelChannel);
cristyf54798b2011-11-21 18:38:23 +00001633 if (black[channel] != white[channel])
cristyda1f9c12011-10-02 21:39:49 +00001634 image->colormap[j].blue=equalize_map[GetPixelChannels(image)*
cristyf54798b2011-11-21 18:38:23 +00001635 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))]+
1636 channel;
cristyf45fec72011-08-23 16:02:32 +00001637 }
cristyf54798b2011-11-21 18:38:23 +00001638 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00001639 {
cristy5a23c552013-02-13 14:34:28 +00001640 PixelChannel channel=GetPixelChannelChannel(image,
1641 AlphaPixelChannel);
cristyf54798b2011-11-21 18:38:23 +00001642 if (black[channel] != white[channel])
cristyda1f9c12011-10-02 21:39:49 +00001643 image->colormap[j].alpha=equalize_map[GetPixelChannels(image)*
cristyf54798b2011-11-21 18:38:23 +00001644 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))]+
1645 channel;
cristyf45fec72011-08-23 16:02:32 +00001646 }
cristy3ed852e2009-09-05 21:47:34 +00001647 }
1648 }
1649 /*
1650 Equalize image.
1651 */
cristy3ed852e2009-09-05 21:47:34 +00001652 progress=0;
cristy46ff2672012-12-14 15:32:26 +00001653 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +00001654#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001655 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy5e6b2592012-12-19 14:08:11 +00001656 magick_threads(image,image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00001657#endif
cristybb503372010-05-27 20:51:26 +00001658 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001659 {
cristy4c08aed2011-07-01 19:47:50 +00001660 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001661 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001662
cristy8d4629b2010-08-30 17:59:46 +00001663 register ssize_t
1664 x;
1665
anthonya322a832013-04-27 06:28:03 +00001666 if( IfMagickFalse(status) )
cristy3ed852e2009-09-05 21:47:34 +00001667 continue;
1668 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001669 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001670 {
1671 status=MagickFalse;
1672 continue;
1673 }
cristybb503372010-05-27 20:51:26 +00001674 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001675 {
cristyf45fec72011-08-23 16:02:32 +00001676 register ssize_t
1677 i;
1678
cristy883fde12013-04-08 00:50:13 +00001679 if (GetPixelReadMask(image,q) == 0)
cristy10a6c612012-01-29 21:41:05 +00001680 {
1681 q+=GetPixelChannels(image);
1682 continue;
1683 }
cristyf45fec72011-08-23 16:02:32 +00001684 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1685 {
cristy5a23c552013-02-13 14:34:28 +00001686 PixelChannel channel=GetPixelChannelChannel(image,i);
1687 PixelTrait traits=GetPixelChannelTraits(image,channel);
cristyd09f8802012-02-04 16:44:10 +00001688 if (((traits & UpdatePixelTrait) == 0) || (black[i] == white[i]))
1689 continue;
1690 q[i]=ClampToQuantum(equalize_map[GetPixelChannels(image)*
1691 ScaleQuantumToMap(q[i])+i]);
cristyf45fec72011-08-23 16:02:32 +00001692 }
cristyed231572011-07-14 02:18:59 +00001693 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001694 }
anthonya322a832013-04-27 06:28:03 +00001695 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
cristy3ed852e2009-09-05 21:47:34 +00001696 status=MagickFalse;
1697 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1698 {
1699 MagickBooleanType
1700 proceed;
1701
cristyb5d5f722009-11-04 03:03:49 +00001702#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00001703 #pragma omp critical (MagickCore_EqualizeImage)
cristy3ed852e2009-09-05 21:47:34 +00001704#endif
1705 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
anthonya322a832013-04-27 06:28:03 +00001706 if( IfMagickFalse(proceed) )
cristy3ed852e2009-09-05 21:47:34 +00001707 status=MagickFalse;
1708 }
1709 }
1710 image_view=DestroyCacheView(image_view);
cristya19f1d72012-08-07 18:24:38 +00001711 equalize_map=(double *) RelinquishMagickMemory(equalize_map);
cristy3ed852e2009-09-05 21:47:34 +00001712 return(status);
1713}
1714
1715/*
1716%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1717% %
1718% %
1719% %
1720% G a m m a I m a g e %
1721% %
1722% %
1723% %
1724%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1725%
1726% GammaImage() gamma-corrects a particular image channel. The same
1727% image viewed on different devices will have perceptual differences in the
1728% way the image's intensities are represented on the screen. Specify
1729% individual gamma levels for the red, green, and blue channels, or adjust
1730% all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1731%
1732% You can also reduce the influence of a particular channel with a gamma
1733% value of 0.
1734%
1735% The format of the GammaImage method is:
1736%
cristyb3e7c6c2011-07-24 01:43:55 +00001737% MagickBooleanType GammaImage(Image *image,const double gamma,
1738% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001739%
1740% A description of each parameter follows:
1741%
1742% o image: the image.
1743%
cristya6360142011-03-23 23:08:04 +00001744% o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
1745%
cristy3ed852e2009-09-05 21:47:34 +00001746% o gamma: the image gamma.
1747%
1748*/
cristy4618c6b2013-04-25 12:42:33 +00001749
1750static inline double gamma_pow(const double value,const double gamma)
1751{
cristy38e97382013-05-05 19:04:17 +00001752 return(value < 0.0 ? value : pow(value,gamma));
cristy4618c6b2013-04-25 12:42:33 +00001753}
1754
cristyb3e7c6c2011-07-24 01:43:55 +00001755MagickExport MagickBooleanType GammaImage(Image *image,const double gamma,
1756 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001757{
1758#define GammaCorrectImageTag "GammaCorrect/Image"
1759
cristyc4c8d132010-01-07 01:58:38 +00001760 CacheView
1761 *image_view;
1762
cristy3ed852e2009-09-05 21:47:34 +00001763 MagickBooleanType
1764 status;
1765
cristybb503372010-05-27 20:51:26 +00001766 MagickOffsetType
1767 progress;
1768
cristy19593872012-01-22 02:00:33 +00001769 Quantum
1770 *gamma_map;
1771
cristybb503372010-05-27 20:51:26 +00001772 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001773 i;
1774
cristybb503372010-05-27 20:51:26 +00001775 ssize_t
1776 y;
1777
cristy3ed852e2009-09-05 21:47:34 +00001778 /*
1779 Allocate and initialize gamma maps.
1780 */
1781 assert(image != (Image *) NULL);
1782 assert(image->signature == MagickSignature);
anthonya322a832013-04-27 06:28:03 +00001783 if( IfMagickTrue(image->debug) )
cristy3ed852e2009-09-05 21:47:34 +00001784 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1785 if (gamma == 1.0)
1786 return(MagickTrue);
cristy19593872012-01-22 02:00:33 +00001787 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
1788 if (gamma_map == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001789 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1790 image->filename);
1791 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
1792 if (gamma != 0.0)
cristybb503372010-05-27 20:51:26 +00001793 for (i=0; i <= (ssize_t) MaxMap; i++)
cristya19f1d72012-08-07 18:24:38 +00001794 gamma_map[i]=ScaleMapToQuantum((double) (MaxMap*pow((double) i/
cristy19593872012-01-22 02:00:33 +00001795 MaxMap,1.0/gamma)));
cristy3ed852e2009-09-05 21:47:34 +00001796 if (image->storage_class == PseudoClass)
nicolase0aa3c32012-08-08 14:57:02 +00001797 for (i=0; i < (ssize_t) image->colors; i++)
nicolasfd74ae32012-08-08 16:00:11 +00001798 {
1799 /*
cristyaeded782012-09-11 23:39:36 +00001800 Gamma-correct colormap.
nicolasfd74ae32012-08-08 16:00:11 +00001801 */
cristy4618c6b2013-04-25 12:42:33 +00001802#if !defined(MAGICKCORE_HDRI_SUPPORT)
nicolasfd74ae32012-08-08 16:00:11 +00001803 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyfaa663d2013-04-29 00:07:58 +00001804 image->colormap[i].red=(double) gamma_map[ScaleQuantumToMap(
1805 ClampToQuantum(image->colormap[i].red))];
nicolasfd74ae32012-08-08 16:00:11 +00001806 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyfaa663d2013-04-29 00:07:58 +00001807 image->colormap[i].green=(double) gamma_map[ScaleQuantumToMap(
1808 ClampToQuantum(image->colormap[i].green))];
nicolasfd74ae32012-08-08 16:00:11 +00001809 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyfaa663d2013-04-29 00:07:58 +00001810 image->colormap[i].blue=(double) gamma_map[ScaleQuantumToMap(
1811 ClampToQuantum(image->colormap[i].blue))];
nicolasfd74ae32012-08-08 16:00:11 +00001812 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristyfaa663d2013-04-29 00:07:58 +00001813 image->colormap[i].alpha=(double) gamma_map[ScaleQuantumToMap(
1814 ClampToQuantum(image->colormap[i].alpha))];
cristy4618c6b2013-04-25 12:42:33 +00001815#else
1816 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy38e97382013-05-05 19:04:17 +00001817 image->colormap[i].red=QuantumRange*gamma_pow(QuantumScale*
1818 image->colormap[i].red,1.0/gamma);
cristy4618c6b2013-04-25 12:42:33 +00001819 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy38e97382013-05-05 19:04:17 +00001820 image->colormap[i].green=QuantumRange*gamma_pow(QuantumScale*
1821 image->colormap[i].green,1.0/gamma);
cristy4618c6b2013-04-25 12:42:33 +00001822 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy38e97382013-05-05 19:04:17 +00001823 image->colormap[i].blue=QuantumRange*gamma_pow(QuantumScale*
1824 image->colormap[i].blue,1.0/gamma);
cristy4618c6b2013-04-25 12:42:33 +00001825 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy38e97382013-05-05 19:04:17 +00001826 image->colormap[i].alpha=QuantumRange*gamma_pow(QuantumScale*
1827 image->colormap[i].alpha,1.0/gamma);
cristy4618c6b2013-04-25 12:42:33 +00001828#endif
nicolasfd74ae32012-08-08 16:00:11 +00001829 }
cristy3ed852e2009-09-05 21:47:34 +00001830 /*
1831 Gamma-correct image.
1832 */
1833 status=MagickTrue;
1834 progress=0;
cristy46ff2672012-12-14 15:32:26 +00001835 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +00001836#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001837 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy5e6b2592012-12-19 14:08:11 +00001838 magick_threads(image,image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00001839#endif
cristybb503372010-05-27 20:51:26 +00001840 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001841 {
cristy4c08aed2011-07-01 19:47:50 +00001842 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001843 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001844
cristy8d4629b2010-08-30 17:59:46 +00001845 register ssize_t
1846 x;
1847
anthonya322a832013-04-27 06:28:03 +00001848 if( IfMagickFalse(status) )
cristy3ed852e2009-09-05 21:47:34 +00001849 continue;
1850 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001851 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001852 {
1853 status=MagickFalse;
1854 continue;
1855 }
cristybb503372010-05-27 20:51:26 +00001856 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001857 {
cristyd476a8e2011-07-23 16:13:22 +00001858 register ssize_t
1859 i;
1860
cristy883fde12013-04-08 00:50:13 +00001861 if (GetPixelReadMask(image,q) == 0)
cristy10a6c612012-01-29 21:41:05 +00001862 {
1863 q+=GetPixelChannels(image);
1864 continue;
1865 }
cristya30d9ba2011-07-23 21:00:48 +00001866 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristyd476a8e2011-07-23 16:13:22 +00001867 {
cristy5a23c552013-02-13 14:34:28 +00001868 PixelChannel channel=GetPixelChannelChannel(image,i);
1869 PixelTrait traits=GetPixelChannelTraits(image,channel);
cristyd09f8802012-02-04 16:44:10 +00001870 if ((traits & UpdatePixelTrait) == 0)
1871 continue;
cristy4618c6b2013-04-25 12:42:33 +00001872#if !defined(MAGICKCORE_HDRI_SUPPORT)
cristyd09f8802012-02-04 16:44:10 +00001873 q[i]=gamma_map[ScaleQuantumToMap(q[i])];
cristy4618c6b2013-04-25 12:42:33 +00001874#else
cristy38e97382013-05-05 19:04:17 +00001875 q[i]=QuantumRange*gamma_pow(QuantumScale*q[i],1.0/gamma);
cristy4618c6b2013-04-25 12:42:33 +00001876#endif
cristyd476a8e2011-07-23 16:13:22 +00001877 }
cristya30d9ba2011-07-23 21:00:48 +00001878 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001879 }
anthonya322a832013-04-27 06:28:03 +00001880 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
cristy3ed852e2009-09-05 21:47:34 +00001881 status=MagickFalse;
1882 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1883 {
1884 MagickBooleanType
1885 proceed;
1886
cristyb5d5f722009-11-04 03:03:49 +00001887#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00001888 #pragma omp critical (MagickCore_GammaImage)
cristy3ed852e2009-09-05 21:47:34 +00001889#endif
1890 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
1891 image->rows);
anthonya322a832013-04-27 06:28:03 +00001892 if( IfMagickFalse(proceed) )
cristy3ed852e2009-09-05 21:47:34 +00001893 status=MagickFalse;
1894 }
1895 }
1896 image_view=DestroyCacheView(image_view);
cristy19593872012-01-22 02:00:33 +00001897 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
cristy3ed852e2009-09-05 21:47:34 +00001898 if (image->gamma != 0.0)
1899 image->gamma*=gamma;
1900 return(status);
1901}
1902
1903/*
1904%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1905% %
1906% %
1907% %
cristyab943592013-03-29 16:47:23 +00001908% G r a y s c a l e I m a g e %
1909% %
1910% %
1911% %
1912%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1913%
1914% GrayscaleImage() converts the image to grayscale.
1915%
1916% The format of the GrayscaleImage method is:
1917%
1918% MagickBooleanType GrayscaleImage(Image *image,
1919% const PixelIntensityMethod method ,ExceptionInfo *exception)
1920%
1921% A description of each parameter follows:
1922%
1923% o image: the image.
1924%
1925% o method: the pixel intensity method.
1926%
1927% o exception: return any errors or warnings in this structure.
1928%
1929*/
1930
1931static inline MagickRealType MagickMax(const MagickRealType x,
1932 const MagickRealType y)
1933{
1934 if (x > y)
1935 return(x);
1936 return(y);
1937}
1938
1939static inline MagickRealType MagickMin(const MagickRealType x,
1940 const MagickRealType y)
1941{
1942 if (x < y)
1943 return(x);
1944 return(y);
1945}
1946
1947MagickExport MagickBooleanType GrayscaleImage(Image *image,
cristy95bbc4f2013-04-25 19:02:42 +00001948 const PixelIntensityMethod method,ExceptionInfo *exception)
cristyab943592013-03-29 16:47:23 +00001949{
1950#define GrayscaleImageTag "Grayscale/Image"
1951
1952 CacheView
1953 *image_view;
1954
1955 MagickBooleanType
1956 status;
1957
1958 MagickOffsetType
1959 progress;
1960
1961 ssize_t
1962 y;
1963
1964 assert(image != (Image *) NULL);
1965 assert(image->signature == MagickSignature);
anthonya322a832013-04-27 06:28:03 +00001966 if( IfMagickTrue(image->debug) )
cristyab943592013-03-29 16:47:23 +00001967 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1968 if (image->storage_class == PseudoClass)
1969 {
anthonya322a832013-04-27 06:28:03 +00001970 if( IfMagickFalse(SyncImage(image,exception)) )
cristyab943592013-03-29 16:47:23 +00001971 return(MagickFalse);
anthonya322a832013-04-27 06:28:03 +00001972 if( IfMagickFalse(SetImageStorageClass(image,DirectClass,exception)) )
cristyab943592013-03-29 16:47:23 +00001973 return(MagickFalse);
1974 }
cristyf96cb212013-04-10 22:28:48 +00001975 switch (image->intensity)
1976 {
1977 case Rec601LuminancePixelIntensityMethod:
1978 case Rec709LuminancePixelIntensityMethod:
1979 {
cristy0c81d062013-04-21 15:22:02 +00001980 (void) SetImageColorspace(image,RGBColorspace,exception);
cristyf96cb212013-04-10 22:28:48 +00001981 break;
1982 }
1983 case Rec601LumaPixelIntensityMethod:
1984 case Rec709LumaPixelIntensityMethod:
1985 case UndefinedPixelIntensityMethod:
1986 {
cristy0c81d062013-04-21 15:22:02 +00001987 (void) SetImageColorspace(image,sRGBColorspace,exception);
cristyf96cb212013-04-10 22:28:48 +00001988 break;
1989 }
1990 default:
1991 break;
1992 }
cristyab943592013-03-29 16:47:23 +00001993 /*
1994 Grayscale image.
1995 */
1996 status=MagickTrue;
1997 progress=0;
1998 image_view=AcquireAuthenticCacheView(image,exception);
1999#if defined(MAGICKCORE_OPENMP_SUPPORT)
2000 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2001 magick_threads(image,image,image->rows,1)
2002#endif
2003 for (y=0; y < (ssize_t) image->rows; y++)
2004 {
2005 register Quantum
2006 *restrict q;
2007
2008 register ssize_t
2009 x;
2010
anthonya322a832013-04-27 06:28:03 +00002011 if( IfMagickFalse(status) )
cristyab943592013-03-29 16:47:23 +00002012 continue;
2013 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
2014 if (q == (Quantum *) NULL)
2015 {
2016 status=MagickFalse;
2017 continue;
2018 }
2019 for (x=0; x < (ssize_t) image->columns; x++)
2020 {
2021 MagickRealType
2022 blue,
2023 green,
2024 red,
2025 intensity;
2026
cristy883fde12013-04-08 00:50:13 +00002027 if (GetPixelReadMask(image,q) == 0)
cristyab943592013-03-29 16:47:23 +00002028 {
2029 q+=GetPixelChannels(image);
2030 continue;
2031 }
2032 red=(MagickRealType) GetPixelRed(image,q);
2033 green=(MagickRealType) GetPixelGreen(image,q);
2034 blue=(MagickRealType) GetPixelBlue(image,q);
cristyf96cb212013-04-10 22:28:48 +00002035 intensity=0.0;
cristy95bbc4f2013-04-25 19:02:42 +00002036 switch (method)
cristyab943592013-03-29 16:47:23 +00002037 {
2038 case AveragePixelIntensityMethod:
2039 {
2040 intensity=(red+green+blue)/3.0;
2041 break;
2042 }
2043 case BrightnessPixelIntensityMethod:
2044 {
2045 intensity=MagickMax(MagickMax(red,green),blue);
2046 break;
2047 }
2048 case LightnessPixelIntensityMethod:
2049 {
cristy06d9c902013-06-02 17:55:06 +00002050 intensity=(MagickMin(MagickMin(red,green),blue)+
2051 MagickMax(MagickMax(red,green),blue))/2.0;
cristyab943592013-03-29 16:47:23 +00002052 break;
2053 }
cristyb04db122013-04-11 10:26:01 +00002054 case MSPixelIntensityMethod:
2055 {
2056 intensity=(MagickRealType) (((double) red*red+green*green+
2057 blue*blue)/3.0);
2058 break;
2059 }
cristyab943592013-03-29 16:47:23 +00002060 case Rec601LumaPixelIntensityMethod:
2061 {
cristy06d9c902013-06-02 17:55:06 +00002062 if (image->colorspace == RGBColorspace)
2063 {
2064 red=EncodePixelGamma(red);
2065 green=EncodePixelGamma(green);
2066 blue=EncodePixelGamma(blue);
2067 }
cristy9e2436a2013-05-02 20:35:59 +00002068 intensity=0.298839*red+0.586811*green+0.114350*blue;
cristyab943592013-03-29 16:47:23 +00002069 break;
2070 }
2071 case Rec601LuminancePixelIntensityMethod:
2072 {
cristy06d9c902013-06-02 17:55:06 +00002073 if (image->colorspace == sRGBColorspace)
2074 {
2075 red=DecodePixelGamma(red);
2076 green=DecodePixelGamma(green);
2077 blue=DecodePixelGamma(blue);
2078 }
cristy9e2436a2013-05-02 20:35:59 +00002079 intensity=0.298839*red+0.586811*green+0.114350*blue;
cristyab943592013-03-29 16:47:23 +00002080 break;
2081 }
2082 case Rec709LumaPixelIntensityMethod:
cristy06d9c902013-06-02 17:55:06 +00002083 default:
cristyab943592013-03-29 16:47:23 +00002084 {
cristy06d9c902013-06-02 17:55:06 +00002085 if (image->colorspace == RGBColorspace)
2086 {
2087 red=EncodePixelGamma(red);
2088 green=EncodePixelGamma(green);
2089 blue=EncodePixelGamma(blue);
2090 }
cristy1352acf2013-06-10 17:16:17 +00002091 intensity=0.212656*red+0.715158*green+0.072186*blue;
cristyab943592013-03-29 16:47:23 +00002092 break;
2093 }
2094 case Rec709LuminancePixelIntensityMethod:
2095 {
cristy06d9c902013-06-02 17:55:06 +00002096 if (image->colorspace == sRGBColorspace)
2097 {
2098 red=DecodePixelGamma(red);
2099 green=DecodePixelGamma(green);
2100 blue=DecodePixelGamma(blue);
2101 }
cristy1352acf2013-06-10 17:16:17 +00002102 intensity=0.212656*red+0.715158*green+0.072186*blue;
cristyab943592013-03-29 16:47:23 +00002103 break;
2104 }
2105 case RMSPixelIntensityMethod:
2106 {
cristy5afee3a2013-04-11 10:18:42 +00002107 intensity=(MagickRealType) (sqrt((double) red*red+green*green+
cristy84d50c62013-04-11 10:26:35 +00002108 blue*blue)/sqrt(3.0));
cristyab943592013-03-29 16:47:23 +00002109 break;
2110 }
2111 }
2112 SetPixelGray(image,ClampToQuantum(intensity),q);
2113 q+=GetPixelChannels(image);
2114 }
cristy06d9c902013-06-02 17:55:06 +00002115 if (IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)))
cristyab943592013-03-29 16:47:23 +00002116 status=MagickFalse;
2117 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2118 {
2119 MagickBooleanType
2120 proceed;
2121
2122#if defined(MAGICKCORE_OPENMP_SUPPORT)
2123 #pragma omp critical (MagickCore_GrayscaleImage)
2124#endif
2125 proceed=SetImageProgress(image,GrayscaleImageTag,progress++,
2126 image->rows);
anthonya322a832013-04-27 06:28:03 +00002127 if( IfMagickFalse(proceed) )
cristyab943592013-03-29 16:47:23 +00002128 status=MagickFalse;
2129 }
2130 }
2131 image_view=DestroyCacheView(image_view);
cristy95bbc4f2013-04-25 19:02:42 +00002132 image->intensity=method;
cristyf96cb212013-04-10 22:28:48 +00002133 image->type=GrayscaleType;
cristy95bbc4f2013-04-25 19:02:42 +00002134 return(SetImageColorspace(image,GRAYColorspace,exception));
cristyab943592013-03-29 16:47:23 +00002135}
2136
2137/*
2138%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2139% %
2140% %
2141% %
cristy3ed852e2009-09-05 21:47:34 +00002142% H a l d C l u t I m a g e %
2143% %
2144% %
2145% %
2146%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2147%
2148% HaldClutImage() applies a Hald color lookup table to the image. A Hald
2149% color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
2150% Create it with the HALD coder. You can apply any color transformation to
2151% the Hald image and then use this method to apply the transform to the
2152% image.
2153%
2154% The format of the HaldClutImage method is:
2155%
cristy7c0a0a42011-08-23 17:57:25 +00002156% MagickBooleanType HaldClutImage(Image *image,Image *hald_image,
2157% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002158%
2159% A description of each parameter follows:
2160%
2161% o image: the image, which is replaced by indexed CLUT values
2162%
2163% o hald_image: the color lookup table image for replacement color values.
2164%
cristy7c0a0a42011-08-23 17:57:25 +00002165% o exception: return any errors or warnings in this structure.
2166%
cristy3ed852e2009-09-05 21:47:34 +00002167*/
cristy3ed852e2009-09-05 21:47:34 +00002168MagickExport MagickBooleanType HaldClutImage(Image *image,
cristy7c0a0a42011-08-23 17:57:25 +00002169 const Image *hald_image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002170{
cristy3ed852e2009-09-05 21:47:34 +00002171#define HaldClutImageTag "Clut/Image"
2172
2173 typedef struct _HaldInfo
2174 {
cristya19f1d72012-08-07 18:24:38 +00002175 double
cristy3ed852e2009-09-05 21:47:34 +00002176 x,
2177 y,
2178 z;
2179 } HaldInfo;
2180
cristyfa112112010-01-04 17:48:07 +00002181 CacheView
cristyd551fbc2011-03-31 18:07:46 +00002182 *hald_view,
cristyfa112112010-01-04 17:48:07 +00002183 *image_view;
2184
cristy3ed852e2009-09-05 21:47:34 +00002185 double
2186 width;
2187
cristy3ed852e2009-09-05 21:47:34 +00002188 MagickBooleanType
2189 status;
2190
cristybb503372010-05-27 20:51:26 +00002191 MagickOffsetType
2192 progress;
2193
cristy4c08aed2011-07-01 19:47:50 +00002194 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002195 zero;
2196
cristy3ed852e2009-09-05 21:47:34 +00002197 size_t
2198 cube_size,
2199 length,
2200 level;
2201
cristybb503372010-05-27 20:51:26 +00002202 ssize_t
2203 y;
2204
cristy3ed852e2009-09-05 21:47:34 +00002205 assert(image != (Image *) NULL);
2206 assert(image->signature == MagickSignature);
anthonya322a832013-04-27 06:28:03 +00002207 if( IfMagickTrue(image->debug) )
cristy3ed852e2009-09-05 21:47:34 +00002208 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2209 assert(hald_image != (Image *) NULL);
2210 assert(hald_image->signature == MagickSignature);
anthonya322a832013-04-27 06:28:03 +00002211 if( IfMagickFalse(SetImageStorageClass(image,DirectClass,exception)) )
cristy3ed852e2009-09-05 21:47:34 +00002212 return(MagickFalse);
cristy8a46d822012-08-28 23:32:39 +00002213 if (image->alpha_trait != BlendPixelTrait)
cristy63240882011-08-05 19:05:27 +00002214 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
cristy3ed852e2009-09-05 21:47:34 +00002215 /*
2216 Hald clut image.
2217 */
2218 status=MagickTrue;
2219 progress=0;
cristyab943592013-03-29 16:47:23 +00002220 length=(size_t) MagickMin((MagickRealType) hald_image->columns,
2221 (MagickRealType) hald_image->rows);
cristy3ed852e2009-09-05 21:47:34 +00002222 for (level=2; (level*level*level) < length; level++) ;
2223 level*=level;
2224 cube_size=level*level;
2225 width=(double) hald_image->columns;
cristy4c08aed2011-07-01 19:47:50 +00002226 GetPixelInfo(hald_image,&zero);
cristy46ff2672012-12-14 15:32:26 +00002227 hald_view=AcquireVirtualCacheView(hald_image,exception);
2228 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +00002229#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00002230 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy5e6b2592012-12-19 14:08:11 +00002231 magick_threads(image,image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00002232#endif
cristybb503372010-05-27 20:51:26 +00002233 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002234 {
cristy4c08aed2011-07-01 19:47:50 +00002235 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002236 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002237
cristy8d4629b2010-08-30 17:59:46 +00002238 register ssize_t
2239 x;
2240
anthonya322a832013-04-27 06:28:03 +00002241 if( IfMagickFalse(status) )
cristy3ed852e2009-09-05 21:47:34 +00002242 continue;
2243 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00002244 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002245 {
2246 status=MagickFalse;
2247 continue;
2248 }
cristybb503372010-05-27 20:51:26 +00002249 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002250 {
cristy7c0a0a42011-08-23 17:57:25 +00002251 double
2252 offset;
2253
2254 HaldInfo
2255 point;
2256
2257 PixelInfo
2258 pixel,
2259 pixel1,
2260 pixel2,
2261 pixel3,
2262 pixel4;
2263
cristy4c08aed2011-07-01 19:47:50 +00002264 point.x=QuantumScale*(level-1.0)*GetPixelRed(image,q);
2265 point.y=QuantumScale*(level-1.0)*GetPixelGreen(image,q);
2266 point.z=QuantumScale*(level-1.0)*GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00002267 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2268 point.x-=floor(point.x);
2269 point.y-=floor(point.y);
2270 point.z-=floor(point.z);
cristy7c0a0a42011-08-23 17:57:25 +00002271 pixel1=zero;
2272 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2273 fmod(offset,width),floor(offset/width),&pixel1,exception);
2274 pixel2=zero;
2275 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2276 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2277 pixel3=zero;
2278 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2279 point.y,&pixel3);
cristy3ed852e2009-09-05 21:47:34 +00002280 offset+=cube_size;
cristy7c0a0a42011-08-23 17:57:25 +00002281 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2282 fmod(offset,width),floor(offset/width),&pixel1,exception);
2283 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2284 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2285 pixel4=zero;
2286 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2287 point.y,&pixel4);
2288 pixel=zero;
2289 CompositePixelInfoAreaBlend(&pixel3,pixel3.alpha,&pixel4,pixel4.alpha,
2290 point.z,&pixel);
cristyed231572011-07-14 02:18:59 +00002291 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00002292 SetPixelRed(image,ClampToQuantum(pixel.red),q);
cristyed231572011-07-14 02:18:59 +00002293 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00002294 SetPixelGreen(image,ClampToQuantum(pixel.green),q);
cristyed231572011-07-14 02:18:59 +00002295 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00002296 SetPixelBlue(image,ClampToQuantum(pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00002297 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002298 (image->colorspace == CMYKColorspace))
cristyf45fec72011-08-23 16:02:32 +00002299 SetPixelBlack(image,ClampToQuantum(pixel.black),q);
2300 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy8a46d822012-08-28 23:32:39 +00002301 (image->alpha_trait == BlendPixelTrait))
cristyf45fec72011-08-23 16:02:32 +00002302 SetPixelAlpha(image,ClampToQuantum(pixel.alpha),q);
cristyed231572011-07-14 02:18:59 +00002303 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002304 }
anthonya322a832013-04-27 06:28:03 +00002305 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
cristy3ed852e2009-09-05 21:47:34 +00002306 status=MagickFalse;
2307 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2308 {
2309 MagickBooleanType
2310 proceed;
2311
cristyb5d5f722009-11-04 03:03:49 +00002312#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00002313 #pragma omp critical (MagickCore_HaldClutImage)
cristy3ed852e2009-09-05 21:47:34 +00002314#endif
2315 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
anthonya322a832013-04-27 06:28:03 +00002316 if( IfMagickFalse(proceed) )
cristy3ed852e2009-09-05 21:47:34 +00002317 status=MagickFalse;
2318 }
2319 }
cristyd551fbc2011-03-31 18:07:46 +00002320 hald_view=DestroyCacheView(hald_view);
cristy3ed852e2009-09-05 21:47:34 +00002321 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002322 return(status);
2323}
2324
2325/*
2326%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2327% %
2328% %
2329% %
2330% L e v e l I m a g e %
2331% %
2332% %
2333% %
2334%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2335%
2336% LevelImage() adjusts the levels of a particular image channel by
2337% scaling the colors falling between specified white and black points to
2338% the full available quantum range.
2339%
2340% The parameters provided represent the black, and white points. The black
2341% point specifies the darkest color in the image. Colors darker than the
2342% black point are set to zero. White point specifies the lightest color in
2343% the image. Colors brighter than the white point are set to the maximum
2344% quantum value.
2345%
2346% If a '!' flag is given, map black and white colors to the given levels
2347% rather than mapping those levels to black and white. See
cristy50fbc382011-07-07 02:19:17 +00002348% LevelizeImage() below.
cristy3ed852e2009-09-05 21:47:34 +00002349%
2350% Gamma specifies a gamma correction to apply to the image.
2351%
2352% The format of the LevelImage method is:
2353%
cristy01e9afd2011-08-10 17:38:41 +00002354% MagickBooleanType LevelImage(Image *image,const double black_point,
2355% const double white_point,const double gamma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002356%
2357% A description of each parameter follows:
2358%
2359% o image: the image.
2360%
cristy01e9afd2011-08-10 17:38:41 +00002361% o black_point: The level to map zero (black) to.
2362%
2363% o white_point: The level to map QuantumRange (white) to.
2364%
2365% o exception: return any errors or warnings in this structure.
cristy3ed852e2009-09-05 21:47:34 +00002366%
2367*/
cristy780e9ef2011-12-18 23:33:50 +00002368
cristya19f1d72012-08-07 18:24:38 +00002369static inline double LevelPixel(const double black_point,
2370 const double white_point,const double gamma,const double pixel)
cristy780e9ef2011-12-18 23:33:50 +00002371{
2372 double
2373 level_pixel,
2374 scale;
2375
cristy780e9ef2011-12-18 23:33:50 +00002376 scale=(white_point != black_point) ? 1.0/(white_point-black_point) : 1.0;
cristye128ac52013-05-25 13:19:06 +00002377 level_pixel=QuantumRange*gamma_pow(scale*((double) pixel-black_point),
2378 1.0/gamma);
cristy00d4fee2013-04-23 18:35:34 +00002379 return(level_pixel);
cristy780e9ef2011-12-18 23:33:50 +00002380}
2381
cristy7c0a0a42011-08-23 17:57:25 +00002382MagickExport MagickBooleanType LevelImage(Image *image,const double black_point,
2383 const double white_point,const double gamma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002384{
2385#define LevelImageTag "Level/Image"
cristy3ed852e2009-09-05 21:47:34 +00002386
cristyc4c8d132010-01-07 01:58:38 +00002387 CacheView
2388 *image_view;
2389
cristy3ed852e2009-09-05 21:47:34 +00002390 MagickBooleanType
2391 status;
2392
cristybb503372010-05-27 20:51:26 +00002393 MagickOffsetType
2394 progress;
2395
cristy8d4629b2010-08-30 17:59:46 +00002396 register ssize_t
2397 i;
2398
cristybb503372010-05-27 20:51:26 +00002399 ssize_t
2400 y;
2401
cristy3ed852e2009-09-05 21:47:34 +00002402 /*
2403 Allocate and initialize levels map.
2404 */
2405 assert(image != (Image *) NULL);
2406 assert(image->signature == MagickSignature);
anthonya322a832013-04-27 06:28:03 +00002407 if( IfMagickTrue(image->debug) )
cristy3ed852e2009-09-05 21:47:34 +00002408 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2409 if (image->storage_class == PseudoClass)
cristybb503372010-05-27 20:51:26 +00002410 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002411 {
2412 /*
2413 Level colormap.
2414 */
cristyed231572011-07-14 02:18:59 +00002415 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy780e9ef2011-12-18 23:33:50 +00002416 image->colormap[i].red=(double) ClampToQuantum(LevelPixel(black_point,
2417 white_point,gamma,image->colormap[i].red));
cristyed231572011-07-14 02:18:59 +00002418 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy780e9ef2011-12-18 23:33:50 +00002419 image->colormap[i].green=(double) ClampToQuantum(LevelPixel(black_point,
2420 white_point,gamma,image->colormap[i].green));
cristyed231572011-07-14 02:18:59 +00002421 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy780e9ef2011-12-18 23:33:50 +00002422 image->colormap[i].blue=(double) ClampToQuantum(LevelPixel(black_point,
2423 white_point,gamma,image->colormap[i].blue));
cristyed231572011-07-14 02:18:59 +00002424 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy780e9ef2011-12-18 23:33:50 +00002425 image->colormap[i].alpha=(double) ClampToQuantum(LevelPixel(black_point,
2426 white_point,gamma,image->colormap[i].alpha));
cristy3ed852e2009-09-05 21:47:34 +00002427 }
2428 /*
2429 Level image.
2430 */
2431 status=MagickTrue;
2432 progress=0;
cristy46ff2672012-12-14 15:32:26 +00002433 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +00002434#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00002435 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy5e6b2592012-12-19 14:08:11 +00002436 magick_threads(image,image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00002437#endif
cristybb503372010-05-27 20:51:26 +00002438 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002439 {
cristy4c08aed2011-07-01 19:47:50 +00002440 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002441 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002442
cristy8d4629b2010-08-30 17:59:46 +00002443 register ssize_t
2444 x;
2445
anthonya322a832013-04-27 06:28:03 +00002446 if( IfMagickFalse(status) )
cristy3ed852e2009-09-05 21:47:34 +00002447 continue;
2448 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00002449 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002450 {
2451 status=MagickFalse;
2452 continue;
2453 }
cristybb503372010-05-27 20:51:26 +00002454 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002455 {
cristy7c0a0a42011-08-23 17:57:25 +00002456 register ssize_t
2457 i;
2458
cristy883fde12013-04-08 00:50:13 +00002459 if (GetPixelReadMask(image,q) == 0)
cristy10a6c612012-01-29 21:41:05 +00002460 {
2461 q+=GetPixelChannels(image);
2462 continue;
2463 }
cristy7c0a0a42011-08-23 17:57:25 +00002464 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2465 {
cristy5a23c552013-02-13 14:34:28 +00002466 PixelChannel channel=GetPixelChannelChannel(image,i);
2467 PixelTrait traits=GetPixelChannelTraits(image,channel);
cristyd09f8802012-02-04 16:44:10 +00002468 if ((traits & UpdatePixelTrait) == 0)
cristy7c0a0a42011-08-23 17:57:25 +00002469 continue;
cristy780e9ef2011-12-18 23:33:50 +00002470 q[i]=ClampToQuantum(LevelPixel(black_point,white_point,gamma,
cristya19f1d72012-08-07 18:24:38 +00002471 (double) q[i]));
cristy7c0a0a42011-08-23 17:57:25 +00002472 }
cristyed231572011-07-14 02:18:59 +00002473 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002474 }
anthonya322a832013-04-27 06:28:03 +00002475 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
cristy3ed852e2009-09-05 21:47:34 +00002476 status=MagickFalse;
2477 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2478 {
2479 MagickBooleanType
2480 proceed;
2481
cristyb5d5f722009-11-04 03:03:49 +00002482#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00002483 #pragma omp critical (MagickCore_LevelImage)
cristy3ed852e2009-09-05 21:47:34 +00002484#endif
2485 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
anthonya322a832013-04-27 06:28:03 +00002486 if( IfMagickFalse(proceed) )
cristy3ed852e2009-09-05 21:47:34 +00002487 status=MagickFalse;
2488 }
2489 }
2490 image_view=DestroyCacheView(image_view);
cristyfb438402013-07-28 19:59:59 +00002491 (void) ClampImage(image,exception);
cristy3ed852e2009-09-05 21:47:34 +00002492 return(status);
2493}
2494
2495/*
2496%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2497% %
2498% %
2499% %
cristy33bd5152011-08-24 01:42:24 +00002500% L e v e l i z e I m a g e %
cristy3ed852e2009-09-05 21:47:34 +00002501% %
2502% %
2503% %
2504%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2505%
cristy50fbc382011-07-07 02:19:17 +00002506% LevelizeImage() applies the reversed LevelImage() operation to just
cristy3ed852e2009-09-05 21:47:34 +00002507% the specific channels specified. It compresses the full range of color
2508% values, so that they lie between the given black and white points. Gamma is
2509% applied before the values are mapped.
2510%
cristy50fbc382011-07-07 02:19:17 +00002511% LevelizeImage() can be called with by using a +level command line
cristy3ed852e2009-09-05 21:47:34 +00002512% API option, or using a '!' on a -level or LevelImage() geometry string.
2513%
anthony31f1bf72012-01-30 12:37:22 +00002514% It can be used to de-contrast a greyscale image to the exact levels
2515% specified. Or by using specific levels for each channel of an image you
2516% can convert a gray-scale image to any linear color gradient, according to
2517% those levels.
cristy3ed852e2009-09-05 21:47:34 +00002518%
cristy50fbc382011-07-07 02:19:17 +00002519% The format of the LevelizeImage method is:
cristy3ed852e2009-09-05 21:47:34 +00002520%
cristy50fbc382011-07-07 02:19:17 +00002521% MagickBooleanType LevelizeImage(Image *image,const double black_point,
cristy7c0a0a42011-08-23 17:57:25 +00002522% const double white_point,const double gamma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002523%
2524% A description of each parameter follows:
2525%
2526% o image: the image.
2527%
cristy3ed852e2009-09-05 21:47:34 +00002528% o black_point: The level to map zero (black) to.
2529%
cristy01e9afd2011-08-10 17:38:41 +00002530% o white_point: The level to map QuantumRange (white) to.
cristy3ed852e2009-09-05 21:47:34 +00002531%
2532% o gamma: adjust gamma by this factor before mapping values.
2533%
cristy7c0a0a42011-08-23 17:57:25 +00002534% o exception: return any errors or warnings in this structure.
2535%
cristy3ed852e2009-09-05 21:47:34 +00002536*/
cristyd1a2c0f2011-02-09 14:14:50 +00002537MagickExport MagickBooleanType LevelizeImage(Image *image,
cristy7c0a0a42011-08-23 17:57:25 +00002538 const double black_point,const double white_point,const double gamma,
2539 ExceptionInfo *exception)
cristyd1a2c0f2011-02-09 14:14:50 +00002540{
cristy3ed852e2009-09-05 21:47:34 +00002541#define LevelizeImageTag "Levelize/Image"
cristy00d4fee2013-04-23 18:35:34 +00002542#define LevelizeValue(x) ClampToQuantum(((MagickRealType) gamma_pow((double) \
2543 (QuantumScale*(x)),gamma))*(white_point-black_point)+black_point)
cristy3ed852e2009-09-05 21:47:34 +00002544
cristyc4c8d132010-01-07 01:58:38 +00002545 CacheView
2546 *image_view;
2547
cristy3ed852e2009-09-05 21:47:34 +00002548 MagickBooleanType
2549 status;
2550
cristybb503372010-05-27 20:51:26 +00002551 MagickOffsetType
2552 progress;
2553
2554 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002555 i;
2556
cristybb503372010-05-27 20:51:26 +00002557 ssize_t
2558 y;
2559
cristy3ed852e2009-09-05 21:47:34 +00002560 /*
2561 Allocate and initialize levels map.
2562 */
2563 assert(image != (Image *) NULL);
2564 assert(image->signature == MagickSignature);
anthonya322a832013-04-27 06:28:03 +00002565 if( IfMagickTrue(image->debug) )
cristy3ed852e2009-09-05 21:47:34 +00002566 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2567 if (image->storage_class == PseudoClass)
cristybb503372010-05-27 20:51:26 +00002568 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002569 {
2570 /*
2571 Level colormap.
2572 */
cristyed231572011-07-14 02:18:59 +00002573 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristya6400b12013-03-15 12:20:18 +00002574 image->colormap[i].red=(double) LevelizeValue(image->colormap[i].red);
cristyed231572011-07-14 02:18:59 +00002575 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00002576 image->colormap[i].green=(double) LevelizeValue(
2577 image->colormap[i].green);
cristyed231572011-07-14 02:18:59 +00002578 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristya6400b12013-03-15 12:20:18 +00002579 image->colormap[i].blue=(double) LevelizeValue(image->colormap[i].blue);
cristyed231572011-07-14 02:18:59 +00002580 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00002581 image->colormap[i].alpha=(double) LevelizeValue(
2582 image->colormap[i].alpha);
cristy3ed852e2009-09-05 21:47:34 +00002583 }
2584 /*
2585 Level image.
2586 */
2587 status=MagickTrue;
2588 progress=0;
cristy46ff2672012-12-14 15:32:26 +00002589 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +00002590#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00002591 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy5e6b2592012-12-19 14:08:11 +00002592 magick_threads(image,image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00002593#endif
cristybb503372010-05-27 20:51:26 +00002594 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002595 {
cristy4c08aed2011-07-01 19:47:50 +00002596 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002597 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002598
cristy8d4629b2010-08-30 17:59:46 +00002599 register ssize_t
2600 x;
2601
anthonya322a832013-04-27 06:28:03 +00002602 if( IfMagickFalse(status) )
cristy3ed852e2009-09-05 21:47:34 +00002603 continue;
2604 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00002605 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002606 {
2607 status=MagickFalse;
2608 continue;
2609 }
cristybb503372010-05-27 20:51:26 +00002610 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002611 {
cristy7c0a0a42011-08-23 17:57:25 +00002612 register ssize_t
2613 i;
2614
cristy883fde12013-04-08 00:50:13 +00002615 if (GetPixelReadMask(image,q) == 0)
cristy10a6c612012-01-29 21:41:05 +00002616 {
2617 q+=GetPixelChannels(image);
2618 continue;
2619 }
cristy7c0a0a42011-08-23 17:57:25 +00002620 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2621 {
cristy5a23c552013-02-13 14:34:28 +00002622 PixelChannel channel=GetPixelChannelChannel(image,i);
2623 PixelTrait traits=GetPixelChannelTraits(image,channel);
cristyd09f8802012-02-04 16:44:10 +00002624 if ((traits & UpdatePixelTrait) == 0)
cristyb8b0d162011-12-18 23:41:28 +00002625 continue;
cristy780e9ef2011-12-18 23:33:50 +00002626 q[i]=LevelizeValue(q[i]);
cristy7c0a0a42011-08-23 17:57:25 +00002627 }
cristyed231572011-07-14 02:18:59 +00002628 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002629 }
anthonya322a832013-04-27 06:28:03 +00002630 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
cristy3ed852e2009-09-05 21:47:34 +00002631 status=MagickFalse;
2632 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2633 {
2634 MagickBooleanType
2635 proceed;
2636
cristyb5d5f722009-11-04 03:03:49 +00002637#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00002638 #pragma omp critical (MagickCore_LevelizeImage)
cristy3ed852e2009-09-05 21:47:34 +00002639#endif
2640 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
anthonya322a832013-04-27 06:28:03 +00002641 if( IfMagickFalse(proceed) )
cristy3ed852e2009-09-05 21:47:34 +00002642 status=MagickFalse;
2643 }
2644 }
cristy8d4629b2010-08-30 17:59:46 +00002645 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002646 return(status);
2647}
2648
2649/*
2650%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2651% %
2652% %
2653% %
2654% L e v e l I m a g e C o l o r s %
2655% %
2656% %
2657% %
2658%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2659%
cristy490408a2011-07-07 14:42:05 +00002660% LevelImageColors() maps the given color to "black" and "white" values,
cristyee0f8d72009-09-19 00:58:29 +00002661% linearly spreading out the colors, and level values on a channel by channel
2662% bases, as per LevelImage(). The given colors allows you to specify
glennrp1e7f7bc2011-03-02 19:25:28 +00002663% different level ranges for each of the color channels separately.
cristy3ed852e2009-09-05 21:47:34 +00002664%
2665% If the boolean 'invert' is set true the image values will modifyed in the
2666% reverse direction. That is any existing "black" and "white" colors in the
2667% image will become the color values given, with all other values compressed
2668% appropriatally. This effectivally maps a greyscale gradient into the given
2669% color gradient.
2670%
cristy490408a2011-07-07 14:42:05 +00002671% The format of the LevelImageColors method is:
cristy3ed852e2009-09-05 21:47:34 +00002672%
cristy490408a2011-07-07 14:42:05 +00002673% MagickBooleanType LevelImageColors(Image *image,
2674% const PixelInfo *black_color,const PixelInfo *white_color,
cristy7c0a0a42011-08-23 17:57:25 +00002675% const MagickBooleanType invert,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002676%
2677% A description of each parameter follows:
2678%
2679% o image: the image.
2680%
cristy3ed852e2009-09-05 21:47:34 +00002681% o black_color: The color to map black to/from
2682%
2683% o white_point: The color to map white to/from
2684%
2685% o invert: if true map the colors (levelize), rather than from (level)
2686%
cristy7c0a0a42011-08-23 17:57:25 +00002687% o exception: return any errors or warnings in this structure.
2688%
cristy3ed852e2009-09-05 21:47:34 +00002689*/
cristy490408a2011-07-07 14:42:05 +00002690MagickExport MagickBooleanType LevelImageColors(Image *image,
cristy4c08aed2011-07-01 19:47:50 +00002691 const PixelInfo *black_color,const PixelInfo *white_color,
cristy7c0a0a42011-08-23 17:57:25 +00002692 const MagickBooleanType invert,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002693{
cristybd5a96c2011-08-21 00:04:26 +00002694 ChannelType
2695 channel_mask;
2696
cristy3ed852e2009-09-05 21:47:34 +00002697 MagickStatusType
2698 status;
2699
2700 /*
2701 Allocate and initialize levels map.
2702 */
2703 assert(image != (Image *) NULL);
2704 assert(image->signature == MagickSignature);
anthonya322a832013-04-27 06:28:03 +00002705 if( IfMagickTrue(image->debug) )
cristy3ed852e2009-09-05 21:47:34 +00002706 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
anthonya322a832013-04-27 06:28:03 +00002707 if( IfMagickTrue(IsGrayColorspace(image->colorspace)) &&
2708 (IfMagickFalse(IsGrayColorspace(black_color->colorspace)) ||
2709 IfMagickFalse(IsGrayColorspace(white_color->colorspace))))
cristy0c81d062013-04-21 15:22:02 +00002710 (void) SetImageColorspace(image,sRGBColorspace,exception);
cristy3ed852e2009-09-05 21:47:34 +00002711 status=MagickFalse;
anthonya322a832013-04-27 06:28:03 +00002712 if( IfMagickFalse(invert) )
cristy3ed852e2009-09-05 21:47:34 +00002713 {
cristyed231572011-07-14 02:18:59 +00002714 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002715 {
cristycf1296e2012-08-26 23:40:49 +00002716 channel_mask=SetImageChannelMask(image,RedChannel);
cristy01e9afd2011-08-10 17:38:41 +00002717 status|=LevelImage(image,black_color->red,white_color->red,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002718 exception);
cristycf1296e2012-08-26 23:40:49 +00002719 (void) SetImageChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002720 }
cristyed231572011-07-14 02:18:59 +00002721 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002722 {
cristycf1296e2012-08-26 23:40:49 +00002723 channel_mask=SetImageChannelMask(image,GreenChannel);
cristy01e9afd2011-08-10 17:38:41 +00002724 status|=LevelImage(image,black_color->green,white_color->green,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002725 exception);
cristycf1296e2012-08-26 23:40:49 +00002726 (void) SetImageChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002727 }
cristyed231572011-07-14 02:18:59 +00002728 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002729 {
cristycf1296e2012-08-26 23:40:49 +00002730 channel_mask=SetImageChannelMask(image,BlueChannel);
cristy01e9afd2011-08-10 17:38:41 +00002731 status|=LevelImage(image,black_color->blue,white_color->blue,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002732 exception);
cristycf1296e2012-08-26 23:40:49 +00002733 (void) SetImageChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002734 }
cristyed231572011-07-14 02:18:59 +00002735 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002736 (image->colorspace == CMYKColorspace))
cristyf89cb1d2011-07-07 01:24:37 +00002737 {
cristycf1296e2012-08-26 23:40:49 +00002738 channel_mask=SetImageChannelMask(image,BlackChannel);
cristy01e9afd2011-08-10 17:38:41 +00002739 status|=LevelImage(image,black_color->black,white_color->black,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002740 exception);
cristycf1296e2012-08-26 23:40:49 +00002741 (void) SetImageChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002742 }
cristyed231572011-07-14 02:18:59 +00002743 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy8a46d822012-08-28 23:32:39 +00002744 (image->alpha_trait == BlendPixelTrait))
cristyf89cb1d2011-07-07 01:24:37 +00002745 {
cristycf1296e2012-08-26 23:40:49 +00002746 channel_mask=SetImageChannelMask(image,AlphaChannel);
cristy01e9afd2011-08-10 17:38:41 +00002747 status|=LevelImage(image,black_color->alpha,white_color->alpha,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002748 exception);
cristycf1296e2012-08-26 23:40:49 +00002749 (void) SetImageChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002750 }
cristy3ed852e2009-09-05 21:47:34 +00002751 }
2752 else
2753 {
cristyed231572011-07-14 02:18:59 +00002754 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002755 {
cristycf1296e2012-08-26 23:40:49 +00002756 channel_mask=SetImageChannelMask(image,RedChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002757 status|=LevelizeImage(image,black_color->red,white_color->red,1.0,
2758 exception);
cristycf1296e2012-08-26 23:40:49 +00002759 (void) SetImageChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002760 }
cristyed231572011-07-14 02:18:59 +00002761 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002762 {
cristycf1296e2012-08-26 23:40:49 +00002763 channel_mask=SetImageChannelMask(image,GreenChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002764 status|=LevelizeImage(image,black_color->green,white_color->green,1.0,
2765 exception);
cristycf1296e2012-08-26 23:40:49 +00002766 (void) SetImageChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002767 }
cristyed231572011-07-14 02:18:59 +00002768 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002769 {
cristycf1296e2012-08-26 23:40:49 +00002770 channel_mask=SetImageChannelMask(image,BlueChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002771 status|=LevelizeImage(image,black_color->blue,white_color->blue,1.0,
2772 exception);
cristycf1296e2012-08-26 23:40:49 +00002773 (void) SetImageChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002774 }
cristyed231572011-07-14 02:18:59 +00002775 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002776 (image->colorspace == CMYKColorspace))
cristyf89cb1d2011-07-07 01:24:37 +00002777 {
cristycf1296e2012-08-26 23:40:49 +00002778 channel_mask=SetImageChannelMask(image,BlackChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002779 status|=LevelizeImage(image,black_color->black,white_color->black,1.0,
2780 exception);
cristycf1296e2012-08-26 23:40:49 +00002781 (void) SetImageChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002782 }
cristyed231572011-07-14 02:18:59 +00002783 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy8a46d822012-08-28 23:32:39 +00002784 (image->alpha_trait == BlendPixelTrait))
cristyf89cb1d2011-07-07 01:24:37 +00002785 {
cristycf1296e2012-08-26 23:40:49 +00002786 channel_mask=SetImageChannelMask(image,AlphaChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002787 status|=LevelizeImage(image,black_color->alpha,white_color->alpha,1.0,
2788 exception);
cristycf1296e2012-08-26 23:40:49 +00002789 (void) SetImageChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002790 }
cristy3ed852e2009-09-05 21:47:34 +00002791 }
cristyee1bdaa2013-07-16 16:45:03 +00002792 return(status != 0 ? MagickTrue : MagickFalse);
cristy3ed852e2009-09-05 21:47:34 +00002793}
2794
2795/*
2796%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2797% %
2798% %
2799% %
2800% L i n e a r S t r e t c h I m a g e %
2801% %
2802% %
2803% %
2804%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2805%
cristyf1611782011-08-01 15:39:13 +00002806% LinearStretchImage() discards any pixels below the black point and above
2807% the white point and levels the remaining pixels.
cristy3ed852e2009-09-05 21:47:34 +00002808%
2809% The format of the LinearStretchImage method is:
2810%
2811% MagickBooleanType LinearStretchImage(Image *image,
cristy33bd5152011-08-24 01:42:24 +00002812% const double black_point,const double white_point,
2813% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002814%
2815% A description of each parameter follows:
2816%
2817% o image: the image.
2818%
2819% o black_point: the black point.
2820%
2821% o white_point: the white point.
2822%
cristy33bd5152011-08-24 01:42:24 +00002823% o exception: return any errors or warnings in this structure.
2824%
cristy3ed852e2009-09-05 21:47:34 +00002825*/
2826MagickExport MagickBooleanType LinearStretchImage(Image *image,
cristy33bd5152011-08-24 01:42:24 +00002827 const double black_point,const double white_point,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002828{
2829#define LinearStretchImageTag "LinearStretch/Image"
2830
cristy33bd5152011-08-24 01:42:24 +00002831 CacheView
2832 *image_view;
cristy3ed852e2009-09-05 21:47:34 +00002833
cristya19f1d72012-08-07 18:24:38 +00002834 double
cristy3ed852e2009-09-05 21:47:34 +00002835 *histogram,
2836 intensity;
2837
cristy1c2f48d2012-12-14 01:20:55 +00002838 MagickBooleanType
2839 status;
2840
cristy8d4629b2010-08-30 17:59:46 +00002841 ssize_t
2842 black,
2843 white,
2844 y;
2845
cristy3ed852e2009-09-05 21:47:34 +00002846 /*
2847 Allocate histogram and linear map.
2848 */
2849 assert(image != (Image *) NULL);
2850 assert(image->signature == MagickSignature);
cristy1c2f48d2012-12-14 01:20:55 +00002851 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*histogram));
cristya19f1d72012-08-07 18:24:38 +00002852 if (histogram == (double *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002853 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2854 image->filename);
2855 /*
2856 Form histogram.
2857 */
2858 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
cristy46ff2672012-12-14 15:32:26 +00002859 image_view=AcquireVirtualCacheView(image,exception);
cristybb503372010-05-27 20:51:26 +00002860 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002861 {
cristy4c08aed2011-07-01 19:47:50 +00002862 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00002863 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002864
cristybb503372010-05-27 20:51:26 +00002865 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002866 x;
2867
cristy33bd5152011-08-24 01:42:24 +00002868 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002869 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002870 break;
cristy33bd5152011-08-24 01:42:24 +00002871 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002872 {
cristyf13c5942012-08-08 23:50:11 +00002873 double
2874 intensity;
2875
2876 intensity=GetPixelIntensity(image,p);
2877 histogram[ScaleQuantumToMap(ClampToQuantum(intensity))]++;
cristyed231572011-07-14 02:18:59 +00002878 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002879 }
2880 }
cristy33bd5152011-08-24 01:42:24 +00002881 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002882 /*
2883 Find the histogram boundaries by locating the black and white point levels.
2884 */
cristy3ed852e2009-09-05 21:47:34 +00002885 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00002886 for (black=0; black < (ssize_t) MaxMap; black++)
cristy3ed852e2009-09-05 21:47:34 +00002887 {
2888 intensity+=histogram[black];
2889 if (intensity >= black_point)
2890 break;
2891 }
2892 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00002893 for (white=(ssize_t) MaxMap; white != 0; white--)
cristy3ed852e2009-09-05 21:47:34 +00002894 {
2895 intensity+=histogram[white];
2896 if (intensity >= white_point)
2897 break;
2898 }
cristya19f1d72012-08-07 18:24:38 +00002899 histogram=(double *) RelinquishMagickMemory(histogram);
cristyc82a27b2011-10-21 01:07:16 +00002900 status=LevelImage(image,(double) black,(double) white,1.0,exception);
cristy3ed852e2009-09-05 21:47:34 +00002901 return(status);
2902}
2903
2904/*
2905%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2906% %
2907% %
2908% %
2909% M o d u l a t e I m a g e %
2910% %
2911% %
2912% %
2913%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2914%
2915% ModulateImage() lets you control the brightness, saturation, and hue
2916% of an image. Modulate represents the brightness, saturation, and hue
2917% as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
cristye83f3402012-08-17 13:12:15 +00002918% modulation is lightness, saturation, and hue. For HWB, use blackness,
2919% whiteness, and hue. And for HCL, use chrome, luma, and hue.
cristy3ed852e2009-09-05 21:47:34 +00002920%
2921% The format of the ModulateImage method is:
2922%
cristy33bd5152011-08-24 01:42:24 +00002923% MagickBooleanType ModulateImage(Image *image,const char *modulate,
2924% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002925%
2926% A description of each parameter follows:
2927%
2928% o image: the image.
2929%
cristy33bd5152011-08-24 01:42:24 +00002930% o modulate: Define the percent change in brightness, saturation, and hue.
2931%
2932% o exception: return any errors or warnings in this structure.
cristy3ed852e2009-09-05 21:47:34 +00002933%
2934*/
2935
cristye83f3402012-08-17 13:12:15 +00002936static inline void ModulateHCL(const double percent_hue,
2937 const double percent_chroma,const double percent_luma,double *red,
2938 double *green,double *blue)
2939{
2940 double
2941 hue,
2942 luma,
2943 chroma;
2944
2945 /*
2946 Increase or decrease color luma, chroma, or hue.
2947 */
2948 ConvertRGBToHCL(*red,*green,*blue,&hue,&chroma,&luma);
2949 hue+=0.5*(0.01*percent_hue-1.0);
2950 while (hue < 0.0)
2951 hue+=1.0;
2952 while (hue > 1.0)
2953 hue-=1.0;
2954 chroma*=0.01*percent_chroma;
2955 luma*=0.01*percent_luma;
2956 ConvertHCLToRGB(hue,chroma,luma,red,green,blue);
2957}
2958
cristy9e2436a2013-05-02 20:35:59 +00002959static inline void ModulateHCLp(const double percent_hue,
2960 const double percent_chroma,const double percent_luma,double *red,
2961 double *green,double *blue)
2962{
2963 double
2964 hue,
2965 luma,
2966 chroma;
2967
2968 /*
2969 Increase or decrease color luma, chroma, or hue.
2970 */
2971 ConvertRGBToHCLp(*red,*green,*blue,&hue,&chroma,&luma);
2972 hue+=0.5*(0.01*percent_hue-1.0);
2973 while (hue < 0.0)
2974 hue+=1.0;
2975 while (hue > 1.0)
2976 hue-=1.0;
2977 chroma*=0.01*percent_chroma;
2978 luma*=0.01*percent_luma;
2979 ConvertHCLpToRGB(hue,chroma,luma,red,green,blue);
2980}
2981
cristye83f3402012-08-17 13:12:15 +00002982static inline void ModulateHSB(const double percent_hue,
cristy3094b7f2011-10-01 23:18:02 +00002983 const double percent_saturation,const double percent_brightness,double *red,
2984 double *green,double *blue)
cristy3ed852e2009-09-05 21:47:34 +00002985{
2986 double
2987 brightness,
2988 hue,
2989 saturation;
2990
2991 /*
2992 Increase or decrease color brightness, saturation, or hue.
2993 */
cristy0a39a5c2012-06-27 12:51:45 +00002994 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
cristy3ed852e2009-09-05 21:47:34 +00002995 hue+=0.5*(0.01*percent_hue-1.0);
2996 while (hue < 0.0)
2997 hue+=1.0;
2998 while (hue > 1.0)
2999 hue-=1.0;
3000 saturation*=0.01*percent_saturation;
3001 brightness*=0.01*percent_brightness;
cristy0a39a5c2012-06-27 12:51:45 +00003002 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
cristy3ed852e2009-09-05 21:47:34 +00003003}
3004
cristyaf64eb22013-05-02 14:07:10 +00003005static inline void ModulateHSI(const double percent_hue,
3006 const double percent_saturation,const double percent_intensity,double *red,
3007 double *green,double *blue)
3008{
3009 double
3010 intensity,
3011 hue,
3012 saturation;
3013
3014 /*
3015 Increase or decrease color intensity, saturation, or hue.
3016 */
3017 ConvertRGBToHSI(*red,*green,*blue,&hue,&saturation,&intensity);
3018 hue+=0.5*(0.01*percent_hue-1.0);
3019 while (hue < 0.0)
3020 hue+=1.0;
3021 while (hue > 1.0)
3022 hue-=1.0;
3023 saturation*=0.01*percent_saturation;
3024 intensity*=0.01*percent_intensity;
3025 ConvertHSIToRGB(hue,saturation,intensity,red,green,blue);
3026}
3027
cristye83f3402012-08-17 13:12:15 +00003028static inline void ModulateHSL(const double percent_hue,
cristy3094b7f2011-10-01 23:18:02 +00003029 const double percent_saturation,const double percent_lightness,double *red,
3030 double *green,double *blue)
cristy3ed852e2009-09-05 21:47:34 +00003031{
3032 double
3033 hue,
3034 lightness,
3035 saturation;
3036
3037 /*
3038 Increase or decrease color lightness, saturation, or hue.
3039 */
cristy0a39a5c2012-06-27 12:51:45 +00003040 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
cristy3ed852e2009-09-05 21:47:34 +00003041 hue+=0.5*(0.01*percent_hue-1.0);
3042 while (hue < 0.0)
3043 hue+=1.0;
cristye8f91f72013-04-05 00:50:58 +00003044 while (hue >= 1.0)
cristy3ed852e2009-09-05 21:47:34 +00003045 hue-=1.0;
3046 saturation*=0.01*percent_saturation;
3047 lightness*=0.01*percent_lightness;
cristy0a39a5c2012-06-27 12:51:45 +00003048 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
cristy3ed852e2009-09-05 21:47:34 +00003049}
3050
cristy246c3132013-05-02 16:35:53 +00003051static inline void ModulateHSV(const double percent_hue,
3052 const double percent_saturation,const double percent_value,double *red,
3053 double *green,double *blue)
3054{
3055 double
3056 hue,
3057 saturation,
3058 value;
3059
3060 /*
3061 Increase or decrease color value, saturation, or hue.
3062 */
3063 ConvertRGBToHSV(*red,*green,*blue,&hue,&saturation,&value);
3064 hue+=0.5*(0.01*percent_hue-1.0);
3065 while (hue < 0.0)
3066 hue+=1.0;
3067 while (hue >= 1.0)
3068 hue-=1.0;
3069 saturation*=0.01*percent_saturation;
3070 value*=0.01*percent_value;
3071 ConvertHSVToRGB(hue,saturation,value,red,green,blue);
3072}
3073
cristye83f3402012-08-17 13:12:15 +00003074static inline void ModulateHWB(const double percent_hue,
3075 const double percent_whiteness,const double percent_blackness,double *red,
3076 double *green,double *blue)
cristy3ed852e2009-09-05 21:47:34 +00003077{
3078 double
3079 blackness,
3080 hue,
3081 whiteness;
3082
3083 /*
3084 Increase or decrease color blackness, whiteness, or hue.
3085 */
cristy0a39a5c2012-06-27 12:51:45 +00003086 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
cristy3ed852e2009-09-05 21:47:34 +00003087 hue+=0.5*(0.01*percent_hue-1.0);
3088 while (hue < 0.0)
3089 hue+=1.0;
cristye8f91f72013-04-05 00:50:58 +00003090 while (hue >= 1.0)
cristy3ed852e2009-09-05 21:47:34 +00003091 hue-=1.0;
3092 blackness*=0.01*percent_blackness;
3093 whiteness*=0.01*percent_whiteness;
cristy0a39a5c2012-06-27 12:51:45 +00003094 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
cristy3ed852e2009-09-05 21:47:34 +00003095}
3096
cristy28e21a72013-04-06 00:24:19 +00003097static inline void ModulateLCHab(const double percent_luma,
3098 const double percent_chroma,const double percent_hue,double *red,
3099 double *green,double *blue)
3100{
3101 double
3102 hue,
3103 luma,
3104 chroma;
3105
3106 /*
3107 Increase or decrease color luma, chroma, or hue.
3108 */
3109 ConvertRGBToLCHab(*red,*green,*blue,&luma,&chroma,&hue);
3110 luma*=0.01*percent_luma;
3111 chroma*=0.01*percent_chroma;
3112 hue+=0.5*(0.01*percent_hue-1.0);
3113 while (hue < 0.0)
3114 hue+=1.0;
3115 while (hue >= 1.0)
3116 hue-=1.0;
3117 ConvertLCHabToRGB(luma,chroma,hue,red,green,blue);
3118}
3119
3120static inline void ModulateLCHuv(const double percent_luma,
cristyb2850952013-04-04 19:00:52 +00003121 const double percent_chroma,const double percent_hue,double *red,
3122 double *green,double *blue)
3123{
3124 double
3125 hue,
3126 luma,
3127 chroma;
3128
3129 /*
3130 Increase or decrease color luma, chroma, or hue.
3131 */
cristy30ad51e2013-04-05 16:39:20 +00003132 ConvertRGBToLCHuv(*red,*green,*blue,&luma,&chroma,&hue);
cristyb2850952013-04-04 19:00:52 +00003133 luma*=0.01*percent_luma;
3134 chroma*=0.01*percent_chroma;
3135 hue+=0.5*(0.01*percent_hue-1.0);
3136 while (hue < 0.0)
3137 hue+=1.0;
cristye8f91f72013-04-05 00:50:58 +00003138 while (hue >= 1.0)
cristyb2850952013-04-04 19:00:52 +00003139 hue-=1.0;
cristy30ad51e2013-04-05 16:39:20 +00003140 ConvertLCHuvToRGB(luma,chroma,hue,red,green,blue);
cristyb2850952013-04-04 19:00:52 +00003141}
3142
cristy33bd5152011-08-24 01:42:24 +00003143MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate,
3144 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003145{
3146#define ModulateImageTag "Modulate/Image"
3147
cristyc4c8d132010-01-07 01:58:38 +00003148 CacheView
3149 *image_view;
3150
cristy3ed852e2009-09-05 21:47:34 +00003151 ColorspaceType
3152 colorspace;
3153
3154 const char
3155 *artifact;
3156
3157 double
3158 percent_brightness,
3159 percent_hue,
cristy4d412212012-06-27 23:14:48 +00003160 percent_saturation;
cristy3ed852e2009-09-05 21:47:34 +00003161
cristy3ed852e2009-09-05 21:47:34 +00003162 GeometryInfo
3163 geometry_info;
3164
cristy3ed852e2009-09-05 21:47:34 +00003165 MagickBooleanType
3166 status;
3167
cristybb503372010-05-27 20:51:26 +00003168 MagickOffsetType
3169 progress;
3170
cristy3ed852e2009-09-05 21:47:34 +00003171 MagickStatusType
3172 flags;
3173
cristybb503372010-05-27 20:51:26 +00003174 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003175 i;
3176
cristybb503372010-05-27 20:51:26 +00003177 ssize_t
3178 y;
3179
cristy3ed852e2009-09-05 21:47:34 +00003180 /*
cristy2b726bd2010-01-11 01:05:39 +00003181 Initialize modulate table.
cristy3ed852e2009-09-05 21:47:34 +00003182 */
3183 assert(image != (Image *) NULL);
3184 assert(image->signature == MagickSignature);
anthonya322a832013-04-27 06:28:03 +00003185 if( IfMagickTrue(image->debug) )
cristy3ed852e2009-09-05 21:47:34 +00003186 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3187 if (modulate == (char *) NULL)
3188 return(MagickFalse);
anthonya322a832013-04-27 06:28:03 +00003189 if( IfMagickFalse(IssRGBCompatibleColorspace(image->colorspace)) )
cristy0c81d062013-04-21 15:22:02 +00003190 (void) SetImageColorspace(image,sRGBColorspace,exception);
cristy3ed852e2009-09-05 21:47:34 +00003191 flags=ParseGeometry(modulate,&geometry_info);
3192 percent_brightness=geometry_info.rho;
3193 percent_saturation=geometry_info.sigma;
3194 if ((flags & SigmaValue) == 0)
3195 percent_saturation=100.0;
3196 percent_hue=geometry_info.xi;
3197 if ((flags & XiValue) == 0)
3198 percent_hue=100.0;
3199 colorspace=UndefinedColorspace;
3200 artifact=GetImageArtifact(image,"modulate:colorspace");
3201 if (artifact != (const char *) NULL)
cristy042ee782011-04-22 18:48:30 +00003202 colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions,
cristy3ed852e2009-09-05 21:47:34 +00003203 MagickFalse,artifact);
3204 if (image->storage_class == PseudoClass)
nicolase0aa3c32012-08-08 14:57:02 +00003205 for (i=0; i < (ssize_t) image->colors; i++)
nicolasfd74ae32012-08-08 16:00:11 +00003206 {
3207 double
3208 blue,
3209 green,
3210 red;
cristy4d412212012-06-27 23:14:48 +00003211
nicolasfd74ae32012-08-08 16:00:11 +00003212 /*
cristy9013d232013-04-04 00:48:36 +00003213 Modulate image colormap.
nicolasfd74ae32012-08-08 16:00:11 +00003214 */
cristycb99efe2013-04-04 10:20:48 +00003215 red=(double) image->colormap[i].red;
3216 green=(double) image->colormap[i].green;
3217 blue=(double) image->colormap[i].blue;
nicolasfd74ae32012-08-08 16:00:11 +00003218 switch (colorspace)
3219 {
cristye83f3402012-08-17 13:12:15 +00003220 case HCLColorspace:
3221 {
3222 ModulateHCL(percent_hue,percent_saturation,percent_brightness,
3223 &red,&green,&blue);
3224 break;
3225 }
cristy9e2436a2013-05-02 20:35:59 +00003226 case HCLpColorspace:
3227 {
3228 ModulateHCLp(percent_hue,percent_saturation,percent_brightness,
3229 &red,&green,&blue);
3230 break;
3231 }
nicolasfd74ae32012-08-08 16:00:11 +00003232 case HSBColorspace:
cristy3ed852e2009-09-05 21:47:34 +00003233 {
nicolasfd74ae32012-08-08 16:00:11 +00003234 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3235 &red,&green,&blue);
3236 break;
3237 }
cristyaf64eb22013-05-02 14:07:10 +00003238 case HSIColorspace:
3239 {
3240 ModulateHSI(percent_hue,percent_saturation,percent_brightness,
3241 &red,&green,&blue);
3242 break;
3243 }
nicolasfd74ae32012-08-08 16:00:11 +00003244 case HSLColorspace:
3245 default:
3246 {
3247 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3248 &red,&green,&blue);
3249 break;
3250 }
cristy246c3132013-05-02 16:35:53 +00003251 case HSVColorspace:
3252 {
3253 ModulateHSV(percent_hue,percent_saturation,percent_brightness,
3254 &red,&green,&blue);
3255 break;
3256 }
nicolasfd74ae32012-08-08 16:00:11 +00003257 case HWBColorspace:
3258 {
3259 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3260 &red,&green,&blue);
3261 break;
cristy3ed852e2009-09-05 21:47:34 +00003262 }
cristy8369dae2013-05-06 20:26:38 +00003263 case LCHColorspace:
cristy28e21a72013-04-06 00:24:19 +00003264 case LCHabColorspace:
3265 {
3266 ModulateLCHab(percent_brightness,percent_saturation,percent_hue,
3267 &red,&green,&blue);
3268 break;
3269 }
cristy30ad51e2013-04-05 16:39:20 +00003270 case LCHuvColorspace:
cristyb2850952013-04-04 19:00:52 +00003271 {
cristy28e21a72013-04-06 00:24:19 +00003272 ModulateLCHuv(percent_brightness,percent_saturation,percent_hue,
cristyb2850952013-04-04 19:00:52 +00003273 &red,&green,&blue);
3274 break;
3275 }
cristy0a39a5c2012-06-27 12:51:45 +00003276 }
cristycb99efe2013-04-04 10:20:48 +00003277 image->colormap[i].red=red;
3278 image->colormap[i].green=green;
3279 image->colormap[i].blue=blue;
nicolasfd74ae32012-08-08 16:00:11 +00003280 }
cristy3ed852e2009-09-05 21:47:34 +00003281 /*
3282 Modulate image.
3283 */
3284 status=MagickTrue;
3285 progress=0;
cristy46ff2672012-12-14 15:32:26 +00003286 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +00003287#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00003288 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy5e6b2592012-12-19 14:08:11 +00003289 magick_threads(image,image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00003290#endif
cristybb503372010-05-27 20:51:26 +00003291 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003292 {
cristy4c08aed2011-07-01 19:47:50 +00003293 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003294 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003295
cristy8d4629b2010-08-30 17:59:46 +00003296 register ssize_t
3297 x;
3298
anthonya322a832013-04-27 06:28:03 +00003299 if( IfMagickFalse(status) )
cristy3ed852e2009-09-05 21:47:34 +00003300 continue;
3301 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00003302 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003303 {
3304 status=MagickFalse;
3305 continue;
3306 }
cristybb503372010-05-27 20:51:26 +00003307 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003308 {
cristy4d412212012-06-27 23:14:48 +00003309 double
3310 blue,
3311 green,
3312 red;
3313
cristycb99efe2013-04-04 10:20:48 +00003314 red=(double) GetPixelRed(image,q);
3315 green=(double) GetPixelGreen(image,q);
3316 blue=(double) GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00003317 switch (colorspace)
3318 {
cristyab3c3272012-08-17 17:21:33 +00003319 case HCLColorspace:
3320 {
3321 ModulateHCL(percent_hue,percent_saturation,percent_brightness,
3322 &red,&green,&blue);
3323 break;
3324 }
cristy690a0eb2013-07-24 16:43:22 +00003325 case HCLpColorspace:
3326 {
3327 ModulateHCLp(percent_hue,percent_saturation,percent_brightness,
3328 &red,&green,&blue);
3329 break;
3330 }
cristy3ed852e2009-09-05 21:47:34 +00003331 case HSBColorspace:
3332 {
3333 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00003334 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00003335 break;
3336 }
3337 case HSLColorspace:
3338 default:
3339 {
3340 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00003341 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00003342 break;
3343 }
cristy5bf65a42013-06-24 14:49:51 +00003344 case HSVColorspace:
3345 {
3346 ModulateHSV(percent_hue,percent_saturation,percent_brightness,
3347 &red,&green,&blue);
3348 break;
3349 }
cristy3ed852e2009-09-05 21:47:34 +00003350 case HWBColorspace:
3351 {
3352 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00003353 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00003354 break;
3355 }
cristy28e21a72013-04-06 00:24:19 +00003356 case LCHabColorspace:
3357 {
3358 ModulateLCHab(percent_brightness,percent_saturation,percent_hue,
3359 &red,&green,&blue);
3360 break;
3361 }
cristyb2850952013-04-04 19:00:52 +00003362 case LCHColorspace:
cristy30ad51e2013-04-05 16:39:20 +00003363 case LCHuvColorspace:
cristyb2850952013-04-04 19:00:52 +00003364 {
cristy28e21a72013-04-06 00:24:19 +00003365 ModulateLCHuv(percent_brightness,percent_saturation,percent_hue,
cristyb2850952013-04-04 19:00:52 +00003366 &red,&green,&blue);
3367 break;
3368 }
cristy3ed852e2009-09-05 21:47:34 +00003369 }
cristycb99efe2013-04-04 10:20:48 +00003370 SetPixelRed(image,ClampToQuantum(red),q);
3371 SetPixelGreen(image,ClampToQuantum(green),q);
3372 SetPixelBlue(image,ClampToQuantum(blue),q);
cristyed231572011-07-14 02:18:59 +00003373 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003374 }
anthonya322a832013-04-27 06:28:03 +00003375 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
cristy3ed852e2009-09-05 21:47:34 +00003376 status=MagickFalse;
3377 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3378 {
3379 MagickBooleanType
3380 proceed;
3381
cristyb5d5f722009-11-04 03:03:49 +00003382#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00003383 #pragma omp critical (MagickCore_ModulateImage)
cristy3ed852e2009-09-05 21:47:34 +00003384#endif
3385 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
anthonya322a832013-04-27 06:28:03 +00003386 if( IfMagickFalse(proceed) )
cristy3ed852e2009-09-05 21:47:34 +00003387 status=MagickFalse;
3388 }
3389 }
3390 image_view=DestroyCacheView(image_view);
3391 return(status);
3392}
3393
3394/*
3395%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3396% %
3397% %
3398% %
3399% N e g a t e I m a g e %
3400% %
3401% %
3402% %
3403%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3404%
3405% NegateImage() negates the colors in the reference image. The grayscale
3406% option means that only grayscale values within the image are negated.
3407%
cristy50fbc382011-07-07 02:19:17 +00003408% The format of the NegateImage method is:
cristy3ed852e2009-09-05 21:47:34 +00003409%
3410% MagickBooleanType NegateImage(Image *image,
cristyb3e7c6c2011-07-24 01:43:55 +00003411% const MagickBooleanType grayscale,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003412%
3413% A description of each parameter follows:
3414%
3415% o image: the image.
3416%
cristy3ed852e2009-09-05 21:47:34 +00003417% o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3418%
cristyb3e7c6c2011-07-24 01:43:55 +00003419% o exception: return any errors or warnings in this structure.
3420%
cristy3ed852e2009-09-05 21:47:34 +00003421*/
cristy3ed852e2009-09-05 21:47:34 +00003422MagickExport MagickBooleanType NegateImage(Image *image,
cristyb3e7c6c2011-07-24 01:43:55 +00003423 const MagickBooleanType grayscale,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003424{
cristy3ed852e2009-09-05 21:47:34 +00003425#define NegateImageTag "Negate/Image"
3426
cristyc4c8d132010-01-07 01:58:38 +00003427 CacheView
3428 *image_view;
3429
cristy3ed852e2009-09-05 21:47:34 +00003430 MagickBooleanType
3431 status;
3432
cristybb503372010-05-27 20:51:26 +00003433 MagickOffsetType
3434 progress;
3435
3436 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003437 i;
3438
cristybb503372010-05-27 20:51:26 +00003439 ssize_t
3440 y;
3441
cristy3ed852e2009-09-05 21:47:34 +00003442 assert(image != (Image *) NULL);
3443 assert(image->signature == MagickSignature);
anthonya322a832013-04-27 06:28:03 +00003444 if( IfMagickTrue(image->debug) )
cristy3ed852e2009-09-05 21:47:34 +00003445 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3446 if (image->storage_class == PseudoClass)
nicolase0aa3c32012-08-08 14:57:02 +00003447 for (i=0; i < (ssize_t) image->colors; i++)
nicolasfd74ae32012-08-08 16:00:11 +00003448 {
3449 /*
cristyaeded782012-09-11 23:39:36 +00003450 Negate colormap.
nicolasfd74ae32012-08-08 16:00:11 +00003451 */
anthonya322a832013-04-27 06:28:03 +00003452 if( IfMagickTrue(grayscale) )
cristyaeded782012-09-11 23:39:36 +00003453 if ((image->colormap[i].red != image->colormap[i].green) ||
3454 (image->colormap[i].green != image->colormap[i].blue))
3455 continue;
nicolasfd74ae32012-08-08 16:00:11 +00003456 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyaeded782012-09-11 23:39:36 +00003457 image->colormap[i].red=QuantumRange-image->colormap[i].red;
nicolasfd74ae32012-08-08 16:00:11 +00003458 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyaeded782012-09-11 23:39:36 +00003459 image->colormap[i].green=QuantumRange-image->colormap[i].green;
nicolasfd74ae32012-08-08 16:00:11 +00003460 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyaeded782012-09-11 23:39:36 +00003461 image->colormap[i].blue=QuantumRange-image->colormap[i].blue;
nicolasfd74ae32012-08-08 16:00:11 +00003462 }
cristy3ed852e2009-09-05 21:47:34 +00003463 /*
3464 Negate image.
3465 */
3466 status=MagickTrue;
3467 progress=0;
cristy46ff2672012-12-14 15:32:26 +00003468 image_view=AcquireAuthenticCacheView(image,exception);
anthonya322a832013-04-27 06:28:03 +00003469 if( IfMagickTrue(grayscale) )
cristy3ed852e2009-09-05 21:47:34 +00003470 {
cristybb503372010-05-27 20:51:26 +00003471 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003472 {
3473 MagickBooleanType
3474 sync;
3475
cristy4c08aed2011-07-01 19:47:50 +00003476 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003477 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003478
cristy8d4629b2010-08-30 17:59:46 +00003479 register ssize_t
3480 x;
3481
anthonya322a832013-04-27 06:28:03 +00003482 if( IfMagickFalse(status) )
cristy3ed852e2009-09-05 21:47:34 +00003483 continue;
3484 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3485 exception);
cristyacd2ed22011-08-30 01:44:23 +00003486 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003487 {
3488 status=MagickFalse;
3489 continue;
3490 }
cristybb503372010-05-27 20:51:26 +00003491 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003492 {
cristy9aa95be2011-07-20 21:56:45 +00003493 register ssize_t
3494 i;
3495
cristy883fde12013-04-08 00:50:13 +00003496 if ((GetPixelReadMask(image,q) == 0) ||
anthonya322a832013-04-27 06:28:03 +00003497 IfMagickTrue(IsPixelGray(image,q)))
cristy3ed852e2009-09-05 21:47:34 +00003498 {
cristya30d9ba2011-07-23 21:00:48 +00003499 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003500 continue;
3501 }
cristya30d9ba2011-07-23 21:00:48 +00003502 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy9aa95be2011-07-20 21:56:45 +00003503 {
cristy5a23c552013-02-13 14:34:28 +00003504 PixelChannel channel=GetPixelChannelChannel(image,i);
3505 PixelTrait traits=GetPixelChannelTraits(image,channel);
cristyd09f8802012-02-04 16:44:10 +00003506 if ((traits & UpdatePixelTrait) == 0)
3507 continue;
3508 q[i]=QuantumRange-q[i];
cristy9aa95be2011-07-20 21:56:45 +00003509 }
cristya30d9ba2011-07-23 21:00:48 +00003510 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003511 }
3512 sync=SyncCacheViewAuthenticPixels(image_view,exception);
anthonya322a832013-04-27 06:28:03 +00003513 if( IfMagickFalse(sync) )
cristy3ed852e2009-09-05 21:47:34 +00003514 status=MagickFalse;
3515 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3516 {
3517 MagickBooleanType
3518 proceed;
3519
cristyb5d5f722009-11-04 03:03:49 +00003520#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00003521 #pragma omp critical (MagickCore_NegateImage)
cristy3ed852e2009-09-05 21:47:34 +00003522#endif
3523 proceed=SetImageProgress(image,NegateImageTag,progress++,
3524 image->rows);
anthonya322a832013-04-27 06:28:03 +00003525 if( IfMagickFalse(proceed) )
cristy3ed852e2009-09-05 21:47:34 +00003526 status=MagickFalse;
3527 }
3528 }
3529 image_view=DestroyCacheView(image_view);
3530 return(MagickTrue);
3531 }
3532 /*
3533 Negate image.
3534 */
cristyb5d5f722009-11-04 03:03:49 +00003535#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy9a5a52f2012-10-09 14:40:31 +00003536 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy5e6b2592012-12-19 14:08:11 +00003537 magick_threads(image,image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00003538#endif
cristybb503372010-05-27 20:51:26 +00003539 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003540 {
cristy4c08aed2011-07-01 19:47:50 +00003541 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003542 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003543
cristy8d4629b2010-08-30 17:59:46 +00003544 register ssize_t
3545 x;
3546
anthonya322a832013-04-27 06:28:03 +00003547 if( IfMagickFalse(status) )
cristy3ed852e2009-09-05 21:47:34 +00003548 continue;
3549 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00003550 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003551 {
3552 status=MagickFalse;
3553 continue;
3554 }
cristybb503372010-05-27 20:51:26 +00003555 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003556 {
cristyf7dc44c2011-07-20 14:41:15 +00003557 register ssize_t
3558 i;
3559
cristy883fde12013-04-08 00:50:13 +00003560 if (GetPixelReadMask(image,q) == 0)
cristyd09f8802012-02-04 16:44:10 +00003561 {
3562 q+=GetPixelChannels(image);
3563 continue;
3564 }
cristya30d9ba2011-07-23 21:00:48 +00003565 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristyf7dc44c2011-07-20 14:41:15 +00003566 {
cristy5a23c552013-02-13 14:34:28 +00003567 PixelChannel channel=GetPixelChannelChannel(image,i);
3568 PixelTrait traits=GetPixelChannelTraits(image,channel);
cristyd09f8802012-02-04 16:44:10 +00003569 if ((traits & UpdatePixelTrait) == 0)
cristyec9e3a62012-02-01 02:09:32 +00003570 continue;
3571 q[i]=QuantumRange-q[i];
cristyf7dc44c2011-07-20 14:41:15 +00003572 }
cristya30d9ba2011-07-23 21:00:48 +00003573 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003574 }
anthonya322a832013-04-27 06:28:03 +00003575 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
cristy3ed852e2009-09-05 21:47:34 +00003576 status=MagickFalse;
3577 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3578 {
3579 MagickBooleanType
3580 proceed;
3581
cristyb5d5f722009-11-04 03:03:49 +00003582#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00003583 #pragma omp critical (MagickCore_NegateImage)
cristy3ed852e2009-09-05 21:47:34 +00003584#endif
3585 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
anthonya322a832013-04-27 06:28:03 +00003586 if( IfMagickFalse(proceed) )
cristy3ed852e2009-09-05 21:47:34 +00003587 status=MagickFalse;
3588 }
3589 }
3590 image_view=DestroyCacheView(image_view);
3591 return(status);
3592}
3593
3594/*
3595%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3596% %
3597% %
3598% %
3599% N o r m a l i z e I m a g e %
3600% %
3601% %
3602% %
3603%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3604%
anthony207ce472012-04-04 06:21:26 +00003605% The NormalizeImage() method enhances the contrast of a color image by
3606% mapping the darkest 2 percent of all pixel to black and the brightest
3607% 1 percent to white.
cristy3ed852e2009-09-05 21:47:34 +00003608%
3609% The format of the NormalizeImage method is:
3610%
cristye23ec9d2011-08-16 18:15:40 +00003611% MagickBooleanType NormalizeImage(Image *image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003612%
3613% A description of each parameter follows:
3614%
3615% o image: the image.
3616%
cristye23ec9d2011-08-16 18:15:40 +00003617% o exception: return any errors or warnings in this structure.
3618%
cristy3ed852e2009-09-05 21:47:34 +00003619*/
cristye23ec9d2011-08-16 18:15:40 +00003620MagickExport MagickBooleanType NormalizeImage(Image *image,
3621 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003622{
cristy3ed852e2009-09-05 21:47:34 +00003623 double
3624 black_point,
3625 white_point;
3626
cristy530239c2010-07-25 17:34:26 +00003627 black_point=(double) image->columns*image->rows*0.0015;
3628 white_point=(double) image->columns*image->rows*0.9995;
cristye23ec9d2011-08-16 18:15:40 +00003629 return(ContrastStretchImage(image,black_point,white_point,exception));
cristy3ed852e2009-09-05 21:47:34 +00003630}
3631
3632/*
3633%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3634% %
3635% %
3636% %
3637% S i g m o i d a l C o n t r a s t I m a g e %
3638% %
3639% %
3640% %
3641%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3642%
3643% SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3644% sigmoidal contrast algorithm. Increase the contrast of the image using a
3645% sigmoidal transfer function without saturating highlights or shadows.
3646% Contrast indicates how much to increase the contrast (0 is none; 3 is
anthony207ce472012-04-04 06:21:26 +00003647% typical; 20 is pushing it); mid-point indicates where midtones fall in the
3648% resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3649% sharpen to MagickTrue to increase the image contrast otherwise the contrast
3650% is reduced.
cristy3ed852e2009-09-05 21:47:34 +00003651%
3652% The format of the SigmoidalContrastImage method is:
3653%
3654% MagickBooleanType SigmoidalContrastImage(Image *image,
cristy33bd5152011-08-24 01:42:24 +00003655% const MagickBooleanType sharpen,const char *levels,
3656% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003657%
3658% A description of each parameter follows:
3659%
3660% o image: the image.
3661%
cristy3ed852e2009-09-05 21:47:34 +00003662% o sharpen: Increase or decrease image contrast.
3663%
nicolas07299f12012-09-10 17:30:07 +00003664% o contrast: strength of the contrast, the larger the number the more
cristyfa769582010-09-30 23:30:03 +00003665% 'threshold-like' it becomes.
cristy3ed852e2009-09-05 21:47:34 +00003666%
nicolas1de98ba2012-09-10 17:34:35 +00003667% o midpoint: midpoint of the function as a color value 0 to QuantumRange.
cristy3ed852e2009-09-05 21:47:34 +00003668%
cristy33bd5152011-08-24 01:42:24 +00003669% o exception: return any errors or warnings in this structure.
3670%
cristy3ed852e2009-09-05 21:47:34 +00003671*/
nicolas678607b2012-09-10 20:05:40 +00003672
nicolas6457d9f2012-09-12 14:40:01 +00003673/*
nicolasef20d0e2012-09-18 16:05:53 +00003674 ImageMagick 6 has a version of this function which uses LUTs.
3675*/
3676
3677/*
nicolas4d6c5342012-09-12 14:59:55 +00003678 Sigmoidal function Sigmoidal with inflexion point moved to b and "slope
3679 constant" set to a.
3680
nicolas6457d9f2012-09-12 14:40:01 +00003681 The first version, based on the hyperbolic tangent tanh, when combined with
3682 the scaling step, is an exact arithmetic clone of the the sigmoid function
3683 based on the logistic curve. The equivalence is based on the identity
3684
nicolas4d6c5342012-09-12 14:59:55 +00003685 1/(1+exp(-t)) = (1+tanh(t/2))/2
nicolas6457d9f2012-09-12 14:40:01 +00003686
nicolas4d6c5342012-09-12 14:59:55 +00003687 (http://de.wikipedia.org/wiki/Sigmoidfunktion) and the fact that the
3688 scaled sigmoidal derivation is invariant under affine transformations of
3689 the ordinate.
3690
3691 The tanh version is almost certainly more accurate and cheaper. The 0.5
3692 factor in the argument is to clone the legacy ImageMagick behavior. The
3693 reason for making the define depend on atanh even though it only uses tanh
3694 has to do with the construction of the inverse of the scaled sigmoidal.
nicolas6457d9f2012-09-12 14:40:01 +00003695*/
nicolase3466022012-09-04 13:51:46 +00003696#if defined(MAGICKCORE_HAVE_ATANH)
nicolas07299f12012-09-10 17:30:07 +00003697#define Sigmoidal(a,b,x) ( tanh((0.5*(a))*((x)-(b))) )
nicolase3466022012-09-04 13:51:46 +00003698#else
nicolas07299f12012-09-10 17:30:07 +00003699#define Sigmoidal(a,b,x) ( 1.0/(1.0+exp((a)*((b)-(x)))) )
nicolase3466022012-09-04 13:51:46 +00003700#endif
cristy5eab5352012-09-12 13:12:43 +00003701/*
nicolas6457d9f2012-09-12 14:40:01 +00003702 Scaled sigmoidal function:
cristy5eab5352012-09-12 13:12:43 +00003703
nicolas07299f12012-09-10 17:30:07 +00003704 ( Sigmoidal(a,b,x) - Sigmoidal(a,b,0) ) /
3705 ( Sigmoidal(a,b,1) - Sigmoidal(a,b,0) )
cristy5eab5352012-09-12 13:12:43 +00003706
3707 See http://osdir.com/ml/video.image-magick.devel/2005-04/msg00006.html and
3708 http://www.cs.dartmouth.edu/farid/downloads/tutorials/fip.pdf. The limit
3709 of ScaledSigmoidal as a->0 is the identity, but a=0 gives a division by
nicolas1f1584e2012-09-12 15:20:56 +00003710 zero. This is fixed below by exiting immediately when contrast is small,
cristy5eab5352012-09-12 13:12:43 +00003711 leaving the image (or colormap) unmodified. This appears to be safe because
3712 the series expansion of the logistic sigmoidal function around x=b is
nicolas6457d9f2012-09-12 14:40:01 +00003713
3714 1/2-a*(b-x)/4+...
3715
3716 so that the key denominator s(1)-s(0) is about a/4 (a/2 with tanh).
cristy5eab5352012-09-12 13:12:43 +00003717*/
nicolas07299f12012-09-10 17:30:07 +00003718#define ScaledSigmoidal(a,b,x) ( \
nicolasf0158cf2012-09-10 20:11:02 +00003719 (Sigmoidal((a),(b),(x))-Sigmoidal((a),(b),0.0)) / \
nicolas07299f12012-09-10 17:30:07 +00003720 (Sigmoidal((a),(b),1.0)-Sigmoidal((a),(b),0.0)) )
cristy5eab5352012-09-12 13:12:43 +00003721/*
nicolas6457d9f2012-09-12 14:40:01 +00003722 Inverse of ScaledSigmoidal, used for +sigmoidal-contrast. Because b
3723 may be 0 or 1, the argument of the hyperbolic tangent (resp. logistic
3724 sigmoidal) may be outside of the interval (-1,1) (resp. (0,1)), even
3725 when creating a LUT from in gamut values, hence the branching. In
3726 addition, HDRI may have out of gamut values.
nicolas7922b462012-09-12 14:50:49 +00003727 InverseScaledSigmoidal is not a two-sided inverse of ScaledSigmoidal:
cristy5eab5352012-09-12 13:12:43 +00003728 It is only a right inverse. This is unavoidable.
3729*/
cristy5eab5352012-09-12 13:12:43 +00003730static inline double InverseScaledSigmoidal(const double a,const double b,
3731 const double x)
3732{
nicolas6457d9f2012-09-12 14:40:01 +00003733 const double sig0=Sigmoidal(a,b,0.0);
nicolasb45b69a2012-09-15 20:00:31 +00003734 const double sig1=Sigmoidal(a,b,1.0);
3735 const double argument=(sig1-sig0)*x+sig0;
nicolas6457d9f2012-09-12 14:40:01 +00003736 const double clamped=
3737 (
nicolas196d63c2012-09-12 14:46:01 +00003738#if defined(MAGICKCORE_HAVE_ATANH)
nicolas6457d9f2012-09-12 14:40:01 +00003739 argument < -1+MagickEpsilon
3740 ?
3741 -1+MagickEpsilon
3742 :
3743 ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
3744 );
3745 return(b+(2.0/a)*atanh(clamped));
nicolase3466022012-09-04 13:51:46 +00003746#else
nicolas6457d9f2012-09-12 14:40:01 +00003747 argument < MagickEpsilon
3748 ?
3749 MagickEpsilon
3750 :
3751 ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
3752 );
nicolas02c9a682012-09-15 20:01:56 +00003753 return(b-log(1.0/clamped-1.0)/a);
nicolase3466022012-09-04 13:51:46 +00003754#endif
nicolas196d63c2012-09-12 14:46:01 +00003755}
cristy5eab5352012-09-12 13:12:43 +00003756
3757MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
3758 const MagickBooleanType sharpen,const double contrast,const double midpoint,
3759 ExceptionInfo *exception)
3760{
3761#define SigmoidalContrastImageTag "SigmoidalContrast/Image"
nicolasf94a4f42012-09-11 09:18:55 +00003762#define ScaledSig(x) ( ClampToQuantum(QuantumRange* \
cristyaeded782012-09-11 23:39:36 +00003763 ScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale*(x))) )
nicolasf94a4f42012-09-11 09:18:55 +00003764#define InverseScaledSig(x) ( ClampToQuantum(QuantumRange* \
cristyaeded782012-09-11 23:39:36 +00003765 InverseScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale*(x))) )
nicolas07299f12012-09-10 17:30:07 +00003766
cristy5eab5352012-09-12 13:12:43 +00003767 CacheView
3768 *image_view;
3769
3770 MagickBooleanType
3771 status;
3772
3773 MagickOffsetType
3774 progress;
3775
3776 ssize_t
3777 y;
3778
3779 /*
3780 Convenience macros.
3781 */
nicolas33c76712012-08-08 17:15:05 +00003782 assert(image != (Image *) NULL);
3783 assert(image->signature == MagickSignature);
anthonya322a832013-04-27 06:28:03 +00003784 if( IfMagickTrue(image->debug) )
nicolas33c76712012-08-08 17:15:05 +00003785 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
nicolas07299f12012-09-10 17:30:07 +00003786 /*
nicolas6457d9f2012-09-12 14:40:01 +00003787 Side effect: may clamp values unless contrast<MagickEpsilon, in which
cristy5eab5352012-09-12 13:12:43 +00003788 case nothing is done.
3789 */
3790 if (contrast < MagickEpsilon)
3791 return(MagickTrue);
3792 /*
nicolas4ff353c2012-09-10 20:15:40 +00003793 Sigmoidal-contrast enhance colormap.
nicolas07299f12012-09-10 17:30:07 +00003794 */
cristy3ed852e2009-09-05 21:47:34 +00003795 if (image->storage_class == PseudoClass)
nicolasfd74ae32012-08-08 16:00:11 +00003796 {
nicolasb06434e2012-09-10 18:06:15 +00003797 register ssize_t
cristyaeded782012-09-11 23:39:36 +00003798 i;
nicolas07299f12012-09-10 17:30:07 +00003799
anthonya322a832013-04-27 06:28:03 +00003800 if( IfMagickTrue(sharpen) )
nicolas80ad4a62012-09-11 15:17:16 +00003801 for (i=0; i < (ssize_t) image->colors; i++)
3802 {
3803 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy6bbed7e2013-04-03 23:59:10 +00003804 image->colormap[i].red=(MagickRealType) ScaledSig(
3805 image->colormap[i].red);
cristyaeded782012-09-11 23:39:36 +00003806 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy6bbed7e2013-04-03 23:59:10 +00003807 image->colormap[i].green=(MagickRealType) ScaledSig(
3808 image->colormap[i].green);
cristyaeded782012-09-11 23:39:36 +00003809 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy6bbed7e2013-04-03 23:59:10 +00003810 image->colormap[i].blue=(MagickRealType) ScaledSig(
3811 image->colormap[i].blue);
cristyaeded782012-09-11 23:39:36 +00003812 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy6bbed7e2013-04-03 23:59:10 +00003813 image->colormap[i].alpha=(MagickRealType) ScaledSig(
3814 image->colormap[i].alpha);
cristyaeded782012-09-11 23:39:36 +00003815 }
nicolasb06434e2012-09-10 18:06:15 +00003816 else
nicolas80ad4a62012-09-11 15:17:16 +00003817 for (i=0; i < (ssize_t) image->colors; i++)
cristyaeded782012-09-11 23:39:36 +00003818 {
3819 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy6bbed7e2013-04-03 23:59:10 +00003820 image->colormap[i].red=(MagickRealType) InverseScaledSig(
3821 image->colormap[i].red);
cristyaeded782012-09-11 23:39:36 +00003822 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy6bbed7e2013-04-03 23:59:10 +00003823 image->colormap[i].green=(MagickRealType) InverseScaledSig(
3824 image->colormap[i].green);
cristyaeded782012-09-11 23:39:36 +00003825 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy6bbed7e2013-04-03 23:59:10 +00003826 image->colormap[i].blue=(MagickRealType) InverseScaledSig(
3827 image->colormap[i].blue);
nicolas80ad4a62012-09-11 15:17:16 +00003828 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy6bbed7e2013-04-03 23:59:10 +00003829 image->colormap[i].alpha=(MagickRealType) InverseScaledSig(
3830 image->colormap[i].alpha);
cristyaeded782012-09-11 23:39:36 +00003831 }
nicolasfd74ae32012-08-08 16:00:11 +00003832 }
cristy3ed852e2009-09-05 21:47:34 +00003833 /*
nicolas4ff353c2012-09-10 20:15:40 +00003834 Sigmoidal-contrast enhance image.
cristy3ed852e2009-09-05 21:47:34 +00003835 */
3836 status=MagickTrue;
3837 progress=0;
cristy46ff2672012-12-14 15:32:26 +00003838 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +00003839#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00003840 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy5e6b2592012-12-19 14:08:11 +00003841 magick_threads(image,image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00003842#endif
cristybb503372010-05-27 20:51:26 +00003843 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003844 {
cristy4c08aed2011-07-01 19:47:50 +00003845 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003846 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003847
cristy8d4629b2010-08-30 17:59:46 +00003848 register ssize_t
3849 x;
3850
anthonya322a832013-04-27 06:28:03 +00003851 if( IfMagickFalse(status) )
cristy3ed852e2009-09-05 21:47:34 +00003852 continue;
3853 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00003854 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003855 {
3856 status=MagickFalse;
3857 continue;
3858 }
cristybb503372010-05-27 20:51:26 +00003859 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003860 {
cristy33bd5152011-08-24 01:42:24 +00003861 register ssize_t
cristyaeded782012-09-11 23:39:36 +00003862 i;
cristy33bd5152011-08-24 01:42:24 +00003863
cristy883fde12013-04-08 00:50:13 +00003864 if (GetPixelReadMask(image,q) == 0)
cristy10a6c612012-01-29 21:41:05 +00003865 {
3866 q+=GetPixelChannels(image);
3867 continue;
3868 }
cristy33bd5152011-08-24 01:42:24 +00003869 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3870 {
cristy5a23c552013-02-13 14:34:28 +00003871 PixelChannel channel=GetPixelChannelChannel(image,i);
3872 PixelTrait traits=GetPixelChannelTraits(image,channel);
cristyd09f8802012-02-04 16:44:10 +00003873 if ((traits & UpdatePixelTrait) == 0)
3874 continue;
anthonya322a832013-04-27 06:28:03 +00003875 if( IfMagickTrue(sharpen) )
cristyaeded782012-09-11 23:39:36 +00003876 q[i]=ScaledSig(q[i]);
3877 else
3878 q[i]=InverseScaledSig(q[i]);
cristy33bd5152011-08-24 01:42:24 +00003879 }
cristyed231572011-07-14 02:18:59 +00003880 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003881 }
anthonya322a832013-04-27 06:28:03 +00003882 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
cristy3ed852e2009-09-05 21:47:34 +00003883 status=MagickFalse;
3884 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3885 {
3886 MagickBooleanType
3887 proceed;
3888
cristyb5d5f722009-11-04 03:03:49 +00003889#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00003890 #pragma omp critical (MagickCore_SigmoidalContrastImage)
cristy3ed852e2009-09-05 21:47:34 +00003891#endif
3892 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3893 image->rows);
anthonya322a832013-04-27 06:28:03 +00003894 if( IfMagickFalse(proceed) )
cristy3ed852e2009-09-05 21:47:34 +00003895 status=MagickFalse;
3896 }
3897 }
3898 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00003899 return(status);
3900}