blob: cb0104a8215fa54b226bcd34408407254d8a0d29 [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% %
cristy1454be72011-12-19 01:52:48 +000020% Copyright 1999-2012 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"
45#include "MagickCore/cache.h"
46#include "MagickCore/cache-view.h"
47#include "MagickCore/color.h"
48#include "MagickCore/color-private.h"
49#include "MagickCore/colorspace.h"
cristy0898eba2012-04-09 16:38:29 +000050#include "MagickCore/colorspace-private.h"
cristy4c08aed2011-07-01 19:47:50 +000051#include "MagickCore/composite-private.h"
52#include "MagickCore/enhance.h"
53#include "MagickCore/exception.h"
54#include "MagickCore/exception-private.h"
55#include "MagickCore/fx.h"
56#include "MagickCore/gem.h"
cristyd1dd6e42011-09-04 01:46:08 +000057#include "MagickCore/gem-private.h"
cristy4c08aed2011-07-01 19:47:50 +000058#include "MagickCore/geometry.h"
59#include "MagickCore/histogram.h"
60#include "MagickCore/image.h"
61#include "MagickCore/image-private.h"
62#include "MagickCore/memory_.h"
63#include "MagickCore/monitor.h"
64#include "MagickCore/monitor-private.h"
65#include "MagickCore/option.h"
cristy1e0fe422012-04-11 18:43:52 +000066#include "MagickCore/pixel.h"
cristy4c08aed2011-07-01 19:47:50 +000067#include "MagickCore/pixel-accessor.h"
68#include "MagickCore/quantum.h"
69#include "MagickCore/quantum-private.h"
70#include "MagickCore/resample.h"
71#include "MagickCore/resample-private.h"
cristyac245f82012-05-05 17:13:57 +000072#include "MagickCore/resource_.h"
cristy4c08aed2011-07-01 19:47:50 +000073#include "MagickCore/statistic.h"
74#include "MagickCore/string_.h"
75#include "MagickCore/string-private.h"
76#include "MagickCore/thread-private.h"
77#include "MagickCore/token.h"
78#include "MagickCore/xml-tree.h"
cristy433d1182011-09-04 13:38:52 +000079#include "MagickCore/xml-tree-private.h"
cristy3ed852e2009-09-05 21:47:34 +000080
81/*
82%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
83% %
84% %
85% %
86% A u t o G a m m a I m a g e %
87% %
88% %
89% %
90%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
91%
92% AutoGammaImage() extract the 'mean' from the image and adjust the image
93% to try make set its gamma appropriatally.
94%
cristy308b4e62009-09-21 14:40:44 +000095% The format of the AutoGammaImage method is:
cristy3ed852e2009-09-05 21:47:34 +000096%
cristy95111202011-08-09 19:41:42 +000097% MagickBooleanType AutoGammaImage(Image *image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +000098%
99% A description of each parameter follows:
100%
101% o image: The image to auto-level
102%
cristy95111202011-08-09 19:41:42 +0000103% o exception: return any errors or warnings in this structure.
104%
cristy3ed852e2009-09-05 21:47:34 +0000105*/
cristy95111202011-08-09 19:41:42 +0000106MagickExport MagickBooleanType AutoGammaImage(Image *image,
107 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000108{
cristy3ed852e2009-09-05 21:47:34 +0000109 double
cristy4c08aed2011-07-01 19:47:50 +0000110 gamma,
111 log_mean,
112 mean,
113 sans;
anthony4efe5972009-09-11 06:46:40 +0000114
cristy95111202011-08-09 19:41:42 +0000115 MagickStatusType
116 status;
117
cristy01e9afd2011-08-10 17:38:41 +0000118 register ssize_t
119 i;
120
cristy4c08aed2011-07-01 19:47:50 +0000121 log_mean=log(0.5);
cristy5f95f4f2011-10-23 01:01:01 +0000122 if (image->channel_mask == DefaultChannels)
cristy3ed852e2009-09-05 21:47:34 +0000123 {
124 /*
cristy01e9afd2011-08-10 17:38:41 +0000125 Apply gamma correction equally across all given channels.
cristy3ed852e2009-09-05 21:47:34 +0000126 */
cristy95111202011-08-09 19:41:42 +0000127 (void) GetImageMean(image,&mean,&sans,exception);
cristy4c08aed2011-07-01 19:47:50 +0000128 gamma=log(mean*QuantumScale)/log_mean;
cristy01e9afd2011-08-10 17:38:41 +0000129 return(LevelImage(image,0.0,(double) QuantumRange,gamma,exception));
cristy3ed852e2009-09-05 21:47:34 +0000130 }
cristy3ed852e2009-09-05 21:47:34 +0000131 /*
cristy4c08aed2011-07-01 19:47:50 +0000132 Auto-gamma each channel separately.
cristy3ed852e2009-09-05 21:47:34 +0000133 */
cristy4c08aed2011-07-01 19:47:50 +0000134 status=MagickTrue;
cristy01e9afd2011-08-10 17:38:41 +0000135 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
136 {
cristybd5a96c2011-08-21 00:04:26 +0000137 ChannelType
138 channel_mask;
139
cristyabace412011-12-11 15:56:53 +0000140 PixelChannel
141 channel;
142
cristy01e9afd2011-08-10 17:38:41 +0000143 PixelTrait
144 traits;
145
cristyabace412011-12-11 15:56:53 +0000146 channel=GetPixelChannelMapChannel(image,i);
147 traits=GetPixelChannelMapTraits(image,channel);
cristy01e9afd2011-08-10 17:38:41 +0000148 if ((traits & UpdatePixelTrait) == 0)
149 continue;
cristya13d0822011-09-19 00:19:19 +0000150 channel_mask=SetPixelChannelMask(image,(ChannelType) (1 << i));
cristy01e9afd2011-08-10 17:38:41 +0000151 status=GetImageMean(image,&mean,&sans,exception);
152 gamma=log(mean*QuantumScale)/log_mean;
153 status&=LevelImage(image,0.0,(double) QuantumRange,gamma,exception);
cristybd5a96c2011-08-21 00:04:26 +0000154 (void) SetPixelChannelMask(image,channel_mask);
cristy01e9afd2011-08-10 17:38:41 +0000155 if (status == MagickFalse)
156 break;
157 }
cristy3ed852e2009-09-05 21:47:34 +0000158 return(status != 0 ? MagickTrue : MagickFalse);
159}
160
161/*
162%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
163% %
164% %
165% %
166% A u t o L e v e l I m a g e %
167% %
168% %
169% %
170%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
171%
172% AutoLevelImage() adjusts the levels of a particular image channel by
173% scaling the minimum and maximum values to the full quantum range.
174%
175% The format of the LevelImage method is:
176%
cristy95111202011-08-09 19:41:42 +0000177% MagickBooleanType AutoLevelImage(Image *image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000178%
179% A description of each parameter follows:
180%
181% o image: The image to auto-level
182%
cristy95111202011-08-09 19:41:42 +0000183% o exception: return any errors or warnings in this structure.
184%
cristy3ed852e2009-09-05 21:47:34 +0000185*/
cristy95111202011-08-09 19:41:42 +0000186MagickExport MagickBooleanType AutoLevelImage(Image *image,
187 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000188{
cristyb303c3d2011-09-09 11:24:40 +0000189 return(MinMaxStretchImage(image,0.0,0.0,1.0,exception));
cristy3ed852e2009-09-05 21:47:34 +0000190}
191
192/*
193%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
194% %
195% %
196% %
cristya28d6b82010-01-11 20:03:47 +0000197% B r i g h t n e s s C o n t r a s t I m a g e %
198% %
199% %
200% %
201%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
202%
cristyf4356f92011-08-01 15:33:48 +0000203% BrightnessContrastImage() changes the brightness and/or contrast of an
204% image. It converts the brightness and contrast parameters into slope and
205% intercept and calls a polynomical function to apply to the image.
cristya28d6b82010-01-11 20:03:47 +0000206%
207% The format of the BrightnessContrastImage method is:
208%
209% MagickBooleanType BrightnessContrastImage(Image *image,
cristy444eda62011-08-10 02:07:46 +0000210% const double brightness,const double contrast,ExceptionInfo *exception)
cristya28d6b82010-01-11 20:03:47 +0000211%
212% A description of each parameter follows:
213%
214% o image: the image.
215%
cristya28d6b82010-01-11 20:03:47 +0000216% o brightness: the brightness percent (-100 .. 100).
217%
218% o contrast: the contrast percent (-100 .. 100).
219%
cristy444eda62011-08-10 02:07:46 +0000220% o exception: return any errors or warnings in this structure.
221%
cristya28d6b82010-01-11 20:03:47 +0000222*/
cristya28d6b82010-01-11 20:03:47 +0000223MagickExport MagickBooleanType BrightnessContrastImage(Image *image,
cristy444eda62011-08-10 02:07:46 +0000224 const double brightness,const double contrast,ExceptionInfo *exception)
cristya28d6b82010-01-11 20:03:47 +0000225{
cristya28d6b82010-01-11 20:03:47 +0000226#define BrightnessContastImageTag "BrightnessContast/Image"
227
228 double
229 alpha,
cristya28d6b82010-01-11 20:03:47 +0000230 coefficients[2],
cristye23ec9d2011-08-16 18:15:40 +0000231 intercept,
cristya28d6b82010-01-11 20:03:47 +0000232 slope;
233
234 MagickBooleanType
235 status;
236
237 /*
238 Compute slope and intercept.
239 */
240 assert(image != (Image *) NULL);
241 assert(image->signature == MagickSignature);
242 if (image->debug != MagickFalse)
243 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
244 alpha=contrast;
cristy4205a3c2010-09-12 20:19:59 +0000245 slope=tan((double) (MagickPI*(alpha/100.0+1.0)/4.0));
cristya28d6b82010-01-11 20:03:47 +0000246 if (slope < 0.0)
247 slope=0.0;
248 intercept=brightness/100.0+((100-brightness)/200.0)*(1.0-slope);
249 coefficients[0]=slope;
250 coefficients[1]=intercept;
cristy444eda62011-08-10 02:07:46 +0000251 status=FunctionImage(image,PolynomialFunction,2,coefficients,exception);
252 return(status);
253}
254
255/*
256%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
257% %
258% %
259% %
260% C l u t I m a g e %
261% %
262% %
263% %
264%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
265%
266% ClutImage() replaces each color value in the given image, by using it as an
267% index to lookup a replacement color value in a Color Look UP Table in the
268% form of an image. The values are extracted along a diagonal of the CLUT
269% image so either a horizontal or vertial gradient image can be used.
270%
271% Typically this is used to either re-color a gray-scale image according to a
272% color gradient in the CLUT image, or to perform a freeform histogram
273% (level) adjustment according to the (typically gray-scale) gradient in the
274% CLUT image.
275%
276% When the 'channel' mask includes the matte/alpha transparency channel but
277% one image has no such channel it is assumed that that image is a simple
278% gray-scale image that will effect the alpha channel values, either for
279% gray-scale coloring (with transparent or semi-transparent colors), or
280% a histogram adjustment of existing alpha channel values. If both images
281% have matte channels, direct and normal indexing is applied, which is rarely
282% used.
283%
284% The format of the ClutImage method is:
285%
286% MagickBooleanType ClutImage(Image *image,Image *clut_image,
cristy5c4e2582011-09-11 19:21:03 +0000287% const PixelInterpolateMethod method,ExceptionInfo *exception)
cristy444eda62011-08-10 02:07:46 +0000288%
289% A description of each parameter follows:
290%
291% o image: the image, which is replaced by indexed CLUT values
292%
293% o clut_image: the color lookup table image for replacement color values.
294%
cristy5c4e2582011-09-11 19:21:03 +0000295% o method: the pixel interpolation method.
cristy444eda62011-08-10 02:07:46 +0000296%
297% o exception: return any errors or warnings in this structure.
298%
299*/
300MagickExport MagickBooleanType ClutImage(Image *image,const Image *clut_image,
cristy5c4e2582011-09-11 19:21:03 +0000301 const PixelInterpolateMethod method,ExceptionInfo *exception)
cristy444eda62011-08-10 02:07:46 +0000302{
cristy444eda62011-08-10 02:07:46 +0000303#define ClutImageTag "Clut/Image"
304
305 CacheView
306 *clut_view,
307 *image_view;
308
cristy444eda62011-08-10 02:07:46 +0000309 MagickBooleanType
310 status;
311
312 MagickOffsetType
313 progress;
314
cristy1e0fe422012-04-11 18:43:52 +0000315 PixelInfo
316 *clut_map;
cristy444eda62011-08-10 02:07:46 +0000317
cristy1e0fe422012-04-11 18:43:52 +0000318 register ssize_t
319 i;
320
321 ssize_t adjust,
cristy444eda62011-08-10 02:07:46 +0000322 y;
323
324 assert(image != (Image *) NULL);
325 assert(image->signature == MagickSignature);
326 if (image->debug != MagickFalse)
327 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
328 assert(clut_image != (Image *) NULL);
329 assert(clut_image->signature == MagickSignature);
330 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
331 return(MagickFalse);
cristy0898eba2012-04-09 16:38:29 +0000332 if (IsGrayColorspace(image->colorspace) != MagickFalse)
333 (void) TransformImageColorspace(image,sRGBColorspace,exception);
cristy1e0fe422012-04-11 18:43:52 +0000334 clut_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*clut_map));
335 if (clut_map == (PixelInfo *) NULL)
cristy444eda62011-08-10 02:07:46 +0000336 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
337 image->filename);
338 /*
339 Clut image.
340 */
341 status=MagickTrue;
342 progress=0;
343 adjust=(ssize_t) (clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1);
cristydb070952012-04-20 14:33:00 +0000344 clut_view=AcquireVirtualCacheView(clut_image,exception);
cristy444eda62011-08-10 02:07:46 +0000345#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000346 #pragma omp parallel for schedule(static,4) \
347 if (MaxMap > 256) \
348 num_threads(GetMagickResourceLimit(ThreadResource))
cristy444eda62011-08-10 02:07:46 +0000349#endif
cristy1e0fe422012-04-11 18:43:52 +0000350 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy444eda62011-08-10 02:07:46 +0000351 {
cristy1e0fe422012-04-11 18:43:52 +0000352 GetPixelInfo(clut_image,clut_map+i);
anthony6feb7802012-04-12 06:24:29 +0000353 (void) InterpolatePixelInfo(clut_image,clut_view,method,
cristy1e0fe422012-04-11 18:43:52 +0000354 QuantumScale*i*(clut_image->columns-adjust),QuantumScale*i*
355 (clut_image->rows-adjust),clut_map+i,exception);
cristy444eda62011-08-10 02:07:46 +0000356 }
357 clut_view=DestroyCacheView(clut_view);
cristydb070952012-04-20 14:33:00 +0000358 image_view=AcquireAuthenticCacheView(image,exception);
cristy444eda62011-08-10 02:07:46 +0000359#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000360 #pragma omp parallel for schedule(static,4) shared(progress,status) \
361 if ((image->rows*image->columns) > 8192) \
362 num_threads(GetMagickResourceLimit(ThreadResource))
cristy444eda62011-08-10 02:07:46 +0000363#endif
364 for (y=0; y < (ssize_t) image->rows; y++)
365 {
cristy1e0fe422012-04-11 18:43:52 +0000366 PixelInfo
367 pixel;
368
cristy444eda62011-08-10 02:07:46 +0000369 register Quantum
370 *restrict q;
371
372 register ssize_t
373 x;
374
375 if (status == MagickFalse)
376 continue;
377 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
378 if (q == (Quantum *) NULL)
379 {
380 status=MagickFalse;
381 continue;
382 }
cristy1e0fe422012-04-11 18:43:52 +0000383 GetPixelInfo(image,&pixel);
cristy444eda62011-08-10 02:07:46 +0000384 for (x=0; x < (ssize_t) image->columns; x++)
385 {
cristy10a6c612012-01-29 21:41:05 +0000386 if (GetPixelMask(image,q) != 0)
387 {
388 q+=GetPixelChannels(image);
389 continue;
390 }
cristy1e0fe422012-04-11 18:43:52 +0000391 GetPixelInfoPixel(image,q,&pixel);
cristyac245f82012-05-05 17:13:57 +0000392 pixel.red=clut_map[ScaleQuantumToMap(
393 ClampToQuantum(pixel.red))].red;
394 pixel.green=clut_map[ScaleQuantumToMap(
395 ClampToQuantum(pixel.green))].green;
396 pixel.blue=clut_map[ScaleQuantumToMap(
397 ClampToQuantum(pixel.blue))].blue;
398 pixel.black=clut_map[ScaleQuantumToMap(
399 ClampToQuantum(pixel.black))].black;
400 pixel.alpha=clut_map[ScaleQuantumToMap(
401 ClampToQuantum(pixel.alpha))].alpha;
cristy1e0fe422012-04-11 18:43:52 +0000402 SetPixelInfoPixel(image,&pixel,q);
cristy444eda62011-08-10 02:07:46 +0000403 q+=GetPixelChannels(image);
404 }
405 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
406 status=MagickFalse;
407 if (image->progress_monitor != (MagickProgressMonitor) NULL)
408 {
409 MagickBooleanType
410 proceed;
411
412#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +0000413 #pragma omp critical (MagickCore_ClutImage)
cristy444eda62011-08-10 02:07:46 +0000414#endif
415 proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
416 if (proceed == MagickFalse)
417 status=MagickFalse;
418 }
419 }
420 image_view=DestroyCacheView(image_view);
cristy1e0fe422012-04-11 18:43:52 +0000421 clut_map=(PixelInfo *) RelinquishMagickMemory(clut_map);
cristy444eda62011-08-10 02:07:46 +0000422 if ((clut_image->matte != MagickFalse) &&
423 ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0))
424 (void) SetImageAlphaChannel(image,ActivateAlphaChannel,exception);
cristya28d6b82010-01-11 20:03:47 +0000425 return(status);
426}
427
428/*
429%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
430% %
431% %
432% %
cristy3ed852e2009-09-05 21:47:34 +0000433% C o l o r D e c i s i o n L i s t I m a g e %
434% %
435% %
436% %
437%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
438%
439% ColorDecisionListImage() accepts a lightweight Color Correction Collection
440% (CCC) file which solely contains one or more color corrections and applies
441% the correction to the image. Here is a sample CCC file:
442%
443% <ColorCorrectionCollection xmlns="urn:ASC:CDL:v1.2">
444% <ColorCorrection id="cc03345">
445% <SOPNode>
446% <Slope> 0.9 1.2 0.5 </Slope>
447% <Offset> 0.4 -0.5 0.6 </Offset>
448% <Power> 1.0 0.8 1.5 </Power>
449% </SOPNode>
450% <SATNode>
451% <Saturation> 0.85 </Saturation>
452% </SATNode>
453% </ColorCorrection>
454% </ColorCorrectionCollection>
455%
456% which includes the slop, offset, and power for each of the RGB channels
457% as well as the saturation.
458%
459% The format of the ColorDecisionListImage method is:
460%
461% MagickBooleanType ColorDecisionListImage(Image *image,
cristy1bfa9f02011-08-11 02:35:43 +0000462% const char *color_correction_collection,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000463%
464% A description of each parameter follows:
465%
466% o image: the image.
467%
468% o color_correction_collection: the color correction collection in XML.
469%
cristy1bfa9f02011-08-11 02:35:43 +0000470% o exception: return any errors or warnings in this structure.
471%
cristy3ed852e2009-09-05 21:47:34 +0000472*/
473MagickExport MagickBooleanType ColorDecisionListImage(Image *image,
cristy1bfa9f02011-08-11 02:35:43 +0000474 const char *color_correction_collection,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000475{
476#define ColorDecisionListCorrectImageTag "ColorDecisionList/Image"
477
478 typedef struct _Correction
479 {
480 double
481 slope,
482 offset,
483 power;
484 } Correction;
485
486 typedef struct _ColorCorrection
487 {
488 Correction
489 red,
490 green,
491 blue;
492
493 double
494 saturation;
495 } ColorCorrection;
496
cristyc4c8d132010-01-07 01:58:38 +0000497 CacheView
498 *image_view;
499
cristy3ed852e2009-09-05 21:47:34 +0000500 char
501 token[MaxTextExtent];
502
503 ColorCorrection
504 color_correction;
505
506 const char
507 *content,
508 *p;
509
cristy3ed852e2009-09-05 21:47:34 +0000510 MagickBooleanType
511 status;
512
cristybb503372010-05-27 20:51:26 +0000513 MagickOffsetType
514 progress;
515
cristy101ab702011-10-13 13:06:32 +0000516 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000517 *cdl_map;
518
cristybb503372010-05-27 20:51:26 +0000519 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000520 i;
521
cristybb503372010-05-27 20:51:26 +0000522 ssize_t
523 y;
524
cristy3ed852e2009-09-05 21:47:34 +0000525 XMLTreeInfo
526 *cc,
527 *ccc,
528 *sat,
529 *sop;
530
cristy3ed852e2009-09-05 21:47:34 +0000531 /*
532 Allocate and initialize cdl maps.
533 */
534 assert(image != (Image *) NULL);
535 assert(image->signature == MagickSignature);
536 if (image->debug != MagickFalse)
537 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
538 if (color_correction_collection == (const char *) NULL)
539 return(MagickFalse);
cristy1bfa9f02011-08-11 02:35:43 +0000540 ccc=NewXMLTree((const char *) color_correction_collection,exception);
cristy3ed852e2009-09-05 21:47:34 +0000541 if (ccc == (XMLTreeInfo *) NULL)
542 return(MagickFalse);
543 cc=GetXMLTreeChild(ccc,"ColorCorrection");
544 if (cc == (XMLTreeInfo *) NULL)
545 {
546 ccc=DestroyXMLTree(ccc);
547 return(MagickFalse);
548 }
549 color_correction.red.slope=1.0;
550 color_correction.red.offset=0.0;
551 color_correction.red.power=1.0;
552 color_correction.green.slope=1.0;
553 color_correction.green.offset=0.0;
554 color_correction.green.power=1.0;
555 color_correction.blue.slope=1.0;
556 color_correction.blue.offset=0.0;
557 color_correction.blue.power=1.0;
558 color_correction.saturation=0.0;
559 sop=GetXMLTreeChild(cc,"SOPNode");
560 if (sop != (XMLTreeInfo *) NULL)
561 {
562 XMLTreeInfo
563 *offset,
564 *power,
565 *slope;
566
567 slope=GetXMLTreeChild(sop,"Slope");
568 if (slope != (XMLTreeInfo *) NULL)
569 {
570 content=GetXMLTreeContent(slope);
571 p=(const char *) content;
572 for (i=0; (*p != '\0') && (i < 3); i++)
573 {
574 GetMagickToken(p,&p,token);
575 if (*token == ',')
576 GetMagickToken(p,&p,token);
577 switch (i)
578 {
cristyc1acd842011-05-19 23:05:47 +0000579 case 0:
580 {
cristy9b34e302011-11-05 02:15:45 +0000581 color_correction.red.slope=StringToDouble(token,(char **) NULL);
cristyc1acd842011-05-19 23:05:47 +0000582 break;
583 }
584 case 1:
585 {
cristydbdd0e32011-11-04 23:29:40 +0000586 color_correction.green.slope=StringToDouble(token,
cristyc1acd842011-05-19 23:05:47 +0000587 (char **) NULL);
588 break;
589 }
590 case 2:
591 {
cristydbdd0e32011-11-04 23:29:40 +0000592 color_correction.blue.slope=StringToDouble(token,
cristyc1acd842011-05-19 23:05:47 +0000593 (char **) NULL);
594 break;
595 }
cristy3ed852e2009-09-05 21:47:34 +0000596 }
597 }
598 }
599 offset=GetXMLTreeChild(sop,"Offset");
600 if (offset != (XMLTreeInfo *) NULL)
601 {
602 content=GetXMLTreeContent(offset);
603 p=(const char *) content;
604 for (i=0; (*p != '\0') && (i < 3); i++)
605 {
606 GetMagickToken(p,&p,token);
607 if (*token == ',')
608 GetMagickToken(p,&p,token);
609 switch (i)
610 {
cristyc1acd842011-05-19 23:05:47 +0000611 case 0:
612 {
cristydbdd0e32011-11-04 23:29:40 +0000613 color_correction.red.offset=StringToDouble(token,
cristyc1acd842011-05-19 23:05:47 +0000614 (char **) NULL);
615 break;
616 }
617 case 1:
618 {
cristydbdd0e32011-11-04 23:29:40 +0000619 color_correction.green.offset=StringToDouble(token,
cristyc1acd842011-05-19 23:05:47 +0000620 (char **) NULL);
621 break;
622 }
623 case 2:
624 {
cristydbdd0e32011-11-04 23:29:40 +0000625 color_correction.blue.offset=StringToDouble(token,
cristyc1acd842011-05-19 23:05:47 +0000626 (char **) NULL);
627 break;
628 }
cristy3ed852e2009-09-05 21:47:34 +0000629 }
630 }
631 }
632 power=GetXMLTreeChild(sop,"Power");
633 if (power != (XMLTreeInfo *) NULL)
634 {
635 content=GetXMLTreeContent(power);
636 p=(const char *) content;
637 for (i=0; (*p != '\0') && (i < 3); i++)
638 {
639 GetMagickToken(p,&p,token);
640 if (*token == ',')
641 GetMagickToken(p,&p,token);
642 switch (i)
643 {
cristyc1acd842011-05-19 23:05:47 +0000644 case 0:
645 {
cristy9b34e302011-11-05 02:15:45 +0000646 color_correction.red.power=StringToDouble(token,(char **) NULL);
cristyc1acd842011-05-19 23:05:47 +0000647 break;
648 }
649 case 1:
650 {
cristydbdd0e32011-11-04 23:29:40 +0000651 color_correction.green.power=StringToDouble(token,
cristyc1acd842011-05-19 23:05:47 +0000652 (char **) NULL);
653 break;
654 }
655 case 2:
656 {
cristydbdd0e32011-11-04 23:29:40 +0000657 color_correction.blue.power=StringToDouble(token,
cristyc1acd842011-05-19 23:05:47 +0000658 (char **) NULL);
659 break;
660 }
cristy3ed852e2009-09-05 21:47:34 +0000661 }
662 }
663 }
664 }
665 sat=GetXMLTreeChild(cc,"SATNode");
666 if (sat != (XMLTreeInfo *) NULL)
667 {
668 XMLTreeInfo
669 *saturation;
670
671 saturation=GetXMLTreeChild(sat,"Saturation");
672 if (saturation != (XMLTreeInfo *) NULL)
673 {
674 content=GetXMLTreeContent(saturation);
675 p=(const char *) content;
676 GetMagickToken(p,&p,token);
cristyca826b52012-01-18 18:50:46 +0000677 color_correction.saturation=StringToDouble(token,(char **) NULL);
cristy3ed852e2009-09-05 21:47:34 +0000678 }
679 }
680 ccc=DestroyXMLTree(ccc);
681 if (image->debug != MagickFalse)
682 {
683 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
684 " Color Correction Collection:");
685 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000686 " color_correction.red.slope: %g",color_correction.red.slope);
cristy3ed852e2009-09-05 21:47:34 +0000687 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000688 " color_correction.red.offset: %g",color_correction.red.offset);
cristy3ed852e2009-09-05 21:47:34 +0000689 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000690 " color_correction.red.power: %g",color_correction.red.power);
cristy3ed852e2009-09-05 21:47:34 +0000691 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000692 " color_correction.green.slope: %g",color_correction.green.slope);
cristy3ed852e2009-09-05 21:47:34 +0000693 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000694 " color_correction.green.offset: %g",color_correction.green.offset);
cristy3ed852e2009-09-05 21:47:34 +0000695 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000696 " color_correction.green.power: %g",color_correction.green.power);
cristy3ed852e2009-09-05 21:47:34 +0000697 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000698 " color_correction.blue.slope: %g",color_correction.blue.slope);
cristy3ed852e2009-09-05 21:47:34 +0000699 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000700 " color_correction.blue.offset: %g",color_correction.blue.offset);
cristy3ed852e2009-09-05 21:47:34 +0000701 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000702 " color_correction.blue.power: %g",color_correction.blue.power);
cristy3ed852e2009-09-05 21:47:34 +0000703 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000704 " color_correction.saturation: %g",color_correction.saturation);
cristy3ed852e2009-09-05 21:47:34 +0000705 }
cristy101ab702011-10-13 13:06:32 +0000706 cdl_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*cdl_map));
707 if (cdl_map == (PixelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000708 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
709 image->filename);
cristyb5d5f722009-11-04 03:03:49 +0000710#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000711 #pragma omp parallel for schedule(static,4) \
712 if (MaxMap > 256) \
713 num_threads(GetMagickResourceLimit(ThreadResource))
cristy3ed852e2009-09-05 21:47:34 +0000714#endif
cristybb503372010-05-27 20:51:26 +0000715 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +0000716 {
cristyda1f9c12011-10-02 21:39:49 +0000717 cdl_map[i].red=(MagickRealType) ScaleMapToQuantum((MagickRealType)
718 (MaxMap*(pow(color_correction.red.slope*i/MaxMap+
719 color_correction.red.offset,color_correction.red.power))));
720 cdl_map[i].green=(MagickRealType) ScaleMapToQuantum((MagickRealType)
721 (MaxMap*(pow(color_correction.green.slope*i/MaxMap+
722 color_correction.green.offset,color_correction.green.power))));
723 cdl_map[i].blue=(MagickRealType) ScaleMapToQuantum((MagickRealType)
724 (MaxMap*(pow(color_correction.blue.slope*i/MaxMap+
725 color_correction.blue.offset,color_correction.blue.power))));
cristy3ed852e2009-09-05 21:47:34 +0000726 }
727 if (image->storage_class == PseudoClass)
728 {
729 /*
730 Apply transfer function to colormap.
731 */
cristyb5d5f722009-11-04 03:03:49 +0000732#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000733 #pragma omp parallel for schedule(static,4) shared(progress,status) \
734 if (image->colors > 256) \
735 num_threads(GetMagickResourceLimit(ThreadResource))
cristy3ed852e2009-09-05 21:47:34 +0000736#endif
cristybb503372010-05-27 20:51:26 +0000737 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +0000738 {
739 double
740 luma;
741
742 luma=0.2126*image->colormap[i].red+0.7152*image->colormap[i].green+
743 0.0722*image->colormap[i].blue;
cristy22e88202012-01-18 19:03:01 +0000744 image->colormap[i].red=luma+color_correction.saturation*cdl_map[
745 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].red))].red-
746 luma;
747 image->colormap[i].green=luma+color_correction.saturation*cdl_map[
748 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].green))].green-
749 luma;
750 image->colormap[i].blue=luma+color_correction.saturation*cdl_map[
751 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].blue))].blue-
752 luma;
cristy3ed852e2009-09-05 21:47:34 +0000753 }
754 }
755 /*
756 Apply transfer function to image.
757 */
758 status=MagickTrue;
759 progress=0;
cristydb070952012-04-20 14:33:00 +0000760 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000761#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000762 #pragma omp parallel for schedule(static,4) shared(progress,status) \
763 if ((image->rows*image->columns) > 8192) \
764 num_threads(GetMagickResourceLimit(ThreadResource))
cristy3ed852e2009-09-05 21:47:34 +0000765#endif
cristybb503372010-05-27 20:51:26 +0000766 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000767 {
768 double
769 luma;
770
cristy4c08aed2011-07-01 19:47:50 +0000771 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000772 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000773
cristy8d4629b2010-08-30 17:59:46 +0000774 register ssize_t
775 x;
776
cristy3ed852e2009-09-05 21:47:34 +0000777 if (status == MagickFalse)
778 continue;
779 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +0000780 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000781 {
782 status=MagickFalse;
783 continue;
784 }
cristybb503372010-05-27 20:51:26 +0000785 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000786 {
cristy4c08aed2011-07-01 19:47:50 +0000787 luma=0.2126*GetPixelRed(image,q)+0.7152*GetPixelGreen(image,q)+0.0722*
788 GetPixelBlue(image,q);
789 SetPixelRed(image,ClampToQuantum(luma+color_correction.saturation*
790 (cdl_map[ScaleQuantumToMap(GetPixelRed(image,q))].red-luma)),q);
791 SetPixelGreen(image,ClampToQuantum(luma+color_correction.saturation*
792 (cdl_map[ScaleQuantumToMap(GetPixelGreen(image,q))].green-luma)),q);
793 SetPixelBlue(image,ClampToQuantum(luma+color_correction.saturation*
794 (cdl_map[ScaleQuantumToMap(GetPixelBlue(image,q))].blue-luma)),q);
cristyed231572011-07-14 02:18:59 +0000795 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000796 }
797 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
798 status=MagickFalse;
799 if (image->progress_monitor != (MagickProgressMonitor) NULL)
800 {
801 MagickBooleanType
802 proceed;
803
cristyb5d5f722009-11-04 03:03:49 +0000804#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +0000805 #pragma omp critical (MagickCore_ColorDecisionListImageChannel)
cristy3ed852e2009-09-05 21:47:34 +0000806#endif
807 proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
808 progress++,image->rows);
809 if (proceed == MagickFalse)
810 status=MagickFalse;
811 }
812 }
813 image_view=DestroyCacheView(image_view);
cristy101ab702011-10-13 13:06:32 +0000814 cdl_map=(PixelInfo *) RelinquishMagickMemory(cdl_map);
cristy3ed852e2009-09-05 21:47:34 +0000815 return(status);
816}
817
818/*
819%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
820% %
821% %
822% %
cristy3ed852e2009-09-05 21:47:34 +0000823% C o n t r a s t I m a g e %
824% %
825% %
826% %
827%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
828%
829% ContrastImage() enhances the intensity differences between the lighter and
830% darker elements of the image. Set sharpen to a MagickTrue to increase the
831% image contrast otherwise the contrast is reduced.
832%
833% The format of the ContrastImage method is:
834%
835% MagickBooleanType ContrastImage(Image *image,
cristye23ec9d2011-08-16 18:15:40 +0000836% const MagickBooleanType sharpen,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000837%
838% A description of each parameter follows:
839%
840% o image: the image.
841%
842% o sharpen: Increase or decrease image contrast.
843%
cristye23ec9d2011-08-16 18:15:40 +0000844% o exception: return any errors or warnings in this structure.
845%
cristy3ed852e2009-09-05 21:47:34 +0000846*/
847
cristy3094b7f2011-10-01 23:18:02 +0000848static void Contrast(const int sign,double *red,double *green,double *blue)
cristy3ed852e2009-09-05 21:47:34 +0000849{
850 double
851 brightness,
852 hue,
853 saturation;
854
855 /*
856 Enhance contrast: dark color become darker, light color become lighter.
857 */
cristy3094b7f2011-10-01 23:18:02 +0000858 assert(red != (double *) NULL);
859 assert(green != (double *) NULL);
860 assert(blue != (double *) NULL);
cristy3ed852e2009-09-05 21:47:34 +0000861 hue=0.0;
862 saturation=0.0;
863 brightness=0.0;
864 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
cristy31ac0f02011-03-12 02:04:47 +0000865 brightness+=0.5*sign*(0.5*(sin((double) (MagickPI*(brightness-0.5)))+1.0)-
cristy4205a3c2010-09-12 20:19:59 +0000866 brightness);
cristy3ed852e2009-09-05 21:47:34 +0000867 if (brightness > 1.0)
868 brightness=1.0;
869 else
870 if (brightness < 0.0)
871 brightness=0.0;
872 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
873}
874
875MagickExport MagickBooleanType ContrastImage(Image *image,
cristye23ec9d2011-08-16 18:15:40 +0000876 const MagickBooleanType sharpen,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000877{
878#define ContrastImageTag "Contrast/Image"
879
cristyc4c8d132010-01-07 01:58:38 +0000880 CacheView
881 *image_view;
882
cristy3ed852e2009-09-05 21:47:34 +0000883 int
884 sign;
885
cristy3ed852e2009-09-05 21:47:34 +0000886 MagickBooleanType
887 status;
888
cristybb503372010-05-27 20:51:26 +0000889 MagickOffsetType
890 progress;
891
892 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000893 i;
894
cristybb503372010-05-27 20:51:26 +0000895 ssize_t
896 y;
897
cristy3ed852e2009-09-05 21:47:34 +0000898 assert(image != (Image *) NULL);
899 assert(image->signature == MagickSignature);
900 if (image->debug != MagickFalse)
901 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
902 sign=sharpen != MagickFalse ? 1 : -1;
903 if (image->storage_class == PseudoClass)
904 {
905 /*
906 Contrast enhance colormap.
907 */
cristybb503372010-05-27 20:51:26 +0000908 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +0000909 Contrast(sign,&image->colormap[i].red,&image->colormap[i].green,
910 &image->colormap[i].blue);
911 }
912 /*
913 Contrast enhance image.
914 */
915 status=MagickTrue;
916 progress=0;
cristydb070952012-04-20 14:33:00 +0000917 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000918#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000919 #pragma omp parallel for schedule(static,4) shared(progress,status) \
920 if ((image->rows*image->columns) > 8192) \
921 num_threads(GetMagickResourceLimit(ThreadResource))
cristy3ed852e2009-09-05 21:47:34 +0000922#endif
cristybb503372010-05-27 20:51:26 +0000923 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000924 {
cristy3094b7f2011-10-01 23:18:02 +0000925 double
cristy5afeab82011-04-30 01:30:09 +0000926 blue,
927 green,
928 red;
929
cristy4c08aed2011-07-01 19:47:50 +0000930 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000931 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000932
cristy8d4629b2010-08-30 17:59:46 +0000933 register ssize_t
934 x;
935
cristy3ed852e2009-09-05 21:47:34 +0000936 if (status == MagickFalse)
937 continue;
938 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +0000939 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000940 {
941 status=MagickFalse;
942 continue;
943 }
cristybb503372010-05-27 20:51:26 +0000944 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000945 {
cristyda1f9c12011-10-02 21:39:49 +0000946 red=(double) GetPixelRed(image,q);
947 green=(double) GetPixelGreen(image,q);
948 blue=(double) GetPixelBlue(image,q);
cristy5afeab82011-04-30 01:30:09 +0000949 Contrast(sign,&red,&green,&blue);
cristyda1f9c12011-10-02 21:39:49 +0000950 SetPixelRed(image,ClampToQuantum(red),q);
951 SetPixelGreen(image,ClampToQuantum(green),q);
952 SetPixelBlue(image,ClampToQuantum(blue),q);
cristyed231572011-07-14 02:18:59 +0000953 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000954 }
955 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
956 status=MagickFalse;
957 if (image->progress_monitor != (MagickProgressMonitor) NULL)
958 {
959 MagickBooleanType
960 proceed;
961
cristyb5d5f722009-11-04 03:03:49 +0000962#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +0000963 #pragma omp critical (MagickCore_ContrastImage)
cristy3ed852e2009-09-05 21:47:34 +0000964#endif
965 proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
966 if (proceed == MagickFalse)
967 status=MagickFalse;
968 }
969 }
970 image_view=DestroyCacheView(image_view);
971 return(status);
972}
973
974/*
975%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
976% %
977% %
978% %
979% C o n t r a s t S t r e t c h I m a g e %
980% %
981% %
982% %
983%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
984%
cristyf1611782011-08-01 15:39:13 +0000985% ContrastStretchImage() is a simple image enhancement technique that attempts
anthonye5b39652012-04-21 05:37:29 +0000986% to improve the contrast in an image by 'stretching' the range of intensity
cristyf1611782011-08-01 15:39:13 +0000987% values it contains to span a desired range of values. It differs from the
988% more sophisticated histogram equalization in that it can only apply a
989% linear scaling function to the image pixel values. As a result the
anthonye5b39652012-04-21 05:37:29 +0000990% 'enhancement' is less harsh.
cristy3ed852e2009-09-05 21:47:34 +0000991%
992% The format of the ContrastStretchImage method is:
993%
994% MagickBooleanType ContrastStretchImage(Image *image,
cristye23ec9d2011-08-16 18:15:40 +0000995% const char *levels,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000996%
997% A description of each parameter follows:
998%
999% o image: the image.
1000%
cristy3ed852e2009-09-05 21:47:34 +00001001% o black_point: the black point.
1002%
1003% o white_point: the white point.
1004%
1005% o levels: Specify the levels where the black and white points have the
1006% range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
1007%
cristye23ec9d2011-08-16 18:15:40 +00001008% o exception: return any errors or warnings in this structure.
1009%
cristy3ed852e2009-09-05 21:47:34 +00001010*/
cristy3ed852e2009-09-05 21:47:34 +00001011MagickExport MagickBooleanType ContrastStretchImage(Image *image,
cristye23ec9d2011-08-16 18:15:40 +00001012 const double black_point,const double white_point,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001013{
1014#define MaxRange(color) ((MagickRealType) ScaleQuantumToMap((Quantum) (color)))
1015#define ContrastStretchImageTag "ContrastStretch/Image"
1016
cristyc4c8d132010-01-07 01:58:38 +00001017 CacheView
1018 *image_view;
1019
cristy3ed852e2009-09-05 21:47:34 +00001020 MagickBooleanType
1021 status;
1022
cristybb503372010-05-27 20:51:26 +00001023 MagickOffsetType
1024 progress;
1025
cristyf45fec72011-08-23 16:02:32 +00001026 double
1027 *black,
cristy3ed852e2009-09-05 21:47:34 +00001028 *histogram,
1029 *stretch_map,
cristyf45fec72011-08-23 16:02:32 +00001030 *white;
cristy3ed852e2009-09-05 21:47:34 +00001031
cristybb503372010-05-27 20:51:26 +00001032 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001033 i;
1034
cristy564a5692012-01-20 23:56:26 +00001035 size_t
1036 number_channels;
1037
cristybb503372010-05-27 20:51:26 +00001038 ssize_t
1039 y;
1040
cristy3ed852e2009-09-05 21:47:34 +00001041 /*
1042 Allocate histogram and stretch map.
1043 */
1044 assert(image != (Image *) NULL);
1045 assert(image->signature == MagickSignature);
1046 if (image->debug != MagickFalse)
1047 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristyf45fec72011-08-23 16:02:32 +00001048 black=(double *) AcquireQuantumMemory(GetPixelChannels(image),sizeof(*black));
1049 white=(double *) AcquireQuantumMemory(GetPixelChannels(image),sizeof(*white));
cristy564a5692012-01-20 23:56:26 +00001050 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,GetPixelChannels(image)*
1051 sizeof(*histogram));
cristyf45fec72011-08-23 16:02:32 +00001052 stretch_map=(double *) AcquireQuantumMemory(MaxMap+1UL,
cristy465571b2011-08-21 20:43:15 +00001053 GetPixelChannels(image)*sizeof(*stretch_map));
cristyf45fec72011-08-23 16:02:32 +00001054 if ((black == (double *) NULL) || (white == (double *) NULL) ||
1055 (histogram == (double *) NULL) || (stretch_map == (double *) NULL))
cristy465571b2011-08-21 20:43:15 +00001056 {
cristyf45fec72011-08-23 16:02:32 +00001057 if (stretch_map != (double *) NULL)
1058 stretch_map=(double *) RelinquishMagickMemory(stretch_map);
1059 if (histogram != (double *) NULL)
1060 histogram=(double *) RelinquishMagickMemory(histogram);
1061 if (white != (double *) NULL)
1062 white=(double *) RelinquishMagickMemory(white);
1063 if (black != (double *) NULL)
1064 black=(double *) RelinquishMagickMemory(black);
cristy465571b2011-08-21 20:43:15 +00001065 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1066 image->filename);
1067 }
cristy3ed852e2009-09-05 21:47:34 +00001068 /*
1069 Form histogram.
1070 */
1071 status=MagickTrue;
cristy465571b2011-08-21 20:43:15 +00001072 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1073 sizeof(*histogram));
cristydb070952012-04-20 14:33:00 +00001074 image_view=AcquireVirtualCacheView(image,exception);
cristybb503372010-05-27 20:51:26 +00001075 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001076 {
cristy4c08aed2011-07-01 19:47:50 +00001077 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001078 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001079
cristybb503372010-05-27 20:51:26 +00001080 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001081 x;
1082
1083 if (status == MagickFalse)
1084 continue;
1085 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001086 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001087 {
1088 status=MagickFalse;
1089 continue;
1090 }
cristy43547a52011-08-09 13:07:05 +00001091 for (x=0; x < (ssize_t) image->columns; x++)
1092 {
cristy465571b2011-08-21 20:43:15 +00001093 register ssize_t
1094 i;
1095
1096 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy465571b2011-08-21 20:43:15 +00001097 histogram[GetPixelChannels(image)*ScaleQuantumToMap(p[i])+i]++;
cristy43547a52011-08-09 13:07:05 +00001098 p+=GetPixelChannels(image);
1099 }
cristy3ed852e2009-09-05 21:47:34 +00001100 }
cristydb070952012-04-20 14:33:00 +00001101 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00001102 /*
1103 Find the histogram boundaries by locating the black/white levels.
1104 */
cristy564a5692012-01-20 23:56:26 +00001105 number_channels=GetPixelChannels(image);
cristyb5d5f722009-11-04 03:03:49 +00001106#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001107 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1108 if (MaxMap > 256) \
1109 num_threads(GetMagickResourceLimit(ThreadResource))
cristy3ed852e2009-09-05 21:47:34 +00001110#endif
cristyc94ba6f2012-01-29 23:19:58 +00001111 for (i=0; i < (ssize_t) number_channels; i++)
cristy3ed852e2009-09-05 21:47:34 +00001112 {
cristy465571b2011-08-21 20:43:15 +00001113 double
1114 intensity;
1115
1116 register ssize_t
1117 j;
1118
1119 black[i]=0.0;
1120 white[i]=MaxRange(QuantumRange);
1121 intensity=0.0;
1122 for (j=0; j <= (ssize_t) MaxMap; j++)
1123 {
1124 intensity+=histogram[GetPixelChannels(image)*j+i];
1125 if (intensity > black_point)
1126 break;
1127 }
1128 black[i]=(MagickRealType) j;
1129 intensity=0.0;
1130 for (j=(ssize_t) MaxMap; j != 0; j--)
1131 {
1132 intensity+=histogram[GetPixelChannels(image)*j+i];
1133 if (intensity > ((double) image->columns*image->rows-white_point))
1134 break;
1135 }
1136 white[i]=(MagickRealType) j;
cristy3ed852e2009-09-05 21:47:34 +00001137 }
cristy465571b2011-08-21 20:43:15 +00001138 histogram=(double *) RelinquishMagickMemory(histogram);
cristy3ed852e2009-09-05 21:47:34 +00001139 /*
cristy465571b2011-08-21 20:43:15 +00001140 Stretch the histogram to create the stretched image mapping.
cristy3ed852e2009-09-05 21:47:34 +00001141 */
cristy465571b2011-08-21 20:43:15 +00001142 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*GetPixelChannels(image)*
1143 sizeof(*stretch_map));
cristy564a5692012-01-20 23:56:26 +00001144 number_channels=GetPixelChannels(image);
cristy465571b2011-08-21 20:43:15 +00001145#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001146 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1147 if (MaxMap > 256) \
1148 num_threads(GetMagickResourceLimit(ThreadResource))
cristy465571b2011-08-21 20:43:15 +00001149#endif
cristy564a5692012-01-20 23:56:26 +00001150 for (i=0; i < (ssize_t) number_channels; i++)
cristy465571b2011-08-21 20:43:15 +00001151 {
1152 register ssize_t
1153 j;
1154
1155 for (j=0; j <= (ssize_t) MaxMap; j++)
1156 {
1157 if (j < (ssize_t) black[i])
1158 stretch_map[GetPixelChannels(image)*j+i]=0.0;
1159 else
1160 if (j > (ssize_t) white[i])
1161 stretch_map[GetPixelChannels(image)*j+i]=(MagickRealType)
1162 QuantumRange;
1163 else
1164 if (black[i] != white[i])
1165 stretch_map[GetPixelChannels(image)*j+i]=(MagickRealType)
1166 ScaleMapToQuantum((MagickRealType) (MaxMap*(j-black[i])/
1167 (white[i]-black[i])));
1168 }
1169 }
cristy3ed852e2009-09-05 21:47:34 +00001170 if (image->storage_class == PseudoClass)
1171 {
cristy465571b2011-08-21 20:43:15 +00001172 register ssize_t
1173 j;
1174
cristy3ed852e2009-09-05 21:47:34 +00001175 /*
cristy465571b2011-08-21 20:43:15 +00001176 Stretch-contrast colormap.
cristy3ed852e2009-09-05 21:47:34 +00001177 */
cristyb5d5f722009-11-04 03:03:49 +00001178#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001179 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1180 if (image->colors > 256) \
1181 num_threads(GetMagickResourceLimit(ThreadResource))
cristy3ed852e2009-09-05 21:47:34 +00001182#endif
cristy465571b2011-08-21 20:43:15 +00001183 for (j=0; j < (ssize_t) image->colors; j++)
cristy3ed852e2009-09-05 21:47:34 +00001184 {
cristyed231572011-07-14 02:18:59 +00001185 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001186 {
cristy465571b2011-08-21 20:43:15 +00001187 i=GetPixelChannelMapChannel(image,RedPixelChannel);
1188 if (black[i] != white[i])
cristyda1f9c12011-10-02 21:39:49 +00001189 image->colormap[j].red=stretch_map[GetPixelChannels(image)*
1190 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))]+i;
cristy3ed852e2009-09-05 21:47:34 +00001191 }
cristyed231572011-07-14 02:18:59 +00001192 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001193 {
cristy465571b2011-08-21 20:43:15 +00001194 i=GetPixelChannelMapChannel(image,GreenPixelChannel);
1195 if (black[i] != white[i])
cristyda1f9c12011-10-02 21:39:49 +00001196 image->colormap[j].green=stretch_map[GetPixelChannels(image)*
1197 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))]+i;
cristy3ed852e2009-09-05 21:47:34 +00001198 }
cristyed231572011-07-14 02:18:59 +00001199 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001200 {
cristy465571b2011-08-21 20:43:15 +00001201 i=GetPixelChannelMapChannel(image,BluePixelChannel);
1202 if (black[i] != white[i])
cristyda1f9c12011-10-02 21:39:49 +00001203 image->colormap[j].blue=stretch_map[GetPixelChannels(image)*
1204 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))]+i;
cristy3ed852e2009-09-05 21:47:34 +00001205 }
cristyed231572011-07-14 02:18:59 +00001206 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001207 {
cristy465571b2011-08-21 20:43:15 +00001208 i=GetPixelChannelMapChannel(image,AlphaPixelChannel);
1209 if (black[i] != white[i])
cristyda1f9c12011-10-02 21:39:49 +00001210 image->colormap[j].alpha=stretch_map[GetPixelChannels(image)*
1211 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))]+i;
cristy3ed852e2009-09-05 21:47:34 +00001212 }
1213 }
1214 }
1215 /*
cristy465571b2011-08-21 20:43:15 +00001216 Stretch-contrast image.
cristy3ed852e2009-09-05 21:47:34 +00001217 */
1218 status=MagickTrue;
1219 progress=0;
cristydb070952012-04-20 14:33:00 +00001220 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +00001221#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001222 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1223 if ((image->rows*image->columns) > 8192) \
1224 num_threads(GetMagickResourceLimit(ThreadResource))
cristy3ed852e2009-09-05 21:47:34 +00001225#endif
cristybb503372010-05-27 20:51:26 +00001226 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001227 {
cristy4c08aed2011-07-01 19:47:50 +00001228 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001229 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001230
cristy8d4629b2010-08-30 17:59:46 +00001231 register ssize_t
1232 x;
1233
cristy3ed852e2009-09-05 21:47:34 +00001234 if (status == MagickFalse)
1235 continue;
1236 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001237 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001238 {
1239 status=MagickFalse;
1240 continue;
1241 }
cristybb503372010-05-27 20:51:26 +00001242 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001243 {
cristy465571b2011-08-21 20:43:15 +00001244 register ssize_t
1245 i;
1246
cristy10a6c612012-01-29 21:41:05 +00001247 if (GetPixelMask(image,q) != 0)
1248 {
1249 q+=GetPixelChannels(image);
1250 continue;
1251 }
cristy465571b2011-08-21 20:43:15 +00001252 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1253 {
cristyabace412011-12-11 15:56:53 +00001254 PixelChannel
1255 channel;
1256
cristy465571b2011-08-21 20:43:15 +00001257 PixelTrait
1258 traits;
1259
cristyabace412011-12-11 15:56:53 +00001260 channel=GetPixelChannelMapChannel(image,i);
1261 traits=GetPixelChannelMapTraits(image,channel);
cristyd09f8802012-02-04 16:44:10 +00001262 if (((traits & UpdatePixelTrait) == 0) || (black[i] == white[i]))
1263 continue;
1264 q[i]=ClampToQuantum(stretch_map[GetPixelChannels(image)*
1265 ScaleQuantumToMap(q[i])+i]);
cristy465571b2011-08-21 20:43:15 +00001266 }
cristyed231572011-07-14 02:18:59 +00001267 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001268 }
1269 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1270 status=MagickFalse;
1271 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1272 {
1273 MagickBooleanType
1274 proceed;
1275
cristyb5d5f722009-11-04 03:03:49 +00001276#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00001277 #pragma omp critical (MagickCore_ContrastStretchImage)
cristy3ed852e2009-09-05 21:47:34 +00001278#endif
1279 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1280 image->rows);
1281 if (proceed == MagickFalse)
1282 status=MagickFalse;
1283 }
1284 }
1285 image_view=DestroyCacheView(image_view);
cristyf45fec72011-08-23 16:02:32 +00001286 stretch_map=(double *) RelinquishMagickMemory(stretch_map);
1287 white=(double *) RelinquishMagickMemory(white);
1288 black=(double *) RelinquishMagickMemory(black);
cristy3ed852e2009-09-05 21:47:34 +00001289 return(status);
1290}
1291
1292/*
1293%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1294% %
1295% %
1296% %
1297% E n h a n c e I m a g e %
1298% %
1299% %
1300% %
1301%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1302%
1303% EnhanceImage() applies a digital filter that improves the quality of a
1304% noisy image.
1305%
1306% The format of the EnhanceImage method is:
1307%
1308% Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1309%
1310% A description of each parameter follows:
1311%
1312% o image: the image.
1313%
1314% o exception: return any errors or warnings in this structure.
1315%
1316*/
1317MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1318{
cristy6d8c3d72011-08-22 01:20:01 +00001319#define EnhancePixel(weight) \
cristy0beccfa2011-09-25 20:47:53 +00001320 mean=((MagickRealType) r[i]+GetPixelChannel(enhance_image,channel,q))/2.0; \
1321 distance=(MagickRealType) r[i]-(MagickRealType) GetPixelChannel( \
1322 enhance_image,channel,q); \
cristy3ed852e2009-09-05 21:47:34 +00001323 distance_squared=QuantumScale*(2.0*((MagickRealType) QuantumRange+1.0)+ \
cristy6d8c3d72011-08-22 01:20:01 +00001324 mean)*distance*distance; \
cristy3ed852e2009-09-05 21:47:34 +00001325 if (distance_squared < ((MagickRealType) QuantumRange*(MagickRealType) \
1326 QuantumRange/25.0f)) \
1327 { \
cristy6d8c3d72011-08-22 01:20:01 +00001328 aggregate+=(weight)*r[i]; \
cristy3ed852e2009-09-05 21:47:34 +00001329 total_weight+=(weight); \
1330 } \
cristy6d8c3d72011-08-22 01:20:01 +00001331 r+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001332#define EnhanceImageTag "Enhance/Image"
1333
cristyc4c8d132010-01-07 01:58:38 +00001334 CacheView
1335 *enhance_view,
1336 *image_view;
1337
cristy3ed852e2009-09-05 21:47:34 +00001338 Image
1339 *enhance_image;
1340
cristy3ed852e2009-09-05 21:47:34 +00001341 MagickBooleanType
1342 status;
1343
cristybb503372010-05-27 20:51:26 +00001344 MagickOffsetType
1345 progress;
1346
cristybb503372010-05-27 20:51:26 +00001347 ssize_t
1348 y;
1349
cristy3ed852e2009-09-05 21:47:34 +00001350 /*
1351 Initialize enhanced image attributes.
1352 */
1353 assert(image != (const Image *) NULL);
1354 assert(image->signature == MagickSignature);
1355 if (image->debug != MagickFalse)
1356 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1357 assert(exception != (ExceptionInfo *) NULL);
1358 assert(exception->signature == MagickSignature);
cristy3ed852e2009-09-05 21:47:34 +00001359 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1360 exception);
1361 if (enhance_image == (Image *) NULL)
1362 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00001363 if (SetImageStorageClass(enhance_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001364 {
cristy3ed852e2009-09-05 21:47:34 +00001365 enhance_image=DestroyImage(enhance_image);
1366 return((Image *) NULL);
1367 }
1368 /*
1369 Enhance image.
1370 */
1371 status=MagickTrue;
1372 progress=0;
cristydb070952012-04-20 14:33:00 +00001373 image_view=AcquireVirtualCacheView(image,exception);
1374 enhance_view=AcquireAuthenticCacheView(enhance_image,exception);
cristyb5d5f722009-11-04 03:03:49 +00001375#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001376 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1377 if ((image->rows*image->columns) > 8192) \
1378 num_threads(GetMagickResourceLimit(ThreadResource))
cristy3ed852e2009-09-05 21:47:34 +00001379#endif
cristybb503372010-05-27 20:51:26 +00001380 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001381 {
cristy4c08aed2011-07-01 19:47:50 +00001382 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001383 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001384
cristy4c08aed2011-07-01 19:47:50 +00001385 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001386 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001387
cristy8d4629b2010-08-30 17:59:46 +00001388 register ssize_t
1389 x;
1390
cristy6d8c3d72011-08-22 01:20:01 +00001391 ssize_t
1392 center;
1393
cristy3ed852e2009-09-05 21:47:34 +00001394 if (status == MagickFalse)
1395 continue;
1396 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1397 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1398 exception);
cristy4c08aed2011-07-01 19:47:50 +00001399 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001400 {
1401 status=MagickFalse;
1402 continue;
1403 }
cristyf45fec72011-08-23 16:02:32 +00001404 center=(ssize_t) GetPixelChannels(image)*(2*(image->columns+4)+2);
cristybb503372010-05-27 20:51:26 +00001405 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001406 {
cristy6d8c3d72011-08-22 01:20:01 +00001407 register ssize_t
1408 i;
cristy3ed852e2009-09-05 21:47:34 +00001409
cristy10a6c612012-01-29 21:41:05 +00001410 if (GetPixelMask(image,p) != 0)
1411 {
1412 p+=GetPixelChannels(image);
1413 q+=GetPixelChannels(enhance_image);
1414 continue;
1415 }
cristy6d8c3d72011-08-22 01:20:01 +00001416 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1417 {
1418 MagickRealType
1419 aggregate,
1420 distance,
1421 distance_squared,
1422 mean,
1423 total_weight;
cristy3ed852e2009-09-05 21:47:34 +00001424
cristy6d8c3d72011-08-22 01:20:01 +00001425 PixelChannel
1426 channel;
cristy3ed852e2009-09-05 21:47:34 +00001427
cristy6d8c3d72011-08-22 01:20:01 +00001428 PixelTrait
1429 enhance_traits,
1430 traits;
cristy3ed852e2009-09-05 21:47:34 +00001431
cristy6d8c3d72011-08-22 01:20:01 +00001432 register const Quantum
1433 *restrict r;
1434
cristye2a912b2011-12-05 20:02:07 +00001435 channel=GetPixelChannelMapChannel(image,i);
cristyabace412011-12-11 15:56:53 +00001436 traits=GetPixelChannelMapTraits(image,channel);
cristy6d8c3d72011-08-22 01:20:01 +00001437 enhance_traits=GetPixelChannelMapTraits(enhance_image,channel);
cristy010d7d12011-08-31 01:02:48 +00001438 if ((traits == UndefinedPixelTrait) ||
1439 (enhance_traits == UndefinedPixelTrait))
cristy6d8c3d72011-08-22 01:20:01 +00001440 continue;
cristy0beccfa2011-09-25 20:47:53 +00001441 SetPixelChannel(enhance_image,channel,p[center+i],q);
cristy6d8c3d72011-08-22 01:20:01 +00001442 if ((enhance_traits & CopyPixelTrait) != 0)
1443 continue;
1444 /*
1445 Compute weighted average of target pixel color components.
1446 */
1447 aggregate=0.0;
1448 total_weight=0.0;
cristyf45fec72011-08-23 16:02:32 +00001449 r=p;
cristy6d8c3d72011-08-22 01:20:01 +00001450 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1451 EnhancePixel(8.0); EnhancePixel(5.0);
1452 r=p+1*GetPixelChannels(image)*(image->columns+4);
1453 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1454 EnhancePixel(20.0); EnhancePixel(8.0);
1455 r=p+2*GetPixelChannels(image)*(image->columns+4);
1456 EnhancePixel(10.0); EnhancePixel(40.0); EnhancePixel(80.0);
1457 EnhancePixel(40.0); EnhancePixel(10.0);
1458 r=p+3*GetPixelChannels(image)*(image->columns+4);
1459 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1460 EnhancePixel(20.0); EnhancePixel(8.0);
1461 r=p+4*GetPixelChannels(image)*(image->columns+4);
1462 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1463 EnhancePixel(8.0); EnhancePixel(5.0);
cristy0beccfa2011-09-25 20:47:53 +00001464 SetPixelChannel(enhance_image,channel,ClampToQuantum(aggregate/
1465 total_weight),q);
cristy6d8c3d72011-08-22 01:20:01 +00001466 }
cristyed231572011-07-14 02:18:59 +00001467 p+=GetPixelChannels(image);
1468 q+=GetPixelChannels(enhance_image);
cristy3ed852e2009-09-05 21:47:34 +00001469 }
1470 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1471 status=MagickFalse;
1472 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1473 {
1474 MagickBooleanType
1475 proceed;
1476
cristyb5d5f722009-11-04 03:03:49 +00001477#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00001478 #pragma omp critical (MagickCore_EnhanceImage)
cristy3ed852e2009-09-05 21:47:34 +00001479#endif
1480 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1481 if (proceed == MagickFalse)
1482 status=MagickFalse;
1483 }
1484 }
1485 enhance_view=DestroyCacheView(enhance_view);
1486 image_view=DestroyCacheView(image_view);
1487 return(enhance_image);
1488}
1489
1490/*
1491%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1492% %
1493% %
1494% %
1495% E q u a l i z e I m a g e %
1496% %
1497% %
1498% %
1499%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1500%
1501% EqualizeImage() applies a histogram equalization to the image.
1502%
1503% The format of the EqualizeImage method is:
1504%
cristy6d8c3d72011-08-22 01:20:01 +00001505% MagickBooleanType EqualizeImage(Image *image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001506%
1507% A description of each parameter follows:
1508%
1509% o image: the image.
1510%
cristy6d8c3d72011-08-22 01:20:01 +00001511% o exception: return any errors or warnings in this structure.
cristy3ed852e2009-09-05 21:47:34 +00001512%
1513*/
cristy6d8c3d72011-08-22 01:20:01 +00001514MagickExport MagickBooleanType EqualizeImage(Image *image,
1515 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001516{
cristy3ed852e2009-09-05 21:47:34 +00001517#define EqualizeImageTag "Equalize/Image"
1518
cristyc4c8d132010-01-07 01:58:38 +00001519 CacheView
1520 *image_view;
1521
cristy3ed852e2009-09-05 21:47:34 +00001522 MagickBooleanType
1523 status;
1524
cristybb503372010-05-27 20:51:26 +00001525 MagickOffsetType
1526 progress;
1527
cristyf45fec72011-08-23 16:02:32 +00001528 MagickRealType
cristy5f95f4f2011-10-23 01:01:01 +00001529 black[CompositePixelChannel],
cristy3ed852e2009-09-05 21:47:34 +00001530 *equalize_map,
1531 *histogram,
cristy3ed852e2009-09-05 21:47:34 +00001532 *map,
cristy5f95f4f2011-10-23 01:01:01 +00001533 white[CompositePixelChannel];
cristy3ed852e2009-09-05 21:47:34 +00001534
cristybb503372010-05-27 20:51:26 +00001535 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001536 i;
1537
cristy564a5692012-01-20 23:56:26 +00001538 size_t
1539 number_channels;
1540
cristybb503372010-05-27 20:51:26 +00001541 ssize_t
1542 y;
1543
cristy3ed852e2009-09-05 21:47:34 +00001544 /*
1545 Allocate and initialize histogram arrays.
1546 */
1547 assert(image != (Image *) NULL);
1548 assert(image->signature == MagickSignature);
1549 if (image->debug != MagickFalse)
1550 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristyf45fec72011-08-23 16:02:32 +00001551 equalize_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
1552 GetPixelChannels(image)*sizeof(*equalize_map));
1553 histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
1554 GetPixelChannels(image)*sizeof(*histogram));
1555 map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
1556 GetPixelChannels(image)*sizeof(*map));
1557 if ((equalize_map == (MagickRealType *) NULL) ||
1558 (histogram == (MagickRealType *) NULL) ||
1559 (map == (MagickRealType *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001560 {
cristyf45fec72011-08-23 16:02:32 +00001561 if (map != (MagickRealType *) NULL)
1562 map=(MagickRealType *) RelinquishMagickMemory(map);
1563 if (histogram != (MagickRealType *) NULL)
1564 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
1565 if (equalize_map != (MagickRealType *) NULL)
1566 equalize_map=(MagickRealType *) RelinquishMagickMemory(equalize_map);
cristy3ed852e2009-09-05 21:47:34 +00001567 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1568 image->filename);
1569 }
1570 /*
1571 Form histogram.
1572 */
cristyf45fec72011-08-23 16:02:32 +00001573 status=MagickTrue;
1574 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1575 sizeof(*histogram));
cristydb070952012-04-20 14:33:00 +00001576 image_view=AcquireVirtualCacheView(image,exception);
cristybb503372010-05-27 20:51:26 +00001577 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001578 {
cristy4c08aed2011-07-01 19:47:50 +00001579 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001580 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001581
cristybb503372010-05-27 20:51:26 +00001582 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001583 x;
1584
cristyf45fec72011-08-23 16:02:32 +00001585 if (status == MagickFalse)
1586 continue;
1587 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001588 if (p == (const Quantum *) NULL)
cristyf45fec72011-08-23 16:02:32 +00001589 {
1590 status=MagickFalse;
1591 continue;
1592 }
cristybb503372010-05-27 20:51:26 +00001593 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001594 {
cristyf45fec72011-08-23 16:02:32 +00001595 register ssize_t
1596 i;
1597
1598 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1599 histogram[GetPixelChannels(image)*ScaleQuantumToMap(p[i])+i]++;
cristyed231572011-07-14 02:18:59 +00001600 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001601 }
1602 }
cristydb070952012-04-20 14:33:00 +00001603 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00001604 /*
1605 Integrate the histogram to get the equalization map.
1606 */
cristy564a5692012-01-20 23:56:26 +00001607 number_channels=GetPixelChannels(image);
cristyb5d5f722009-11-04 03:03:49 +00001608#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001609 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1610 if (MaxMap > 256) \
1611 num_threads(GetMagickResourceLimit(ThreadResource))
cristy3ed852e2009-09-05 21:47:34 +00001612#endif
cristyc94ba6f2012-01-29 23:19:58 +00001613 for (i=0; i < (ssize_t) number_channels; i++)
cristy3ed852e2009-09-05 21:47:34 +00001614 {
cristyf45fec72011-08-23 16:02:32 +00001615 MagickRealType
1616 intensity;
1617
1618 register ssize_t
1619 j;
1620
1621 intensity=0.0;
1622 for (j=0; j <= (ssize_t) MaxMap; j++)
1623 {
1624 intensity+=histogram[GetPixelChannels(image)*j+i];
1625 map[GetPixelChannels(image)*j+i]=intensity;
1626 }
cristy3ed852e2009-09-05 21:47:34 +00001627 }
cristyf45fec72011-08-23 16:02:32 +00001628 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*GetPixelChannels(image)*
1629 sizeof(*equalize_map));
cristy564a5692012-01-20 23:56:26 +00001630 number_channels=GetPixelChannels(image);
cristyf45fec72011-08-23 16:02:32 +00001631#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001632 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1633 if (MaxMap > 256) \
1634 num_threads(GetMagickResourceLimit(ThreadResource))
cristyf45fec72011-08-23 16:02:32 +00001635#endif
cristy564a5692012-01-20 23:56:26 +00001636 for (i=0; i < (ssize_t) number_channels; i++)
cristyf45fec72011-08-23 16:02:32 +00001637 {
1638 register ssize_t
1639 j;
1640
1641 black[i]=map[i];
1642 white[i]=map[GetPixelChannels(image)*MaxMap+i];
1643 if (black[i] != white[i])
1644 for (j=0; j <= (ssize_t) MaxMap; j++)
1645 equalize_map[GetPixelChannels(image)*j+i]=(MagickRealType)
1646 ScaleMapToQuantum((MagickRealType) ((MaxMap*(map[
1647 GetPixelChannels(image)*j+i]-black[i]))/(white[i]-black[i])));
1648 }
1649 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
1650 map=(MagickRealType *) RelinquishMagickMemory(map);
cristy3ed852e2009-09-05 21:47:34 +00001651 if (image->storage_class == PseudoClass)
1652 {
cristyf54798b2011-11-21 18:38:23 +00001653 PixelChannel
1654 channel;
1655
cristyf45fec72011-08-23 16:02:32 +00001656 register ssize_t
1657 j;
1658
cristy3ed852e2009-09-05 21:47:34 +00001659 /*
1660 Equalize colormap.
1661 */
cristyb5d5f722009-11-04 03:03:49 +00001662#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001663 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1664 if (image->colors > 256) \
1665 num_threads(GetMagickResourceLimit(ThreadResource))
cristy3ed852e2009-09-05 21:47:34 +00001666#endif
cristyf45fec72011-08-23 16:02:32 +00001667 for (j=0; j < (ssize_t) image->colors; j++)
cristy3ed852e2009-09-05 21:47:34 +00001668 {
cristyf54798b2011-11-21 18:38:23 +00001669 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00001670 {
cristyf54798b2011-11-21 18:38:23 +00001671 channel=GetPixelChannelMapChannel(image,RedPixelChannel);
1672 if (black[channel] != white[channel])
cristyda1f9c12011-10-02 21:39:49 +00001673 image->colormap[j].red=equalize_map[GetPixelChannels(image)*
cristyf54798b2011-11-21 18:38:23 +00001674 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))]+
1675 channel;
cristyf45fec72011-08-23 16:02:32 +00001676 }
cristyf54798b2011-11-21 18:38:23 +00001677 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00001678 {
cristyf54798b2011-11-21 18:38:23 +00001679 channel=GetPixelChannelMapChannel(image,GreenPixelChannel);
1680 if (black[channel] != white[channel])
cristyda1f9c12011-10-02 21:39:49 +00001681 image->colormap[j].green=equalize_map[GetPixelChannels(image)*
cristyf54798b2011-11-21 18:38:23 +00001682 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))]+
1683 channel;
cristyf45fec72011-08-23 16:02:32 +00001684 }
cristyf54798b2011-11-21 18:38:23 +00001685 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00001686 {
cristyf54798b2011-11-21 18:38:23 +00001687 channel=GetPixelChannelMapChannel(image,BluePixelChannel);
1688 if (black[channel] != white[channel])
cristyda1f9c12011-10-02 21:39:49 +00001689 image->colormap[j].blue=equalize_map[GetPixelChannels(image)*
cristyf54798b2011-11-21 18:38:23 +00001690 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))]+
1691 channel;
cristyf45fec72011-08-23 16:02:32 +00001692 }
cristyf54798b2011-11-21 18:38:23 +00001693 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00001694 {
cristyf54798b2011-11-21 18:38:23 +00001695 channel=GetPixelChannelMapChannel(image,AlphaPixelChannel);
1696 if (black[channel] != white[channel])
cristyda1f9c12011-10-02 21:39:49 +00001697 image->colormap[j].alpha=equalize_map[GetPixelChannels(image)*
cristyf54798b2011-11-21 18:38:23 +00001698 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))]+
1699 channel;
cristyf45fec72011-08-23 16:02:32 +00001700 }
cristy3ed852e2009-09-05 21:47:34 +00001701 }
1702 }
1703 /*
1704 Equalize image.
1705 */
cristy3ed852e2009-09-05 21:47:34 +00001706 progress=0;
cristydb070952012-04-20 14:33:00 +00001707 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +00001708#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001709 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1710 if ((image->rows*image->columns) > 8192) \
1711 num_threads(GetMagickResourceLimit(ThreadResource))
cristy3ed852e2009-09-05 21:47:34 +00001712#endif
cristybb503372010-05-27 20:51:26 +00001713 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001714 {
cristy4c08aed2011-07-01 19:47:50 +00001715 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001716 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001717
cristy8d4629b2010-08-30 17:59:46 +00001718 register ssize_t
1719 x;
1720
cristy3ed852e2009-09-05 21:47:34 +00001721 if (status == MagickFalse)
1722 continue;
1723 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001724 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001725 {
1726 status=MagickFalse;
1727 continue;
1728 }
cristybb503372010-05-27 20:51:26 +00001729 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001730 {
cristyf45fec72011-08-23 16:02:32 +00001731 register ssize_t
1732 i;
1733
cristy10a6c612012-01-29 21:41:05 +00001734 if (GetPixelMask(image,q) != 0)
1735 {
1736 q+=GetPixelChannels(image);
1737 continue;
1738 }
cristyf45fec72011-08-23 16:02:32 +00001739 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1740 {
cristyabace412011-12-11 15:56:53 +00001741 PixelChannel
1742 channel;
1743
cristyf45fec72011-08-23 16:02:32 +00001744 PixelTrait
1745 traits;
1746
cristyabace412011-12-11 15:56:53 +00001747 channel=GetPixelChannelMapChannel(image,i);
1748 traits=GetPixelChannelMapTraits(image,channel);
cristyd09f8802012-02-04 16:44:10 +00001749 if (((traits & UpdatePixelTrait) == 0) || (black[i] == white[i]))
1750 continue;
1751 q[i]=ClampToQuantum(equalize_map[GetPixelChannels(image)*
1752 ScaleQuantumToMap(q[i])+i]);
cristyf45fec72011-08-23 16:02:32 +00001753 }
cristyed231572011-07-14 02:18:59 +00001754 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001755 }
1756 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1757 status=MagickFalse;
1758 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1759 {
1760 MagickBooleanType
1761 proceed;
1762
cristyb5d5f722009-11-04 03:03:49 +00001763#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00001764 #pragma omp critical (MagickCore_EqualizeImage)
cristy3ed852e2009-09-05 21:47:34 +00001765#endif
1766 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1767 if (proceed == MagickFalse)
1768 status=MagickFalse;
1769 }
1770 }
1771 image_view=DestroyCacheView(image_view);
cristyf45fec72011-08-23 16:02:32 +00001772 equalize_map=(MagickRealType *) RelinquishMagickMemory(equalize_map);
cristy3ed852e2009-09-05 21:47:34 +00001773 return(status);
1774}
1775
1776/*
1777%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1778% %
1779% %
1780% %
1781% G a m m a I m a g e %
1782% %
1783% %
1784% %
1785%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1786%
1787% GammaImage() gamma-corrects a particular image channel. The same
1788% image viewed on different devices will have perceptual differences in the
1789% way the image's intensities are represented on the screen. Specify
1790% individual gamma levels for the red, green, and blue channels, or adjust
1791% all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1792%
1793% You can also reduce the influence of a particular channel with a gamma
1794% value of 0.
1795%
1796% The format of the GammaImage method is:
1797%
cristyb3e7c6c2011-07-24 01:43:55 +00001798% MagickBooleanType GammaImage(Image *image,const double gamma,
1799% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001800%
1801% A description of each parameter follows:
1802%
1803% o image: the image.
1804%
cristya6360142011-03-23 23:08:04 +00001805% o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
1806%
cristy3ed852e2009-09-05 21:47:34 +00001807% o gamma: the image gamma.
1808%
1809*/
cristyb3e7c6c2011-07-24 01:43:55 +00001810MagickExport MagickBooleanType GammaImage(Image *image,const double gamma,
1811 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001812{
1813#define GammaCorrectImageTag "GammaCorrect/Image"
1814
cristyc4c8d132010-01-07 01:58:38 +00001815 CacheView
1816 *image_view;
1817
cristy3ed852e2009-09-05 21:47:34 +00001818 MagickBooleanType
1819 status;
1820
cristybb503372010-05-27 20:51:26 +00001821 MagickOffsetType
1822 progress;
1823
cristy19593872012-01-22 02:00:33 +00001824 Quantum
1825 *gamma_map;
1826
cristybb503372010-05-27 20:51:26 +00001827 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001828 i;
1829
cristybb503372010-05-27 20:51:26 +00001830 ssize_t
1831 y;
1832
cristy3ed852e2009-09-05 21:47:34 +00001833 /*
1834 Allocate and initialize gamma maps.
1835 */
1836 assert(image != (Image *) NULL);
1837 assert(image->signature == MagickSignature);
1838 if (image->debug != MagickFalse)
1839 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1840 if (gamma == 1.0)
1841 return(MagickTrue);
cristy19593872012-01-22 02:00:33 +00001842 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
1843 if (gamma_map == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001844 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1845 image->filename);
1846 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
1847 if (gamma != 0.0)
cristyd476a8e2011-07-23 16:13:22 +00001848#if defined(MAGICKCORE_OPENMP_SUPPORT) && (MaxMap > 256)
cristyac245f82012-05-05 17:13:57 +00001849 #pragma omp parallel for \
1850 if (MaxMap > 256) \
1851 num_threads(GetMagickResourceLimit(ThreadResource))
cristy3ed852e2009-09-05 21:47:34 +00001852#endif
cristybb503372010-05-27 20:51:26 +00001853 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy19593872012-01-22 02:00:33 +00001854 gamma_map[i]=ScaleMapToQuantum((MagickRealType) (MaxMap*pow((double) i/
1855 MaxMap,1.0/gamma)));
cristy3ed852e2009-09-05 21:47:34 +00001856 if (image->storage_class == PseudoClass)
1857 {
1858 /*
1859 Gamma-correct colormap.
1860 */
cristyb5d5f722009-11-04 03:03:49 +00001861#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001862 #pragma omp parallel for schedule(static) shared(progress,status) \
1863 if (image->colors > 256) \
1864 num_threads(GetMagickResourceLimit(ThreadResource))
cristy3ed852e2009-09-05 21:47:34 +00001865#endif
cristybb503372010-05-27 20:51:26 +00001866 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00001867 {
cristyed231572011-07-14 02:18:59 +00001868 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyc94ba6f2012-01-29 23:19:58 +00001869 image->colormap[i].red=(MagickRealType) gamma_map[
cristyda1f9c12011-10-02 21:39:49 +00001870 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].red))];
cristyed231572011-07-14 02:18:59 +00001871 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyc94ba6f2012-01-29 23:19:58 +00001872 image->colormap[i].green=(MagickRealType) gamma_map[
cristyda1f9c12011-10-02 21:39:49 +00001873 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].green))];
cristyed231572011-07-14 02:18:59 +00001874 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyc94ba6f2012-01-29 23:19:58 +00001875 image->colormap[i].blue=(MagickRealType) gamma_map[
cristyda1f9c12011-10-02 21:39:49 +00001876 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].blue))];
cristyed231572011-07-14 02:18:59 +00001877 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristyc94ba6f2012-01-29 23:19:58 +00001878 image->colormap[i].alpha=(MagickRealType) gamma_map[
cristyda1f9c12011-10-02 21:39:49 +00001879 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].alpha))];
cristy3ed852e2009-09-05 21:47:34 +00001880 }
1881 }
1882 /*
1883 Gamma-correct image.
1884 */
1885 status=MagickTrue;
1886 progress=0;
cristydb070952012-04-20 14:33:00 +00001887 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +00001888#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001889 #pragma omp parallel for schedule(static,4) shared(progress,status) \
1890 if ((image->rows*image->columns) > 8192) \
1891 num_threads(GetMagickResourceLimit(ThreadResource))
cristy3ed852e2009-09-05 21:47:34 +00001892#endif
cristybb503372010-05-27 20:51:26 +00001893 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001894 {
cristy4c08aed2011-07-01 19:47:50 +00001895 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001896 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001897
cristy8d4629b2010-08-30 17:59:46 +00001898 register ssize_t
1899 x;
1900
cristy3ed852e2009-09-05 21:47:34 +00001901 if (status == MagickFalse)
1902 continue;
1903 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001904 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001905 {
1906 status=MagickFalse;
1907 continue;
1908 }
cristybb503372010-05-27 20:51:26 +00001909 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001910 {
cristyd476a8e2011-07-23 16:13:22 +00001911 register ssize_t
1912 i;
1913
cristy10a6c612012-01-29 21:41:05 +00001914 if (GetPixelMask(image,q) != 0)
1915 {
1916 q+=GetPixelChannels(image);
1917 continue;
1918 }
cristya30d9ba2011-07-23 21:00:48 +00001919 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristyd476a8e2011-07-23 16:13:22 +00001920 {
cristyabace412011-12-11 15:56:53 +00001921 PixelChannel
1922 channel;
1923
cristyd476a8e2011-07-23 16:13:22 +00001924 PixelTrait
1925 traits;
1926
cristyabace412011-12-11 15:56:53 +00001927 channel=GetPixelChannelMapChannel(image,i);
1928 traits=GetPixelChannelMapTraits(image,channel);
cristyd09f8802012-02-04 16:44:10 +00001929 if ((traits & UpdatePixelTrait) == 0)
1930 continue;
1931 q[i]=gamma_map[ScaleQuantumToMap(q[i])];
cristyd476a8e2011-07-23 16:13:22 +00001932 }
cristya30d9ba2011-07-23 21:00:48 +00001933 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001934 }
cristy3ed852e2009-09-05 21:47:34 +00001935 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1936 status=MagickFalse;
1937 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1938 {
1939 MagickBooleanType
1940 proceed;
1941
cristyb5d5f722009-11-04 03:03:49 +00001942#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00001943 #pragma omp critical (MagickCore_GammaImage)
cristy3ed852e2009-09-05 21:47:34 +00001944#endif
1945 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
1946 image->rows);
1947 if (proceed == MagickFalse)
1948 status=MagickFalse;
1949 }
1950 }
1951 image_view=DestroyCacheView(image_view);
cristy19593872012-01-22 02:00:33 +00001952 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
cristy3ed852e2009-09-05 21:47:34 +00001953 if (image->gamma != 0.0)
1954 image->gamma*=gamma;
1955 return(status);
1956}
1957
1958/*
1959%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1960% %
1961% %
1962% %
1963% H a l d C l u t I m a g e %
1964% %
1965% %
1966% %
1967%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1968%
1969% HaldClutImage() applies a Hald color lookup table to the image. A Hald
1970% color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
1971% Create it with the HALD coder. You can apply any color transformation to
1972% the Hald image and then use this method to apply the transform to the
1973% image.
1974%
1975% The format of the HaldClutImage method is:
1976%
cristy7c0a0a42011-08-23 17:57:25 +00001977% MagickBooleanType HaldClutImage(Image *image,Image *hald_image,
1978% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001979%
1980% A description of each parameter follows:
1981%
1982% o image: the image, which is replaced by indexed CLUT values
1983%
1984% o hald_image: the color lookup table image for replacement color values.
1985%
cristy7c0a0a42011-08-23 17:57:25 +00001986% o exception: return any errors or warnings in this structure.
1987%
cristy3ed852e2009-09-05 21:47:34 +00001988*/
1989
1990static inline size_t MagickMin(const size_t x,const size_t y)
1991{
1992 if (x < y)
1993 return(x);
1994 return(y);
1995}
1996
1997MagickExport MagickBooleanType HaldClutImage(Image *image,
cristy7c0a0a42011-08-23 17:57:25 +00001998 const Image *hald_image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001999{
cristy3ed852e2009-09-05 21:47:34 +00002000#define HaldClutImageTag "Clut/Image"
2001
2002 typedef struct _HaldInfo
2003 {
2004 MagickRealType
2005 x,
2006 y,
2007 z;
2008 } HaldInfo;
2009
cristyfa112112010-01-04 17:48:07 +00002010 CacheView
cristyd551fbc2011-03-31 18:07:46 +00002011 *hald_view,
cristyfa112112010-01-04 17:48:07 +00002012 *image_view;
2013
cristy3ed852e2009-09-05 21:47:34 +00002014 double
2015 width;
2016
cristy3ed852e2009-09-05 21:47:34 +00002017 MagickBooleanType
2018 status;
2019
cristybb503372010-05-27 20:51:26 +00002020 MagickOffsetType
2021 progress;
2022
cristy4c08aed2011-07-01 19:47:50 +00002023 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00002024 zero;
2025
cristy3ed852e2009-09-05 21:47:34 +00002026 size_t
2027 cube_size,
2028 length,
2029 level;
2030
cristybb503372010-05-27 20:51:26 +00002031 ssize_t
2032 y;
2033
cristy3ed852e2009-09-05 21:47:34 +00002034 assert(image != (Image *) NULL);
2035 assert(image->signature == MagickSignature);
2036 if (image->debug != MagickFalse)
2037 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2038 assert(hald_image != (Image *) NULL);
2039 assert(hald_image->signature == MagickSignature);
cristy574cc262011-08-05 01:23:58 +00002040 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00002041 return(MagickFalse);
cristy0898eba2012-04-09 16:38:29 +00002042 if (IsGrayColorspace(image->colorspace) != MagickFalse)
2043 (void) TransformImageColorspace(image,sRGBColorspace,exception);
cristy3ed852e2009-09-05 21:47:34 +00002044 if (image->matte == MagickFalse)
cristy63240882011-08-05 19:05:27 +00002045 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
cristy3ed852e2009-09-05 21:47:34 +00002046 /*
2047 Hald clut image.
2048 */
2049 status=MagickTrue;
2050 progress=0;
2051 length=MagickMin(hald_image->columns,hald_image->rows);
2052 for (level=2; (level*level*level) < length; level++) ;
2053 level*=level;
2054 cube_size=level*level;
2055 width=(double) hald_image->columns;
cristy4c08aed2011-07-01 19:47:50 +00002056 GetPixelInfo(hald_image,&zero);
cristydb070952012-04-20 14:33:00 +00002057 hald_view=AcquireVirtualCacheView(hald_image,exception);
2058 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +00002059#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00002060 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2061 if ((image->rows*image->columns) > 8192) \
2062 num_threads(GetMagickResourceLimit(ThreadResource))
cristy3ed852e2009-09-05 21:47:34 +00002063#endif
cristybb503372010-05-27 20:51:26 +00002064 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002065 {
cristy4c08aed2011-07-01 19:47:50 +00002066 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002067 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002068
cristy8d4629b2010-08-30 17:59:46 +00002069 register ssize_t
2070 x;
2071
cristy3ed852e2009-09-05 21:47:34 +00002072 if (status == MagickFalse)
2073 continue;
2074 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00002075 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002076 {
2077 status=MagickFalse;
2078 continue;
2079 }
cristybb503372010-05-27 20:51:26 +00002080 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002081 {
cristy7c0a0a42011-08-23 17:57:25 +00002082 double
2083 offset;
2084
2085 HaldInfo
2086 point;
2087
2088 PixelInfo
2089 pixel,
2090 pixel1,
2091 pixel2,
2092 pixel3,
2093 pixel4;
2094
cristy4c08aed2011-07-01 19:47:50 +00002095 point.x=QuantumScale*(level-1.0)*GetPixelRed(image,q);
2096 point.y=QuantumScale*(level-1.0)*GetPixelGreen(image,q);
2097 point.z=QuantumScale*(level-1.0)*GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00002098 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2099 point.x-=floor(point.x);
2100 point.y-=floor(point.y);
2101 point.z-=floor(point.z);
cristy7c0a0a42011-08-23 17:57:25 +00002102 pixel1=zero;
2103 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2104 fmod(offset,width),floor(offset/width),&pixel1,exception);
2105 pixel2=zero;
2106 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2107 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2108 pixel3=zero;
2109 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2110 point.y,&pixel3);
cristy3ed852e2009-09-05 21:47:34 +00002111 offset+=cube_size;
cristy7c0a0a42011-08-23 17:57:25 +00002112 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2113 fmod(offset,width),floor(offset/width),&pixel1,exception);
2114 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2115 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2116 pixel4=zero;
2117 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2118 point.y,&pixel4);
2119 pixel=zero;
2120 CompositePixelInfoAreaBlend(&pixel3,pixel3.alpha,&pixel4,pixel4.alpha,
2121 point.z,&pixel);
cristyed231572011-07-14 02:18:59 +00002122 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00002123 SetPixelRed(image,ClampToQuantum(pixel.red),q);
cristyed231572011-07-14 02:18:59 +00002124 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00002125 SetPixelGreen(image,ClampToQuantum(pixel.green),q);
cristyed231572011-07-14 02:18:59 +00002126 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00002127 SetPixelBlue(image,ClampToQuantum(pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00002128 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002129 (image->colorspace == CMYKColorspace))
cristyf45fec72011-08-23 16:02:32 +00002130 SetPixelBlack(image,ClampToQuantum(pixel.black),q);
2131 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
2132 (image->matte != MagickFalse))
2133 SetPixelAlpha(image,ClampToQuantum(pixel.alpha),q);
cristyed231572011-07-14 02:18:59 +00002134 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002135 }
2136 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2137 status=MagickFalse;
2138 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2139 {
2140 MagickBooleanType
2141 proceed;
2142
cristyb5d5f722009-11-04 03:03:49 +00002143#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00002144 #pragma omp critical (MagickCore_HaldClutImage)
cristy3ed852e2009-09-05 21:47:34 +00002145#endif
2146 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2147 if (proceed == MagickFalse)
2148 status=MagickFalse;
2149 }
2150 }
cristyd551fbc2011-03-31 18:07:46 +00002151 hald_view=DestroyCacheView(hald_view);
cristy3ed852e2009-09-05 21:47:34 +00002152 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002153 return(status);
2154}
2155
2156/*
2157%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2158% %
2159% %
2160% %
2161% L e v e l I m a g e %
2162% %
2163% %
2164% %
2165%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2166%
2167% LevelImage() adjusts the levels of a particular image channel by
2168% scaling the colors falling between specified white and black points to
2169% the full available quantum range.
2170%
2171% The parameters provided represent the black, and white points. The black
2172% point specifies the darkest color in the image. Colors darker than the
2173% black point are set to zero. White point specifies the lightest color in
2174% the image. Colors brighter than the white point are set to the maximum
2175% quantum value.
2176%
2177% If a '!' flag is given, map black and white colors to the given levels
2178% rather than mapping those levels to black and white. See
cristy50fbc382011-07-07 02:19:17 +00002179% LevelizeImage() below.
cristy3ed852e2009-09-05 21:47:34 +00002180%
2181% Gamma specifies a gamma correction to apply to the image.
2182%
2183% The format of the LevelImage method is:
2184%
cristy01e9afd2011-08-10 17:38:41 +00002185% MagickBooleanType LevelImage(Image *image,const double black_point,
2186% const double white_point,const double gamma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002187%
2188% A description of each parameter follows:
2189%
2190% o image: the image.
2191%
cristy01e9afd2011-08-10 17:38:41 +00002192% o black_point: The level to map zero (black) to.
2193%
2194% o white_point: The level to map QuantumRange (white) to.
2195%
2196% o exception: return any errors or warnings in this structure.
cristy3ed852e2009-09-05 21:47:34 +00002197%
2198*/
cristy780e9ef2011-12-18 23:33:50 +00002199
cristyb8b0d162011-12-18 23:41:28 +00002200static inline MagickRealType LevelPixel(const double black_point,
cristy780e9ef2011-12-18 23:33:50 +00002201 const double white_point,const double gamma,const MagickRealType pixel)
2202{
2203 double
2204 level_pixel,
2205 scale;
2206
cristy780e9ef2011-12-18 23:33:50 +00002207 scale=(white_point != black_point) ? 1.0/(white_point-black_point) : 1.0;
2208 level_pixel=(MagickRealType) QuantumRange*pow(scale*((double) pixel-
2209 black_point),1.0/gamma);
2210 return(level_pixel);
2211}
2212
cristy7c0a0a42011-08-23 17:57:25 +00002213MagickExport MagickBooleanType LevelImage(Image *image,const double black_point,
2214 const double white_point,const double gamma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002215{
2216#define LevelImageTag "Level/Image"
cristy3ed852e2009-09-05 21:47:34 +00002217
cristyc4c8d132010-01-07 01:58:38 +00002218 CacheView
2219 *image_view;
2220
cristy3ed852e2009-09-05 21:47:34 +00002221 MagickBooleanType
2222 status;
2223
cristybb503372010-05-27 20:51:26 +00002224 MagickOffsetType
2225 progress;
2226
cristy8d4629b2010-08-30 17:59:46 +00002227 register ssize_t
2228 i;
2229
cristybb503372010-05-27 20:51:26 +00002230 ssize_t
2231 y;
2232
cristy3ed852e2009-09-05 21:47:34 +00002233 /*
2234 Allocate and initialize levels map.
2235 */
2236 assert(image != (Image *) NULL);
2237 assert(image->signature == MagickSignature);
2238 if (image->debug != MagickFalse)
2239 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2240 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002241#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00002242 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2243 if (image->colors > 256) \
2244 num_threads(GetMagickResourceLimit(ThreadResource))
cristy3ed852e2009-09-05 21:47:34 +00002245#endif
cristybb503372010-05-27 20:51:26 +00002246 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002247 {
2248 /*
2249 Level colormap.
2250 */
cristyed231572011-07-14 02:18:59 +00002251 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy780e9ef2011-12-18 23:33:50 +00002252 image->colormap[i].red=(double) ClampToQuantum(LevelPixel(black_point,
2253 white_point,gamma,image->colormap[i].red));
cristyed231572011-07-14 02:18:59 +00002254 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy780e9ef2011-12-18 23:33:50 +00002255 image->colormap[i].green=(double) ClampToQuantum(LevelPixel(black_point,
2256 white_point,gamma,image->colormap[i].green));
cristyed231572011-07-14 02:18:59 +00002257 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy780e9ef2011-12-18 23:33:50 +00002258 image->colormap[i].blue=(double) ClampToQuantum(LevelPixel(black_point,
2259 white_point,gamma,image->colormap[i].blue));
cristyed231572011-07-14 02:18:59 +00002260 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy780e9ef2011-12-18 23:33:50 +00002261 image->colormap[i].alpha=(double) ClampToQuantum(LevelPixel(black_point,
2262 white_point,gamma,image->colormap[i].alpha));
cristy3ed852e2009-09-05 21:47:34 +00002263 }
2264 /*
2265 Level image.
2266 */
2267 status=MagickTrue;
2268 progress=0;
cristydb070952012-04-20 14:33:00 +00002269 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +00002270#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00002271 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2272 if ((image->rows*image->columns) > 8192) \
2273 num_threads(GetMagickResourceLimit(ThreadResource))
cristy3ed852e2009-09-05 21:47:34 +00002274#endif
cristybb503372010-05-27 20:51:26 +00002275 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002276 {
cristy4c08aed2011-07-01 19:47:50 +00002277 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002278 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002279
cristy8d4629b2010-08-30 17:59:46 +00002280 register ssize_t
2281 x;
2282
cristy3ed852e2009-09-05 21:47:34 +00002283 if (status == MagickFalse)
2284 continue;
2285 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00002286 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002287 {
2288 status=MagickFalse;
2289 continue;
2290 }
cristybb503372010-05-27 20:51:26 +00002291 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002292 {
cristy7c0a0a42011-08-23 17:57:25 +00002293 register ssize_t
2294 i;
2295
cristy10a6c612012-01-29 21:41:05 +00002296 if (GetPixelMask(image,q) != 0)
2297 {
2298 q+=GetPixelChannels(image);
2299 continue;
2300 }
cristy7c0a0a42011-08-23 17:57:25 +00002301 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2302 {
cristyabace412011-12-11 15:56:53 +00002303 PixelChannel
2304 channel;
2305
cristy7c0a0a42011-08-23 17:57:25 +00002306 PixelTrait
2307 traits;
2308
cristyabace412011-12-11 15:56:53 +00002309 channel=GetPixelChannelMapChannel(image,i);
2310 traits=GetPixelChannelMapTraits(image,channel);
cristyd09f8802012-02-04 16:44:10 +00002311 if ((traits & UpdatePixelTrait) == 0)
cristy7c0a0a42011-08-23 17:57:25 +00002312 continue;
cristy780e9ef2011-12-18 23:33:50 +00002313 q[i]=ClampToQuantum(LevelPixel(black_point,white_point,gamma,
2314 (MagickRealType) q[i]));
cristy7c0a0a42011-08-23 17:57:25 +00002315 }
cristyed231572011-07-14 02:18:59 +00002316 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002317 }
2318 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2319 status=MagickFalse;
2320 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2321 {
2322 MagickBooleanType
2323 proceed;
2324
cristyb5d5f722009-11-04 03:03:49 +00002325#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00002326 #pragma omp critical (MagickCore_LevelImage)
cristy3ed852e2009-09-05 21:47:34 +00002327#endif
2328 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2329 if (proceed == MagickFalse)
2330 status=MagickFalse;
2331 }
2332 }
2333 image_view=DestroyCacheView(image_view);
2334 return(status);
2335}
2336
2337/*
2338%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2339% %
2340% %
2341% %
cristy33bd5152011-08-24 01:42:24 +00002342% L e v e l i z e I m a g e %
cristy3ed852e2009-09-05 21:47:34 +00002343% %
2344% %
2345% %
2346%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2347%
cristy50fbc382011-07-07 02:19:17 +00002348% LevelizeImage() applies the reversed LevelImage() operation to just
cristy3ed852e2009-09-05 21:47:34 +00002349% the specific channels specified. It compresses the full range of color
2350% values, so that they lie between the given black and white points. Gamma is
2351% applied before the values are mapped.
2352%
cristy50fbc382011-07-07 02:19:17 +00002353% LevelizeImage() can be called with by using a +level command line
cristy3ed852e2009-09-05 21:47:34 +00002354% API option, or using a '!' on a -level or LevelImage() geometry string.
2355%
anthony31f1bf72012-01-30 12:37:22 +00002356% It can be used to de-contrast a greyscale image to the exact levels
2357% specified. Or by using specific levels for each channel of an image you
2358% can convert a gray-scale image to any linear color gradient, according to
2359% those levels.
cristy3ed852e2009-09-05 21:47:34 +00002360%
cristy50fbc382011-07-07 02:19:17 +00002361% The format of the LevelizeImage method is:
cristy3ed852e2009-09-05 21:47:34 +00002362%
cristy50fbc382011-07-07 02:19:17 +00002363% MagickBooleanType LevelizeImage(Image *image,const double black_point,
cristy7c0a0a42011-08-23 17:57:25 +00002364% const double white_point,const double gamma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002365%
2366% A description of each parameter follows:
2367%
2368% o image: the image.
2369%
cristy3ed852e2009-09-05 21:47:34 +00002370% o black_point: The level to map zero (black) to.
2371%
cristy01e9afd2011-08-10 17:38:41 +00002372% o white_point: The level to map QuantumRange (white) to.
cristy3ed852e2009-09-05 21:47:34 +00002373%
2374% o gamma: adjust gamma by this factor before mapping values.
2375%
cristy7c0a0a42011-08-23 17:57:25 +00002376% o exception: return any errors or warnings in this structure.
2377%
cristy3ed852e2009-09-05 21:47:34 +00002378*/
cristyd1a2c0f2011-02-09 14:14:50 +00002379MagickExport MagickBooleanType LevelizeImage(Image *image,
cristy7c0a0a42011-08-23 17:57:25 +00002380 const double black_point,const double white_point,const double gamma,
2381 ExceptionInfo *exception)
cristyd1a2c0f2011-02-09 14:14:50 +00002382{
cristy3ed852e2009-09-05 21:47:34 +00002383#define LevelizeImageTag "Levelize/Image"
cristyce70c172010-01-07 17:15:30 +00002384#define LevelizeValue(x) (ClampToQuantum(((MagickRealType) \
cristy50fbc382011-07-07 02:19:17 +00002385 pow((double) (QuantumScale*(x)),1.0/gamma))*(white_point-black_point)+ \
cristy3ed852e2009-09-05 21:47:34 +00002386 black_point))
2387
cristyc4c8d132010-01-07 01:58:38 +00002388 CacheView
2389 *image_view;
2390
cristy3ed852e2009-09-05 21:47:34 +00002391 MagickBooleanType
2392 status;
2393
cristybb503372010-05-27 20:51:26 +00002394 MagickOffsetType
2395 progress;
2396
2397 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002398 i;
2399
cristybb503372010-05-27 20:51:26 +00002400 ssize_t
2401 y;
2402
cristy3ed852e2009-09-05 21:47:34 +00002403 /*
2404 Allocate and initialize levels map.
2405 */
2406 assert(image != (Image *) NULL);
2407 assert(image->signature == MagickSignature);
2408 if (image->debug != MagickFalse)
2409 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2410 if (image->storage_class == PseudoClass)
cristyb5d5f722009-11-04 03:03:49 +00002411#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00002412 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2413 if (image->colors > 256) \
2414 num_threads(GetMagickResourceLimit(ThreadResource))
cristy3ed852e2009-09-05 21:47:34 +00002415#endif
cristybb503372010-05-27 20:51:26 +00002416 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002417 {
2418 /*
2419 Level colormap.
2420 */
cristyed231572011-07-14 02:18:59 +00002421 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00002422 image->colormap[i].red=(double) LevelizeValue(
2423 image->colormap[i].red);
cristyed231572011-07-14 02:18:59 +00002424 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00002425 image->colormap[i].green=(double) LevelizeValue(
2426 image->colormap[i].green);
cristyed231572011-07-14 02:18:59 +00002427 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00002428 image->colormap[i].blue=(double) LevelizeValue(
2429 image->colormap[i].blue);
cristyed231572011-07-14 02:18:59 +00002430 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00002431 image->colormap[i].alpha=(double) LevelizeValue(
2432 image->colormap[i].alpha);
cristy3ed852e2009-09-05 21:47:34 +00002433 }
2434 /*
2435 Level image.
2436 */
2437 status=MagickTrue;
2438 progress=0;
cristydb070952012-04-20 14:33:00 +00002439 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +00002440#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00002441 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2442 if ((image->rows*image->columns) > 8192) \
2443 num_threads(GetMagickResourceLimit(ThreadResource))
cristy3ed852e2009-09-05 21:47:34 +00002444#endif
cristybb503372010-05-27 20:51:26 +00002445 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002446 {
cristy4c08aed2011-07-01 19:47:50 +00002447 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002448 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002449
cristy8d4629b2010-08-30 17:59:46 +00002450 register ssize_t
2451 x;
2452
cristy3ed852e2009-09-05 21:47:34 +00002453 if (status == MagickFalse)
2454 continue;
2455 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00002456 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002457 {
2458 status=MagickFalse;
2459 continue;
2460 }
cristybb503372010-05-27 20:51:26 +00002461 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002462 {
cristy7c0a0a42011-08-23 17:57:25 +00002463 register ssize_t
2464 i;
2465
cristy10a6c612012-01-29 21:41:05 +00002466 if (GetPixelMask(image,q) != 0)
2467 {
2468 q+=GetPixelChannels(image);
2469 continue;
2470 }
cristy7c0a0a42011-08-23 17:57:25 +00002471 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2472 {
cristyabace412011-12-11 15:56:53 +00002473 PixelChannel
2474 channel;
2475
cristy7c0a0a42011-08-23 17:57:25 +00002476 PixelTrait
2477 traits;
2478
cristyabace412011-12-11 15:56:53 +00002479 channel=GetPixelChannelMapChannel(image,i);
2480 traits=GetPixelChannelMapTraits(image,channel);
cristyd09f8802012-02-04 16:44:10 +00002481 if ((traits & UpdatePixelTrait) == 0)
cristyb8b0d162011-12-18 23:41:28 +00002482 continue;
cristy780e9ef2011-12-18 23:33:50 +00002483 q[i]=LevelizeValue(q[i]);
cristy7c0a0a42011-08-23 17:57:25 +00002484 }
cristyed231572011-07-14 02:18:59 +00002485 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002486 }
2487 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2488 status=MagickFalse;
2489 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2490 {
2491 MagickBooleanType
2492 proceed;
2493
cristyb5d5f722009-11-04 03:03:49 +00002494#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00002495 #pragma omp critical (MagickCore_LevelizeImage)
cristy3ed852e2009-09-05 21:47:34 +00002496#endif
2497 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2498 if (proceed == MagickFalse)
2499 status=MagickFalse;
2500 }
2501 }
cristy8d4629b2010-08-30 17:59:46 +00002502 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002503 return(status);
2504}
2505
2506/*
2507%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2508% %
2509% %
2510% %
2511% L e v e l I m a g e C o l o r s %
2512% %
2513% %
2514% %
2515%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2516%
cristy490408a2011-07-07 14:42:05 +00002517% LevelImageColors() maps the given color to "black" and "white" values,
cristyee0f8d72009-09-19 00:58:29 +00002518% linearly spreading out the colors, and level values on a channel by channel
2519% bases, as per LevelImage(). The given colors allows you to specify
glennrp1e7f7bc2011-03-02 19:25:28 +00002520% different level ranges for each of the color channels separately.
cristy3ed852e2009-09-05 21:47:34 +00002521%
2522% If the boolean 'invert' is set true the image values will modifyed in the
2523% reverse direction. That is any existing "black" and "white" colors in the
2524% image will become the color values given, with all other values compressed
2525% appropriatally. This effectivally maps a greyscale gradient into the given
2526% color gradient.
2527%
cristy490408a2011-07-07 14:42:05 +00002528% The format of the LevelImageColors method is:
cristy3ed852e2009-09-05 21:47:34 +00002529%
cristy490408a2011-07-07 14:42:05 +00002530% MagickBooleanType LevelImageColors(Image *image,
2531% const PixelInfo *black_color,const PixelInfo *white_color,
cristy7c0a0a42011-08-23 17:57:25 +00002532% const MagickBooleanType invert,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002533%
2534% A description of each parameter follows:
2535%
2536% o image: the image.
2537%
cristy3ed852e2009-09-05 21:47:34 +00002538% o black_color: The color to map black to/from
2539%
2540% o white_point: The color to map white to/from
2541%
2542% o invert: if true map the colors (levelize), rather than from (level)
2543%
cristy7c0a0a42011-08-23 17:57:25 +00002544% o exception: return any errors or warnings in this structure.
2545%
cristy3ed852e2009-09-05 21:47:34 +00002546*/
cristy490408a2011-07-07 14:42:05 +00002547MagickExport MagickBooleanType LevelImageColors(Image *image,
cristy4c08aed2011-07-01 19:47:50 +00002548 const PixelInfo *black_color,const PixelInfo *white_color,
cristy7c0a0a42011-08-23 17:57:25 +00002549 const MagickBooleanType invert,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002550{
cristybd5a96c2011-08-21 00:04:26 +00002551 ChannelType
2552 channel_mask;
2553
cristy3ed852e2009-09-05 21:47:34 +00002554 MagickStatusType
2555 status;
2556
2557 /*
2558 Allocate and initialize levels map.
2559 */
2560 assert(image != (Image *) NULL);
2561 assert(image->signature == MagickSignature);
2562 if (image->debug != MagickFalse)
2563 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2564 status=MagickFalse;
2565 if (invert == MagickFalse)
2566 {
cristyed231572011-07-14 02:18:59 +00002567 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002568 {
cristybd5a96c2011-08-21 00:04:26 +00002569 channel_mask=SetPixelChannelMask(image,RedChannel);
cristy01e9afd2011-08-10 17:38:41 +00002570 status|=LevelImage(image,black_color->red,white_color->red,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002571 exception);
cristybd5a96c2011-08-21 00:04:26 +00002572 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002573 }
cristyed231572011-07-14 02:18:59 +00002574 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002575 {
cristybd5a96c2011-08-21 00:04:26 +00002576 channel_mask=SetPixelChannelMask(image,GreenChannel);
cristy01e9afd2011-08-10 17:38:41 +00002577 status|=LevelImage(image,black_color->green,white_color->green,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002578 exception);
cristybd5a96c2011-08-21 00:04:26 +00002579 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002580 }
cristyed231572011-07-14 02:18:59 +00002581 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002582 {
cristybd5a96c2011-08-21 00:04:26 +00002583 channel_mask=SetPixelChannelMask(image,BlueChannel);
cristy01e9afd2011-08-10 17:38:41 +00002584 status|=LevelImage(image,black_color->blue,white_color->blue,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002585 exception);
cristybd5a96c2011-08-21 00:04:26 +00002586 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002587 }
cristyed231572011-07-14 02:18:59 +00002588 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002589 (image->colorspace == CMYKColorspace))
cristyf89cb1d2011-07-07 01:24:37 +00002590 {
cristybd5a96c2011-08-21 00:04:26 +00002591 channel_mask=SetPixelChannelMask(image,BlackChannel);
cristy01e9afd2011-08-10 17:38:41 +00002592 status|=LevelImage(image,black_color->black,white_color->black,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002593 exception);
cristybd5a96c2011-08-21 00:04:26 +00002594 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002595 }
cristyed231572011-07-14 02:18:59 +00002596 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002597 (image->matte == MagickTrue))
cristyf89cb1d2011-07-07 01:24:37 +00002598 {
cristybd5a96c2011-08-21 00:04:26 +00002599 channel_mask=SetPixelChannelMask(image,AlphaChannel);
cristy01e9afd2011-08-10 17:38:41 +00002600 status|=LevelImage(image,black_color->alpha,white_color->alpha,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002601 exception);
cristybd5a96c2011-08-21 00:04:26 +00002602 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002603 }
cristy3ed852e2009-09-05 21:47:34 +00002604 }
2605 else
2606 {
cristyed231572011-07-14 02:18:59 +00002607 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002608 {
cristybd5a96c2011-08-21 00:04:26 +00002609 channel_mask=SetPixelChannelMask(image,RedChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002610 status|=LevelizeImage(image,black_color->red,white_color->red,1.0,
2611 exception);
cristybd5a96c2011-08-21 00:04:26 +00002612 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002613 }
cristyed231572011-07-14 02:18:59 +00002614 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002615 {
cristybd5a96c2011-08-21 00:04:26 +00002616 channel_mask=SetPixelChannelMask(image,GreenChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002617 status|=LevelizeImage(image,black_color->green,white_color->green,1.0,
2618 exception);
cristybd5a96c2011-08-21 00:04:26 +00002619 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002620 }
cristyed231572011-07-14 02:18:59 +00002621 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002622 {
cristybd5a96c2011-08-21 00:04:26 +00002623 channel_mask=SetPixelChannelMask(image,BlueChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002624 status|=LevelizeImage(image,black_color->blue,white_color->blue,1.0,
2625 exception);
cristybd5a96c2011-08-21 00:04:26 +00002626 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002627 }
cristyed231572011-07-14 02:18:59 +00002628 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002629 (image->colorspace == CMYKColorspace))
cristyf89cb1d2011-07-07 01:24:37 +00002630 {
cristybd5a96c2011-08-21 00:04:26 +00002631 channel_mask=SetPixelChannelMask(image,BlackChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002632 status|=LevelizeImage(image,black_color->black,white_color->black,1.0,
2633 exception);
cristybd5a96c2011-08-21 00:04:26 +00002634 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002635 }
cristyed231572011-07-14 02:18:59 +00002636 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002637 (image->matte == MagickTrue))
cristyf89cb1d2011-07-07 01:24:37 +00002638 {
cristybd5a96c2011-08-21 00:04:26 +00002639 channel_mask=SetPixelChannelMask(image,AlphaChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002640 status|=LevelizeImage(image,black_color->alpha,white_color->alpha,1.0,
2641 exception);
cristybd5a96c2011-08-21 00:04:26 +00002642 (void) SetPixelChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002643 }
cristy3ed852e2009-09-05 21:47:34 +00002644 }
2645 return(status == 0 ? MagickFalse : MagickTrue);
2646}
2647
2648/*
2649%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2650% %
2651% %
2652% %
2653% L i n e a r S t r e t c h I m a g e %
2654% %
2655% %
2656% %
2657%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2658%
cristyf1611782011-08-01 15:39:13 +00002659% LinearStretchImage() discards any pixels below the black point and above
2660% the white point and levels the remaining pixels.
cristy3ed852e2009-09-05 21:47:34 +00002661%
2662% The format of the LinearStretchImage method is:
2663%
2664% MagickBooleanType LinearStretchImage(Image *image,
cristy33bd5152011-08-24 01:42:24 +00002665% const double black_point,const double white_point,
2666% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002667%
2668% A description of each parameter follows:
2669%
2670% o image: the image.
2671%
2672% o black_point: the black point.
2673%
2674% o white_point: the white point.
2675%
cristy33bd5152011-08-24 01:42:24 +00002676% o exception: return any errors or warnings in this structure.
2677%
cristy3ed852e2009-09-05 21:47:34 +00002678*/
2679MagickExport MagickBooleanType LinearStretchImage(Image *image,
cristy33bd5152011-08-24 01:42:24 +00002680 const double black_point,const double white_point,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002681{
2682#define LinearStretchImageTag "LinearStretch/Image"
2683
cristy33bd5152011-08-24 01:42:24 +00002684 CacheView
2685 *image_view;
cristy3ed852e2009-09-05 21:47:34 +00002686
cristy3ed852e2009-09-05 21:47:34 +00002687 MagickBooleanType
2688 status;
2689
2690 MagickRealType
2691 *histogram,
2692 intensity;
2693
cristy8d4629b2010-08-30 17:59:46 +00002694 ssize_t
2695 black,
2696 white,
2697 y;
2698
cristy3ed852e2009-09-05 21:47:34 +00002699 /*
2700 Allocate histogram and linear map.
2701 */
2702 assert(image != (Image *) NULL);
2703 assert(image->signature == MagickSignature);
2704 histogram=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
2705 sizeof(*histogram));
2706 if (histogram == (MagickRealType *) NULL)
2707 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2708 image->filename);
2709 /*
2710 Form histogram.
2711 */
2712 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
cristydb070952012-04-20 14:33:00 +00002713 image_view=AcquireVirtualCacheView(image,exception);
cristybb503372010-05-27 20:51:26 +00002714 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002715 {
cristy4c08aed2011-07-01 19:47:50 +00002716 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00002717 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002718
cristybb503372010-05-27 20:51:26 +00002719 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002720 x;
2721
cristy33bd5152011-08-24 01:42:24 +00002722 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002723 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002724 break;
cristy33bd5152011-08-24 01:42:24 +00002725 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002726 {
cristy4c08aed2011-07-01 19:47:50 +00002727 histogram[ScaleQuantumToMap(GetPixelIntensity(image,p))]++;
cristyed231572011-07-14 02:18:59 +00002728 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002729 }
2730 }
cristy33bd5152011-08-24 01:42:24 +00002731 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002732 /*
2733 Find the histogram boundaries by locating the black and white point levels.
2734 */
cristy3ed852e2009-09-05 21:47:34 +00002735 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00002736 for (black=0; black < (ssize_t) MaxMap; black++)
cristy3ed852e2009-09-05 21:47:34 +00002737 {
2738 intensity+=histogram[black];
2739 if (intensity >= black_point)
2740 break;
2741 }
2742 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00002743 for (white=(ssize_t) MaxMap; white != 0; white--)
cristy3ed852e2009-09-05 21:47:34 +00002744 {
2745 intensity+=histogram[white];
2746 if (intensity >= white_point)
2747 break;
2748 }
2749 histogram=(MagickRealType *) RelinquishMagickMemory(histogram);
cristyc82a27b2011-10-21 01:07:16 +00002750 status=LevelImage(image,(double) black,(double) white,1.0,exception);
cristy3ed852e2009-09-05 21:47:34 +00002751 return(status);
2752}
2753
2754/*
2755%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2756% %
2757% %
2758% %
2759% M o d u l a t e I m a g e %
2760% %
2761% %
2762% %
2763%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2764%
2765% ModulateImage() lets you control the brightness, saturation, and hue
2766% of an image. Modulate represents the brightness, saturation, and hue
2767% as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
2768% modulation is lightness, saturation, and hue. And if the colorspace is
2769% HWB, use blackness, whiteness, and hue.
2770%
2771% The format of the ModulateImage method is:
2772%
cristy33bd5152011-08-24 01:42:24 +00002773% MagickBooleanType ModulateImage(Image *image,const char *modulate,
2774% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002775%
2776% A description of each parameter follows:
2777%
2778% o image: the image.
2779%
cristy33bd5152011-08-24 01:42:24 +00002780% o modulate: Define the percent change in brightness, saturation, and hue.
2781%
2782% o exception: return any errors or warnings in this structure.
cristy3ed852e2009-09-05 21:47:34 +00002783%
2784*/
2785
2786static void ModulateHSB(const double percent_hue,
cristy3094b7f2011-10-01 23:18:02 +00002787 const double percent_saturation,const double percent_brightness,double *red,
2788 double *green,double *blue)
cristy3ed852e2009-09-05 21:47:34 +00002789{
2790 double
2791 brightness,
2792 hue,
2793 saturation;
2794
2795 /*
2796 Increase or decrease color brightness, saturation, or hue.
2797 */
cristy3094b7f2011-10-01 23:18:02 +00002798 assert(red != (double *) NULL);
2799 assert(green != (double *) NULL);
2800 assert(blue != (double *) NULL);
cristy3ed852e2009-09-05 21:47:34 +00002801 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
2802 hue+=0.5*(0.01*percent_hue-1.0);
2803 while (hue < 0.0)
2804 hue+=1.0;
2805 while (hue > 1.0)
2806 hue-=1.0;
2807 saturation*=0.01*percent_saturation;
2808 brightness*=0.01*percent_brightness;
2809 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
2810}
2811
2812static void ModulateHSL(const double percent_hue,
cristy3094b7f2011-10-01 23:18:02 +00002813 const double percent_saturation,const double percent_lightness,double *red,
2814 double *green,double *blue)
cristy3ed852e2009-09-05 21:47:34 +00002815{
2816 double
2817 hue,
2818 lightness,
2819 saturation;
2820
2821 /*
2822 Increase or decrease color lightness, saturation, or hue.
2823 */
cristy3094b7f2011-10-01 23:18:02 +00002824 assert(red != (double *) NULL);
2825 assert(green != (double *) NULL);
2826 assert(blue != (double *) NULL);
cristy3ed852e2009-09-05 21:47:34 +00002827 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
2828 hue+=0.5*(0.01*percent_hue-1.0);
2829 while (hue < 0.0)
2830 hue+=1.0;
2831 while (hue > 1.0)
2832 hue-=1.0;
2833 saturation*=0.01*percent_saturation;
2834 lightness*=0.01*percent_lightness;
2835 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
2836}
2837
cristy3094b7f2011-10-01 23:18:02 +00002838static void ModulateHWB(const double percent_hue,const double percent_whiteness, const double percent_blackness,double *red,double *green,double *blue)
cristy3ed852e2009-09-05 21:47:34 +00002839{
2840 double
2841 blackness,
2842 hue,
2843 whiteness;
2844
2845 /*
2846 Increase or decrease color blackness, whiteness, or hue.
2847 */
cristy3094b7f2011-10-01 23:18:02 +00002848 assert(red != (double *) NULL);
2849 assert(green != (double *) NULL);
2850 assert(blue != (double *) NULL);
cristy3ed852e2009-09-05 21:47:34 +00002851 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
2852 hue+=0.5*(0.01*percent_hue-1.0);
2853 while (hue < 0.0)
2854 hue+=1.0;
2855 while (hue > 1.0)
2856 hue-=1.0;
2857 blackness*=0.01*percent_blackness;
2858 whiteness*=0.01*percent_whiteness;
2859 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
2860}
2861
cristy33bd5152011-08-24 01:42:24 +00002862MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate,
2863 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002864{
2865#define ModulateImageTag "Modulate/Image"
2866
cristyc4c8d132010-01-07 01:58:38 +00002867 CacheView
2868 *image_view;
2869
cristy3ed852e2009-09-05 21:47:34 +00002870 ColorspaceType
2871 colorspace;
2872
2873 const char
2874 *artifact;
2875
2876 double
2877 percent_brightness,
2878 percent_hue,
2879 percent_saturation;
2880
cristy3ed852e2009-09-05 21:47:34 +00002881 GeometryInfo
2882 geometry_info;
2883
cristy3ed852e2009-09-05 21:47:34 +00002884 MagickBooleanType
2885 status;
2886
cristybb503372010-05-27 20:51:26 +00002887 MagickOffsetType
2888 progress;
2889
cristy3ed852e2009-09-05 21:47:34 +00002890 MagickStatusType
2891 flags;
2892
cristybb503372010-05-27 20:51:26 +00002893 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002894 i;
2895
cristybb503372010-05-27 20:51:26 +00002896 ssize_t
2897 y;
2898
cristy3ed852e2009-09-05 21:47:34 +00002899 /*
cristy2b726bd2010-01-11 01:05:39 +00002900 Initialize modulate table.
cristy3ed852e2009-09-05 21:47:34 +00002901 */
2902 assert(image != (Image *) NULL);
2903 assert(image->signature == MagickSignature);
2904 if (image->debug != MagickFalse)
2905 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2906 if (modulate == (char *) NULL)
2907 return(MagickFalse);
2908 flags=ParseGeometry(modulate,&geometry_info);
2909 percent_brightness=geometry_info.rho;
2910 percent_saturation=geometry_info.sigma;
2911 if ((flags & SigmaValue) == 0)
2912 percent_saturation=100.0;
2913 percent_hue=geometry_info.xi;
2914 if ((flags & XiValue) == 0)
2915 percent_hue=100.0;
2916 colorspace=UndefinedColorspace;
2917 artifact=GetImageArtifact(image,"modulate:colorspace");
2918 if (artifact != (const char *) NULL)
cristy042ee782011-04-22 18:48:30 +00002919 colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions,
cristy3ed852e2009-09-05 21:47:34 +00002920 MagickFalse,artifact);
2921 if (image->storage_class == PseudoClass)
2922 {
2923 /*
2924 Modulate colormap.
2925 */
cristyb5d5f722009-11-04 03:03:49 +00002926#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00002927 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2928 if (image->colors > 256) \
2929 num_threads(GetMagickResourceLimit(ThreadResource))
cristy3ed852e2009-09-05 21:47:34 +00002930#endif
cristybb503372010-05-27 20:51:26 +00002931 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002932 switch (colorspace)
2933 {
2934 case HSBColorspace:
2935 {
2936 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
2937 &image->colormap[i].red,&image->colormap[i].green,
2938 &image->colormap[i].blue);
2939 break;
2940 }
2941 case HSLColorspace:
2942 default:
2943 {
2944 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
2945 &image->colormap[i].red,&image->colormap[i].green,
2946 &image->colormap[i].blue);
2947 break;
2948 }
2949 case HWBColorspace:
2950 {
2951 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
2952 &image->colormap[i].red,&image->colormap[i].green,
2953 &image->colormap[i].blue);
2954 break;
2955 }
2956 }
2957 }
2958 /*
2959 Modulate image.
2960 */
2961 status=MagickTrue;
2962 progress=0;
cristydb070952012-04-20 14:33:00 +00002963 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +00002964#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00002965 #pragma omp parallel for schedule(static,4) shared(progress,status) \
2966 if ((image->rows*image->columns) > 8192) \
2967 num_threads(GetMagickResourceLimit(ThreadResource))
cristy3ed852e2009-09-05 21:47:34 +00002968#endif
cristybb503372010-05-27 20:51:26 +00002969 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002970 {
cristy3094b7f2011-10-01 23:18:02 +00002971 double
cristy5afeab82011-04-30 01:30:09 +00002972 blue,
2973 green,
2974 red;
2975
cristy4c08aed2011-07-01 19:47:50 +00002976 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002977 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002978
cristy8d4629b2010-08-30 17:59:46 +00002979 register ssize_t
2980 x;
2981
cristy3ed852e2009-09-05 21:47:34 +00002982 if (status == MagickFalse)
2983 continue;
2984 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00002985 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002986 {
2987 status=MagickFalse;
2988 continue;
2989 }
cristybb503372010-05-27 20:51:26 +00002990 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002991 {
cristyda1f9c12011-10-02 21:39:49 +00002992 red=(double) GetPixelRed(image,q);
2993 green=(double) GetPixelGreen(image,q);
2994 blue=(double) GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00002995 switch (colorspace)
2996 {
2997 case HSBColorspace:
2998 {
2999 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00003000 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00003001 break;
3002 }
3003 case HSLColorspace:
3004 default:
3005 {
3006 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00003007 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00003008 break;
3009 }
3010 case HWBColorspace:
3011 {
3012 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00003013 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00003014 break;
3015 }
3016 }
cristy3094b7f2011-10-01 23:18:02 +00003017 SetPixelRed(image,ClampToQuantum(red),q);
3018 SetPixelGreen(image,ClampToQuantum(green),q);
3019 SetPixelBlue(image,ClampToQuantum(blue),q);
cristyed231572011-07-14 02:18:59 +00003020 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003021 }
3022 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3023 status=MagickFalse;
3024 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3025 {
3026 MagickBooleanType
3027 proceed;
3028
cristyb5d5f722009-11-04 03:03:49 +00003029#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00003030 #pragma omp critical (MagickCore_ModulateImage)
cristy3ed852e2009-09-05 21:47:34 +00003031#endif
3032 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3033 if (proceed == MagickFalse)
3034 status=MagickFalse;
3035 }
3036 }
3037 image_view=DestroyCacheView(image_view);
3038 return(status);
3039}
3040
3041/*
3042%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3043% %
3044% %
3045% %
3046% N e g a t e I m a g e %
3047% %
3048% %
3049% %
3050%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3051%
3052% NegateImage() negates the colors in the reference image. The grayscale
3053% option means that only grayscale values within the image are negated.
3054%
cristy50fbc382011-07-07 02:19:17 +00003055% The format of the NegateImage method is:
cristy3ed852e2009-09-05 21:47:34 +00003056%
3057% MagickBooleanType NegateImage(Image *image,
cristyb3e7c6c2011-07-24 01:43:55 +00003058% const MagickBooleanType grayscale,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003059%
3060% A description of each parameter follows:
3061%
3062% o image: the image.
3063%
cristy3ed852e2009-09-05 21:47:34 +00003064% o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3065%
cristyb3e7c6c2011-07-24 01:43:55 +00003066% o exception: return any errors or warnings in this structure.
3067%
cristy3ed852e2009-09-05 21:47:34 +00003068*/
cristy3ed852e2009-09-05 21:47:34 +00003069MagickExport MagickBooleanType NegateImage(Image *image,
cristyb3e7c6c2011-07-24 01:43:55 +00003070 const MagickBooleanType grayscale,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003071{
cristy3ed852e2009-09-05 21:47:34 +00003072#define NegateImageTag "Negate/Image"
3073
cristyc4c8d132010-01-07 01:58:38 +00003074 CacheView
3075 *image_view;
3076
cristy3ed852e2009-09-05 21:47:34 +00003077 MagickBooleanType
3078 status;
3079
cristybb503372010-05-27 20:51:26 +00003080 MagickOffsetType
3081 progress;
3082
3083 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003084 i;
3085
cristybb503372010-05-27 20:51:26 +00003086 ssize_t
3087 y;
3088
cristy3ed852e2009-09-05 21:47:34 +00003089 assert(image != (Image *) NULL);
3090 assert(image->signature == MagickSignature);
3091 if (image->debug != MagickFalse)
3092 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3093 if (image->storage_class == PseudoClass)
3094 {
3095 /*
3096 Negate colormap.
3097 */
cristyb5d5f722009-11-04 03:03:49 +00003098#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00003099 #pragma omp parallel for schedule(static) shared(progress,status) \
3100 if (image->colors > 256) \
3101 num_threads(GetMagickResourceLimit(ThreadResource))
cristy3ed852e2009-09-05 21:47:34 +00003102#endif
cristybb503372010-05-27 20:51:26 +00003103 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003104 {
3105 if (grayscale != MagickFalse)
3106 if ((image->colormap[i].red != image->colormap[i].green) ||
3107 (image->colormap[i].green != image->colormap[i].blue))
3108 continue;
cristyed231572011-07-14 02:18:59 +00003109 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003110 image->colormap[i].red=(Quantum) QuantumRange-
3111 image->colormap[i].red;
cristyed231572011-07-14 02:18:59 +00003112 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003113 image->colormap[i].green=(Quantum) QuantumRange-
3114 image->colormap[i].green;
cristyed231572011-07-14 02:18:59 +00003115 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00003116 image->colormap[i].blue=(Quantum) QuantumRange-
3117 image->colormap[i].blue;
3118 }
3119 }
3120 /*
3121 Negate image.
3122 */
3123 status=MagickTrue;
3124 progress=0;
cristydb070952012-04-20 14:33:00 +00003125 image_view=AcquireAuthenticCacheView(image,exception);
cristy3ed852e2009-09-05 21:47:34 +00003126 if (grayscale != MagickFalse)
3127 {
cristyb5d5f722009-11-04 03:03:49 +00003128#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00003129 #pragma omp parallel for schedule(static) shared(progress,status) \
3130 if ((image->rows*image->columns) > 8192) \
3131 num_threads(GetMagickResourceLimit(ThreadResource))
cristy3ed852e2009-09-05 21:47:34 +00003132#endif
cristybb503372010-05-27 20:51:26 +00003133 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003134 {
3135 MagickBooleanType
3136 sync;
3137
cristy4c08aed2011-07-01 19:47:50 +00003138 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003139 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003140
cristy8d4629b2010-08-30 17:59:46 +00003141 register ssize_t
3142 x;
3143
cristy3ed852e2009-09-05 21:47:34 +00003144 if (status == MagickFalse)
3145 continue;
3146 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3147 exception);
cristyacd2ed22011-08-30 01:44:23 +00003148 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003149 {
3150 status=MagickFalse;
3151 continue;
3152 }
cristybb503372010-05-27 20:51:26 +00003153 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003154 {
cristy9aa95be2011-07-20 21:56:45 +00003155 register ssize_t
3156 i;
3157
cristy10a6c612012-01-29 21:41:05 +00003158 if ((GetPixelMask(image,q) != 0) ||
3159 (IsPixelGray(image,q) != MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00003160 {
cristya30d9ba2011-07-23 21:00:48 +00003161 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003162 continue;
3163 }
cristya30d9ba2011-07-23 21:00:48 +00003164 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy9aa95be2011-07-20 21:56:45 +00003165 {
cristyabace412011-12-11 15:56:53 +00003166 PixelChannel
3167 channel;
3168
cristy1aaa3cd2011-08-21 23:48:17 +00003169 PixelTrait
cristy9aa95be2011-07-20 21:56:45 +00003170 traits;
3171
cristyabace412011-12-11 15:56:53 +00003172 channel=GetPixelChannelMapChannel(image,i);
3173 traits=GetPixelChannelMapTraits(image,channel);
cristyd09f8802012-02-04 16:44:10 +00003174 if ((traits & UpdatePixelTrait) == 0)
3175 continue;
3176 q[i]=QuantumRange-q[i];
cristy9aa95be2011-07-20 21:56:45 +00003177 }
cristya30d9ba2011-07-23 21:00:48 +00003178 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003179 }
3180 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3181 if (sync == MagickFalse)
3182 status=MagickFalse;
3183 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3184 {
3185 MagickBooleanType
3186 proceed;
3187
cristyb5d5f722009-11-04 03:03:49 +00003188#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00003189 #pragma omp critical (MagickCore_NegateImage)
cristy3ed852e2009-09-05 21:47:34 +00003190#endif
3191 proceed=SetImageProgress(image,NegateImageTag,progress++,
3192 image->rows);
3193 if (proceed == MagickFalse)
3194 status=MagickFalse;
3195 }
3196 }
3197 image_view=DestroyCacheView(image_view);
3198 return(MagickTrue);
3199 }
3200 /*
3201 Negate image.
3202 */
cristyb5d5f722009-11-04 03:03:49 +00003203#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00003204 #pragma omp parallel for schedule(static) shared(progress,status) \
3205 if ((image->rows*image->columns) > 8192) \
3206 num_threads(GetMagickResourceLimit(ThreadResource))
cristy3ed852e2009-09-05 21:47:34 +00003207#endif
cristybb503372010-05-27 20:51:26 +00003208 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003209 {
cristy4c08aed2011-07-01 19:47:50 +00003210 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003211 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003212
cristy8d4629b2010-08-30 17:59:46 +00003213 register ssize_t
3214 x;
3215
cristy3ed852e2009-09-05 21:47:34 +00003216 if (status == MagickFalse)
3217 continue;
3218 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00003219 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003220 {
3221 status=MagickFalse;
3222 continue;
3223 }
cristybb503372010-05-27 20:51:26 +00003224 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003225 {
cristyf7dc44c2011-07-20 14:41:15 +00003226 register ssize_t
3227 i;
3228
cristyd09f8802012-02-04 16:44:10 +00003229 if (GetPixelMask(image,q) != 0)
3230 {
3231 q+=GetPixelChannels(image);
3232 continue;
3233 }
cristya30d9ba2011-07-23 21:00:48 +00003234 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristyf7dc44c2011-07-20 14:41:15 +00003235 {
cristyabace412011-12-11 15:56:53 +00003236 PixelChannel
3237 channel;
3238
cristy1aaa3cd2011-08-21 23:48:17 +00003239 PixelTrait
cristyf7dc44c2011-07-20 14:41:15 +00003240 traits;
3241
cristyabace412011-12-11 15:56:53 +00003242 channel=GetPixelChannelMapChannel(image,i);
3243 traits=GetPixelChannelMapTraits(image,channel);
cristyd09f8802012-02-04 16:44:10 +00003244 if ((traits & UpdatePixelTrait) == 0)
cristyec9e3a62012-02-01 02:09:32 +00003245 continue;
3246 q[i]=QuantumRange-q[i];
cristyf7dc44c2011-07-20 14:41:15 +00003247 }
cristya30d9ba2011-07-23 21:00:48 +00003248 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003249 }
3250 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3251 status=MagickFalse;
3252 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3253 {
3254 MagickBooleanType
3255 proceed;
3256
cristyb5d5f722009-11-04 03:03:49 +00003257#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00003258 #pragma omp critical (MagickCore_NegateImage)
cristy3ed852e2009-09-05 21:47:34 +00003259#endif
3260 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3261 if (proceed == MagickFalse)
3262 status=MagickFalse;
3263 }
3264 }
3265 image_view=DestroyCacheView(image_view);
3266 return(status);
3267}
3268
3269/*
3270%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3271% %
3272% %
3273% %
3274% N o r m a l i z e I m a g e %
3275% %
3276% %
3277% %
3278%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3279%
anthony207ce472012-04-04 06:21:26 +00003280% The NormalizeImage() method enhances the contrast of a color image by
3281% mapping the darkest 2 percent of all pixel to black and the brightest
3282% 1 percent to white.
cristy3ed852e2009-09-05 21:47:34 +00003283%
3284% The format of the NormalizeImage method is:
3285%
cristye23ec9d2011-08-16 18:15:40 +00003286% MagickBooleanType NormalizeImage(Image *image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003287%
3288% A description of each parameter follows:
3289%
3290% o image: the image.
3291%
cristye23ec9d2011-08-16 18:15:40 +00003292% o exception: return any errors or warnings in this structure.
3293%
cristy3ed852e2009-09-05 21:47:34 +00003294*/
cristye23ec9d2011-08-16 18:15:40 +00003295MagickExport MagickBooleanType NormalizeImage(Image *image,
3296 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003297{
cristy3ed852e2009-09-05 21:47:34 +00003298 double
3299 black_point,
3300 white_point;
3301
cristy530239c2010-07-25 17:34:26 +00003302 black_point=(double) image->columns*image->rows*0.0015;
3303 white_point=(double) image->columns*image->rows*0.9995;
cristye23ec9d2011-08-16 18:15:40 +00003304 return(ContrastStretchImage(image,black_point,white_point,exception));
cristy3ed852e2009-09-05 21:47:34 +00003305}
3306
3307/*
3308%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3309% %
3310% %
3311% %
3312% S i g m o i d a l C o n t r a s t I m a g e %
3313% %
3314% %
3315% %
3316%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3317%
3318% SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3319% sigmoidal contrast algorithm. Increase the contrast of the image using a
3320% sigmoidal transfer function without saturating highlights or shadows.
3321% Contrast indicates how much to increase the contrast (0 is none; 3 is
anthony207ce472012-04-04 06:21:26 +00003322% typical; 20 is pushing it); mid-point indicates where midtones fall in the
3323% resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3324% sharpen to MagickTrue to increase the image contrast otherwise the contrast
3325% is reduced.
cristy3ed852e2009-09-05 21:47:34 +00003326%
3327% The format of the SigmoidalContrastImage method is:
3328%
3329% MagickBooleanType SigmoidalContrastImage(Image *image,
cristy33bd5152011-08-24 01:42:24 +00003330% const MagickBooleanType sharpen,const char *levels,
3331% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003332%
3333% A description of each parameter follows:
3334%
3335% o image: the image.
3336%
cristy3ed852e2009-09-05 21:47:34 +00003337% o sharpen: Increase or decrease image contrast.
3338%
cristyfa769582010-09-30 23:30:03 +00003339% o alpha: strength of the contrast, the larger the number the more
3340% 'threshold-like' it becomes.
cristy3ed852e2009-09-05 21:47:34 +00003341%
cristyfa769582010-09-30 23:30:03 +00003342% o beta: midpoint of the function as a color value 0 to QuantumRange.
cristy3ed852e2009-09-05 21:47:34 +00003343%
cristy33bd5152011-08-24 01:42:24 +00003344% o exception: return any errors or warnings in this structure.
3345%
cristy3ed852e2009-09-05 21:47:34 +00003346*/
cristy3ed852e2009-09-05 21:47:34 +00003347MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
cristy33bd5152011-08-24 01:42:24 +00003348 const MagickBooleanType sharpen,const double contrast,const double midpoint,
3349 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003350{
3351#define SigmoidalContrastImageTag "SigmoidalContrast/Image"
3352
cristyc4c8d132010-01-07 01:58:38 +00003353 CacheView
3354 *image_view;
3355
cristy3ed852e2009-09-05 21:47:34 +00003356 MagickBooleanType
3357 status;
3358
cristybb503372010-05-27 20:51:26 +00003359 MagickOffsetType
3360 progress;
3361
cristy3ed852e2009-09-05 21:47:34 +00003362 MagickRealType
3363 *sigmoidal_map;
3364
cristybb503372010-05-27 20:51:26 +00003365 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003366 i;
3367
cristybb503372010-05-27 20:51:26 +00003368 ssize_t
3369 y;
3370
cristy3ed852e2009-09-05 21:47:34 +00003371 /*
3372 Allocate and initialize sigmoidal maps.
3373 */
3374 assert(image != (Image *) NULL);
3375 assert(image->signature == MagickSignature);
3376 if (image->debug != MagickFalse)
3377 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3378 sigmoidal_map=(MagickRealType *) AcquireQuantumMemory(MaxMap+1UL,
3379 sizeof(*sigmoidal_map));
3380 if (sigmoidal_map == (MagickRealType *) NULL)
3381 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
3382 image->filename);
3383 (void) ResetMagickMemory(sigmoidal_map,0,(MaxMap+1)*sizeof(*sigmoidal_map));
cristyb5d5f722009-11-04 03:03:49 +00003384#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00003385 #pragma omp parallel for schedule(static) shared(progress,status) \
3386 if (MaxMap > 256) \
3387 num_threads(GetMagickResourceLimit(ThreadResource))
cristy3ed852e2009-09-05 21:47:34 +00003388#endif
cristybb503372010-05-27 20:51:26 +00003389 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +00003390 {
3391 if (sharpen != MagickFalse)
3392 {
anthony207ce472012-04-04 06:21:26 +00003393#define sigmoidal(a,b,x) (1/(1+exp((a)*((b)-(x)))))
3394#if 1
3395 /* Simpilified function scaling,
3396 * with better 'contrast=0' or 'flatline' handling (greyscale)
3397 */
3398 double
3399 u0 = sigmoidal(contrast,QuantumScale*midpoint,0.0),
3400 u1 = sigmoidal(contrast,QuantumScale*midpoint,1.0);
3401 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum(
3402 (MagickRealType)(MaxMap*(
3403 (sigmoidal(contrast,QuantumScale*midpoint,(double)i/MaxMap)
3404 -(u0+u1)/2.0)/(u1-u0+MagickEpsilon)+0.5) ));
3405#else
3406 /* Scaled sigmoidal formula...
3407 (1/(1+exp(a*(b-u))) - 1/(1+exp(a))) /
3408 (1/(1+exp(a*(b-1)))/(1+exp(a)))) */
cristy3ed852e2009-09-05 21:47:34 +00003409 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
3410 (MaxMap*((1.0/(1.0+exp(contrast*(midpoint/(double) QuantumRange-
anthony207ce472012-04-04 06:21:26 +00003411 (double) i/MaxMap))))-(1.0/(1.0+exp(contrast*(midpoint/
3412 (double) QuantumRange)))))/((1.0/(1.0+exp(contrast*(midpoint/
3413 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(contrast*(midpoint/
3414 (double) QuantumRange)))))+0.5));
3415#endif
cristy3ed852e2009-09-05 21:47:34 +00003416 continue;
3417 }
anthony207ce472012-04-04 06:21:26 +00003418#if 1
3419 {
3420 /* Inverse -- See
3421 http://osdir.com/ml/video.image-magick.devel/2005-04/msg00006.html
3422 */
3423 double
3424 min = sigmoidal(contrast,1.0,0.0),
3425 max = sigmoidal(contrast,QuantumScale*midpoint,1.0),
3426 xi = min+(double)i/MaxMap*(max-min);
3427 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum(
3428 (MagickRealType)(MaxMap*(
3429 QuantumScale*midpoint-log((1-xi)/xi)/contrast) ));
3430 }
3431#else
3432 /* expanded form of the above */
cristy3ed852e2009-09-05 21:47:34 +00003433 sigmoidal_map[i]=(MagickRealType) ScaleMapToQuantum((MagickRealType)
anthony207ce472012-04-04 06:21:26 +00003434 (MaxMap*(QuantumScale*midpoint-log((1.0-(1.0/(1.0+exp(midpoint/
3435 (double) QuantumRange*contrast))+((double) i/MaxMap)*((1.0/
3436 (1.0+exp(contrast*(midpoint/(double) QuantumRange-1.0))))-(1.0/
3437 (1.0+exp(midpoint/(double) QuantumRange*contrast))))))/
3438 (1.0/(1.0+exp(midpoint/(double) QuantumRange*contrast))+
3439 ((double) i/MaxMap)*((1.0/(1.0+exp(contrast*(midpoint/
3440 (double) QuantumRange-1.0))))-(1.0/(1.0+exp(midpoint/
3441 (double) QuantumRange*contrast))))))/contrast)));
3442#endif
cristy3ed852e2009-09-05 21:47:34 +00003443 }
3444 if (image->storage_class == PseudoClass)
3445 {
3446 /*
3447 Sigmoidal-contrast enhance colormap.
3448 */
cristyb5d5f722009-11-04 03:03:49 +00003449#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00003450 #pragma omp parallel for schedule(static,4) shared(progress,status) \
3451 if (image->colors > 256) \
3452 num_threads(GetMagickResourceLimit(ThreadResource))
cristy3ed852e2009-09-05 21:47:34 +00003453#endif
cristybb503372010-05-27 20:51:26 +00003454 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00003455 {
cristyed231572011-07-14 02:18:59 +00003456 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00003457 image->colormap[i].red=sigmoidal_map[ScaleQuantumToMap(
3458 ClampToQuantum(image->colormap[i].red))];
cristyed231572011-07-14 02:18:59 +00003459 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00003460 image->colormap[i].green=sigmoidal_map[ScaleQuantumToMap(
3461 ClampToQuantum(image->colormap[i].green))];
cristyed231572011-07-14 02:18:59 +00003462 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00003463 image->colormap[i].blue=sigmoidal_map[ScaleQuantumToMap(
3464 ClampToQuantum(image->colormap[i].blue))];
cristyed231572011-07-14 02:18:59 +00003465 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00003466 image->colormap[i].alpha=sigmoidal_map[ScaleQuantumToMap(
3467 ClampToQuantum(image->colormap[i].alpha))];
cristy3ed852e2009-09-05 21:47:34 +00003468 }
3469 }
3470 /*
3471 Sigmoidal-contrast enhance image.
3472 */
3473 status=MagickTrue;
3474 progress=0;
cristydb070952012-04-20 14:33:00 +00003475 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +00003476#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00003477 #pragma omp parallel for schedule(static,4) shared(progress,status) \
3478 if ((image->rows*image->columns) > 8192) \
3479 num_threads(GetMagickResourceLimit(ThreadResource))
cristy3ed852e2009-09-05 21:47:34 +00003480#endif
cristybb503372010-05-27 20:51:26 +00003481 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003482 {
cristy4c08aed2011-07-01 19:47:50 +00003483 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003484 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003485
cristy8d4629b2010-08-30 17:59:46 +00003486 register ssize_t
3487 x;
3488
cristy3ed852e2009-09-05 21:47:34 +00003489 if (status == MagickFalse)
3490 continue;
3491 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00003492 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003493 {
3494 status=MagickFalse;
3495 continue;
3496 }
cristybb503372010-05-27 20:51:26 +00003497 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003498 {
cristy33bd5152011-08-24 01:42:24 +00003499 register ssize_t
3500 i;
3501
cristy10a6c612012-01-29 21:41:05 +00003502 if (GetPixelMask(image,q) != 0)
3503 {
3504 q+=GetPixelChannels(image);
3505 continue;
3506 }
cristy33bd5152011-08-24 01:42:24 +00003507 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3508 {
cristyabace412011-12-11 15:56:53 +00003509 PixelChannel
3510 channel;
3511
cristy33bd5152011-08-24 01:42:24 +00003512 PixelTrait
3513 traits;
3514
cristyabace412011-12-11 15:56:53 +00003515 channel=GetPixelChannelMapChannel(image,i);
3516 traits=GetPixelChannelMapTraits(image,channel);
cristyd09f8802012-02-04 16:44:10 +00003517 if ((traits & UpdatePixelTrait) == 0)
3518 continue;
3519 q[i]=ClampToQuantum(sigmoidal_map[ScaleQuantumToMap(q[i])]);
cristy33bd5152011-08-24 01:42:24 +00003520 }
cristyed231572011-07-14 02:18:59 +00003521 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003522 }
3523 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3524 status=MagickFalse;
3525 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3526 {
3527 MagickBooleanType
3528 proceed;
3529
cristyb5d5f722009-11-04 03:03:49 +00003530#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00003531 #pragma omp critical (MagickCore_SigmoidalContrastImage)
cristy3ed852e2009-09-05 21:47:34 +00003532#endif
3533 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3534 image->rows);
3535 if (proceed == MagickFalse)
3536 status=MagickFalse;
3537 }
3538 }
3539 image_view=DestroyCacheView(image_view);
3540 sigmoidal_map=(MagickRealType *) RelinquishMagickMemory(sigmoidal_map);
3541 return(status);
3542}