blob: 737e2a95837c51f0092ac65c27d48aec4cc2ce8d [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 }
anthonya322a832013-04-27 06:28:03 +0000155 return(status);
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));
cristya19f1d72012-08-07 18:24:38 +00001516 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,
cristyf45fec72011-08-23 16:02:32 +00001517 GetPixelChannels(image)*sizeof(*histogram));
cristya19f1d72012-08-07 18:24:38 +00001518 map=(double *) AcquireQuantumMemory(MaxMap+1UL,
cristyf45fec72011-08-23 16:02:32 +00001519 GetPixelChannels(image)*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 }
cristybf2eb892013-06-07 00:33:05 +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 }
cristybf2eb892013-06-07 00:33:05 +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);
2491 return(status);
2492}
2493
2494/*
2495%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2496% %
2497% %
2498% %
cristy33bd5152011-08-24 01:42:24 +00002499% L e v e l i z e I m a g e %
cristy3ed852e2009-09-05 21:47:34 +00002500% %
2501% %
2502% %
2503%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2504%
cristy50fbc382011-07-07 02:19:17 +00002505% LevelizeImage() applies the reversed LevelImage() operation to just
cristy3ed852e2009-09-05 21:47:34 +00002506% the specific channels specified. It compresses the full range of color
2507% values, so that they lie between the given black and white points. Gamma is
2508% applied before the values are mapped.
2509%
cristy50fbc382011-07-07 02:19:17 +00002510% LevelizeImage() can be called with by using a +level command line
cristy3ed852e2009-09-05 21:47:34 +00002511% API option, or using a '!' on a -level or LevelImage() geometry string.
2512%
anthony31f1bf72012-01-30 12:37:22 +00002513% It can be used to de-contrast a greyscale image to the exact levels
2514% specified. Or by using specific levels for each channel of an image you
2515% can convert a gray-scale image to any linear color gradient, according to
2516% those levels.
cristy3ed852e2009-09-05 21:47:34 +00002517%
cristy50fbc382011-07-07 02:19:17 +00002518% The format of the LevelizeImage method is:
cristy3ed852e2009-09-05 21:47:34 +00002519%
cristy50fbc382011-07-07 02:19:17 +00002520% MagickBooleanType LevelizeImage(Image *image,const double black_point,
cristy7c0a0a42011-08-23 17:57:25 +00002521% const double white_point,const double gamma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002522%
2523% A description of each parameter follows:
2524%
2525% o image: the image.
2526%
cristy3ed852e2009-09-05 21:47:34 +00002527% o black_point: The level to map zero (black) to.
2528%
cristy01e9afd2011-08-10 17:38:41 +00002529% o white_point: The level to map QuantumRange (white) to.
cristy3ed852e2009-09-05 21:47:34 +00002530%
2531% o gamma: adjust gamma by this factor before mapping values.
2532%
cristy7c0a0a42011-08-23 17:57:25 +00002533% o exception: return any errors or warnings in this structure.
2534%
cristy3ed852e2009-09-05 21:47:34 +00002535*/
cristyd1a2c0f2011-02-09 14:14:50 +00002536MagickExport MagickBooleanType LevelizeImage(Image *image,
cristy7c0a0a42011-08-23 17:57:25 +00002537 const double black_point,const double white_point,const double gamma,
2538 ExceptionInfo *exception)
cristyd1a2c0f2011-02-09 14:14:50 +00002539{
cristy3ed852e2009-09-05 21:47:34 +00002540#define LevelizeImageTag "Levelize/Image"
cristy00d4fee2013-04-23 18:35:34 +00002541#define LevelizeValue(x) ClampToQuantum(((MagickRealType) gamma_pow((double) \
2542 (QuantumScale*(x)),gamma))*(white_point-black_point)+black_point)
cristy3ed852e2009-09-05 21:47:34 +00002543
cristyc4c8d132010-01-07 01:58:38 +00002544 CacheView
2545 *image_view;
2546
cristy3ed852e2009-09-05 21:47:34 +00002547 MagickBooleanType
2548 status;
2549
cristybb503372010-05-27 20:51:26 +00002550 MagickOffsetType
2551 progress;
2552
2553 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002554 i;
2555
cristybb503372010-05-27 20:51:26 +00002556 ssize_t
2557 y;
2558
cristy3ed852e2009-09-05 21:47:34 +00002559 /*
2560 Allocate and initialize levels map.
2561 */
2562 assert(image != (Image *) NULL);
2563 assert(image->signature == MagickSignature);
anthonya322a832013-04-27 06:28:03 +00002564 if( IfMagickTrue(image->debug) )
cristy3ed852e2009-09-05 21:47:34 +00002565 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2566 if (image->storage_class == PseudoClass)
cristybb503372010-05-27 20:51:26 +00002567 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002568 {
2569 /*
2570 Level colormap.
2571 */
cristyed231572011-07-14 02:18:59 +00002572 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristya6400b12013-03-15 12:20:18 +00002573 image->colormap[i].red=(double) LevelizeValue(image->colormap[i].red);
cristyed231572011-07-14 02:18:59 +00002574 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00002575 image->colormap[i].green=(double) LevelizeValue(
2576 image->colormap[i].green);
cristyed231572011-07-14 02:18:59 +00002577 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristya6400b12013-03-15 12:20:18 +00002578 image->colormap[i].blue=(double) LevelizeValue(image->colormap[i].blue);
cristyed231572011-07-14 02:18:59 +00002579 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00002580 image->colormap[i].alpha=(double) LevelizeValue(
2581 image->colormap[i].alpha);
cristy3ed852e2009-09-05 21:47:34 +00002582 }
2583 /*
2584 Level image.
2585 */
2586 status=MagickTrue;
2587 progress=0;
cristy46ff2672012-12-14 15:32:26 +00002588 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +00002589#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00002590 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy5e6b2592012-12-19 14:08:11 +00002591 magick_threads(image,image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00002592#endif
cristybb503372010-05-27 20:51:26 +00002593 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002594 {
cristy4c08aed2011-07-01 19:47:50 +00002595 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002596 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002597
cristy8d4629b2010-08-30 17:59:46 +00002598 register ssize_t
2599 x;
2600
anthonya322a832013-04-27 06:28:03 +00002601 if( IfMagickFalse(status) )
cristy3ed852e2009-09-05 21:47:34 +00002602 continue;
2603 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00002604 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002605 {
2606 status=MagickFalse;
2607 continue;
2608 }
cristybb503372010-05-27 20:51:26 +00002609 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002610 {
cristy7c0a0a42011-08-23 17:57:25 +00002611 register ssize_t
2612 i;
2613
cristy883fde12013-04-08 00:50:13 +00002614 if (GetPixelReadMask(image,q) == 0)
cristy10a6c612012-01-29 21:41:05 +00002615 {
2616 q+=GetPixelChannels(image);
2617 continue;
2618 }
cristy7c0a0a42011-08-23 17:57:25 +00002619 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2620 {
cristy5a23c552013-02-13 14:34:28 +00002621 PixelChannel channel=GetPixelChannelChannel(image,i);
2622 PixelTrait traits=GetPixelChannelTraits(image,channel);
cristyd09f8802012-02-04 16:44:10 +00002623 if ((traits & UpdatePixelTrait) == 0)
cristyb8b0d162011-12-18 23:41:28 +00002624 continue;
cristy780e9ef2011-12-18 23:33:50 +00002625 q[i]=LevelizeValue(q[i]);
cristy7c0a0a42011-08-23 17:57:25 +00002626 }
cristyed231572011-07-14 02:18:59 +00002627 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002628 }
anthonya322a832013-04-27 06:28:03 +00002629 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
cristy3ed852e2009-09-05 21:47:34 +00002630 status=MagickFalse;
2631 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2632 {
2633 MagickBooleanType
2634 proceed;
2635
cristyb5d5f722009-11-04 03:03:49 +00002636#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00002637 #pragma omp critical (MagickCore_LevelizeImage)
cristy3ed852e2009-09-05 21:47:34 +00002638#endif
2639 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
anthonya322a832013-04-27 06:28:03 +00002640 if( IfMagickFalse(proceed) )
cristy3ed852e2009-09-05 21:47:34 +00002641 status=MagickFalse;
2642 }
2643 }
cristy8d4629b2010-08-30 17:59:46 +00002644 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002645 return(status);
2646}
2647
2648/*
2649%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2650% %
2651% %
2652% %
2653% L e v e l I m a g e C o l o r s %
2654% %
2655% %
2656% %
2657%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2658%
cristy490408a2011-07-07 14:42:05 +00002659% LevelImageColors() maps the given color to "black" and "white" values,
cristyee0f8d72009-09-19 00:58:29 +00002660% linearly spreading out the colors, and level values on a channel by channel
2661% bases, as per LevelImage(). The given colors allows you to specify
glennrp1e7f7bc2011-03-02 19:25:28 +00002662% different level ranges for each of the color channels separately.
cristy3ed852e2009-09-05 21:47:34 +00002663%
2664% If the boolean 'invert' is set true the image values will modifyed in the
2665% reverse direction. That is any existing "black" and "white" colors in the
2666% image will become the color values given, with all other values compressed
2667% appropriatally. This effectivally maps a greyscale gradient into the given
2668% color gradient.
2669%
cristy490408a2011-07-07 14:42:05 +00002670% The format of the LevelImageColors method is:
cristy3ed852e2009-09-05 21:47:34 +00002671%
cristy490408a2011-07-07 14:42:05 +00002672% MagickBooleanType LevelImageColors(Image *image,
2673% const PixelInfo *black_color,const PixelInfo *white_color,
cristy7c0a0a42011-08-23 17:57:25 +00002674% const MagickBooleanType invert,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002675%
2676% A description of each parameter follows:
2677%
2678% o image: the image.
2679%
cristy3ed852e2009-09-05 21:47:34 +00002680% o black_color: The color to map black to/from
2681%
2682% o white_point: The color to map white to/from
2683%
2684% o invert: if true map the colors (levelize), rather than from (level)
2685%
cristy7c0a0a42011-08-23 17:57:25 +00002686% o exception: return any errors or warnings in this structure.
2687%
cristy3ed852e2009-09-05 21:47:34 +00002688*/
cristy490408a2011-07-07 14:42:05 +00002689MagickExport MagickBooleanType LevelImageColors(Image *image,
cristy4c08aed2011-07-01 19:47:50 +00002690 const PixelInfo *black_color,const PixelInfo *white_color,
cristy7c0a0a42011-08-23 17:57:25 +00002691 const MagickBooleanType invert,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002692{
cristybd5a96c2011-08-21 00:04:26 +00002693 ChannelType
2694 channel_mask;
2695
cristy3ed852e2009-09-05 21:47:34 +00002696 MagickStatusType
2697 status;
2698
2699 /*
2700 Allocate and initialize levels map.
2701 */
2702 assert(image != (Image *) NULL);
2703 assert(image->signature == MagickSignature);
anthonya322a832013-04-27 06:28:03 +00002704 if( IfMagickTrue(image->debug) )
cristy3ed852e2009-09-05 21:47:34 +00002705 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
anthonya322a832013-04-27 06:28:03 +00002706 if( IfMagickTrue(IsGrayColorspace(image->colorspace)) &&
2707 (IfMagickFalse(IsGrayColorspace(black_color->colorspace)) ||
2708 IfMagickFalse(IsGrayColorspace(white_color->colorspace))))
cristy0c81d062013-04-21 15:22:02 +00002709 (void) SetImageColorspace(image,sRGBColorspace,exception);
cristy3ed852e2009-09-05 21:47:34 +00002710 status=MagickFalse;
anthonya322a832013-04-27 06:28:03 +00002711 if( IfMagickFalse(invert) )
cristy3ed852e2009-09-05 21:47:34 +00002712 {
cristyed231572011-07-14 02:18:59 +00002713 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002714 {
cristycf1296e2012-08-26 23:40:49 +00002715 channel_mask=SetImageChannelMask(image,RedChannel);
cristy01e9afd2011-08-10 17:38:41 +00002716 status|=LevelImage(image,black_color->red,white_color->red,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002717 exception);
cristycf1296e2012-08-26 23:40:49 +00002718 (void) SetImageChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002719 }
cristyed231572011-07-14 02:18:59 +00002720 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002721 {
cristycf1296e2012-08-26 23:40:49 +00002722 channel_mask=SetImageChannelMask(image,GreenChannel);
cristy01e9afd2011-08-10 17:38:41 +00002723 status|=LevelImage(image,black_color->green,white_color->green,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002724 exception);
cristycf1296e2012-08-26 23:40:49 +00002725 (void) SetImageChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002726 }
cristyed231572011-07-14 02:18:59 +00002727 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002728 {
cristycf1296e2012-08-26 23:40:49 +00002729 channel_mask=SetImageChannelMask(image,BlueChannel);
cristy01e9afd2011-08-10 17:38:41 +00002730 status|=LevelImage(image,black_color->blue,white_color->blue,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002731 exception);
cristycf1296e2012-08-26 23:40:49 +00002732 (void) SetImageChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002733 }
cristyed231572011-07-14 02:18:59 +00002734 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002735 (image->colorspace == CMYKColorspace))
cristyf89cb1d2011-07-07 01:24:37 +00002736 {
cristycf1296e2012-08-26 23:40:49 +00002737 channel_mask=SetImageChannelMask(image,BlackChannel);
cristy01e9afd2011-08-10 17:38:41 +00002738 status|=LevelImage(image,black_color->black,white_color->black,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002739 exception);
cristycf1296e2012-08-26 23:40:49 +00002740 (void) SetImageChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002741 }
cristyed231572011-07-14 02:18:59 +00002742 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy8a46d822012-08-28 23:32:39 +00002743 (image->alpha_trait == BlendPixelTrait))
cristyf89cb1d2011-07-07 01:24:37 +00002744 {
cristycf1296e2012-08-26 23:40:49 +00002745 channel_mask=SetImageChannelMask(image,AlphaChannel);
cristy01e9afd2011-08-10 17:38:41 +00002746 status|=LevelImage(image,black_color->alpha,white_color->alpha,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002747 exception);
cristycf1296e2012-08-26 23:40:49 +00002748 (void) SetImageChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002749 }
cristy3ed852e2009-09-05 21:47:34 +00002750 }
2751 else
2752 {
cristyed231572011-07-14 02:18:59 +00002753 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002754 {
cristycf1296e2012-08-26 23:40:49 +00002755 channel_mask=SetImageChannelMask(image,RedChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002756 status|=LevelizeImage(image,black_color->red,white_color->red,1.0,
2757 exception);
cristycf1296e2012-08-26 23:40:49 +00002758 (void) SetImageChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002759 }
cristyed231572011-07-14 02:18:59 +00002760 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002761 {
cristycf1296e2012-08-26 23:40:49 +00002762 channel_mask=SetImageChannelMask(image,GreenChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002763 status|=LevelizeImage(image,black_color->green,white_color->green,1.0,
2764 exception);
cristycf1296e2012-08-26 23:40:49 +00002765 (void) SetImageChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002766 }
cristyed231572011-07-14 02:18:59 +00002767 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002768 {
cristycf1296e2012-08-26 23:40:49 +00002769 channel_mask=SetImageChannelMask(image,BlueChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002770 status|=LevelizeImage(image,black_color->blue,white_color->blue,1.0,
2771 exception);
cristycf1296e2012-08-26 23:40:49 +00002772 (void) SetImageChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002773 }
cristyed231572011-07-14 02:18:59 +00002774 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002775 (image->colorspace == CMYKColorspace))
cristyf89cb1d2011-07-07 01:24:37 +00002776 {
cristycf1296e2012-08-26 23:40:49 +00002777 channel_mask=SetImageChannelMask(image,BlackChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002778 status|=LevelizeImage(image,black_color->black,white_color->black,1.0,
2779 exception);
cristycf1296e2012-08-26 23:40:49 +00002780 (void) SetImageChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002781 }
cristyed231572011-07-14 02:18:59 +00002782 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy8a46d822012-08-28 23:32:39 +00002783 (image->alpha_trait == BlendPixelTrait))
cristyf89cb1d2011-07-07 01:24:37 +00002784 {
cristycf1296e2012-08-26 23:40:49 +00002785 channel_mask=SetImageChannelMask(image,AlphaChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002786 status|=LevelizeImage(image,black_color->alpha,white_color->alpha,1.0,
2787 exception);
cristycf1296e2012-08-26 23:40:49 +00002788 (void) SetImageChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002789 }
cristy3ed852e2009-09-05 21:47:34 +00002790 }
anthonya322a832013-04-27 06:28:03 +00002791 return(status);
cristy3ed852e2009-09-05 21:47:34 +00002792}
2793
2794/*
2795%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2796% %
2797% %
2798% %
2799% L i n e a r S t r e t c h I m a g e %
2800% %
2801% %
2802% %
2803%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2804%
cristyf1611782011-08-01 15:39:13 +00002805% LinearStretchImage() discards any pixels below the black point and above
2806% the white point and levels the remaining pixels.
cristy3ed852e2009-09-05 21:47:34 +00002807%
2808% The format of the LinearStretchImage method is:
2809%
2810% MagickBooleanType LinearStretchImage(Image *image,
cristy33bd5152011-08-24 01:42:24 +00002811% const double black_point,const double white_point,
2812% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002813%
2814% A description of each parameter follows:
2815%
2816% o image: the image.
2817%
2818% o black_point: the black point.
2819%
2820% o white_point: the white point.
2821%
cristy33bd5152011-08-24 01:42:24 +00002822% o exception: return any errors or warnings in this structure.
2823%
cristy3ed852e2009-09-05 21:47:34 +00002824*/
2825MagickExport MagickBooleanType LinearStretchImage(Image *image,
cristy33bd5152011-08-24 01:42:24 +00002826 const double black_point,const double white_point,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002827{
2828#define LinearStretchImageTag "LinearStretch/Image"
2829
cristy33bd5152011-08-24 01:42:24 +00002830 CacheView
2831 *image_view;
cristy3ed852e2009-09-05 21:47:34 +00002832
cristya19f1d72012-08-07 18:24:38 +00002833 double
cristy3ed852e2009-09-05 21:47:34 +00002834 *histogram,
2835 intensity;
2836
cristy1c2f48d2012-12-14 01:20:55 +00002837 MagickBooleanType
2838 status;
2839
cristy8d4629b2010-08-30 17:59:46 +00002840 ssize_t
2841 black,
2842 white,
2843 y;
2844
cristy3ed852e2009-09-05 21:47:34 +00002845 /*
2846 Allocate histogram and linear map.
2847 */
2848 assert(image != (Image *) NULL);
2849 assert(image->signature == MagickSignature);
cristy1c2f48d2012-12-14 01:20:55 +00002850 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*histogram));
cristya19f1d72012-08-07 18:24:38 +00002851 if (histogram == (double *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002852 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2853 image->filename);
2854 /*
2855 Form histogram.
2856 */
2857 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
cristy46ff2672012-12-14 15:32:26 +00002858 image_view=AcquireVirtualCacheView(image,exception);
cristybb503372010-05-27 20:51:26 +00002859 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002860 {
cristy4c08aed2011-07-01 19:47:50 +00002861 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00002862 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002863
cristybb503372010-05-27 20:51:26 +00002864 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002865 x;
2866
cristy33bd5152011-08-24 01:42:24 +00002867 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002868 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002869 break;
cristy33bd5152011-08-24 01:42:24 +00002870 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002871 {
cristyf13c5942012-08-08 23:50:11 +00002872 double
2873 intensity;
2874
2875 intensity=GetPixelIntensity(image,p);
2876 histogram[ScaleQuantumToMap(ClampToQuantum(intensity))]++;
cristyed231572011-07-14 02:18:59 +00002877 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002878 }
2879 }
cristy33bd5152011-08-24 01:42:24 +00002880 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002881 /*
2882 Find the histogram boundaries by locating the black and white point levels.
2883 */
cristy3ed852e2009-09-05 21:47:34 +00002884 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00002885 for (black=0; black < (ssize_t) MaxMap; black++)
cristy3ed852e2009-09-05 21:47:34 +00002886 {
2887 intensity+=histogram[black];
2888 if (intensity >= black_point)
2889 break;
2890 }
2891 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00002892 for (white=(ssize_t) MaxMap; white != 0; white--)
cristy3ed852e2009-09-05 21:47:34 +00002893 {
2894 intensity+=histogram[white];
2895 if (intensity >= white_point)
2896 break;
2897 }
cristya19f1d72012-08-07 18:24:38 +00002898 histogram=(double *) RelinquishMagickMemory(histogram);
cristyc82a27b2011-10-21 01:07:16 +00002899 status=LevelImage(image,(double) black,(double) white,1.0,exception);
cristy3ed852e2009-09-05 21:47:34 +00002900 return(status);
2901}
2902
2903/*
2904%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2905% %
2906% %
2907% %
2908% M o d u l a t e I m a g e %
2909% %
2910% %
2911% %
2912%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2913%
2914% ModulateImage() lets you control the brightness, saturation, and hue
2915% of an image. Modulate represents the brightness, saturation, and hue
2916% as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
cristye83f3402012-08-17 13:12:15 +00002917% modulation is lightness, saturation, and hue. For HWB, use blackness,
2918% whiteness, and hue. And for HCL, use chrome, luma, and hue.
cristy3ed852e2009-09-05 21:47:34 +00002919%
2920% The format of the ModulateImage method is:
2921%
cristy33bd5152011-08-24 01:42:24 +00002922% MagickBooleanType ModulateImage(Image *image,const char *modulate,
2923% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002924%
2925% A description of each parameter follows:
2926%
2927% o image: the image.
2928%
cristy33bd5152011-08-24 01:42:24 +00002929% o modulate: Define the percent change in brightness, saturation, and hue.
2930%
2931% o exception: return any errors or warnings in this structure.
cristy3ed852e2009-09-05 21:47:34 +00002932%
2933*/
2934
cristye83f3402012-08-17 13:12:15 +00002935static inline void ModulateHCL(const double percent_hue,
2936 const double percent_chroma,const double percent_luma,double *red,
2937 double *green,double *blue)
2938{
2939 double
2940 hue,
2941 luma,
2942 chroma;
2943
2944 /*
2945 Increase or decrease color luma, chroma, or hue.
2946 */
2947 ConvertRGBToHCL(*red,*green,*blue,&hue,&chroma,&luma);
2948 hue+=0.5*(0.01*percent_hue-1.0);
2949 while (hue < 0.0)
2950 hue+=1.0;
2951 while (hue > 1.0)
2952 hue-=1.0;
2953 chroma*=0.01*percent_chroma;
2954 luma*=0.01*percent_luma;
2955 ConvertHCLToRGB(hue,chroma,luma,red,green,blue);
2956}
2957
cristy9e2436a2013-05-02 20:35:59 +00002958static inline void ModulateHCLp(const double percent_hue,
2959 const double percent_chroma,const double percent_luma,double *red,
2960 double *green,double *blue)
2961{
2962 double
2963 hue,
2964 luma,
2965 chroma;
2966
2967 /*
2968 Increase or decrease color luma, chroma, or hue.
2969 */
2970 ConvertRGBToHCLp(*red,*green,*blue,&hue,&chroma,&luma);
2971 hue+=0.5*(0.01*percent_hue-1.0);
2972 while (hue < 0.0)
2973 hue+=1.0;
2974 while (hue > 1.0)
2975 hue-=1.0;
2976 chroma*=0.01*percent_chroma;
2977 luma*=0.01*percent_luma;
2978 ConvertHCLpToRGB(hue,chroma,luma,red,green,blue);
2979}
2980
cristye83f3402012-08-17 13:12:15 +00002981static inline void ModulateHSB(const double percent_hue,
cristy3094b7f2011-10-01 23:18:02 +00002982 const double percent_saturation,const double percent_brightness,double *red,
2983 double *green,double *blue)
cristy3ed852e2009-09-05 21:47:34 +00002984{
2985 double
2986 brightness,
2987 hue,
2988 saturation;
2989
2990 /*
2991 Increase or decrease color brightness, saturation, or hue.
2992 */
cristy0a39a5c2012-06-27 12:51:45 +00002993 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
cristy3ed852e2009-09-05 21:47:34 +00002994 hue+=0.5*(0.01*percent_hue-1.0);
2995 while (hue < 0.0)
2996 hue+=1.0;
2997 while (hue > 1.0)
2998 hue-=1.0;
2999 saturation*=0.01*percent_saturation;
3000 brightness*=0.01*percent_brightness;
cristy0a39a5c2012-06-27 12:51:45 +00003001 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
cristy3ed852e2009-09-05 21:47:34 +00003002}
3003
cristyaf64eb22013-05-02 14:07:10 +00003004static inline void ModulateHSI(const double percent_hue,
3005 const double percent_saturation,const double percent_intensity,double *red,
3006 double *green,double *blue)
3007{
3008 double
3009 intensity,
3010 hue,
3011 saturation;
3012
3013 /*
3014 Increase or decrease color intensity, saturation, or hue.
3015 */
3016 ConvertRGBToHSI(*red,*green,*blue,&hue,&saturation,&intensity);
3017 hue+=0.5*(0.01*percent_hue-1.0);
3018 while (hue < 0.0)
3019 hue+=1.0;
3020 while (hue > 1.0)
3021 hue-=1.0;
3022 saturation*=0.01*percent_saturation;
3023 intensity*=0.01*percent_intensity;
3024 ConvertHSIToRGB(hue,saturation,intensity,red,green,blue);
3025}
3026
cristye83f3402012-08-17 13:12:15 +00003027static inline void ModulateHSL(const double percent_hue,
cristy3094b7f2011-10-01 23:18:02 +00003028 const double percent_saturation,const double percent_lightness,double *red,
3029 double *green,double *blue)
cristy3ed852e2009-09-05 21:47:34 +00003030{
3031 double
3032 hue,
3033 lightness,
3034 saturation;
3035
3036 /*
3037 Increase or decrease color lightness, saturation, or hue.
3038 */
cristy0a39a5c2012-06-27 12:51:45 +00003039 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
cristy3ed852e2009-09-05 21:47:34 +00003040 hue+=0.5*(0.01*percent_hue-1.0);
3041 while (hue < 0.0)
3042 hue+=1.0;
cristye8f91f72013-04-05 00:50:58 +00003043 while (hue >= 1.0)
cristy3ed852e2009-09-05 21:47:34 +00003044 hue-=1.0;
3045 saturation*=0.01*percent_saturation;
3046 lightness*=0.01*percent_lightness;
cristy0a39a5c2012-06-27 12:51:45 +00003047 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
cristy3ed852e2009-09-05 21:47:34 +00003048}
3049
cristy246c3132013-05-02 16:35:53 +00003050static inline void ModulateHSV(const double percent_hue,
3051 const double percent_saturation,const double percent_value,double *red,
3052 double *green,double *blue)
3053{
3054 double
3055 hue,
3056 saturation,
3057 value;
3058
3059 /*
3060 Increase or decrease color value, saturation, or hue.
3061 */
3062 ConvertRGBToHSV(*red,*green,*blue,&hue,&saturation,&value);
3063 hue+=0.5*(0.01*percent_hue-1.0);
3064 while (hue < 0.0)
3065 hue+=1.0;
3066 while (hue >= 1.0)
3067 hue-=1.0;
3068 saturation*=0.01*percent_saturation;
3069 value*=0.01*percent_value;
3070 ConvertHSVToRGB(hue,saturation,value,red,green,blue);
3071}
3072
cristye83f3402012-08-17 13:12:15 +00003073static inline void ModulateHWB(const double percent_hue,
3074 const double percent_whiteness,const double percent_blackness,double *red,
3075 double *green,double *blue)
cristy3ed852e2009-09-05 21:47:34 +00003076{
3077 double
3078 blackness,
3079 hue,
3080 whiteness;
3081
3082 /*
3083 Increase or decrease color blackness, whiteness, or hue.
3084 */
cristy0a39a5c2012-06-27 12:51:45 +00003085 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
cristy3ed852e2009-09-05 21:47:34 +00003086 hue+=0.5*(0.01*percent_hue-1.0);
3087 while (hue < 0.0)
3088 hue+=1.0;
cristye8f91f72013-04-05 00:50:58 +00003089 while (hue >= 1.0)
cristy3ed852e2009-09-05 21:47:34 +00003090 hue-=1.0;
3091 blackness*=0.01*percent_blackness;
3092 whiteness*=0.01*percent_whiteness;
cristy0a39a5c2012-06-27 12:51:45 +00003093 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
cristy3ed852e2009-09-05 21:47:34 +00003094}
3095
cristy28e21a72013-04-06 00:24:19 +00003096static inline void ModulateLCHab(const double percent_luma,
3097 const double percent_chroma,const double percent_hue,double *red,
3098 double *green,double *blue)
3099{
3100 double
3101 hue,
3102 luma,
3103 chroma;
3104
3105 /*
3106 Increase or decrease color luma, chroma, or hue.
3107 */
3108 ConvertRGBToLCHab(*red,*green,*blue,&luma,&chroma,&hue);
3109 luma*=0.01*percent_luma;
3110 chroma*=0.01*percent_chroma;
3111 hue+=0.5*(0.01*percent_hue-1.0);
3112 while (hue < 0.0)
3113 hue+=1.0;
3114 while (hue >= 1.0)
3115 hue-=1.0;
3116 ConvertLCHabToRGB(luma,chroma,hue,red,green,blue);
3117}
3118
3119static inline void ModulateLCHuv(const double percent_luma,
cristyb2850952013-04-04 19:00:52 +00003120 const double percent_chroma,const double percent_hue,double *red,
3121 double *green,double *blue)
3122{
3123 double
3124 hue,
3125 luma,
3126 chroma;
3127
3128 /*
3129 Increase or decrease color luma, chroma, or hue.
3130 */
cristy30ad51e2013-04-05 16:39:20 +00003131 ConvertRGBToLCHuv(*red,*green,*blue,&luma,&chroma,&hue);
cristyb2850952013-04-04 19:00:52 +00003132 luma*=0.01*percent_luma;
3133 chroma*=0.01*percent_chroma;
3134 hue+=0.5*(0.01*percent_hue-1.0);
3135 while (hue < 0.0)
3136 hue+=1.0;
cristye8f91f72013-04-05 00:50:58 +00003137 while (hue >= 1.0)
cristyb2850952013-04-04 19:00:52 +00003138 hue-=1.0;
cristy30ad51e2013-04-05 16:39:20 +00003139 ConvertLCHuvToRGB(luma,chroma,hue,red,green,blue);
cristyb2850952013-04-04 19:00:52 +00003140}
3141
cristy33bd5152011-08-24 01:42:24 +00003142MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate,
3143 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003144{
3145#define ModulateImageTag "Modulate/Image"
3146
cristyc4c8d132010-01-07 01:58:38 +00003147 CacheView
3148 *image_view;
3149
cristy3ed852e2009-09-05 21:47:34 +00003150 ColorspaceType
3151 colorspace;
3152
3153 const char
3154 *artifact;
3155
3156 double
3157 percent_brightness,
3158 percent_hue,
cristy4d412212012-06-27 23:14:48 +00003159 percent_saturation;
cristy3ed852e2009-09-05 21:47:34 +00003160
cristy3ed852e2009-09-05 21:47:34 +00003161 GeometryInfo
3162 geometry_info;
3163
cristy3ed852e2009-09-05 21:47:34 +00003164 MagickBooleanType
3165 status;
3166
cristybb503372010-05-27 20:51:26 +00003167 MagickOffsetType
3168 progress;
3169
cristy3ed852e2009-09-05 21:47:34 +00003170 MagickStatusType
3171 flags;
3172
cristybb503372010-05-27 20:51:26 +00003173 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003174 i;
3175
cristybb503372010-05-27 20:51:26 +00003176 ssize_t
3177 y;
3178
cristy3ed852e2009-09-05 21:47:34 +00003179 /*
cristy2b726bd2010-01-11 01:05:39 +00003180 Initialize modulate table.
cristy3ed852e2009-09-05 21:47:34 +00003181 */
3182 assert(image != (Image *) NULL);
3183 assert(image->signature == MagickSignature);
anthonya322a832013-04-27 06:28:03 +00003184 if( IfMagickTrue(image->debug) )
cristy3ed852e2009-09-05 21:47:34 +00003185 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3186 if (modulate == (char *) NULL)
3187 return(MagickFalse);
anthonya322a832013-04-27 06:28:03 +00003188 if( IfMagickFalse(IssRGBCompatibleColorspace(image->colorspace)) )
cristy0c81d062013-04-21 15:22:02 +00003189 (void) SetImageColorspace(image,sRGBColorspace,exception);
cristy3ed852e2009-09-05 21:47:34 +00003190 flags=ParseGeometry(modulate,&geometry_info);
3191 percent_brightness=geometry_info.rho;
3192 percent_saturation=geometry_info.sigma;
3193 if ((flags & SigmaValue) == 0)
3194 percent_saturation=100.0;
3195 percent_hue=geometry_info.xi;
3196 if ((flags & XiValue) == 0)
3197 percent_hue=100.0;
3198 colorspace=UndefinedColorspace;
3199 artifact=GetImageArtifact(image,"modulate:colorspace");
3200 if (artifact != (const char *) NULL)
cristy042ee782011-04-22 18:48:30 +00003201 colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions,
cristy3ed852e2009-09-05 21:47:34 +00003202 MagickFalse,artifact);
3203 if (image->storage_class == PseudoClass)
nicolase0aa3c32012-08-08 14:57:02 +00003204 for (i=0; i < (ssize_t) image->colors; i++)
nicolasfd74ae32012-08-08 16:00:11 +00003205 {
3206 double
3207 blue,
3208 green,
3209 red;
cristy4d412212012-06-27 23:14:48 +00003210
nicolasfd74ae32012-08-08 16:00:11 +00003211 /*
cristy9013d232013-04-04 00:48:36 +00003212 Modulate image colormap.
nicolasfd74ae32012-08-08 16:00:11 +00003213 */
cristycb99efe2013-04-04 10:20:48 +00003214 red=(double) image->colormap[i].red;
3215 green=(double) image->colormap[i].green;
3216 blue=(double) image->colormap[i].blue;
anthonya322a832013-04-27 06:28:03 +00003217 if( IfMagickTrue(IssRGBColorspace(image->colorspace)) )
cristycb99efe2013-04-04 10:20:48 +00003218 {
3219 red=DecodePixelGamma((MagickRealType) red);
3220 green=DecodePixelGamma((MagickRealType) green);
3221 blue=DecodePixelGamma((MagickRealType) blue);
3222 }
nicolasfd74ae32012-08-08 16:00:11 +00003223 switch (colorspace)
3224 {
cristye83f3402012-08-17 13:12:15 +00003225 case HCLColorspace:
3226 {
3227 ModulateHCL(percent_hue,percent_saturation,percent_brightness,
3228 &red,&green,&blue);
3229 break;
3230 }
cristy9e2436a2013-05-02 20:35:59 +00003231 case HCLpColorspace:
3232 {
3233 ModulateHCLp(percent_hue,percent_saturation,percent_brightness,
3234 &red,&green,&blue);
3235 break;
3236 }
nicolasfd74ae32012-08-08 16:00:11 +00003237 case HSBColorspace:
cristy3ed852e2009-09-05 21:47:34 +00003238 {
nicolasfd74ae32012-08-08 16:00:11 +00003239 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
3240 &red,&green,&blue);
3241 break;
3242 }
cristyaf64eb22013-05-02 14:07:10 +00003243 case HSIColorspace:
3244 {
3245 ModulateHSI(percent_hue,percent_saturation,percent_brightness,
3246 &red,&green,&blue);
3247 break;
3248 }
nicolasfd74ae32012-08-08 16:00:11 +00003249 case HSLColorspace:
3250 default:
3251 {
3252 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
3253 &red,&green,&blue);
3254 break;
3255 }
cristy246c3132013-05-02 16:35:53 +00003256 case HSVColorspace:
3257 {
3258 ModulateHSV(percent_hue,percent_saturation,percent_brightness,
3259 &red,&green,&blue);
3260 break;
3261 }
nicolasfd74ae32012-08-08 16:00:11 +00003262 case HWBColorspace:
3263 {
3264 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
3265 &red,&green,&blue);
3266 break;
cristy3ed852e2009-09-05 21:47:34 +00003267 }
cristy8369dae2013-05-06 20:26:38 +00003268 case LCHColorspace:
cristy28e21a72013-04-06 00:24:19 +00003269 case LCHabColorspace:
3270 {
3271 ModulateLCHab(percent_brightness,percent_saturation,percent_hue,
3272 &red,&green,&blue);
3273 break;
3274 }
cristy30ad51e2013-04-05 16:39:20 +00003275 case LCHuvColorspace:
cristyb2850952013-04-04 19:00:52 +00003276 {
cristy28e21a72013-04-06 00:24:19 +00003277 ModulateLCHuv(percent_brightness,percent_saturation,percent_hue,
cristyb2850952013-04-04 19:00:52 +00003278 &red,&green,&blue);
3279 break;
3280 }
cristy0a39a5c2012-06-27 12:51:45 +00003281 }
anthonya322a832013-04-27 06:28:03 +00003282 if( IfMagickTrue(IssRGBColorspace(image->colorspace)) )
cristycb99efe2013-04-04 10:20:48 +00003283 {
3284 red=EncodePixelGamma(red);
3285 green=EncodePixelGamma(green);
3286 blue=EncodePixelGamma(blue);
3287 }
3288 image->colormap[i].red=red;
3289 image->colormap[i].green=green;
3290 image->colormap[i].blue=blue;
nicolasfd74ae32012-08-08 16:00:11 +00003291 }
cristy3ed852e2009-09-05 21:47:34 +00003292 /*
3293 Modulate image.
3294 */
3295 status=MagickTrue;
3296 progress=0;
cristy46ff2672012-12-14 15:32:26 +00003297 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +00003298#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00003299 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy5e6b2592012-12-19 14:08:11 +00003300 magick_threads(image,image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00003301#endif
cristybb503372010-05-27 20:51:26 +00003302 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003303 {
cristy4c08aed2011-07-01 19:47:50 +00003304 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003305 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003306
cristy8d4629b2010-08-30 17:59:46 +00003307 register ssize_t
3308 x;
3309
anthonya322a832013-04-27 06:28:03 +00003310 if( IfMagickFalse(status) )
cristy3ed852e2009-09-05 21:47:34 +00003311 continue;
3312 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00003313 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003314 {
3315 status=MagickFalse;
3316 continue;
3317 }
cristybb503372010-05-27 20:51:26 +00003318 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003319 {
cristy4d412212012-06-27 23:14:48 +00003320 double
3321 blue,
3322 green,
3323 red;
3324
cristycb99efe2013-04-04 10:20:48 +00003325 red=(double) GetPixelRed(image,q);
3326 green=(double) GetPixelGreen(image,q);
3327 blue=(double) GetPixelBlue(image,q);
anthonya322a832013-04-27 06:28:03 +00003328 if( IfMagickTrue(IssRGBColorspace(image->colorspace)) )
cristycb99efe2013-04-04 10:20:48 +00003329 {
3330 red=DecodePixelGamma((MagickRealType) red);
3331 green=DecodePixelGamma((MagickRealType) green);
3332 blue=DecodePixelGamma((MagickRealType) blue);
3333 }
cristy3ed852e2009-09-05 21:47:34 +00003334 switch (colorspace)
3335 {
cristyab3c3272012-08-17 17:21:33 +00003336 case HCLColorspace:
3337 {
3338 ModulateHCL(percent_hue,percent_saturation,percent_brightness,
3339 &red,&green,&blue);
3340 break;
3341 }
cristy3ed852e2009-09-05 21:47:34 +00003342 case HSBColorspace:
3343 {
3344 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00003345 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00003346 break;
3347 }
3348 case HSLColorspace:
3349 default:
3350 {
3351 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00003352 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00003353 break;
3354 }
3355 case HWBColorspace:
3356 {
3357 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00003358 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00003359 break;
3360 }
cristy28e21a72013-04-06 00:24:19 +00003361 case LCHabColorspace:
3362 {
3363 ModulateLCHab(percent_brightness,percent_saturation,percent_hue,
3364 &red,&green,&blue);
3365 break;
3366 }
cristyb2850952013-04-04 19:00:52 +00003367 case LCHColorspace:
cristy30ad51e2013-04-05 16:39:20 +00003368 case LCHuvColorspace:
cristyb2850952013-04-04 19:00:52 +00003369 {
cristy28e21a72013-04-06 00:24:19 +00003370 ModulateLCHuv(percent_brightness,percent_saturation,percent_hue,
cristyb2850952013-04-04 19:00:52 +00003371 &red,&green,&blue);
3372 break;
3373 }
cristy3ed852e2009-09-05 21:47:34 +00003374 }
anthonya322a832013-04-27 06:28:03 +00003375 if( IfMagickTrue(IssRGBColorspace(image->colorspace)) )
cristycb99efe2013-04-04 10:20:48 +00003376 {
3377 red=EncodePixelGamma(red);
3378 green=EncodePixelGamma(green);
3379 blue=EncodePixelGamma(blue);
3380 }
3381 SetPixelRed(image,ClampToQuantum(red),q);
3382 SetPixelGreen(image,ClampToQuantum(green),q);
3383 SetPixelBlue(image,ClampToQuantum(blue),q);
cristyed231572011-07-14 02:18:59 +00003384 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003385 }
anthonya322a832013-04-27 06:28:03 +00003386 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
cristy3ed852e2009-09-05 21:47:34 +00003387 status=MagickFalse;
3388 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3389 {
3390 MagickBooleanType
3391 proceed;
3392
cristyb5d5f722009-11-04 03:03:49 +00003393#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00003394 #pragma omp critical (MagickCore_ModulateImage)
cristy3ed852e2009-09-05 21:47:34 +00003395#endif
3396 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
anthonya322a832013-04-27 06:28:03 +00003397 if( IfMagickFalse(proceed) )
cristy3ed852e2009-09-05 21:47:34 +00003398 status=MagickFalse;
3399 }
3400 }
3401 image_view=DestroyCacheView(image_view);
3402 return(status);
3403}
3404
3405/*
3406%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3407% %
3408% %
3409% %
3410% N e g a t e I m a g e %
3411% %
3412% %
3413% %
3414%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3415%
3416% NegateImage() negates the colors in the reference image. The grayscale
3417% option means that only grayscale values within the image are negated.
3418%
cristy50fbc382011-07-07 02:19:17 +00003419% The format of the NegateImage method is:
cristy3ed852e2009-09-05 21:47:34 +00003420%
3421% MagickBooleanType NegateImage(Image *image,
cristyb3e7c6c2011-07-24 01:43:55 +00003422% const MagickBooleanType grayscale,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003423%
3424% A description of each parameter follows:
3425%
3426% o image: the image.
3427%
cristy3ed852e2009-09-05 21:47:34 +00003428% o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3429%
cristyb3e7c6c2011-07-24 01:43:55 +00003430% o exception: return any errors or warnings in this structure.
3431%
cristy3ed852e2009-09-05 21:47:34 +00003432*/
cristy3ed852e2009-09-05 21:47:34 +00003433MagickExport MagickBooleanType NegateImage(Image *image,
cristyb3e7c6c2011-07-24 01:43:55 +00003434 const MagickBooleanType grayscale,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003435{
cristy3ed852e2009-09-05 21:47:34 +00003436#define NegateImageTag "Negate/Image"
3437
cristyc4c8d132010-01-07 01:58:38 +00003438 CacheView
3439 *image_view;
3440
cristy3ed852e2009-09-05 21:47:34 +00003441 MagickBooleanType
3442 status;
3443
cristybb503372010-05-27 20:51:26 +00003444 MagickOffsetType
3445 progress;
3446
3447 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003448 i;
3449
cristybb503372010-05-27 20:51:26 +00003450 ssize_t
3451 y;
3452
cristy3ed852e2009-09-05 21:47:34 +00003453 assert(image != (Image *) NULL);
3454 assert(image->signature == MagickSignature);
anthonya322a832013-04-27 06:28:03 +00003455 if( IfMagickTrue(image->debug) )
cristy3ed852e2009-09-05 21:47:34 +00003456 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3457 if (image->storage_class == PseudoClass)
nicolase0aa3c32012-08-08 14:57:02 +00003458 for (i=0; i < (ssize_t) image->colors; i++)
nicolasfd74ae32012-08-08 16:00:11 +00003459 {
3460 /*
cristyaeded782012-09-11 23:39:36 +00003461 Negate colormap.
nicolasfd74ae32012-08-08 16:00:11 +00003462 */
anthonya322a832013-04-27 06:28:03 +00003463 if( IfMagickTrue(grayscale) )
cristyaeded782012-09-11 23:39:36 +00003464 if ((image->colormap[i].red != image->colormap[i].green) ||
3465 (image->colormap[i].green != image->colormap[i].blue))
3466 continue;
nicolasfd74ae32012-08-08 16:00:11 +00003467 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyaeded782012-09-11 23:39:36 +00003468 image->colormap[i].red=QuantumRange-image->colormap[i].red;
nicolasfd74ae32012-08-08 16:00:11 +00003469 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyaeded782012-09-11 23:39:36 +00003470 image->colormap[i].green=QuantumRange-image->colormap[i].green;
nicolasfd74ae32012-08-08 16:00:11 +00003471 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyaeded782012-09-11 23:39:36 +00003472 image->colormap[i].blue=QuantumRange-image->colormap[i].blue;
nicolasfd74ae32012-08-08 16:00:11 +00003473 }
cristy3ed852e2009-09-05 21:47:34 +00003474 /*
3475 Negate image.
3476 */
3477 status=MagickTrue;
3478 progress=0;
cristy46ff2672012-12-14 15:32:26 +00003479 image_view=AcquireAuthenticCacheView(image,exception);
anthonya322a832013-04-27 06:28:03 +00003480 if( IfMagickTrue(grayscale) )
cristy3ed852e2009-09-05 21:47:34 +00003481 {
cristybb503372010-05-27 20:51:26 +00003482 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003483 {
3484 MagickBooleanType
3485 sync;
3486
cristy4c08aed2011-07-01 19:47:50 +00003487 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003488 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003489
cristy8d4629b2010-08-30 17:59:46 +00003490 register ssize_t
3491 x;
3492
anthonya322a832013-04-27 06:28:03 +00003493 if( IfMagickFalse(status) )
cristy3ed852e2009-09-05 21:47:34 +00003494 continue;
3495 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3496 exception);
cristyacd2ed22011-08-30 01:44:23 +00003497 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003498 {
3499 status=MagickFalse;
3500 continue;
3501 }
cristybb503372010-05-27 20:51:26 +00003502 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003503 {
cristy9aa95be2011-07-20 21:56:45 +00003504 register ssize_t
3505 i;
3506
cristy883fde12013-04-08 00:50:13 +00003507 if ((GetPixelReadMask(image,q) == 0) ||
anthonya322a832013-04-27 06:28:03 +00003508 IfMagickTrue(IsPixelGray(image,q)))
cristy3ed852e2009-09-05 21:47:34 +00003509 {
cristya30d9ba2011-07-23 21:00:48 +00003510 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003511 continue;
3512 }
cristya30d9ba2011-07-23 21:00:48 +00003513 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy9aa95be2011-07-20 21:56:45 +00003514 {
cristy5a23c552013-02-13 14:34:28 +00003515 PixelChannel channel=GetPixelChannelChannel(image,i);
3516 PixelTrait traits=GetPixelChannelTraits(image,channel);
cristyd09f8802012-02-04 16:44:10 +00003517 if ((traits & UpdatePixelTrait) == 0)
3518 continue;
3519 q[i]=QuantumRange-q[i];
cristy9aa95be2011-07-20 21:56:45 +00003520 }
cristya30d9ba2011-07-23 21:00:48 +00003521 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003522 }
3523 sync=SyncCacheViewAuthenticPixels(image_view,exception);
anthonya322a832013-04-27 06:28:03 +00003524 if( IfMagickFalse(sync) )
cristy3ed852e2009-09-05 21:47:34 +00003525 status=MagickFalse;
3526 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3527 {
3528 MagickBooleanType
3529 proceed;
3530
cristyb5d5f722009-11-04 03:03:49 +00003531#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00003532 #pragma omp critical (MagickCore_NegateImage)
cristy3ed852e2009-09-05 21:47:34 +00003533#endif
3534 proceed=SetImageProgress(image,NegateImageTag,progress++,
3535 image->rows);
anthonya322a832013-04-27 06:28:03 +00003536 if( IfMagickFalse(proceed) )
cristy3ed852e2009-09-05 21:47:34 +00003537 status=MagickFalse;
3538 }
3539 }
3540 image_view=DestroyCacheView(image_view);
3541 return(MagickTrue);
3542 }
3543 /*
3544 Negate image.
3545 */
cristyb5d5f722009-11-04 03:03:49 +00003546#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy9a5a52f2012-10-09 14:40:31 +00003547 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy5e6b2592012-12-19 14:08:11 +00003548 magick_threads(image,image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00003549#endif
cristybb503372010-05-27 20:51:26 +00003550 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003551 {
cristy4c08aed2011-07-01 19:47:50 +00003552 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003553 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003554
cristy8d4629b2010-08-30 17:59:46 +00003555 register ssize_t
3556 x;
3557
anthonya322a832013-04-27 06:28:03 +00003558 if( IfMagickFalse(status) )
cristy3ed852e2009-09-05 21:47:34 +00003559 continue;
3560 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00003561 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003562 {
3563 status=MagickFalse;
3564 continue;
3565 }
cristybb503372010-05-27 20:51:26 +00003566 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003567 {
cristyf7dc44c2011-07-20 14:41:15 +00003568 register ssize_t
3569 i;
3570
cristy883fde12013-04-08 00:50:13 +00003571 if (GetPixelReadMask(image,q) == 0)
cristyd09f8802012-02-04 16:44:10 +00003572 {
3573 q+=GetPixelChannels(image);
3574 continue;
3575 }
cristya30d9ba2011-07-23 21:00:48 +00003576 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristyf7dc44c2011-07-20 14:41:15 +00003577 {
cristy5a23c552013-02-13 14:34:28 +00003578 PixelChannel channel=GetPixelChannelChannel(image,i);
3579 PixelTrait traits=GetPixelChannelTraits(image,channel);
cristyd09f8802012-02-04 16:44:10 +00003580 if ((traits & UpdatePixelTrait) == 0)
cristyec9e3a62012-02-01 02:09:32 +00003581 continue;
3582 q[i]=QuantumRange-q[i];
cristyf7dc44c2011-07-20 14:41:15 +00003583 }
cristya30d9ba2011-07-23 21:00:48 +00003584 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003585 }
anthonya322a832013-04-27 06:28:03 +00003586 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
cristy3ed852e2009-09-05 21:47:34 +00003587 status=MagickFalse;
3588 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3589 {
3590 MagickBooleanType
3591 proceed;
3592
cristyb5d5f722009-11-04 03:03:49 +00003593#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00003594 #pragma omp critical (MagickCore_NegateImage)
cristy3ed852e2009-09-05 21:47:34 +00003595#endif
3596 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
anthonya322a832013-04-27 06:28:03 +00003597 if( IfMagickFalse(proceed) )
cristy3ed852e2009-09-05 21:47:34 +00003598 status=MagickFalse;
3599 }
3600 }
3601 image_view=DestroyCacheView(image_view);
3602 return(status);
3603}
3604
3605/*
3606%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3607% %
3608% %
3609% %
3610% N o r m a l i z e I m a g e %
3611% %
3612% %
3613% %
3614%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3615%
anthony207ce472012-04-04 06:21:26 +00003616% The NormalizeImage() method enhances the contrast of a color image by
3617% mapping the darkest 2 percent of all pixel to black and the brightest
3618% 1 percent to white.
cristy3ed852e2009-09-05 21:47:34 +00003619%
3620% The format of the NormalizeImage method is:
3621%
cristye23ec9d2011-08-16 18:15:40 +00003622% MagickBooleanType NormalizeImage(Image *image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003623%
3624% A description of each parameter follows:
3625%
3626% o image: the image.
3627%
cristye23ec9d2011-08-16 18:15:40 +00003628% o exception: return any errors or warnings in this structure.
3629%
cristy3ed852e2009-09-05 21:47:34 +00003630*/
cristye23ec9d2011-08-16 18:15:40 +00003631MagickExport MagickBooleanType NormalizeImage(Image *image,
3632 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003633{
cristy3ed852e2009-09-05 21:47:34 +00003634 double
3635 black_point,
3636 white_point;
3637
cristy530239c2010-07-25 17:34:26 +00003638 black_point=(double) image->columns*image->rows*0.0015;
3639 white_point=(double) image->columns*image->rows*0.9995;
cristye23ec9d2011-08-16 18:15:40 +00003640 return(ContrastStretchImage(image,black_point,white_point,exception));
cristy3ed852e2009-09-05 21:47:34 +00003641}
3642
3643/*
3644%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3645% %
3646% %
3647% %
3648% S i g m o i d a l C o n t r a s t I m a g e %
3649% %
3650% %
3651% %
3652%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3653%
3654% SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3655% sigmoidal contrast algorithm. Increase the contrast of the image using a
3656% sigmoidal transfer function without saturating highlights or shadows.
3657% Contrast indicates how much to increase the contrast (0 is none; 3 is
anthony207ce472012-04-04 06:21:26 +00003658% typical; 20 is pushing it); mid-point indicates where midtones fall in the
3659% resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3660% sharpen to MagickTrue to increase the image contrast otherwise the contrast
3661% is reduced.
cristy3ed852e2009-09-05 21:47:34 +00003662%
3663% The format of the SigmoidalContrastImage method is:
3664%
3665% MagickBooleanType SigmoidalContrastImage(Image *image,
cristy33bd5152011-08-24 01:42:24 +00003666% const MagickBooleanType sharpen,const char *levels,
3667% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003668%
3669% A description of each parameter follows:
3670%
3671% o image: the image.
3672%
cristy3ed852e2009-09-05 21:47:34 +00003673% o sharpen: Increase or decrease image contrast.
3674%
nicolas07299f12012-09-10 17:30:07 +00003675% o contrast: strength of the contrast, the larger the number the more
cristyfa769582010-09-30 23:30:03 +00003676% 'threshold-like' it becomes.
cristy3ed852e2009-09-05 21:47:34 +00003677%
nicolas1de98ba2012-09-10 17:34:35 +00003678% o midpoint: midpoint of the function as a color value 0 to QuantumRange.
cristy3ed852e2009-09-05 21:47:34 +00003679%
cristy33bd5152011-08-24 01:42:24 +00003680% o exception: return any errors or warnings in this structure.
3681%
cristy3ed852e2009-09-05 21:47:34 +00003682*/
nicolas678607b2012-09-10 20:05:40 +00003683
nicolas6457d9f2012-09-12 14:40:01 +00003684/*
nicolasef20d0e2012-09-18 16:05:53 +00003685 ImageMagick 6 has a version of this function which uses LUTs.
3686*/
3687
3688/*
nicolas4d6c5342012-09-12 14:59:55 +00003689 Sigmoidal function Sigmoidal with inflexion point moved to b and "slope
3690 constant" set to a.
3691
nicolas6457d9f2012-09-12 14:40:01 +00003692 The first version, based on the hyperbolic tangent tanh, when combined with
3693 the scaling step, is an exact arithmetic clone of the the sigmoid function
3694 based on the logistic curve. The equivalence is based on the identity
3695
nicolas4d6c5342012-09-12 14:59:55 +00003696 1/(1+exp(-t)) = (1+tanh(t/2))/2
nicolas6457d9f2012-09-12 14:40:01 +00003697
nicolas4d6c5342012-09-12 14:59:55 +00003698 (http://de.wikipedia.org/wiki/Sigmoidfunktion) and the fact that the
3699 scaled sigmoidal derivation is invariant under affine transformations of
3700 the ordinate.
3701
3702 The tanh version is almost certainly more accurate and cheaper. The 0.5
3703 factor in the argument is to clone the legacy ImageMagick behavior. The
3704 reason for making the define depend on atanh even though it only uses tanh
3705 has to do with the construction of the inverse of the scaled sigmoidal.
nicolas6457d9f2012-09-12 14:40:01 +00003706*/
nicolase3466022012-09-04 13:51:46 +00003707#if defined(MAGICKCORE_HAVE_ATANH)
nicolas07299f12012-09-10 17:30:07 +00003708#define Sigmoidal(a,b,x) ( tanh((0.5*(a))*((x)-(b))) )
nicolase3466022012-09-04 13:51:46 +00003709#else
nicolas07299f12012-09-10 17:30:07 +00003710#define Sigmoidal(a,b,x) ( 1.0/(1.0+exp((a)*((b)-(x)))) )
nicolase3466022012-09-04 13:51:46 +00003711#endif
cristy5eab5352012-09-12 13:12:43 +00003712/*
nicolas6457d9f2012-09-12 14:40:01 +00003713 Scaled sigmoidal function:
cristy5eab5352012-09-12 13:12:43 +00003714
nicolas07299f12012-09-10 17:30:07 +00003715 ( Sigmoidal(a,b,x) - Sigmoidal(a,b,0) ) /
3716 ( Sigmoidal(a,b,1) - Sigmoidal(a,b,0) )
cristy5eab5352012-09-12 13:12:43 +00003717
3718 See http://osdir.com/ml/video.image-magick.devel/2005-04/msg00006.html and
3719 http://www.cs.dartmouth.edu/farid/downloads/tutorials/fip.pdf. The limit
3720 of ScaledSigmoidal as a->0 is the identity, but a=0 gives a division by
nicolas1f1584e2012-09-12 15:20:56 +00003721 zero. This is fixed below by exiting immediately when contrast is small,
cristy5eab5352012-09-12 13:12:43 +00003722 leaving the image (or colormap) unmodified. This appears to be safe because
3723 the series expansion of the logistic sigmoidal function around x=b is
nicolas6457d9f2012-09-12 14:40:01 +00003724
3725 1/2-a*(b-x)/4+...
3726
3727 so that the key denominator s(1)-s(0) is about a/4 (a/2 with tanh).
cristy5eab5352012-09-12 13:12:43 +00003728*/
nicolas07299f12012-09-10 17:30:07 +00003729#define ScaledSigmoidal(a,b,x) ( \
nicolasf0158cf2012-09-10 20:11:02 +00003730 (Sigmoidal((a),(b),(x))-Sigmoidal((a),(b),0.0)) / \
nicolas07299f12012-09-10 17:30:07 +00003731 (Sigmoidal((a),(b),1.0)-Sigmoidal((a),(b),0.0)) )
cristy5eab5352012-09-12 13:12:43 +00003732/*
nicolas6457d9f2012-09-12 14:40:01 +00003733 Inverse of ScaledSigmoidal, used for +sigmoidal-contrast. Because b
3734 may be 0 or 1, the argument of the hyperbolic tangent (resp. logistic
3735 sigmoidal) may be outside of the interval (-1,1) (resp. (0,1)), even
3736 when creating a LUT from in gamut values, hence the branching. In
3737 addition, HDRI may have out of gamut values.
nicolas7922b462012-09-12 14:50:49 +00003738 InverseScaledSigmoidal is not a two-sided inverse of ScaledSigmoidal:
cristy5eab5352012-09-12 13:12:43 +00003739 It is only a right inverse. This is unavoidable.
3740*/
cristy5eab5352012-09-12 13:12:43 +00003741static inline double InverseScaledSigmoidal(const double a,const double b,
3742 const double x)
3743{
nicolas6457d9f2012-09-12 14:40:01 +00003744 const double sig0=Sigmoidal(a,b,0.0);
nicolasb45b69a2012-09-15 20:00:31 +00003745 const double sig1=Sigmoidal(a,b,1.0);
3746 const double argument=(sig1-sig0)*x+sig0;
nicolas6457d9f2012-09-12 14:40:01 +00003747 const double clamped=
3748 (
nicolas196d63c2012-09-12 14:46:01 +00003749#if defined(MAGICKCORE_HAVE_ATANH)
nicolas6457d9f2012-09-12 14:40:01 +00003750 argument < -1+MagickEpsilon
3751 ?
3752 -1+MagickEpsilon
3753 :
3754 ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
3755 );
3756 return(b+(2.0/a)*atanh(clamped));
nicolase3466022012-09-04 13:51:46 +00003757#else
nicolas6457d9f2012-09-12 14:40:01 +00003758 argument < MagickEpsilon
3759 ?
3760 MagickEpsilon
3761 :
3762 ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
3763 );
nicolas02c9a682012-09-15 20:01:56 +00003764 return(b-log(1.0/clamped-1.0)/a);
nicolase3466022012-09-04 13:51:46 +00003765#endif
nicolas196d63c2012-09-12 14:46:01 +00003766}
cristy5eab5352012-09-12 13:12:43 +00003767
3768MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
3769 const MagickBooleanType sharpen,const double contrast,const double midpoint,
3770 ExceptionInfo *exception)
3771{
3772#define SigmoidalContrastImageTag "SigmoidalContrast/Image"
nicolasf94a4f42012-09-11 09:18:55 +00003773#define ScaledSig(x) ( ClampToQuantum(QuantumRange* \
cristyaeded782012-09-11 23:39:36 +00003774 ScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale*(x))) )
nicolasf94a4f42012-09-11 09:18:55 +00003775#define InverseScaledSig(x) ( ClampToQuantum(QuantumRange* \
cristyaeded782012-09-11 23:39:36 +00003776 InverseScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale*(x))) )
nicolas07299f12012-09-10 17:30:07 +00003777
cristy5eab5352012-09-12 13:12:43 +00003778 CacheView
3779 *image_view;
3780
3781 MagickBooleanType
3782 status;
3783
3784 MagickOffsetType
3785 progress;
3786
3787 ssize_t
3788 y;
3789
3790 /*
3791 Convenience macros.
3792 */
nicolas33c76712012-08-08 17:15:05 +00003793 assert(image != (Image *) NULL);
3794 assert(image->signature == MagickSignature);
anthonya322a832013-04-27 06:28:03 +00003795 if( IfMagickTrue(image->debug) )
nicolas33c76712012-08-08 17:15:05 +00003796 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
nicolas07299f12012-09-10 17:30:07 +00003797 /*
nicolas6457d9f2012-09-12 14:40:01 +00003798 Side effect: may clamp values unless contrast<MagickEpsilon, in which
cristy5eab5352012-09-12 13:12:43 +00003799 case nothing is done.
3800 */
3801 if (contrast < MagickEpsilon)
3802 return(MagickTrue);
3803 /*
nicolas4ff353c2012-09-10 20:15:40 +00003804 Sigmoidal-contrast enhance colormap.
nicolas07299f12012-09-10 17:30:07 +00003805 */
cristy3ed852e2009-09-05 21:47:34 +00003806 if (image->storage_class == PseudoClass)
nicolasfd74ae32012-08-08 16:00:11 +00003807 {
nicolasb06434e2012-09-10 18:06:15 +00003808 register ssize_t
cristyaeded782012-09-11 23:39:36 +00003809 i;
nicolas07299f12012-09-10 17:30:07 +00003810
anthonya322a832013-04-27 06:28:03 +00003811 if( IfMagickTrue(sharpen) )
nicolas80ad4a62012-09-11 15:17:16 +00003812 for (i=0; i < (ssize_t) image->colors; i++)
3813 {
3814 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy6bbed7e2013-04-03 23:59:10 +00003815 image->colormap[i].red=(MagickRealType) ScaledSig(
3816 image->colormap[i].red);
cristyaeded782012-09-11 23:39:36 +00003817 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy6bbed7e2013-04-03 23:59:10 +00003818 image->colormap[i].green=(MagickRealType) ScaledSig(
3819 image->colormap[i].green);
cristyaeded782012-09-11 23:39:36 +00003820 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy6bbed7e2013-04-03 23:59:10 +00003821 image->colormap[i].blue=(MagickRealType) ScaledSig(
3822 image->colormap[i].blue);
cristyaeded782012-09-11 23:39:36 +00003823 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy6bbed7e2013-04-03 23:59:10 +00003824 image->colormap[i].alpha=(MagickRealType) ScaledSig(
3825 image->colormap[i].alpha);
cristyaeded782012-09-11 23:39:36 +00003826 }
nicolasb06434e2012-09-10 18:06:15 +00003827 else
nicolas80ad4a62012-09-11 15:17:16 +00003828 for (i=0; i < (ssize_t) image->colors; i++)
cristyaeded782012-09-11 23:39:36 +00003829 {
3830 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy6bbed7e2013-04-03 23:59:10 +00003831 image->colormap[i].red=(MagickRealType) InverseScaledSig(
3832 image->colormap[i].red);
cristyaeded782012-09-11 23:39:36 +00003833 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy6bbed7e2013-04-03 23:59:10 +00003834 image->colormap[i].green=(MagickRealType) InverseScaledSig(
3835 image->colormap[i].green);
cristyaeded782012-09-11 23:39:36 +00003836 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy6bbed7e2013-04-03 23:59:10 +00003837 image->colormap[i].blue=(MagickRealType) InverseScaledSig(
3838 image->colormap[i].blue);
nicolas80ad4a62012-09-11 15:17:16 +00003839 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy6bbed7e2013-04-03 23:59:10 +00003840 image->colormap[i].alpha=(MagickRealType) InverseScaledSig(
3841 image->colormap[i].alpha);
cristyaeded782012-09-11 23:39:36 +00003842 }
nicolasfd74ae32012-08-08 16:00:11 +00003843 }
cristy3ed852e2009-09-05 21:47:34 +00003844 /*
nicolas4ff353c2012-09-10 20:15:40 +00003845 Sigmoidal-contrast enhance image.
cristy3ed852e2009-09-05 21:47:34 +00003846 */
3847 status=MagickTrue;
3848 progress=0;
cristy46ff2672012-12-14 15:32:26 +00003849 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +00003850#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00003851 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy5e6b2592012-12-19 14:08:11 +00003852 magick_threads(image,image,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00003853#endif
cristybb503372010-05-27 20:51:26 +00003854 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003855 {
cristy4c08aed2011-07-01 19:47:50 +00003856 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003857 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003858
cristy8d4629b2010-08-30 17:59:46 +00003859 register ssize_t
3860 x;
3861
anthonya322a832013-04-27 06:28:03 +00003862 if( IfMagickFalse(status) )
cristy3ed852e2009-09-05 21:47:34 +00003863 continue;
3864 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00003865 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003866 {
3867 status=MagickFalse;
3868 continue;
3869 }
cristybb503372010-05-27 20:51:26 +00003870 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003871 {
cristy33bd5152011-08-24 01:42:24 +00003872 register ssize_t
cristyaeded782012-09-11 23:39:36 +00003873 i;
cristy33bd5152011-08-24 01:42:24 +00003874
cristy883fde12013-04-08 00:50:13 +00003875 if (GetPixelReadMask(image,q) == 0)
cristy10a6c612012-01-29 21:41:05 +00003876 {
3877 q+=GetPixelChannels(image);
3878 continue;
3879 }
cristy33bd5152011-08-24 01:42:24 +00003880 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3881 {
cristy5a23c552013-02-13 14:34:28 +00003882 PixelChannel channel=GetPixelChannelChannel(image,i);
3883 PixelTrait traits=GetPixelChannelTraits(image,channel);
cristyd09f8802012-02-04 16:44:10 +00003884 if ((traits & UpdatePixelTrait) == 0)
3885 continue;
anthonya322a832013-04-27 06:28:03 +00003886 if( IfMagickTrue(sharpen) )
cristyaeded782012-09-11 23:39:36 +00003887 q[i]=ScaledSig(q[i]);
3888 else
3889 q[i]=InverseScaledSig(q[i]);
cristy33bd5152011-08-24 01:42:24 +00003890 }
cristyed231572011-07-14 02:18:59 +00003891 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003892 }
anthonya322a832013-04-27 06:28:03 +00003893 if( IfMagickFalse(SyncCacheViewAuthenticPixels(image_view,exception)) )
cristy3ed852e2009-09-05 21:47:34 +00003894 status=MagickFalse;
3895 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3896 {
3897 MagickBooleanType
3898 proceed;
3899
cristyb5d5f722009-11-04 03:03:49 +00003900#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00003901 #pragma omp critical (MagickCore_SigmoidalContrastImage)
cristy3ed852e2009-09-05 21:47:34 +00003902#endif
3903 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3904 image->rows);
anthonya322a832013-04-27 06:28:03 +00003905 if( IfMagickFalse(proceed) )
cristy3ed852e2009-09-05 21:47:34 +00003906 status=MagickFalse;
3907 }
3908 }
3909 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00003910 return(status);
3911}