blob: 75420331734c44354c9c6ecd9fc303fdd683c3ab [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"
cristyd1e1c222012-08-13 01:13:28 +000077#include "MagickCore/threshold.h"
cristy4c08aed2011-07-01 19:47:50 +000078#include "MagickCore/token.h"
79#include "MagickCore/xml-tree.h"
cristy433d1182011-09-04 13:38:52 +000080#include "MagickCore/xml-tree-private.h"
cristy3ed852e2009-09-05 21:47:34 +000081
82/*
83%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
84% %
85% %
86% %
87% A u t o G a m m a I m a g e %
88% %
89% %
90% %
91%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
92%
93% AutoGammaImage() extract the 'mean' from the image and adjust the image
94% to try make set its gamma appropriatally.
95%
cristy308b4e62009-09-21 14:40:44 +000096% The format of the AutoGammaImage method is:
cristy3ed852e2009-09-05 21:47:34 +000097%
cristy95111202011-08-09 19:41:42 +000098% MagickBooleanType AutoGammaImage(Image *image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +000099%
100% A description of each parameter follows:
101%
102% o image: The image to auto-level
103%
cristy95111202011-08-09 19:41:42 +0000104% o exception: return any errors or warnings in this structure.
105%
cristy3ed852e2009-09-05 21:47:34 +0000106*/
cristy95111202011-08-09 19:41:42 +0000107MagickExport MagickBooleanType AutoGammaImage(Image *image,
108 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000109{
cristy3ed852e2009-09-05 21:47:34 +0000110 double
cristy4c08aed2011-07-01 19:47:50 +0000111 gamma,
112 log_mean,
113 mean,
114 sans;
anthony4efe5972009-09-11 06:46:40 +0000115
cristy95111202011-08-09 19:41:42 +0000116 MagickStatusType
117 status;
118
cristy01e9afd2011-08-10 17:38:41 +0000119 register ssize_t
120 i;
121
cristy4c08aed2011-07-01 19:47:50 +0000122 log_mean=log(0.5);
cristy5f95f4f2011-10-23 01:01:01 +0000123 if (image->channel_mask == DefaultChannels)
cristy3ed852e2009-09-05 21:47:34 +0000124 {
125 /*
cristy01e9afd2011-08-10 17:38:41 +0000126 Apply gamma correction equally across all given channels.
cristy3ed852e2009-09-05 21:47:34 +0000127 */
cristy95111202011-08-09 19:41:42 +0000128 (void) GetImageMean(image,&mean,&sans,exception);
cristy4c08aed2011-07-01 19:47:50 +0000129 gamma=log(mean*QuantumScale)/log_mean;
cristy01e9afd2011-08-10 17:38:41 +0000130 return(LevelImage(image,0.0,(double) QuantumRange,gamma,exception));
cristy3ed852e2009-09-05 21:47:34 +0000131 }
cristy3ed852e2009-09-05 21:47:34 +0000132 /*
cristy4c08aed2011-07-01 19:47:50 +0000133 Auto-gamma each channel separately.
cristy3ed852e2009-09-05 21:47:34 +0000134 */
cristy4c08aed2011-07-01 19:47:50 +0000135 status=MagickTrue;
cristy01e9afd2011-08-10 17:38:41 +0000136 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
137 {
cristybd5a96c2011-08-21 00:04:26 +0000138 ChannelType
139 channel_mask;
140
cristyabace412011-12-11 15:56:53 +0000141 PixelChannel
142 channel;
143
cristy01e9afd2011-08-10 17:38:41 +0000144 PixelTrait
145 traits;
146
cristycf1296e2012-08-26 23:40:49 +0000147 channel=GetPixelChannelChannel(image,i);
148 traits=GetPixelChannelTraits(image,channel);
cristy01e9afd2011-08-10 17:38:41 +0000149 if ((traits & UpdatePixelTrait) == 0)
150 continue;
cristycf1296e2012-08-26 23:40:49 +0000151 channel_mask=SetImageChannelMask(image,(ChannelType) (1 << i));
cristy01e9afd2011-08-10 17:38:41 +0000152 status=GetImageMean(image,&mean,&sans,exception);
153 gamma=log(mean*QuantumScale)/log_mean;
154 status&=LevelImage(image,0.0,(double) QuantumRange,gamma,exception);
cristycf1296e2012-08-26 23:40:49 +0000155 (void) SetImageChannelMask(image,channel_mask);
cristy01e9afd2011-08-10 17:38:41 +0000156 if (status == MagickFalse)
157 break;
158 }
cristy3ed852e2009-09-05 21:47:34 +0000159 return(status != 0 ? MagickTrue : MagickFalse);
160}
161
162/*
163%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
164% %
165% %
166% %
167% A u t o L e v e l I m a g e %
168% %
169% %
170% %
171%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
172%
173% AutoLevelImage() adjusts the levels of a particular image channel by
174% scaling the minimum and maximum values to the full quantum range.
175%
176% The format of the LevelImage method is:
177%
cristy95111202011-08-09 19:41:42 +0000178% MagickBooleanType AutoLevelImage(Image *image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000179%
180% A description of each parameter follows:
181%
182% o image: The image to auto-level
183%
cristy95111202011-08-09 19:41:42 +0000184% o exception: return any errors or warnings in this structure.
185%
cristy3ed852e2009-09-05 21:47:34 +0000186*/
cristy95111202011-08-09 19:41:42 +0000187MagickExport MagickBooleanType AutoLevelImage(Image *image,
188 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000189{
cristyb303c3d2011-09-09 11:24:40 +0000190 return(MinMaxStretchImage(image,0.0,0.0,1.0,exception));
cristy3ed852e2009-09-05 21:47:34 +0000191}
192
193/*
194%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
195% %
196% %
197% %
cristya28d6b82010-01-11 20:03:47 +0000198% B r i g h t n e s s C o n t r a s t I m a g e %
199% %
200% %
201% %
202%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
203%
cristyf4356f92011-08-01 15:33:48 +0000204% BrightnessContrastImage() changes the brightness and/or contrast of an
205% image. It converts the brightness and contrast parameters into slope and
206% intercept and calls a polynomical function to apply to the image.
cristya28d6b82010-01-11 20:03:47 +0000207%
208% The format of the BrightnessContrastImage method is:
209%
210% MagickBooleanType BrightnessContrastImage(Image *image,
cristy444eda62011-08-10 02:07:46 +0000211% const double brightness,const double contrast,ExceptionInfo *exception)
cristya28d6b82010-01-11 20:03:47 +0000212%
213% A description of each parameter follows:
214%
215% o image: the image.
216%
cristya28d6b82010-01-11 20:03:47 +0000217% o brightness: the brightness percent (-100 .. 100).
218%
219% o contrast: the contrast percent (-100 .. 100).
220%
cristy444eda62011-08-10 02:07:46 +0000221% o exception: return any errors or warnings in this structure.
222%
cristya28d6b82010-01-11 20:03:47 +0000223*/
cristya28d6b82010-01-11 20:03:47 +0000224MagickExport MagickBooleanType BrightnessContrastImage(Image *image,
cristy444eda62011-08-10 02:07:46 +0000225 const double brightness,const double contrast,ExceptionInfo *exception)
cristya28d6b82010-01-11 20:03:47 +0000226{
cristya28d6b82010-01-11 20:03:47 +0000227#define BrightnessContastImageTag "BrightnessContast/Image"
228
229 double
230 alpha,
cristya28d6b82010-01-11 20:03:47 +0000231 coefficients[2],
cristye23ec9d2011-08-16 18:15:40 +0000232 intercept,
cristya28d6b82010-01-11 20:03:47 +0000233 slope;
234
235 MagickBooleanType
236 status;
237
238 /*
239 Compute slope and intercept.
240 */
241 assert(image != (Image *) NULL);
242 assert(image->signature == MagickSignature);
243 if (image->debug != MagickFalse)
244 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
245 alpha=contrast;
cristy4205a3c2010-09-12 20:19:59 +0000246 slope=tan((double) (MagickPI*(alpha/100.0+1.0)/4.0));
cristya28d6b82010-01-11 20:03:47 +0000247 if (slope < 0.0)
248 slope=0.0;
249 intercept=brightness/100.0+((100-brightness)/200.0)*(1.0-slope);
250 coefficients[0]=slope;
251 coefficients[1]=intercept;
cristy444eda62011-08-10 02:07:46 +0000252 status=FunctionImage(image,PolynomialFunction,2,coefficients,exception);
253 return(status);
254}
255
256/*
257%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
258% %
259% %
260% %
261% C l u t I m a g e %
262% %
263% %
264% %
265%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
266%
267% ClutImage() replaces each color value in the given image, by using it as an
268% index to lookup a replacement color value in a Color Look UP Table in the
269% form of an image. The values are extracted along a diagonal of the CLUT
270% image so either a horizontal or vertial gradient image can be used.
271%
272% Typically this is used to either re-color a gray-scale image according to a
273% color gradient in the CLUT image, or to perform a freeform histogram
274% (level) adjustment according to the (typically gray-scale) gradient in the
275% CLUT image.
276%
277% When the 'channel' mask includes the matte/alpha transparency channel but
278% one image has no such channel it is assumed that that image is a simple
279% gray-scale image that will effect the alpha channel values, either for
280% gray-scale coloring (with transparent or semi-transparent colors), or
281% a histogram adjustment of existing alpha channel values. If both images
282% have matte channels, direct and normal indexing is applied, which is rarely
283% used.
284%
285% The format of the ClutImage method is:
286%
287% MagickBooleanType ClutImage(Image *image,Image *clut_image,
cristy5c4e2582011-09-11 19:21:03 +0000288% const PixelInterpolateMethod method,ExceptionInfo *exception)
cristy444eda62011-08-10 02:07:46 +0000289%
290% A description of each parameter follows:
291%
292% o image: the image, which is replaced by indexed CLUT values
293%
294% o clut_image: the color lookup table image for replacement color values.
295%
cristy5c4e2582011-09-11 19:21:03 +0000296% o method: the pixel interpolation method.
cristy444eda62011-08-10 02:07:46 +0000297%
298% o exception: return any errors or warnings in this structure.
299%
300*/
301MagickExport MagickBooleanType ClutImage(Image *image,const Image *clut_image,
cristy5c4e2582011-09-11 19:21:03 +0000302 const PixelInterpolateMethod method,ExceptionInfo *exception)
cristy444eda62011-08-10 02:07:46 +0000303{
cristy444eda62011-08-10 02:07:46 +0000304#define ClutImageTag "Clut/Image"
305
306 CacheView
307 *clut_view,
308 *image_view;
309
cristy444eda62011-08-10 02:07:46 +0000310 MagickBooleanType
311 status;
312
313 MagickOffsetType
314 progress;
315
cristy1e0fe422012-04-11 18:43:52 +0000316 PixelInfo
317 *clut_map;
cristy444eda62011-08-10 02:07:46 +0000318
cristy1e0fe422012-04-11 18:43:52 +0000319 register ssize_t
320 i;
321
322 ssize_t adjust,
cristy444eda62011-08-10 02:07:46 +0000323 y;
324
325 assert(image != (Image *) NULL);
326 assert(image->signature == MagickSignature);
327 if (image->debug != MagickFalse)
328 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
329 assert(clut_image != (Image *) NULL);
330 assert(clut_image->signature == MagickSignature);
331 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
332 return(MagickFalse);
cristy0898eba2012-04-09 16:38:29 +0000333 if (IsGrayColorspace(image->colorspace) != MagickFalse)
cristyb09db112012-07-11 12:04:31 +0000334 (void) TransformImageColorspace(image,RGBColorspace,exception);
cristy1e0fe422012-04-11 18:43:52 +0000335 clut_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*clut_map));
336 if (clut_map == (PixelInfo *) NULL)
cristy444eda62011-08-10 02:07:46 +0000337 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
338 image->filename);
339 /*
340 Clut image.
341 */
342 status=MagickTrue;
343 progress=0;
344 adjust=(ssize_t) (clut_image->interpolate == IntegerInterpolatePixel ? 0 : 1);
cristydb070952012-04-20 14:33:00 +0000345 clut_view=AcquireVirtualCacheView(clut_image,exception);
cristy1e0fe422012-04-11 18:43:52 +0000346 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy444eda62011-08-10 02:07:46 +0000347 {
cristy1e0fe422012-04-11 18:43:52 +0000348 GetPixelInfo(clut_image,clut_map+i);
anthony6feb7802012-04-12 06:24:29 +0000349 (void) InterpolatePixelInfo(clut_image,clut_view,method,
cristy1e0fe422012-04-11 18:43:52 +0000350 QuantumScale*i*(clut_image->columns-adjust),QuantumScale*i*
351 (clut_image->rows-adjust),clut_map+i,exception);
cristy444eda62011-08-10 02:07:46 +0000352 }
353 clut_view=DestroyCacheView(clut_view);
cristydb070952012-04-20 14:33:00 +0000354 image_view=AcquireAuthenticCacheView(image,exception);
cristy444eda62011-08-10 02:07:46 +0000355#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000356 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy4ee2b0c2012-05-15 00:30:35 +0000357 dynamic_number_threads(image,image->columns,image->rows,1)
cristy444eda62011-08-10 02:07:46 +0000358#endif
359 for (y=0; y < (ssize_t) image->rows; y++)
360 {
cristy1e0fe422012-04-11 18:43:52 +0000361 PixelInfo
362 pixel;
363
cristy444eda62011-08-10 02:07:46 +0000364 register Quantum
365 *restrict q;
366
367 register ssize_t
368 x;
369
370 if (status == MagickFalse)
371 continue;
372 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
373 if (q == (Quantum *) NULL)
374 {
375 status=MagickFalse;
376 continue;
377 }
cristy1e0fe422012-04-11 18:43:52 +0000378 GetPixelInfo(image,&pixel);
cristy444eda62011-08-10 02:07:46 +0000379 for (x=0; x < (ssize_t) image->columns; x++)
380 {
cristy10a6c612012-01-29 21:41:05 +0000381 if (GetPixelMask(image,q) != 0)
382 {
383 q+=GetPixelChannels(image);
384 continue;
385 }
cristy1e0fe422012-04-11 18:43:52 +0000386 GetPixelInfoPixel(image,q,&pixel);
cristyac245f82012-05-05 17:13:57 +0000387 pixel.red=clut_map[ScaleQuantumToMap(
388 ClampToQuantum(pixel.red))].red;
389 pixel.green=clut_map[ScaleQuantumToMap(
390 ClampToQuantum(pixel.green))].green;
391 pixel.blue=clut_map[ScaleQuantumToMap(
392 ClampToQuantum(pixel.blue))].blue;
393 pixel.black=clut_map[ScaleQuantumToMap(
394 ClampToQuantum(pixel.black))].black;
395 pixel.alpha=clut_map[ScaleQuantumToMap(
396 ClampToQuantum(pixel.alpha))].alpha;
cristy1e0fe422012-04-11 18:43:52 +0000397 SetPixelInfoPixel(image,&pixel,q);
cristy444eda62011-08-10 02:07:46 +0000398 q+=GetPixelChannels(image);
399 }
400 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
401 status=MagickFalse;
402 if (image->progress_monitor != (MagickProgressMonitor) NULL)
403 {
404 MagickBooleanType
405 proceed;
406
407#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +0000408 #pragma omp critical (MagickCore_ClutImage)
cristy444eda62011-08-10 02:07:46 +0000409#endif
410 proceed=SetImageProgress(image,ClutImageTag,progress++,image->rows);
411 if (proceed == MagickFalse)
412 status=MagickFalse;
413 }
414 }
415 image_view=DestroyCacheView(image_view);
cristy1e0fe422012-04-11 18:43:52 +0000416 clut_map=(PixelInfo *) RelinquishMagickMemory(clut_map);
cristy8a46d822012-08-28 23:32:39 +0000417 if ((clut_image->alpha_trait == BlendPixelTrait) &&
cristy444eda62011-08-10 02:07:46 +0000418 ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0))
419 (void) SetImageAlphaChannel(image,ActivateAlphaChannel,exception);
cristya28d6b82010-01-11 20:03:47 +0000420 return(status);
421}
422
423/*
424%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
425% %
426% %
427% %
cristy3ed852e2009-09-05 21:47:34 +0000428% C o l o r D e c i s i o n L i s t I m a g e %
429% %
430% %
431% %
432%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
433%
434% ColorDecisionListImage() accepts a lightweight Color Correction Collection
435% (CCC) file which solely contains one or more color corrections and applies
436% the correction to the image. Here is a sample CCC file:
437%
438% <ColorCorrectionCollection xmlns="urn:ASC:CDL:v1.2">
439% <ColorCorrection id="cc03345">
440% <SOPNode>
441% <Slope> 0.9 1.2 0.5 </Slope>
442% <Offset> 0.4 -0.5 0.6 </Offset>
443% <Power> 1.0 0.8 1.5 </Power>
444% </SOPNode>
445% <SATNode>
446% <Saturation> 0.85 </Saturation>
447% </SATNode>
448% </ColorCorrection>
449% </ColorCorrectionCollection>
450%
451% which includes the slop, offset, and power for each of the RGB channels
452% as well as the saturation.
453%
454% The format of the ColorDecisionListImage method is:
455%
456% MagickBooleanType ColorDecisionListImage(Image *image,
cristy1bfa9f02011-08-11 02:35:43 +0000457% const char *color_correction_collection,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000458%
459% A description of each parameter follows:
460%
461% o image: the image.
462%
463% o color_correction_collection: the color correction collection in XML.
464%
cristy1bfa9f02011-08-11 02:35:43 +0000465% o exception: return any errors or warnings in this structure.
466%
cristy3ed852e2009-09-05 21:47:34 +0000467*/
468MagickExport MagickBooleanType ColorDecisionListImage(Image *image,
cristy1bfa9f02011-08-11 02:35:43 +0000469 const char *color_correction_collection,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000470{
471#define ColorDecisionListCorrectImageTag "ColorDecisionList/Image"
472
473 typedef struct _Correction
474 {
475 double
476 slope,
477 offset,
478 power;
479 } Correction;
480
481 typedef struct _ColorCorrection
482 {
483 Correction
484 red,
485 green,
486 blue;
487
488 double
489 saturation;
490 } ColorCorrection;
491
cristyc4c8d132010-01-07 01:58:38 +0000492 CacheView
493 *image_view;
494
cristy3ed852e2009-09-05 21:47:34 +0000495 char
496 token[MaxTextExtent];
497
498 ColorCorrection
499 color_correction;
500
501 const char
502 *content,
503 *p;
504
cristy3ed852e2009-09-05 21:47:34 +0000505 MagickBooleanType
506 status;
507
cristybb503372010-05-27 20:51:26 +0000508 MagickOffsetType
509 progress;
510
cristy101ab702011-10-13 13:06:32 +0000511 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +0000512 *cdl_map;
513
cristybb503372010-05-27 20:51:26 +0000514 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000515 i;
516
cristybb503372010-05-27 20:51:26 +0000517 ssize_t
518 y;
519
cristy3ed852e2009-09-05 21:47:34 +0000520 XMLTreeInfo
521 *cc,
522 *ccc,
523 *sat,
524 *sop;
525
cristy3ed852e2009-09-05 21:47:34 +0000526 /*
527 Allocate and initialize cdl maps.
528 */
529 assert(image != (Image *) NULL);
530 assert(image->signature == MagickSignature);
531 if (image->debug != MagickFalse)
532 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
533 if (color_correction_collection == (const char *) NULL)
534 return(MagickFalse);
cristy1bfa9f02011-08-11 02:35:43 +0000535 ccc=NewXMLTree((const char *) color_correction_collection,exception);
cristy3ed852e2009-09-05 21:47:34 +0000536 if (ccc == (XMLTreeInfo *) NULL)
537 return(MagickFalse);
538 cc=GetXMLTreeChild(ccc,"ColorCorrection");
539 if (cc == (XMLTreeInfo *) NULL)
540 {
541 ccc=DestroyXMLTree(ccc);
542 return(MagickFalse);
543 }
544 color_correction.red.slope=1.0;
545 color_correction.red.offset=0.0;
546 color_correction.red.power=1.0;
547 color_correction.green.slope=1.0;
548 color_correction.green.offset=0.0;
549 color_correction.green.power=1.0;
550 color_correction.blue.slope=1.0;
551 color_correction.blue.offset=0.0;
552 color_correction.blue.power=1.0;
553 color_correction.saturation=0.0;
554 sop=GetXMLTreeChild(cc,"SOPNode");
555 if (sop != (XMLTreeInfo *) NULL)
556 {
557 XMLTreeInfo
558 *offset,
559 *power,
560 *slope;
561
562 slope=GetXMLTreeChild(sop,"Slope");
563 if (slope != (XMLTreeInfo *) NULL)
564 {
565 content=GetXMLTreeContent(slope);
566 p=(const char *) content;
567 for (i=0; (*p != '\0') && (i < 3); i++)
568 {
569 GetMagickToken(p,&p,token);
570 if (*token == ',')
571 GetMagickToken(p,&p,token);
572 switch (i)
573 {
cristyc1acd842011-05-19 23:05:47 +0000574 case 0:
575 {
cristy9b34e302011-11-05 02:15:45 +0000576 color_correction.red.slope=StringToDouble(token,(char **) NULL);
cristyc1acd842011-05-19 23:05:47 +0000577 break;
578 }
579 case 1:
580 {
cristydbdd0e32011-11-04 23:29:40 +0000581 color_correction.green.slope=StringToDouble(token,
cristyc1acd842011-05-19 23:05:47 +0000582 (char **) NULL);
583 break;
584 }
585 case 2:
586 {
cristydbdd0e32011-11-04 23:29:40 +0000587 color_correction.blue.slope=StringToDouble(token,
cristyc1acd842011-05-19 23:05:47 +0000588 (char **) NULL);
589 break;
590 }
cristy3ed852e2009-09-05 21:47:34 +0000591 }
592 }
593 }
594 offset=GetXMLTreeChild(sop,"Offset");
595 if (offset != (XMLTreeInfo *) NULL)
596 {
597 content=GetXMLTreeContent(offset);
598 p=(const char *) content;
599 for (i=0; (*p != '\0') && (i < 3); i++)
600 {
601 GetMagickToken(p,&p,token);
602 if (*token == ',')
603 GetMagickToken(p,&p,token);
604 switch (i)
605 {
cristyc1acd842011-05-19 23:05:47 +0000606 case 0:
607 {
cristydbdd0e32011-11-04 23:29:40 +0000608 color_correction.red.offset=StringToDouble(token,
cristyc1acd842011-05-19 23:05:47 +0000609 (char **) NULL);
610 break;
611 }
612 case 1:
613 {
cristydbdd0e32011-11-04 23:29:40 +0000614 color_correction.green.offset=StringToDouble(token,
cristyc1acd842011-05-19 23:05:47 +0000615 (char **) NULL);
616 break;
617 }
618 case 2:
619 {
cristydbdd0e32011-11-04 23:29:40 +0000620 color_correction.blue.offset=StringToDouble(token,
cristyc1acd842011-05-19 23:05:47 +0000621 (char **) NULL);
622 break;
623 }
cristy3ed852e2009-09-05 21:47:34 +0000624 }
625 }
626 }
627 power=GetXMLTreeChild(sop,"Power");
628 if (power != (XMLTreeInfo *) NULL)
629 {
630 content=GetXMLTreeContent(power);
631 p=(const char *) content;
632 for (i=0; (*p != '\0') && (i < 3); i++)
633 {
634 GetMagickToken(p,&p,token);
635 if (*token == ',')
636 GetMagickToken(p,&p,token);
637 switch (i)
638 {
cristyc1acd842011-05-19 23:05:47 +0000639 case 0:
640 {
cristy9b34e302011-11-05 02:15:45 +0000641 color_correction.red.power=StringToDouble(token,(char **) NULL);
cristyc1acd842011-05-19 23:05:47 +0000642 break;
643 }
644 case 1:
645 {
cristydbdd0e32011-11-04 23:29:40 +0000646 color_correction.green.power=StringToDouble(token,
cristyc1acd842011-05-19 23:05:47 +0000647 (char **) NULL);
648 break;
649 }
650 case 2:
651 {
cristydbdd0e32011-11-04 23:29:40 +0000652 color_correction.blue.power=StringToDouble(token,
cristyc1acd842011-05-19 23:05:47 +0000653 (char **) NULL);
654 break;
655 }
cristy3ed852e2009-09-05 21:47:34 +0000656 }
657 }
658 }
659 }
660 sat=GetXMLTreeChild(cc,"SATNode");
661 if (sat != (XMLTreeInfo *) NULL)
662 {
663 XMLTreeInfo
664 *saturation;
665
666 saturation=GetXMLTreeChild(sat,"Saturation");
667 if (saturation != (XMLTreeInfo *) NULL)
668 {
669 content=GetXMLTreeContent(saturation);
670 p=(const char *) content;
671 GetMagickToken(p,&p,token);
cristyca826b52012-01-18 18:50:46 +0000672 color_correction.saturation=StringToDouble(token,(char **) NULL);
cristy3ed852e2009-09-05 21:47:34 +0000673 }
674 }
675 ccc=DestroyXMLTree(ccc);
676 if (image->debug != MagickFalse)
677 {
678 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
679 " Color Correction Collection:");
680 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000681 " color_correction.red.slope: %g",color_correction.red.slope);
cristy3ed852e2009-09-05 21:47:34 +0000682 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000683 " color_correction.red.offset: %g",color_correction.red.offset);
cristy3ed852e2009-09-05 21:47:34 +0000684 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000685 " color_correction.red.power: %g",color_correction.red.power);
cristy3ed852e2009-09-05 21:47:34 +0000686 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000687 " color_correction.green.slope: %g",color_correction.green.slope);
cristy3ed852e2009-09-05 21:47:34 +0000688 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000689 " color_correction.green.offset: %g",color_correction.green.offset);
cristy3ed852e2009-09-05 21:47:34 +0000690 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000691 " color_correction.green.power: %g",color_correction.green.power);
cristy3ed852e2009-09-05 21:47:34 +0000692 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000693 " color_correction.blue.slope: %g",color_correction.blue.slope);
cristy3ed852e2009-09-05 21:47:34 +0000694 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000695 " color_correction.blue.offset: %g",color_correction.blue.offset);
cristy3ed852e2009-09-05 21:47:34 +0000696 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000697 " color_correction.blue.power: %g",color_correction.blue.power);
cristy3ed852e2009-09-05 21:47:34 +0000698 (void) LogMagickEvent(TransformEvent,GetMagickModule(),
cristye7f51092010-01-17 00:39:37 +0000699 " color_correction.saturation: %g",color_correction.saturation);
cristy3ed852e2009-09-05 21:47:34 +0000700 }
cristy101ab702011-10-13 13:06:32 +0000701 cdl_map=(PixelInfo *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*cdl_map));
702 if (cdl_map == (PixelInfo *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000703 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
704 image->filename);
cristybb503372010-05-27 20:51:26 +0000705 for (i=0; i <= (ssize_t) MaxMap; i++)
cristy3ed852e2009-09-05 21:47:34 +0000706 {
cristya19f1d72012-08-07 18:24:38 +0000707 cdl_map[i].red=(double) ScaleMapToQuantum((double)
cristyda1f9c12011-10-02 21:39:49 +0000708 (MaxMap*(pow(color_correction.red.slope*i/MaxMap+
709 color_correction.red.offset,color_correction.red.power))));
cristya19f1d72012-08-07 18:24:38 +0000710 cdl_map[i].green=(double) ScaleMapToQuantum((double)
cristyda1f9c12011-10-02 21:39:49 +0000711 (MaxMap*(pow(color_correction.green.slope*i/MaxMap+
712 color_correction.green.offset,color_correction.green.power))));
cristya19f1d72012-08-07 18:24:38 +0000713 cdl_map[i].blue=(double) ScaleMapToQuantum((double)
cristyda1f9c12011-10-02 21:39:49 +0000714 (MaxMap*(pow(color_correction.blue.slope*i/MaxMap+
715 color_correction.blue.offset,color_correction.blue.power))));
cristy3ed852e2009-09-05 21:47:34 +0000716 }
717 if (image->storage_class == PseudoClass)
nicolase0aa3c32012-08-08 14:57:02 +0000718 for (i=0; i < (ssize_t) image->colors; i++)
nicolasfd74ae32012-08-08 16:00:11 +0000719 {
720 /*
cristyaeded782012-09-11 23:39:36 +0000721 Apply transfer function to colormap.
nicolasfd74ae32012-08-08 16:00:11 +0000722 */
723 double
cristyaeded782012-09-11 23:39:36 +0000724 luma;
cristy3ed852e2009-09-05 21:47:34 +0000725
cristya86a5cb2012-10-14 13:40:33 +0000726 luma=0.21267f*image->colormap[i].red+0.71526*image->colormap[i].green+
727 0.07217f*image->colormap[i].blue;
nicolasfd74ae32012-08-08 16:00:11 +0000728 image->colormap[i].red=luma+color_correction.saturation*cdl_map[
cristyaeded782012-09-11 23:39:36 +0000729 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].red))].red-luma;
nicolasfd74ae32012-08-08 16:00:11 +0000730 image->colormap[i].green=luma+color_correction.saturation*cdl_map[
731 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].green))].green-luma;
732 image->colormap[i].blue=luma+color_correction.saturation*cdl_map[
733 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].blue))].blue-luma;
734 }
cristy3ed852e2009-09-05 21:47:34 +0000735 /*
736 Apply transfer function to image.
737 */
738 status=MagickTrue;
739 progress=0;
cristydb070952012-04-20 14:33:00 +0000740 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000741#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000742 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy4ee2b0c2012-05-15 00:30:35 +0000743 dynamic_number_threads(image,image->columns,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +0000744#endif
cristybb503372010-05-27 20:51:26 +0000745 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000746 {
747 double
748 luma;
749
cristy4c08aed2011-07-01 19:47:50 +0000750 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000751 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000752
cristy8d4629b2010-08-30 17:59:46 +0000753 register ssize_t
754 x;
755
cristy3ed852e2009-09-05 21:47:34 +0000756 if (status == MagickFalse)
757 continue;
758 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +0000759 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000760 {
761 status=MagickFalse;
762 continue;
763 }
cristybb503372010-05-27 20:51:26 +0000764 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000765 {
cristya86a5cb2012-10-14 13:40:33 +0000766 luma=0.21267f*GetPixelRed(image,q)+0.71526*GetPixelGreen(image,q)+0.07217f*
cristy4c08aed2011-07-01 19:47:50 +0000767 GetPixelBlue(image,q);
768 SetPixelRed(image,ClampToQuantum(luma+color_correction.saturation*
769 (cdl_map[ScaleQuantumToMap(GetPixelRed(image,q))].red-luma)),q);
770 SetPixelGreen(image,ClampToQuantum(luma+color_correction.saturation*
771 (cdl_map[ScaleQuantumToMap(GetPixelGreen(image,q))].green-luma)),q);
772 SetPixelBlue(image,ClampToQuantum(luma+color_correction.saturation*
773 (cdl_map[ScaleQuantumToMap(GetPixelBlue(image,q))].blue-luma)),q);
cristyed231572011-07-14 02:18:59 +0000774 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000775 }
776 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
777 status=MagickFalse;
778 if (image->progress_monitor != (MagickProgressMonitor) NULL)
779 {
780 MagickBooleanType
781 proceed;
782
cristyb5d5f722009-11-04 03:03:49 +0000783#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +0000784 #pragma omp critical (MagickCore_ColorDecisionListImageChannel)
cristy3ed852e2009-09-05 21:47:34 +0000785#endif
786 proceed=SetImageProgress(image,ColorDecisionListCorrectImageTag,
787 progress++,image->rows);
788 if (proceed == MagickFalse)
789 status=MagickFalse;
790 }
791 }
792 image_view=DestroyCacheView(image_view);
cristy101ab702011-10-13 13:06:32 +0000793 cdl_map=(PixelInfo *) RelinquishMagickMemory(cdl_map);
cristy3ed852e2009-09-05 21:47:34 +0000794 return(status);
795}
796
797/*
798%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
799% %
800% %
801% %
cristy3ed852e2009-09-05 21:47:34 +0000802% C o n t r a s t I m a g e %
803% %
804% %
805% %
806%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
807%
808% ContrastImage() enhances the intensity differences between the lighter and
809% darker elements of the image. Set sharpen to a MagickTrue to increase the
810% image contrast otherwise the contrast is reduced.
811%
812% The format of the ContrastImage method is:
813%
814% MagickBooleanType ContrastImage(Image *image,
cristye23ec9d2011-08-16 18:15:40 +0000815% const MagickBooleanType sharpen,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000816%
817% A description of each parameter follows:
818%
819% o image: the image.
820%
821% o sharpen: Increase or decrease image contrast.
822%
cristye23ec9d2011-08-16 18:15:40 +0000823% o exception: return any errors or warnings in this structure.
824%
cristy3ed852e2009-09-05 21:47:34 +0000825*/
826
cristy3094b7f2011-10-01 23:18:02 +0000827static void Contrast(const int sign,double *red,double *green,double *blue)
cristy3ed852e2009-09-05 21:47:34 +0000828{
829 double
830 brightness,
831 hue,
832 saturation;
833
834 /*
835 Enhance contrast: dark color become darker, light color become lighter.
836 */
cristy3094b7f2011-10-01 23:18:02 +0000837 assert(red != (double *) NULL);
838 assert(green != (double *) NULL);
839 assert(blue != (double *) NULL);
cristy3ed852e2009-09-05 21:47:34 +0000840 hue=0.0;
841 saturation=0.0;
842 brightness=0.0;
cristy0a39a5c2012-06-27 12:51:45 +0000843 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
cristy31ac0f02011-03-12 02:04:47 +0000844 brightness+=0.5*sign*(0.5*(sin((double) (MagickPI*(brightness-0.5)))+1.0)-
cristy4205a3c2010-09-12 20:19:59 +0000845 brightness);
cristy3ed852e2009-09-05 21:47:34 +0000846 if (brightness > 1.0)
847 brightness=1.0;
848 else
849 if (brightness < 0.0)
850 brightness=0.0;
cristy0a39a5c2012-06-27 12:51:45 +0000851 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
cristy3ed852e2009-09-05 21:47:34 +0000852}
853
854MagickExport MagickBooleanType ContrastImage(Image *image,
cristye23ec9d2011-08-16 18:15:40 +0000855 const MagickBooleanType sharpen,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000856{
857#define ContrastImageTag "Contrast/Image"
858
cristyc4c8d132010-01-07 01:58:38 +0000859 CacheView
860 *image_view;
861
cristy3ed852e2009-09-05 21:47:34 +0000862 int
863 sign;
864
cristy3ed852e2009-09-05 21:47:34 +0000865 MagickBooleanType
866 status;
867
cristybb503372010-05-27 20:51:26 +0000868 MagickOffsetType
869 progress;
870
871 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +0000872 i;
873
cristybb503372010-05-27 20:51:26 +0000874 ssize_t
875 y;
876
cristy3ed852e2009-09-05 21:47:34 +0000877 assert(image != (Image *) NULL);
878 assert(image->signature == MagickSignature);
879 if (image->debug != MagickFalse)
880 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
881 sign=sharpen != MagickFalse ? 1 : -1;
882 if (image->storage_class == PseudoClass)
883 {
884 /*
885 Contrast enhance colormap.
886 */
cristybb503372010-05-27 20:51:26 +0000887 for (i=0; i < (ssize_t) image->colors; i++)
cristybdf42e52012-10-10 23:58:20 +0000888 {
889 double
890 blue,
891 green,
892 red;
893
894 Contrast(sign,&red,&green,&blue);
895 image->colormap[i].red=(MagickRealType) red;
896 image->colormap[i].red=(MagickRealType) red;
897 image->colormap[i].red=(MagickRealType) red;
898 }
cristy3ed852e2009-09-05 21:47:34 +0000899 }
900 /*
901 Contrast enhance image.
902 */
903 status=MagickTrue;
904 progress=0;
cristydb070952012-04-20 14:33:00 +0000905 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +0000906#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +0000907 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy4ee2b0c2012-05-15 00:30:35 +0000908 dynamic_number_threads(image,image->columns,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +0000909#endif
cristybb503372010-05-27 20:51:26 +0000910 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +0000911 {
cristy3094b7f2011-10-01 23:18:02 +0000912 double
cristy5afeab82011-04-30 01:30:09 +0000913 blue,
914 green,
915 red;
916
cristy4c08aed2011-07-01 19:47:50 +0000917 register Quantum
cristyc47d1f82009-11-26 01:44:43 +0000918 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +0000919
cristy8d4629b2010-08-30 17:59:46 +0000920 register ssize_t
921 x;
922
cristy3ed852e2009-09-05 21:47:34 +0000923 if (status == MagickFalse)
924 continue;
925 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +0000926 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +0000927 {
928 status=MagickFalse;
929 continue;
930 }
cristybb503372010-05-27 20:51:26 +0000931 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +0000932 {
cristyda1f9c12011-10-02 21:39:49 +0000933 red=(double) GetPixelRed(image,q);
934 green=(double) GetPixelGreen(image,q);
935 blue=(double) GetPixelBlue(image,q);
cristy5afeab82011-04-30 01:30:09 +0000936 Contrast(sign,&red,&green,&blue);
cristyda1f9c12011-10-02 21:39:49 +0000937 SetPixelRed(image,ClampToQuantum(red),q);
938 SetPixelGreen(image,ClampToQuantum(green),q);
939 SetPixelBlue(image,ClampToQuantum(blue),q);
cristyed231572011-07-14 02:18:59 +0000940 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +0000941 }
942 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
943 status=MagickFalse;
944 if (image->progress_monitor != (MagickProgressMonitor) NULL)
945 {
946 MagickBooleanType
947 proceed;
948
cristyb5d5f722009-11-04 03:03:49 +0000949#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +0000950 #pragma omp critical (MagickCore_ContrastImage)
cristy3ed852e2009-09-05 21:47:34 +0000951#endif
952 proceed=SetImageProgress(image,ContrastImageTag,progress++,image->rows);
953 if (proceed == MagickFalse)
954 status=MagickFalse;
955 }
956 }
957 image_view=DestroyCacheView(image_view);
958 return(status);
959}
960
961/*
962%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
963% %
964% %
965% %
966% C o n t r a s t S t r e t c h I m a g e %
967% %
968% %
969% %
970%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
971%
cristyf1611782011-08-01 15:39:13 +0000972% ContrastStretchImage() is a simple image enhancement technique that attempts
anthonye5b39652012-04-21 05:37:29 +0000973% to improve the contrast in an image by 'stretching' the range of intensity
cristyf1611782011-08-01 15:39:13 +0000974% values it contains to span a desired range of values. It differs from the
975% more sophisticated histogram equalization in that it can only apply a
976% linear scaling function to the image pixel values. As a result the
anthonye5b39652012-04-21 05:37:29 +0000977% 'enhancement' is less harsh.
cristy3ed852e2009-09-05 21:47:34 +0000978%
979% The format of the ContrastStretchImage method is:
980%
981% MagickBooleanType ContrastStretchImage(Image *image,
cristye23ec9d2011-08-16 18:15:40 +0000982% const char *levels,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +0000983%
984% A description of each parameter follows:
985%
986% o image: the image.
987%
cristy3ed852e2009-09-05 21:47:34 +0000988% o black_point: the black point.
989%
990% o white_point: the white point.
991%
992% o levels: Specify the levels where the black and white points have the
993% range of 0 to number-of-pixels (e.g. 1%, 10x90%, etc.).
994%
cristye23ec9d2011-08-16 18:15:40 +0000995% o exception: return any errors or warnings in this structure.
996%
cristy3ed852e2009-09-05 21:47:34 +0000997*/
cristy3ed852e2009-09-05 21:47:34 +0000998MagickExport MagickBooleanType ContrastStretchImage(Image *image,
cristye23ec9d2011-08-16 18:15:40 +0000999 const double black_point,const double white_point,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001000{
cristya19f1d72012-08-07 18:24:38 +00001001#define MaxRange(color) ((double) ScaleQuantumToMap((Quantum) (color)))
cristy3ed852e2009-09-05 21:47:34 +00001002#define ContrastStretchImageTag "ContrastStretch/Image"
1003
cristyc4c8d132010-01-07 01:58:38 +00001004 CacheView
1005 *image_view;
1006
cristy3ed852e2009-09-05 21:47:34 +00001007 MagickBooleanType
1008 status;
1009
cristybb503372010-05-27 20:51:26 +00001010 MagickOffsetType
1011 progress;
1012
cristyf45fec72011-08-23 16:02:32 +00001013 double
1014 *black,
cristy3ed852e2009-09-05 21:47:34 +00001015 *histogram,
1016 *stretch_map,
cristyf45fec72011-08-23 16:02:32 +00001017 *white;
cristy3ed852e2009-09-05 21:47:34 +00001018
cristybb503372010-05-27 20:51:26 +00001019 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001020 i;
1021
cristy564a5692012-01-20 23:56:26 +00001022 size_t
1023 number_channels;
1024
cristybb503372010-05-27 20:51:26 +00001025 ssize_t
1026 y;
1027
cristy3ed852e2009-09-05 21:47:34 +00001028 /*
1029 Allocate histogram and stretch map.
1030 */
1031 assert(image != (Image *) NULL);
1032 assert(image->signature == MagickSignature);
1033 if (image->debug != MagickFalse)
1034 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristyf45fec72011-08-23 16:02:32 +00001035 black=(double *) AcquireQuantumMemory(GetPixelChannels(image),sizeof(*black));
1036 white=(double *) AcquireQuantumMemory(GetPixelChannels(image),sizeof(*white));
cristy564a5692012-01-20 23:56:26 +00001037 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,GetPixelChannels(image)*
1038 sizeof(*histogram));
cristyf45fec72011-08-23 16:02:32 +00001039 stretch_map=(double *) AcquireQuantumMemory(MaxMap+1UL,
cristy465571b2011-08-21 20:43:15 +00001040 GetPixelChannels(image)*sizeof(*stretch_map));
cristyf45fec72011-08-23 16:02:32 +00001041 if ((black == (double *) NULL) || (white == (double *) NULL) ||
1042 (histogram == (double *) NULL) || (stretch_map == (double *) NULL))
cristy465571b2011-08-21 20:43:15 +00001043 {
cristyf45fec72011-08-23 16:02:32 +00001044 if (stretch_map != (double *) NULL)
1045 stretch_map=(double *) RelinquishMagickMemory(stretch_map);
1046 if (histogram != (double *) NULL)
1047 histogram=(double *) RelinquishMagickMemory(histogram);
1048 if (white != (double *) NULL)
1049 white=(double *) RelinquishMagickMemory(white);
1050 if (black != (double *) NULL)
1051 black=(double *) RelinquishMagickMemory(black);
cristy465571b2011-08-21 20:43:15 +00001052 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1053 image->filename);
1054 }
cristy3ed852e2009-09-05 21:47:34 +00001055 /*
1056 Form histogram.
1057 */
1058 status=MagickTrue;
cristy465571b2011-08-21 20:43:15 +00001059 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1060 sizeof(*histogram));
cristydb070952012-04-20 14:33:00 +00001061 image_view=AcquireVirtualCacheView(image,exception);
cristybb503372010-05-27 20:51:26 +00001062 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001063 {
cristy4c08aed2011-07-01 19:47:50 +00001064 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001065 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001066
cristybb503372010-05-27 20:51:26 +00001067 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001068 x;
1069
1070 if (status == MagickFalse)
1071 continue;
1072 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001073 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001074 {
1075 status=MagickFalse;
1076 continue;
1077 }
cristy43547a52011-08-09 13:07:05 +00001078 for (x=0; x < (ssize_t) image->columns; x++)
1079 {
cristya64f4b92012-07-11 23:59:00 +00001080 double
1081 pixel;
1082
cristy465571b2011-08-21 20:43:15 +00001083 register ssize_t
1084 i;
1085
cristyf13c5942012-08-08 23:50:11 +00001086 pixel=GetPixelIntensity(image,p);
cristy465571b2011-08-21 20:43:15 +00001087 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristya64f4b92012-07-11 23:59:00 +00001088 {
1089 if (image->channel_mask != DefaultChannels)
cristy1fc77e82012-08-08 15:43:42 +00001090 pixel=(double) p[i];
1091 histogram[GetPixelChannels(image)*ScaleQuantumToMap(
1092 ClampToQuantum(pixel))+i]++;
cristya64f4b92012-07-11 23:59:00 +00001093 }
cristy43547a52011-08-09 13:07:05 +00001094 p+=GetPixelChannels(image);
1095 }
cristy3ed852e2009-09-05 21:47:34 +00001096 }
cristydb070952012-04-20 14:33:00 +00001097 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00001098 /*
1099 Find the histogram boundaries by locating the black/white levels.
1100 */
cristy564a5692012-01-20 23:56:26 +00001101 number_channels=GetPixelChannels(image);
cristyc94ba6f2012-01-29 23:19:58 +00001102 for (i=0; i < (ssize_t) number_channels; i++)
cristy3ed852e2009-09-05 21:47:34 +00001103 {
cristy465571b2011-08-21 20:43:15 +00001104 double
1105 intensity;
1106
1107 register ssize_t
1108 j;
1109
1110 black[i]=0.0;
1111 white[i]=MaxRange(QuantumRange);
1112 intensity=0.0;
1113 for (j=0; j <= (ssize_t) MaxMap; j++)
1114 {
1115 intensity+=histogram[GetPixelChannels(image)*j+i];
1116 if (intensity > black_point)
1117 break;
1118 }
cristya19f1d72012-08-07 18:24:38 +00001119 black[i]=(double) j;
cristy465571b2011-08-21 20:43:15 +00001120 intensity=0.0;
1121 for (j=(ssize_t) MaxMap; j != 0; j--)
1122 {
1123 intensity+=histogram[GetPixelChannels(image)*j+i];
1124 if (intensity > ((double) image->columns*image->rows-white_point))
1125 break;
1126 }
cristya19f1d72012-08-07 18:24:38 +00001127 white[i]=(double) j;
cristy3ed852e2009-09-05 21:47:34 +00001128 }
cristy465571b2011-08-21 20:43:15 +00001129 histogram=(double *) RelinquishMagickMemory(histogram);
cristy3ed852e2009-09-05 21:47:34 +00001130 /*
cristy465571b2011-08-21 20:43:15 +00001131 Stretch the histogram to create the stretched image mapping.
cristy3ed852e2009-09-05 21:47:34 +00001132 */
cristy465571b2011-08-21 20:43:15 +00001133 (void) ResetMagickMemory(stretch_map,0,(MaxMap+1)*GetPixelChannels(image)*
1134 sizeof(*stretch_map));
cristy564a5692012-01-20 23:56:26 +00001135 number_channels=GetPixelChannels(image);
cristy564a5692012-01-20 23:56:26 +00001136 for (i=0; i < (ssize_t) number_channels; i++)
cristy465571b2011-08-21 20:43:15 +00001137 {
1138 register ssize_t
1139 j;
1140
1141 for (j=0; j <= (ssize_t) MaxMap; j++)
1142 {
1143 if (j < (ssize_t) black[i])
1144 stretch_map[GetPixelChannels(image)*j+i]=0.0;
1145 else
1146 if (j > (ssize_t) white[i])
cristya19f1d72012-08-07 18:24:38 +00001147 stretch_map[GetPixelChannels(image)*j+i]=(double)
cristy465571b2011-08-21 20:43:15 +00001148 QuantumRange;
1149 else
1150 if (black[i] != white[i])
cristya19f1d72012-08-07 18:24:38 +00001151 stretch_map[GetPixelChannels(image)*j+i]=(double)
1152 ScaleMapToQuantum((double) (MaxMap*(j-black[i])/
cristy465571b2011-08-21 20:43:15 +00001153 (white[i]-black[i])));
1154 }
1155 }
cristy3ed852e2009-09-05 21:47:34 +00001156 if (image->storage_class == PseudoClass)
1157 {
cristy465571b2011-08-21 20:43:15 +00001158 register ssize_t
1159 j;
1160
cristy3ed852e2009-09-05 21:47:34 +00001161 /*
cristy465571b2011-08-21 20:43:15 +00001162 Stretch-contrast colormap.
cristy3ed852e2009-09-05 21:47:34 +00001163 */
cristy465571b2011-08-21 20:43:15 +00001164 for (j=0; j < (ssize_t) image->colors; j++)
cristy3ed852e2009-09-05 21:47:34 +00001165 {
cristyed231572011-07-14 02:18:59 +00001166 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001167 {
cristycf1296e2012-08-26 23:40:49 +00001168 i=GetPixelChannelChannel(image,RedPixelChannel);
cristy465571b2011-08-21 20:43:15 +00001169 if (black[i] != white[i])
cristyda1f9c12011-10-02 21:39:49 +00001170 image->colormap[j].red=stretch_map[GetPixelChannels(image)*
1171 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))]+i;
cristy3ed852e2009-09-05 21:47:34 +00001172 }
cristyed231572011-07-14 02:18:59 +00001173 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001174 {
cristycf1296e2012-08-26 23:40:49 +00001175 i=GetPixelChannelChannel(image,GreenPixelChannel);
cristy465571b2011-08-21 20:43:15 +00001176 if (black[i] != white[i])
cristyda1f9c12011-10-02 21:39:49 +00001177 image->colormap[j].green=stretch_map[GetPixelChannels(image)*
1178 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))]+i;
cristy3ed852e2009-09-05 21:47:34 +00001179 }
cristyed231572011-07-14 02:18:59 +00001180 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001181 {
cristycf1296e2012-08-26 23:40:49 +00001182 i=GetPixelChannelChannel(image,BluePixelChannel);
cristy465571b2011-08-21 20:43:15 +00001183 if (black[i] != white[i])
cristyda1f9c12011-10-02 21:39:49 +00001184 image->colormap[j].blue=stretch_map[GetPixelChannels(image)*
1185 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))]+i;
cristy3ed852e2009-09-05 21:47:34 +00001186 }
cristyed231572011-07-14 02:18:59 +00001187 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy3ed852e2009-09-05 21:47:34 +00001188 {
cristycf1296e2012-08-26 23:40:49 +00001189 i=GetPixelChannelChannel(image,AlphaPixelChannel);
cristy465571b2011-08-21 20:43:15 +00001190 if (black[i] != white[i])
cristyda1f9c12011-10-02 21:39:49 +00001191 image->colormap[j].alpha=stretch_map[GetPixelChannels(image)*
1192 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))]+i;
cristy3ed852e2009-09-05 21:47:34 +00001193 }
1194 }
1195 }
1196 /*
cristy465571b2011-08-21 20:43:15 +00001197 Stretch-contrast image.
cristy3ed852e2009-09-05 21:47:34 +00001198 */
1199 status=MagickTrue;
1200 progress=0;
cristydb070952012-04-20 14:33:00 +00001201 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +00001202#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001203 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy4ee2b0c2012-05-15 00:30:35 +00001204 dynamic_number_threads(image,image->columns,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00001205#endif
cristybb503372010-05-27 20:51:26 +00001206 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001207 {
cristy4c08aed2011-07-01 19:47:50 +00001208 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001209 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001210
cristy8d4629b2010-08-30 17:59:46 +00001211 register ssize_t
1212 x;
1213
cristy3ed852e2009-09-05 21:47:34 +00001214 if (status == MagickFalse)
1215 continue;
1216 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001217 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001218 {
1219 status=MagickFalse;
1220 continue;
1221 }
cristybb503372010-05-27 20:51:26 +00001222 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001223 {
cristy465571b2011-08-21 20:43:15 +00001224 register ssize_t
1225 i;
1226
cristy10a6c612012-01-29 21:41:05 +00001227 if (GetPixelMask(image,q) != 0)
1228 {
1229 q+=GetPixelChannels(image);
1230 continue;
1231 }
cristy465571b2011-08-21 20:43:15 +00001232 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1233 {
cristyabace412011-12-11 15:56:53 +00001234 PixelChannel
1235 channel;
1236
cristy465571b2011-08-21 20:43:15 +00001237 PixelTrait
1238 traits;
1239
cristycf1296e2012-08-26 23:40:49 +00001240 channel=GetPixelChannelChannel(image,i);
1241 traits=GetPixelChannelTraits(image,channel);
cristyd09f8802012-02-04 16:44:10 +00001242 if (((traits & UpdatePixelTrait) == 0) || (black[i] == white[i]))
1243 continue;
1244 q[i]=ClampToQuantum(stretch_map[GetPixelChannels(image)*
1245 ScaleQuantumToMap(q[i])+i]);
cristy465571b2011-08-21 20:43:15 +00001246 }
cristyed231572011-07-14 02:18:59 +00001247 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001248 }
1249 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1250 status=MagickFalse;
1251 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1252 {
1253 MagickBooleanType
1254 proceed;
1255
cristyb5d5f722009-11-04 03:03:49 +00001256#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00001257 #pragma omp critical (MagickCore_ContrastStretchImage)
cristy3ed852e2009-09-05 21:47:34 +00001258#endif
1259 proceed=SetImageProgress(image,ContrastStretchImageTag,progress++,
1260 image->rows);
1261 if (proceed == MagickFalse)
1262 status=MagickFalse;
1263 }
1264 }
1265 image_view=DestroyCacheView(image_view);
cristyf45fec72011-08-23 16:02:32 +00001266 stretch_map=(double *) RelinquishMagickMemory(stretch_map);
1267 white=(double *) RelinquishMagickMemory(white);
1268 black=(double *) RelinquishMagickMemory(black);
cristy3ed852e2009-09-05 21:47:34 +00001269 return(status);
1270}
1271
1272/*
1273%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1274% %
1275% %
1276% %
1277% E n h a n c e I m a g e %
1278% %
1279% %
1280% %
1281%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1282%
1283% EnhanceImage() applies a digital filter that improves the quality of a
1284% noisy image.
1285%
1286% The format of the EnhanceImage method is:
1287%
1288% Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1289%
1290% A description of each parameter follows:
1291%
1292% o image: the image.
1293%
1294% o exception: return any errors or warnings in this structure.
1295%
1296*/
1297MagickExport Image *EnhanceImage(const Image *image,ExceptionInfo *exception)
1298{
cristy6d8c3d72011-08-22 01:20:01 +00001299#define EnhancePixel(weight) \
cristya19f1d72012-08-07 18:24:38 +00001300 mean=((double) r[i]+GetPixelChannel(enhance_image,channel,q))/2.0; \
cristy76c670b2012-10-06 21:30:50 +00001301 distance=(double) r[i]-(double) GetPixelChannel(enhance_image,channel,q); \
1302 distance_squared=QuantumScale*(2.0*((double) QuantumRange+1.0)+mean)* \
1303 distance*distance; \
1304 if (distance_squared < ((double) QuantumRange*(double) QuantumRange/25.0f)) \
cristy3ed852e2009-09-05 21:47:34 +00001305 { \
cristy6d8c3d72011-08-22 01:20:01 +00001306 aggregate+=(weight)*r[i]; \
cristy3ed852e2009-09-05 21:47:34 +00001307 total_weight+=(weight); \
1308 } \
cristy6d8c3d72011-08-22 01:20:01 +00001309 r+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001310#define EnhanceImageTag "Enhance/Image"
1311
cristyc4c8d132010-01-07 01:58:38 +00001312 CacheView
1313 *enhance_view,
1314 *image_view;
1315
cristy3ed852e2009-09-05 21:47:34 +00001316 Image
1317 *enhance_image;
1318
cristy3ed852e2009-09-05 21:47:34 +00001319 MagickBooleanType
1320 status;
1321
cristybb503372010-05-27 20:51:26 +00001322 MagickOffsetType
1323 progress;
1324
cristybb503372010-05-27 20:51:26 +00001325 ssize_t
1326 y;
1327
cristy3ed852e2009-09-05 21:47:34 +00001328 /*
1329 Initialize enhanced image attributes.
1330 */
1331 assert(image != (const Image *) NULL);
1332 assert(image->signature == MagickSignature);
1333 if (image->debug != MagickFalse)
1334 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1335 assert(exception != (ExceptionInfo *) NULL);
1336 assert(exception->signature == MagickSignature);
cristy3ed852e2009-09-05 21:47:34 +00001337 enhance_image=CloneImage(image,image->columns,image->rows,MagickTrue,
1338 exception);
1339 if (enhance_image == (Image *) NULL)
1340 return((Image *) NULL);
cristy574cc262011-08-05 01:23:58 +00001341 if (SetImageStorageClass(enhance_image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001342 {
cristy3ed852e2009-09-05 21:47:34 +00001343 enhance_image=DestroyImage(enhance_image);
1344 return((Image *) NULL);
1345 }
1346 /*
1347 Enhance image.
1348 */
1349 status=MagickTrue;
1350 progress=0;
cristydb070952012-04-20 14:33:00 +00001351 image_view=AcquireVirtualCacheView(image,exception);
1352 enhance_view=AcquireAuthenticCacheView(enhance_image,exception);
cristyb5d5f722009-11-04 03:03:49 +00001353#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001354 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy4ee2b0c2012-05-15 00:30:35 +00001355 dynamic_number_threads(image,image->columns,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00001356#endif
cristybb503372010-05-27 20:51:26 +00001357 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001358 {
cristy4c08aed2011-07-01 19:47:50 +00001359 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001360 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001361
cristy4c08aed2011-07-01 19:47:50 +00001362 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001363 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001364
cristy8d4629b2010-08-30 17:59:46 +00001365 register ssize_t
1366 x;
1367
cristy6d8c3d72011-08-22 01:20:01 +00001368 ssize_t
1369 center;
1370
cristy3ed852e2009-09-05 21:47:34 +00001371 if (status == MagickFalse)
1372 continue;
1373 p=GetCacheViewVirtualPixels(image_view,-2,y-2,image->columns+4,5,exception);
1374 q=QueueCacheViewAuthenticPixels(enhance_view,0,y,enhance_image->columns,1,
1375 exception);
cristy4c08aed2011-07-01 19:47:50 +00001376 if ((p == (const Quantum *) NULL) || (q == (Quantum *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001377 {
1378 status=MagickFalse;
1379 continue;
1380 }
cristyf45fec72011-08-23 16:02:32 +00001381 center=(ssize_t) GetPixelChannels(image)*(2*(image->columns+4)+2);
cristybb503372010-05-27 20:51:26 +00001382 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001383 {
cristy6d8c3d72011-08-22 01:20:01 +00001384 register ssize_t
1385 i;
cristy3ed852e2009-09-05 21:47:34 +00001386
cristy10a6c612012-01-29 21:41:05 +00001387 if (GetPixelMask(image,p) != 0)
1388 {
1389 p+=GetPixelChannels(image);
1390 q+=GetPixelChannels(enhance_image);
1391 continue;
1392 }
cristy6d8c3d72011-08-22 01:20:01 +00001393 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1394 {
cristya19f1d72012-08-07 18:24:38 +00001395 double
cristy6d8c3d72011-08-22 01:20:01 +00001396 aggregate,
1397 distance,
1398 distance_squared,
1399 mean,
1400 total_weight;
cristy3ed852e2009-09-05 21:47:34 +00001401
cristy6d8c3d72011-08-22 01:20:01 +00001402 PixelChannel
1403 channel;
cristy3ed852e2009-09-05 21:47:34 +00001404
cristy6d8c3d72011-08-22 01:20:01 +00001405 PixelTrait
1406 enhance_traits,
1407 traits;
cristy3ed852e2009-09-05 21:47:34 +00001408
cristy6d8c3d72011-08-22 01:20:01 +00001409 register const Quantum
1410 *restrict r;
1411
cristycf1296e2012-08-26 23:40:49 +00001412 channel=GetPixelChannelChannel(image,i);
1413 traits=GetPixelChannelTraits(image,channel);
1414 enhance_traits=GetPixelChannelTraits(enhance_image,channel);
cristy010d7d12011-08-31 01:02:48 +00001415 if ((traits == UndefinedPixelTrait) ||
1416 (enhance_traits == UndefinedPixelTrait))
cristy6d8c3d72011-08-22 01:20:01 +00001417 continue;
cristy0beccfa2011-09-25 20:47:53 +00001418 SetPixelChannel(enhance_image,channel,p[center+i],q);
cristy6d8c3d72011-08-22 01:20:01 +00001419 if ((enhance_traits & CopyPixelTrait) != 0)
1420 continue;
1421 /*
1422 Compute weighted average of target pixel color components.
1423 */
1424 aggregate=0.0;
1425 total_weight=0.0;
cristyf45fec72011-08-23 16:02:32 +00001426 r=p;
cristy6d8c3d72011-08-22 01:20:01 +00001427 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1428 EnhancePixel(8.0); EnhancePixel(5.0);
1429 r=p+1*GetPixelChannels(image)*(image->columns+4);
1430 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1431 EnhancePixel(20.0); EnhancePixel(8.0);
1432 r=p+2*GetPixelChannels(image)*(image->columns+4);
1433 EnhancePixel(10.0); EnhancePixel(40.0); EnhancePixel(80.0);
1434 EnhancePixel(40.0); EnhancePixel(10.0);
1435 r=p+3*GetPixelChannels(image)*(image->columns+4);
1436 EnhancePixel(8.0); EnhancePixel(20.0); EnhancePixel(40.0);
1437 EnhancePixel(20.0); EnhancePixel(8.0);
1438 r=p+4*GetPixelChannels(image)*(image->columns+4);
1439 EnhancePixel(5.0); EnhancePixel(8.0); EnhancePixel(10.0);
1440 EnhancePixel(8.0); EnhancePixel(5.0);
cristy0beccfa2011-09-25 20:47:53 +00001441 SetPixelChannel(enhance_image,channel,ClampToQuantum(aggregate/
1442 total_weight),q);
cristy6d8c3d72011-08-22 01:20:01 +00001443 }
cristyed231572011-07-14 02:18:59 +00001444 p+=GetPixelChannels(image);
1445 q+=GetPixelChannels(enhance_image);
cristy3ed852e2009-09-05 21:47:34 +00001446 }
1447 if (SyncCacheViewAuthenticPixels(enhance_view,exception) == MagickFalse)
1448 status=MagickFalse;
1449 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1450 {
1451 MagickBooleanType
1452 proceed;
1453
cristyb5d5f722009-11-04 03:03:49 +00001454#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00001455 #pragma omp critical (MagickCore_EnhanceImage)
cristy3ed852e2009-09-05 21:47:34 +00001456#endif
1457 proceed=SetImageProgress(image,EnhanceImageTag,progress++,image->rows);
1458 if (proceed == MagickFalse)
1459 status=MagickFalse;
1460 }
1461 }
1462 enhance_view=DestroyCacheView(enhance_view);
1463 image_view=DestroyCacheView(image_view);
1464 return(enhance_image);
1465}
1466
1467/*
1468%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1469% %
1470% %
1471% %
1472% E q u a l i z e I m a g e %
1473% %
1474% %
1475% %
1476%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1477%
1478% EqualizeImage() applies a histogram equalization to the image.
1479%
1480% The format of the EqualizeImage method is:
1481%
cristy6d8c3d72011-08-22 01:20:01 +00001482% MagickBooleanType EqualizeImage(Image *image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001483%
1484% A description of each parameter follows:
1485%
1486% o image: the image.
1487%
cristy6d8c3d72011-08-22 01:20:01 +00001488% o exception: return any errors or warnings in this structure.
cristy3ed852e2009-09-05 21:47:34 +00001489%
1490*/
cristy6d8c3d72011-08-22 01:20:01 +00001491MagickExport MagickBooleanType EqualizeImage(Image *image,
1492 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001493{
cristy3ed852e2009-09-05 21:47:34 +00001494#define EqualizeImageTag "Equalize/Image"
1495
cristyc4c8d132010-01-07 01:58:38 +00001496 CacheView
1497 *image_view;
1498
cristy3ed852e2009-09-05 21:47:34 +00001499 MagickBooleanType
1500 status;
1501
cristybb503372010-05-27 20:51:26 +00001502 MagickOffsetType
1503 progress;
1504
cristya19f1d72012-08-07 18:24:38 +00001505 double
cristy5f95f4f2011-10-23 01:01:01 +00001506 black[CompositePixelChannel],
cristy3ed852e2009-09-05 21:47:34 +00001507 *equalize_map,
1508 *histogram,
cristy3ed852e2009-09-05 21:47:34 +00001509 *map,
cristy5f95f4f2011-10-23 01:01:01 +00001510 white[CompositePixelChannel];
cristy3ed852e2009-09-05 21:47:34 +00001511
cristybb503372010-05-27 20:51:26 +00001512 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001513 i;
1514
cristy564a5692012-01-20 23:56:26 +00001515 size_t
1516 number_channels;
1517
cristybb503372010-05-27 20:51:26 +00001518 ssize_t
1519 y;
1520
cristy3ed852e2009-09-05 21:47:34 +00001521 /*
1522 Allocate and initialize histogram arrays.
1523 */
1524 assert(image != (Image *) NULL);
1525 assert(image->signature == MagickSignature);
1526 if (image->debug != MagickFalse)
1527 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristya19f1d72012-08-07 18:24:38 +00001528 equalize_map=(double *) AcquireQuantumMemory(MaxMap+1UL,
cristyf45fec72011-08-23 16:02:32 +00001529 GetPixelChannels(image)*sizeof(*equalize_map));
cristya19f1d72012-08-07 18:24:38 +00001530 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,
cristyf45fec72011-08-23 16:02:32 +00001531 GetPixelChannels(image)*sizeof(*histogram));
cristya19f1d72012-08-07 18:24:38 +00001532 map=(double *) AcquireQuantumMemory(MaxMap+1UL,
cristyf45fec72011-08-23 16:02:32 +00001533 GetPixelChannels(image)*sizeof(*map));
cristya19f1d72012-08-07 18:24:38 +00001534 if ((equalize_map == (double *) NULL) ||
1535 (histogram == (double *) NULL) ||
1536 (map == (double *) NULL))
cristy3ed852e2009-09-05 21:47:34 +00001537 {
cristya19f1d72012-08-07 18:24:38 +00001538 if (map != (double *) NULL)
1539 map=(double *) RelinquishMagickMemory(map);
1540 if (histogram != (double *) NULL)
1541 histogram=(double *) RelinquishMagickMemory(histogram);
1542 if (equalize_map != (double *) NULL)
1543 equalize_map=(double *) RelinquishMagickMemory(equalize_map);
cristy3ed852e2009-09-05 21:47:34 +00001544 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1545 image->filename);
1546 }
1547 /*
1548 Form histogram.
1549 */
cristyf45fec72011-08-23 16:02:32 +00001550 status=MagickTrue;
1551 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*GetPixelChannels(image)*
1552 sizeof(*histogram));
cristydb070952012-04-20 14:33:00 +00001553 image_view=AcquireVirtualCacheView(image,exception);
cristybb503372010-05-27 20:51:26 +00001554 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001555 {
cristy4c08aed2011-07-01 19:47:50 +00001556 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00001557 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00001558
cristybb503372010-05-27 20:51:26 +00001559 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001560 x;
1561
cristyf45fec72011-08-23 16:02:32 +00001562 if (status == MagickFalse)
1563 continue;
1564 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00001565 if (p == (const Quantum *) NULL)
cristyf45fec72011-08-23 16:02:32 +00001566 {
1567 status=MagickFalse;
1568 continue;
1569 }
cristybb503372010-05-27 20:51:26 +00001570 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001571 {
cristyf45fec72011-08-23 16:02:32 +00001572 register ssize_t
1573 i;
1574
1575 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1576 histogram[GetPixelChannels(image)*ScaleQuantumToMap(p[i])+i]++;
cristyed231572011-07-14 02:18:59 +00001577 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001578 }
1579 }
cristydb070952012-04-20 14:33:00 +00001580 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00001581 /*
1582 Integrate the histogram to get the equalization map.
1583 */
cristy564a5692012-01-20 23:56:26 +00001584 number_channels=GetPixelChannels(image);
cristyc94ba6f2012-01-29 23:19:58 +00001585 for (i=0; i < (ssize_t) number_channels; i++)
cristy3ed852e2009-09-05 21:47:34 +00001586 {
cristya19f1d72012-08-07 18:24:38 +00001587 double
cristyf45fec72011-08-23 16:02:32 +00001588 intensity;
1589
1590 register ssize_t
1591 j;
1592
1593 intensity=0.0;
1594 for (j=0; j <= (ssize_t) MaxMap; j++)
1595 {
1596 intensity+=histogram[GetPixelChannels(image)*j+i];
1597 map[GetPixelChannels(image)*j+i]=intensity;
1598 }
cristy3ed852e2009-09-05 21:47:34 +00001599 }
cristyf45fec72011-08-23 16:02:32 +00001600 (void) ResetMagickMemory(equalize_map,0,(MaxMap+1)*GetPixelChannels(image)*
1601 sizeof(*equalize_map));
cristy564a5692012-01-20 23:56:26 +00001602 number_channels=GetPixelChannels(image);
cristy564a5692012-01-20 23:56:26 +00001603 for (i=0; i < (ssize_t) number_channels; i++)
cristyf45fec72011-08-23 16:02:32 +00001604 {
1605 register ssize_t
1606 j;
1607
1608 black[i]=map[i];
1609 white[i]=map[GetPixelChannels(image)*MaxMap+i];
1610 if (black[i] != white[i])
1611 for (j=0; j <= (ssize_t) MaxMap; j++)
cristya19f1d72012-08-07 18:24:38 +00001612 equalize_map[GetPixelChannels(image)*j+i]=(double)
1613 ScaleMapToQuantum((double) ((MaxMap*(map[
cristyf45fec72011-08-23 16:02:32 +00001614 GetPixelChannels(image)*j+i]-black[i]))/(white[i]-black[i])));
1615 }
cristya19f1d72012-08-07 18:24:38 +00001616 histogram=(double *) RelinquishMagickMemory(histogram);
1617 map=(double *) RelinquishMagickMemory(map);
cristy3ed852e2009-09-05 21:47:34 +00001618 if (image->storage_class == PseudoClass)
1619 {
cristyf54798b2011-11-21 18:38:23 +00001620 PixelChannel
1621 channel;
1622
cristyf45fec72011-08-23 16:02:32 +00001623 register ssize_t
1624 j;
1625
cristy3ed852e2009-09-05 21:47:34 +00001626 /*
1627 Equalize colormap.
1628 */
cristyf45fec72011-08-23 16:02:32 +00001629 for (j=0; j < (ssize_t) image->colors; j++)
cristy3ed852e2009-09-05 21:47:34 +00001630 {
cristyf54798b2011-11-21 18:38:23 +00001631 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00001632 {
cristycf1296e2012-08-26 23:40:49 +00001633 channel=GetPixelChannelChannel(image,RedPixelChannel);
cristyf54798b2011-11-21 18:38:23 +00001634 if (black[channel] != white[channel])
cristyda1f9c12011-10-02 21:39:49 +00001635 image->colormap[j].red=equalize_map[GetPixelChannels(image)*
cristyf54798b2011-11-21 18:38:23 +00001636 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].red))]+
1637 channel;
cristyf45fec72011-08-23 16:02:32 +00001638 }
cristyf54798b2011-11-21 18:38:23 +00001639 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00001640 {
cristycf1296e2012-08-26 23:40:49 +00001641 channel=GetPixelChannelChannel(image,GreenPixelChannel);
cristyf54798b2011-11-21 18:38:23 +00001642 if (black[channel] != white[channel])
cristyda1f9c12011-10-02 21:39:49 +00001643 image->colormap[j].green=equalize_map[GetPixelChannels(image)*
cristyf54798b2011-11-21 18:38:23 +00001644 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].green))]+
1645 channel;
cristyf45fec72011-08-23 16:02:32 +00001646 }
cristyf54798b2011-11-21 18:38:23 +00001647 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00001648 {
cristycf1296e2012-08-26 23:40:49 +00001649 channel=GetPixelChannelChannel(image,BluePixelChannel);
cristyf54798b2011-11-21 18:38:23 +00001650 if (black[channel] != white[channel])
cristyda1f9c12011-10-02 21:39:49 +00001651 image->colormap[j].blue=equalize_map[GetPixelChannels(image)*
cristyf54798b2011-11-21 18:38:23 +00001652 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].blue))]+
1653 channel;
cristyf45fec72011-08-23 16:02:32 +00001654 }
cristyf54798b2011-11-21 18:38:23 +00001655 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00001656 {
cristycf1296e2012-08-26 23:40:49 +00001657 channel=GetPixelChannelChannel(image,AlphaPixelChannel);
cristyf54798b2011-11-21 18:38:23 +00001658 if (black[channel] != white[channel])
cristyda1f9c12011-10-02 21:39:49 +00001659 image->colormap[j].alpha=equalize_map[GetPixelChannels(image)*
cristyf54798b2011-11-21 18:38:23 +00001660 ScaleQuantumToMap(ClampToQuantum(image->colormap[j].alpha))]+
1661 channel;
cristyf45fec72011-08-23 16:02:32 +00001662 }
cristy3ed852e2009-09-05 21:47:34 +00001663 }
1664 }
1665 /*
1666 Equalize image.
1667 */
cristy3ed852e2009-09-05 21:47:34 +00001668 progress=0;
cristydb070952012-04-20 14:33:00 +00001669 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +00001670#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001671 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy4ee2b0c2012-05-15 00:30:35 +00001672 dynamic_number_threads(image,image->columns,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00001673#endif
cristybb503372010-05-27 20:51:26 +00001674 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001675 {
cristy4c08aed2011-07-01 19:47:50 +00001676 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001677 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001678
cristy8d4629b2010-08-30 17:59:46 +00001679 register ssize_t
1680 x;
1681
cristy3ed852e2009-09-05 21:47:34 +00001682 if (status == MagickFalse)
1683 continue;
1684 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001685 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001686 {
1687 status=MagickFalse;
1688 continue;
1689 }
cristybb503372010-05-27 20:51:26 +00001690 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001691 {
cristyf45fec72011-08-23 16:02:32 +00001692 register ssize_t
1693 i;
1694
cristy10a6c612012-01-29 21:41:05 +00001695 if (GetPixelMask(image,q) != 0)
1696 {
1697 q+=GetPixelChannels(image);
1698 continue;
1699 }
cristyf45fec72011-08-23 16:02:32 +00001700 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
1701 {
cristyabace412011-12-11 15:56:53 +00001702 PixelChannel
1703 channel;
1704
cristyf45fec72011-08-23 16:02:32 +00001705 PixelTrait
1706 traits;
1707
cristycf1296e2012-08-26 23:40:49 +00001708 channel=GetPixelChannelChannel(image,i);
1709 traits=GetPixelChannelTraits(image,channel);
cristyd09f8802012-02-04 16:44:10 +00001710 if (((traits & UpdatePixelTrait) == 0) || (black[i] == white[i]))
1711 continue;
1712 q[i]=ClampToQuantum(equalize_map[GetPixelChannels(image)*
1713 ScaleQuantumToMap(q[i])+i]);
cristyf45fec72011-08-23 16:02:32 +00001714 }
cristyed231572011-07-14 02:18:59 +00001715 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001716 }
1717 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1718 status=MagickFalse;
1719 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1720 {
1721 MagickBooleanType
1722 proceed;
1723
cristyb5d5f722009-11-04 03:03:49 +00001724#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00001725 #pragma omp critical (MagickCore_EqualizeImage)
cristy3ed852e2009-09-05 21:47:34 +00001726#endif
1727 proceed=SetImageProgress(image,EqualizeImageTag,progress++,image->rows);
1728 if (proceed == MagickFalse)
1729 status=MagickFalse;
1730 }
1731 }
1732 image_view=DestroyCacheView(image_view);
cristya19f1d72012-08-07 18:24:38 +00001733 equalize_map=(double *) RelinquishMagickMemory(equalize_map);
cristy3ed852e2009-09-05 21:47:34 +00001734 return(status);
1735}
1736
1737/*
1738%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1739% %
1740% %
1741% %
1742% G a m m a I m a g e %
1743% %
1744% %
1745% %
1746%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1747%
1748% GammaImage() gamma-corrects a particular image channel. The same
1749% image viewed on different devices will have perceptual differences in the
1750% way the image's intensities are represented on the screen. Specify
1751% individual gamma levels for the red, green, and blue channels, or adjust
1752% all three with the gamma parameter. Values typically range from 0.8 to 2.3.
1753%
1754% You can also reduce the influence of a particular channel with a gamma
1755% value of 0.
1756%
1757% The format of the GammaImage method is:
1758%
cristyb3e7c6c2011-07-24 01:43:55 +00001759% MagickBooleanType GammaImage(Image *image,const double gamma,
1760% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001761%
1762% A description of each parameter follows:
1763%
1764% o image: the image.
1765%
cristya6360142011-03-23 23:08:04 +00001766% o level: the image gamma as a string (e.g. 1.6,1.2,1.0).
1767%
cristy3ed852e2009-09-05 21:47:34 +00001768% o gamma: the image gamma.
1769%
1770*/
cristyb3e7c6c2011-07-24 01:43:55 +00001771MagickExport MagickBooleanType GammaImage(Image *image,const double gamma,
1772 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001773{
1774#define GammaCorrectImageTag "GammaCorrect/Image"
1775
cristyc4c8d132010-01-07 01:58:38 +00001776 CacheView
1777 *image_view;
1778
cristy3ed852e2009-09-05 21:47:34 +00001779 MagickBooleanType
1780 status;
1781
cristybb503372010-05-27 20:51:26 +00001782 MagickOffsetType
1783 progress;
1784
cristy19593872012-01-22 02:00:33 +00001785 Quantum
1786 *gamma_map;
1787
cristybb503372010-05-27 20:51:26 +00001788 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00001789 i;
1790
cristybb503372010-05-27 20:51:26 +00001791 ssize_t
1792 y;
1793
cristy3ed852e2009-09-05 21:47:34 +00001794 /*
1795 Allocate and initialize gamma maps.
1796 */
1797 assert(image != (Image *) NULL);
1798 assert(image->signature == MagickSignature);
1799 if (image->debug != MagickFalse)
1800 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1801 if (gamma == 1.0)
1802 return(MagickTrue);
cristy19593872012-01-22 02:00:33 +00001803 gamma_map=(Quantum *) AcquireQuantumMemory(MaxMap+1UL,sizeof(*gamma_map));
1804 if (gamma_map == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001805 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
1806 image->filename);
1807 (void) ResetMagickMemory(gamma_map,0,(MaxMap+1)*sizeof(*gamma_map));
1808 if (gamma != 0.0)
cristybb503372010-05-27 20:51:26 +00001809 for (i=0; i <= (ssize_t) MaxMap; i++)
cristya19f1d72012-08-07 18:24:38 +00001810 gamma_map[i]=ScaleMapToQuantum((double) (MaxMap*pow((double) i/
cristy19593872012-01-22 02:00:33 +00001811 MaxMap,1.0/gamma)));
cristy3ed852e2009-09-05 21:47:34 +00001812 if (image->storage_class == PseudoClass)
nicolase0aa3c32012-08-08 14:57:02 +00001813 for (i=0; i < (ssize_t) image->colors; i++)
nicolasfd74ae32012-08-08 16:00:11 +00001814 {
1815 /*
cristyaeded782012-09-11 23:39:36 +00001816 Gamma-correct colormap.
nicolasfd74ae32012-08-08 16:00:11 +00001817 */
1818 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyaeded782012-09-11 23:39:36 +00001819 image->colormap[i].red=(double) gamma_map[
nicolasfd74ae32012-08-08 16:00:11 +00001820 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].red))];
1821 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
1822 image->colormap[i].green=(double) gamma_map[
1823 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].green))];
1824 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
1825 image->colormap[i].blue=(double) gamma_map[
1826 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].blue))];
1827 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
1828 image->colormap[i].alpha=(double) gamma_map[
1829 ScaleQuantumToMap(ClampToQuantum(image->colormap[i].alpha))];
1830 }
cristy3ed852e2009-09-05 21:47:34 +00001831 /*
1832 Gamma-correct image.
1833 */
1834 status=MagickTrue;
1835 progress=0;
cristydb070952012-04-20 14:33:00 +00001836 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +00001837#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00001838 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy4ee2b0c2012-05-15 00:30:35 +00001839 dynamic_number_threads(image,image->columns,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00001840#endif
cristybb503372010-05-27 20:51:26 +00001841 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00001842 {
cristy4c08aed2011-07-01 19:47:50 +00001843 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00001844 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00001845
cristy8d4629b2010-08-30 17:59:46 +00001846 register ssize_t
1847 x;
1848
cristy3ed852e2009-09-05 21:47:34 +00001849 if (status == MagickFalse)
1850 continue;
1851 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00001852 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00001853 {
1854 status=MagickFalse;
1855 continue;
1856 }
cristybb503372010-05-27 20:51:26 +00001857 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00001858 {
cristyd476a8e2011-07-23 16:13:22 +00001859 register ssize_t
1860 i;
1861
cristy10a6c612012-01-29 21:41:05 +00001862 if (GetPixelMask(image,q) != 0)
1863 {
1864 q+=GetPixelChannels(image);
1865 continue;
1866 }
cristya30d9ba2011-07-23 21:00:48 +00001867 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristyd476a8e2011-07-23 16:13:22 +00001868 {
cristyabace412011-12-11 15:56:53 +00001869 PixelChannel
1870 channel;
1871
cristyd476a8e2011-07-23 16:13:22 +00001872 PixelTrait
1873 traits;
1874
cristycf1296e2012-08-26 23:40:49 +00001875 channel=GetPixelChannelChannel(image,i);
1876 traits=GetPixelChannelTraits(image,channel);
cristyd09f8802012-02-04 16:44:10 +00001877 if ((traits & UpdatePixelTrait) == 0)
1878 continue;
1879 q[i]=gamma_map[ScaleQuantumToMap(q[i])];
cristyd476a8e2011-07-23 16:13:22 +00001880 }
cristya30d9ba2011-07-23 21:00:48 +00001881 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00001882 }
cristy3ed852e2009-09-05 21:47:34 +00001883 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
1884 status=MagickFalse;
1885 if (image->progress_monitor != (MagickProgressMonitor) NULL)
1886 {
1887 MagickBooleanType
1888 proceed;
1889
cristyb5d5f722009-11-04 03:03:49 +00001890#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00001891 #pragma omp critical (MagickCore_GammaImage)
cristy3ed852e2009-09-05 21:47:34 +00001892#endif
1893 proceed=SetImageProgress(image,GammaCorrectImageTag,progress++,
1894 image->rows);
1895 if (proceed == MagickFalse)
1896 status=MagickFalse;
1897 }
1898 }
1899 image_view=DestroyCacheView(image_view);
cristy19593872012-01-22 02:00:33 +00001900 gamma_map=(Quantum *) RelinquishMagickMemory(gamma_map);
cristy3ed852e2009-09-05 21:47:34 +00001901 if (image->gamma != 0.0)
1902 image->gamma*=gamma;
1903 return(status);
1904}
1905
1906/*
1907%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1908% %
1909% %
1910% %
1911% H a l d C l u t I m a g e %
1912% %
1913% %
1914% %
1915%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1916%
1917% HaldClutImage() applies a Hald color lookup table to the image. A Hald
1918% color lookup table is a 3-dimensional color cube mapped to 2 dimensions.
1919% Create it with the HALD coder. You can apply any color transformation to
1920% the Hald image and then use this method to apply the transform to the
1921% image.
1922%
1923% The format of the HaldClutImage method is:
1924%
cristy7c0a0a42011-08-23 17:57:25 +00001925% MagickBooleanType HaldClutImage(Image *image,Image *hald_image,
1926% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001927%
1928% A description of each parameter follows:
1929%
1930% o image: the image, which is replaced by indexed CLUT values
1931%
1932% o hald_image: the color lookup table image for replacement color values.
1933%
cristy7c0a0a42011-08-23 17:57:25 +00001934% o exception: return any errors or warnings in this structure.
1935%
cristy3ed852e2009-09-05 21:47:34 +00001936*/
1937
1938static inline size_t MagickMin(const size_t x,const size_t y)
1939{
1940 if (x < y)
1941 return(x);
1942 return(y);
1943}
1944
1945MagickExport MagickBooleanType HaldClutImage(Image *image,
cristy7c0a0a42011-08-23 17:57:25 +00001946 const Image *hald_image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00001947{
cristy3ed852e2009-09-05 21:47:34 +00001948#define HaldClutImageTag "Clut/Image"
1949
1950 typedef struct _HaldInfo
1951 {
cristya19f1d72012-08-07 18:24:38 +00001952 double
cristy3ed852e2009-09-05 21:47:34 +00001953 x,
1954 y,
1955 z;
1956 } HaldInfo;
1957
cristyfa112112010-01-04 17:48:07 +00001958 CacheView
cristyd551fbc2011-03-31 18:07:46 +00001959 *hald_view,
cristyfa112112010-01-04 17:48:07 +00001960 *image_view;
1961
cristy3ed852e2009-09-05 21:47:34 +00001962 double
1963 width;
1964
cristy3ed852e2009-09-05 21:47:34 +00001965 MagickBooleanType
1966 status;
1967
cristybb503372010-05-27 20:51:26 +00001968 MagickOffsetType
1969 progress;
1970
cristy4c08aed2011-07-01 19:47:50 +00001971 PixelInfo
cristy3ed852e2009-09-05 21:47:34 +00001972 zero;
1973
cristy3ed852e2009-09-05 21:47:34 +00001974 size_t
1975 cube_size,
1976 length,
1977 level;
1978
cristybb503372010-05-27 20:51:26 +00001979 ssize_t
1980 y;
1981
cristy3ed852e2009-09-05 21:47:34 +00001982 assert(image != (Image *) NULL);
1983 assert(image->signature == MagickSignature);
1984 if (image->debug != MagickFalse)
1985 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
1986 assert(hald_image != (Image *) NULL);
1987 assert(hald_image->signature == MagickSignature);
cristy574cc262011-08-05 01:23:58 +00001988 if (SetImageStorageClass(image,DirectClass,exception) == MagickFalse)
cristy3ed852e2009-09-05 21:47:34 +00001989 return(MagickFalse);
cristy0898eba2012-04-09 16:38:29 +00001990 if (IsGrayColorspace(image->colorspace) != MagickFalse)
cristyb09db112012-07-11 12:04:31 +00001991 (void) TransformImageColorspace(image,RGBColorspace,exception);
cristy8a46d822012-08-28 23:32:39 +00001992 if (image->alpha_trait != BlendPixelTrait)
cristy63240882011-08-05 19:05:27 +00001993 (void) SetImageAlphaChannel(image,OpaqueAlphaChannel,exception);
cristy3ed852e2009-09-05 21:47:34 +00001994 /*
1995 Hald clut image.
1996 */
1997 status=MagickTrue;
1998 progress=0;
1999 length=MagickMin(hald_image->columns,hald_image->rows);
2000 for (level=2; (level*level*level) < length; level++) ;
2001 level*=level;
2002 cube_size=level*level;
2003 width=(double) hald_image->columns;
cristy4c08aed2011-07-01 19:47:50 +00002004 GetPixelInfo(hald_image,&zero);
cristydb070952012-04-20 14:33:00 +00002005 hald_view=AcquireVirtualCacheView(hald_image,exception);
2006 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +00002007#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00002008 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy4ee2b0c2012-05-15 00:30:35 +00002009 dynamic_number_threads(image,image->columns,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00002010#endif
cristybb503372010-05-27 20:51:26 +00002011 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002012 {
cristy4c08aed2011-07-01 19:47:50 +00002013 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002014 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002015
cristy8d4629b2010-08-30 17:59:46 +00002016 register ssize_t
2017 x;
2018
cristy3ed852e2009-09-05 21:47:34 +00002019 if (status == MagickFalse)
2020 continue;
2021 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00002022 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002023 {
2024 status=MagickFalse;
2025 continue;
2026 }
cristybb503372010-05-27 20:51:26 +00002027 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002028 {
cristy7c0a0a42011-08-23 17:57:25 +00002029 double
2030 offset;
2031
2032 HaldInfo
2033 point;
2034
2035 PixelInfo
2036 pixel,
2037 pixel1,
2038 pixel2,
2039 pixel3,
2040 pixel4;
2041
cristy4c08aed2011-07-01 19:47:50 +00002042 point.x=QuantumScale*(level-1.0)*GetPixelRed(image,q);
2043 point.y=QuantumScale*(level-1.0)*GetPixelGreen(image,q);
2044 point.z=QuantumScale*(level-1.0)*GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00002045 offset=point.x+level*floor(point.y)+cube_size*floor(point.z);
2046 point.x-=floor(point.x);
2047 point.y-=floor(point.y);
2048 point.z-=floor(point.z);
cristy7c0a0a42011-08-23 17:57:25 +00002049 pixel1=zero;
2050 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2051 fmod(offset,width),floor(offset/width),&pixel1,exception);
2052 pixel2=zero;
2053 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2054 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2055 pixel3=zero;
2056 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2057 point.y,&pixel3);
cristy3ed852e2009-09-05 21:47:34 +00002058 offset+=cube_size;
cristy7c0a0a42011-08-23 17:57:25 +00002059 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2060 fmod(offset,width),floor(offset/width),&pixel1,exception);
2061 (void) InterpolatePixelInfo(image,hald_view,image->interpolate,
2062 fmod(offset+level,width),floor((offset+level)/width),&pixel2,exception);
2063 pixel4=zero;
2064 CompositePixelInfoAreaBlend(&pixel1,pixel1.alpha,&pixel2,pixel2.alpha,
2065 point.y,&pixel4);
2066 pixel=zero;
2067 CompositePixelInfoAreaBlend(&pixel3,pixel3.alpha,&pixel4,pixel4.alpha,
2068 point.z,&pixel);
cristyed231572011-07-14 02:18:59 +00002069 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00002070 SetPixelRed(image,ClampToQuantum(pixel.red),q);
cristyed231572011-07-14 02:18:59 +00002071 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00002072 SetPixelGreen(image,ClampToQuantum(pixel.green),q);
cristyed231572011-07-14 02:18:59 +00002073 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyf45fec72011-08-23 16:02:32 +00002074 SetPixelBlue(image,ClampToQuantum(pixel.blue),q);
cristyed231572011-07-14 02:18:59 +00002075 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy3ed852e2009-09-05 21:47:34 +00002076 (image->colorspace == CMYKColorspace))
cristyf45fec72011-08-23 16:02:32 +00002077 SetPixelBlack(image,ClampToQuantum(pixel.black),q);
2078 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy8a46d822012-08-28 23:32:39 +00002079 (image->alpha_trait == BlendPixelTrait))
cristyf45fec72011-08-23 16:02:32 +00002080 SetPixelAlpha(image,ClampToQuantum(pixel.alpha),q);
cristyed231572011-07-14 02:18:59 +00002081 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002082 }
2083 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2084 status=MagickFalse;
2085 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2086 {
2087 MagickBooleanType
2088 proceed;
2089
cristyb5d5f722009-11-04 03:03:49 +00002090#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00002091 #pragma omp critical (MagickCore_HaldClutImage)
cristy3ed852e2009-09-05 21:47:34 +00002092#endif
2093 proceed=SetImageProgress(image,HaldClutImageTag,progress++,image->rows);
2094 if (proceed == MagickFalse)
2095 status=MagickFalse;
2096 }
2097 }
cristyd551fbc2011-03-31 18:07:46 +00002098 hald_view=DestroyCacheView(hald_view);
cristy3ed852e2009-09-05 21:47:34 +00002099 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002100 return(status);
2101}
2102
2103/*
2104%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2105% %
2106% %
2107% %
2108% L e v e l I m a g e %
2109% %
2110% %
2111% %
2112%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2113%
2114% LevelImage() adjusts the levels of a particular image channel by
2115% scaling the colors falling between specified white and black points to
2116% the full available quantum range.
2117%
2118% The parameters provided represent the black, and white points. The black
2119% point specifies the darkest color in the image. Colors darker than the
2120% black point are set to zero. White point specifies the lightest color in
2121% the image. Colors brighter than the white point are set to the maximum
2122% quantum value.
2123%
2124% If a '!' flag is given, map black and white colors to the given levels
2125% rather than mapping those levels to black and white. See
cristy50fbc382011-07-07 02:19:17 +00002126% LevelizeImage() below.
cristy3ed852e2009-09-05 21:47:34 +00002127%
2128% Gamma specifies a gamma correction to apply to the image.
2129%
2130% The format of the LevelImage method is:
2131%
cristy01e9afd2011-08-10 17:38:41 +00002132% MagickBooleanType LevelImage(Image *image,const double black_point,
2133% const double white_point,const double gamma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002134%
2135% A description of each parameter follows:
2136%
2137% o image: the image.
2138%
cristy01e9afd2011-08-10 17:38:41 +00002139% o black_point: The level to map zero (black) to.
2140%
2141% o white_point: The level to map QuantumRange (white) to.
2142%
2143% o exception: return any errors or warnings in this structure.
cristy3ed852e2009-09-05 21:47:34 +00002144%
2145*/
cristy780e9ef2011-12-18 23:33:50 +00002146
cristya19f1d72012-08-07 18:24:38 +00002147static inline double LevelPixel(const double black_point,
2148 const double white_point,const double gamma,const double pixel)
cristy780e9ef2011-12-18 23:33:50 +00002149{
2150 double
2151 level_pixel,
2152 scale;
2153
cristy780e9ef2011-12-18 23:33:50 +00002154 scale=(white_point != black_point) ? 1.0/(white_point-black_point) : 1.0;
cristya19f1d72012-08-07 18:24:38 +00002155 level_pixel=(double) QuantumRange*pow(scale*((double) pixel-
cristy780e9ef2011-12-18 23:33:50 +00002156 black_point),1.0/gamma);
2157 return(level_pixel);
2158}
2159
cristy7c0a0a42011-08-23 17:57:25 +00002160MagickExport MagickBooleanType LevelImage(Image *image,const double black_point,
2161 const double white_point,const double gamma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002162{
2163#define LevelImageTag "Level/Image"
cristy3ed852e2009-09-05 21:47:34 +00002164
cristyc4c8d132010-01-07 01:58:38 +00002165 CacheView
2166 *image_view;
2167
cristy3ed852e2009-09-05 21:47:34 +00002168 MagickBooleanType
2169 status;
2170
cristybb503372010-05-27 20:51:26 +00002171 MagickOffsetType
2172 progress;
2173
cristy8d4629b2010-08-30 17:59:46 +00002174 register ssize_t
2175 i;
2176
cristybb503372010-05-27 20:51:26 +00002177 ssize_t
2178 y;
2179
cristy3ed852e2009-09-05 21:47:34 +00002180 /*
2181 Allocate and initialize levels map.
2182 */
2183 assert(image != (Image *) NULL);
2184 assert(image->signature == MagickSignature);
2185 if (image->debug != MagickFalse)
2186 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2187 if (image->storage_class == PseudoClass)
cristybb503372010-05-27 20:51:26 +00002188 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002189 {
2190 /*
2191 Level colormap.
2192 */
cristyed231572011-07-14 02:18:59 +00002193 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristy780e9ef2011-12-18 23:33:50 +00002194 image->colormap[i].red=(double) ClampToQuantum(LevelPixel(black_point,
2195 white_point,gamma,image->colormap[i].red));
cristyed231572011-07-14 02:18:59 +00002196 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristy780e9ef2011-12-18 23:33:50 +00002197 image->colormap[i].green=(double) ClampToQuantum(LevelPixel(black_point,
2198 white_point,gamma,image->colormap[i].green));
cristyed231572011-07-14 02:18:59 +00002199 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristy780e9ef2011-12-18 23:33:50 +00002200 image->colormap[i].blue=(double) ClampToQuantum(LevelPixel(black_point,
2201 white_point,gamma,image->colormap[i].blue));
cristyed231572011-07-14 02:18:59 +00002202 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristy780e9ef2011-12-18 23:33:50 +00002203 image->colormap[i].alpha=(double) ClampToQuantum(LevelPixel(black_point,
2204 white_point,gamma,image->colormap[i].alpha));
cristy3ed852e2009-09-05 21:47:34 +00002205 }
2206 /*
2207 Level image.
2208 */
2209 status=MagickTrue;
2210 progress=0;
cristydb070952012-04-20 14:33:00 +00002211 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +00002212#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00002213 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy4ee2b0c2012-05-15 00:30:35 +00002214 dynamic_number_threads(image,image->columns,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00002215#endif
cristybb503372010-05-27 20:51:26 +00002216 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002217 {
cristy4c08aed2011-07-01 19:47:50 +00002218 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002219 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002220
cristy8d4629b2010-08-30 17:59:46 +00002221 register ssize_t
2222 x;
2223
cristy3ed852e2009-09-05 21:47:34 +00002224 if (status == MagickFalse)
2225 continue;
2226 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00002227 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002228 {
2229 status=MagickFalse;
2230 continue;
2231 }
cristybb503372010-05-27 20:51:26 +00002232 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002233 {
cristy7c0a0a42011-08-23 17:57:25 +00002234 register ssize_t
2235 i;
2236
cristy10a6c612012-01-29 21:41:05 +00002237 if (GetPixelMask(image,q) != 0)
2238 {
2239 q+=GetPixelChannels(image);
2240 continue;
2241 }
cristy7c0a0a42011-08-23 17:57:25 +00002242 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2243 {
cristyabace412011-12-11 15:56:53 +00002244 PixelChannel
2245 channel;
2246
cristy7c0a0a42011-08-23 17:57:25 +00002247 PixelTrait
2248 traits;
2249
cristycf1296e2012-08-26 23:40:49 +00002250 channel=GetPixelChannelChannel(image,i);
2251 traits=GetPixelChannelTraits(image,channel);
cristyd09f8802012-02-04 16:44:10 +00002252 if ((traits & UpdatePixelTrait) == 0)
cristy7c0a0a42011-08-23 17:57:25 +00002253 continue;
cristy780e9ef2011-12-18 23:33:50 +00002254 q[i]=ClampToQuantum(LevelPixel(black_point,white_point,gamma,
cristya19f1d72012-08-07 18:24:38 +00002255 (double) q[i]));
cristy7c0a0a42011-08-23 17:57:25 +00002256 }
cristyed231572011-07-14 02:18:59 +00002257 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002258 }
2259 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2260 status=MagickFalse;
2261 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2262 {
2263 MagickBooleanType
2264 proceed;
2265
cristyb5d5f722009-11-04 03:03:49 +00002266#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00002267 #pragma omp critical (MagickCore_LevelImage)
cristy3ed852e2009-09-05 21:47:34 +00002268#endif
2269 proceed=SetImageProgress(image,LevelImageTag,progress++,image->rows);
2270 if (proceed == MagickFalse)
2271 status=MagickFalse;
2272 }
2273 }
2274 image_view=DestroyCacheView(image_view);
cristyd1e1c222012-08-13 01:13:28 +00002275 if (status != MagickFalse)
2276 (void) ClampImage(image,exception);
cristy3ed852e2009-09-05 21:47:34 +00002277 return(status);
2278}
2279
2280/*
2281%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2282% %
2283% %
2284% %
cristy33bd5152011-08-24 01:42:24 +00002285% L e v e l i z e I m a g e %
cristy3ed852e2009-09-05 21:47:34 +00002286% %
2287% %
2288% %
2289%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2290%
cristy50fbc382011-07-07 02:19:17 +00002291% LevelizeImage() applies the reversed LevelImage() operation to just
cristy3ed852e2009-09-05 21:47:34 +00002292% the specific channels specified. It compresses the full range of color
2293% values, so that they lie between the given black and white points. Gamma is
2294% applied before the values are mapped.
2295%
cristy50fbc382011-07-07 02:19:17 +00002296% LevelizeImage() can be called with by using a +level command line
cristy3ed852e2009-09-05 21:47:34 +00002297% API option, or using a '!' on a -level or LevelImage() geometry string.
2298%
anthony31f1bf72012-01-30 12:37:22 +00002299% It can be used to de-contrast a greyscale image to the exact levels
2300% specified. Or by using specific levels for each channel of an image you
2301% can convert a gray-scale image to any linear color gradient, according to
2302% those levels.
cristy3ed852e2009-09-05 21:47:34 +00002303%
cristy50fbc382011-07-07 02:19:17 +00002304% The format of the LevelizeImage method is:
cristy3ed852e2009-09-05 21:47:34 +00002305%
cristy50fbc382011-07-07 02:19:17 +00002306% MagickBooleanType LevelizeImage(Image *image,const double black_point,
cristy7c0a0a42011-08-23 17:57:25 +00002307% const double white_point,const double gamma,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002308%
2309% A description of each parameter follows:
2310%
2311% o image: the image.
2312%
cristy3ed852e2009-09-05 21:47:34 +00002313% o black_point: The level to map zero (black) to.
2314%
cristy01e9afd2011-08-10 17:38:41 +00002315% o white_point: The level to map QuantumRange (white) to.
cristy3ed852e2009-09-05 21:47:34 +00002316%
2317% o gamma: adjust gamma by this factor before mapping values.
2318%
cristy7c0a0a42011-08-23 17:57:25 +00002319% o exception: return any errors or warnings in this structure.
2320%
cristy3ed852e2009-09-05 21:47:34 +00002321*/
cristyd1a2c0f2011-02-09 14:14:50 +00002322MagickExport MagickBooleanType LevelizeImage(Image *image,
cristy7c0a0a42011-08-23 17:57:25 +00002323 const double black_point,const double white_point,const double gamma,
2324 ExceptionInfo *exception)
cristyd1a2c0f2011-02-09 14:14:50 +00002325{
cristy3ed852e2009-09-05 21:47:34 +00002326#define LevelizeImageTag "Levelize/Image"
cristy8cd03c32012-07-07 18:57:59 +00002327#define LevelizeValue(x) (ClampToQuantum((pow((double) (QuantumScale*(x)), \
2328 1.0/gamma))*(white_point-black_point)+black_point))
cristy3ed852e2009-09-05 21:47:34 +00002329
cristyc4c8d132010-01-07 01:58:38 +00002330 CacheView
2331 *image_view;
2332
cristy3ed852e2009-09-05 21:47:34 +00002333 MagickBooleanType
2334 status;
2335
cristybb503372010-05-27 20:51:26 +00002336 MagickOffsetType
2337 progress;
2338
2339 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002340 i;
2341
cristybb503372010-05-27 20:51:26 +00002342 ssize_t
2343 y;
2344
cristy3ed852e2009-09-05 21:47:34 +00002345 /*
2346 Allocate and initialize levels map.
2347 */
2348 assert(image != (Image *) NULL);
2349 assert(image->signature == MagickSignature);
2350 if (image->debug != MagickFalse)
2351 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
cristy25ee24d2012-05-18 12:25:59 +00002352 if (IsGrayColorspace(image->colorspace) != MagickFalse)
cristyb09db112012-07-11 12:04:31 +00002353 (void) SetImageColorspace(image,RGBColorspace,exception);
cristy3ed852e2009-09-05 21:47:34 +00002354 if (image->storage_class == PseudoClass)
cristybb503372010-05-27 20:51:26 +00002355 for (i=0; i < (ssize_t) image->colors; i++)
cristy3ed852e2009-09-05 21:47:34 +00002356 {
2357 /*
2358 Level colormap.
2359 */
cristyed231572011-07-14 02:18:59 +00002360 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00002361 image->colormap[i].red=(double) LevelizeValue(
2362 image->colormap[i].red);
cristyed231572011-07-14 02:18:59 +00002363 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00002364 image->colormap[i].green=(double) LevelizeValue(
2365 image->colormap[i].green);
cristyed231572011-07-14 02:18:59 +00002366 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00002367 image->colormap[i].blue=(double) LevelizeValue(
2368 image->colormap[i].blue);
cristyed231572011-07-14 02:18:59 +00002369 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristyda1f9c12011-10-02 21:39:49 +00002370 image->colormap[i].alpha=(double) LevelizeValue(
2371 image->colormap[i].alpha);
cristy3ed852e2009-09-05 21:47:34 +00002372 }
2373 /*
2374 Level image.
2375 */
2376 status=MagickTrue;
2377 progress=0;
cristydb070952012-04-20 14:33:00 +00002378 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +00002379#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00002380 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy4ee2b0c2012-05-15 00:30:35 +00002381 dynamic_number_threads(image,image->columns,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00002382#endif
cristybb503372010-05-27 20:51:26 +00002383 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002384 {
cristy4c08aed2011-07-01 19:47:50 +00002385 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002386 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002387
cristy8d4629b2010-08-30 17:59:46 +00002388 register ssize_t
2389 x;
2390
cristy3ed852e2009-09-05 21:47:34 +00002391 if (status == MagickFalse)
2392 continue;
2393 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00002394 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002395 {
2396 status=MagickFalse;
2397 continue;
2398 }
cristybb503372010-05-27 20:51:26 +00002399 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002400 {
cristy7c0a0a42011-08-23 17:57:25 +00002401 register ssize_t
2402 i;
2403
cristy10a6c612012-01-29 21:41:05 +00002404 if (GetPixelMask(image,q) != 0)
2405 {
2406 q+=GetPixelChannels(image);
2407 continue;
2408 }
cristy7c0a0a42011-08-23 17:57:25 +00002409 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
2410 {
cristyabace412011-12-11 15:56:53 +00002411 PixelChannel
2412 channel;
2413
cristy7c0a0a42011-08-23 17:57:25 +00002414 PixelTrait
2415 traits;
2416
cristycf1296e2012-08-26 23:40:49 +00002417 channel=GetPixelChannelChannel(image,i);
2418 traits=GetPixelChannelTraits(image,channel);
cristyd09f8802012-02-04 16:44:10 +00002419 if ((traits & UpdatePixelTrait) == 0)
cristyb8b0d162011-12-18 23:41:28 +00002420 continue;
cristy780e9ef2011-12-18 23:33:50 +00002421 q[i]=LevelizeValue(q[i]);
cristy7c0a0a42011-08-23 17:57:25 +00002422 }
cristyed231572011-07-14 02:18:59 +00002423 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002424 }
2425 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2426 status=MagickFalse;
2427 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2428 {
2429 MagickBooleanType
2430 proceed;
2431
cristyb5d5f722009-11-04 03:03:49 +00002432#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00002433 #pragma omp critical (MagickCore_LevelizeImage)
cristy3ed852e2009-09-05 21:47:34 +00002434#endif
2435 proceed=SetImageProgress(image,LevelizeImageTag,progress++,image->rows);
2436 if (proceed == MagickFalse)
2437 status=MagickFalse;
2438 }
2439 }
cristy8d4629b2010-08-30 17:59:46 +00002440 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002441 return(status);
2442}
2443
2444/*
2445%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2446% %
2447% %
2448% %
2449% L e v e l I m a g e C o l o r s %
2450% %
2451% %
2452% %
2453%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2454%
cristy490408a2011-07-07 14:42:05 +00002455% LevelImageColors() maps the given color to "black" and "white" values,
cristyee0f8d72009-09-19 00:58:29 +00002456% linearly spreading out the colors, and level values on a channel by channel
2457% bases, as per LevelImage(). The given colors allows you to specify
glennrp1e7f7bc2011-03-02 19:25:28 +00002458% different level ranges for each of the color channels separately.
cristy3ed852e2009-09-05 21:47:34 +00002459%
2460% If the boolean 'invert' is set true the image values will modifyed in the
2461% reverse direction. That is any existing "black" and "white" colors in the
2462% image will become the color values given, with all other values compressed
2463% appropriatally. This effectivally maps a greyscale gradient into the given
2464% color gradient.
2465%
cristy490408a2011-07-07 14:42:05 +00002466% The format of the LevelImageColors method is:
cristy3ed852e2009-09-05 21:47:34 +00002467%
cristy490408a2011-07-07 14:42:05 +00002468% MagickBooleanType LevelImageColors(Image *image,
2469% const PixelInfo *black_color,const PixelInfo *white_color,
cristy7c0a0a42011-08-23 17:57:25 +00002470% const MagickBooleanType invert,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002471%
2472% A description of each parameter follows:
2473%
2474% o image: the image.
2475%
cristy3ed852e2009-09-05 21:47:34 +00002476% o black_color: The color to map black to/from
2477%
2478% o white_point: The color to map white to/from
2479%
2480% o invert: if true map the colors (levelize), rather than from (level)
2481%
cristy7c0a0a42011-08-23 17:57:25 +00002482% o exception: return any errors or warnings in this structure.
2483%
cristy3ed852e2009-09-05 21:47:34 +00002484*/
cristy490408a2011-07-07 14:42:05 +00002485MagickExport MagickBooleanType LevelImageColors(Image *image,
cristy4c08aed2011-07-01 19:47:50 +00002486 const PixelInfo *black_color,const PixelInfo *white_color,
cristy7c0a0a42011-08-23 17:57:25 +00002487 const MagickBooleanType invert,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002488{
cristybd5a96c2011-08-21 00:04:26 +00002489 ChannelType
2490 channel_mask;
2491
cristy3ed852e2009-09-05 21:47:34 +00002492 MagickStatusType
2493 status;
2494
2495 /*
2496 Allocate and initialize levels map.
2497 */
2498 assert(image != (Image *) NULL);
2499 assert(image->signature == MagickSignature);
2500 if (image->debug != MagickFalse)
2501 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2502 status=MagickFalse;
2503 if (invert == MagickFalse)
2504 {
cristyed231572011-07-14 02:18:59 +00002505 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002506 {
cristycf1296e2012-08-26 23:40:49 +00002507 channel_mask=SetImageChannelMask(image,RedChannel);
cristy01e9afd2011-08-10 17:38:41 +00002508 status|=LevelImage(image,black_color->red,white_color->red,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002509 exception);
cristycf1296e2012-08-26 23:40:49 +00002510 (void) SetImageChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002511 }
cristyed231572011-07-14 02:18:59 +00002512 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002513 {
cristycf1296e2012-08-26 23:40:49 +00002514 channel_mask=SetImageChannelMask(image,GreenChannel);
cristy01e9afd2011-08-10 17:38:41 +00002515 status|=LevelImage(image,black_color->green,white_color->green,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002516 exception);
cristycf1296e2012-08-26 23:40:49 +00002517 (void) SetImageChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002518 }
cristyed231572011-07-14 02:18:59 +00002519 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002520 {
cristycf1296e2012-08-26 23:40:49 +00002521 channel_mask=SetImageChannelMask(image,BlueChannel);
cristy01e9afd2011-08-10 17:38:41 +00002522 status|=LevelImage(image,black_color->blue,white_color->blue,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002523 exception);
cristycf1296e2012-08-26 23:40:49 +00002524 (void) SetImageChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002525 }
cristyed231572011-07-14 02:18:59 +00002526 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002527 (image->colorspace == CMYKColorspace))
cristyf89cb1d2011-07-07 01:24:37 +00002528 {
cristycf1296e2012-08-26 23:40:49 +00002529 channel_mask=SetImageChannelMask(image,BlackChannel);
cristy01e9afd2011-08-10 17:38:41 +00002530 status|=LevelImage(image,black_color->black,white_color->black,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002531 exception);
cristycf1296e2012-08-26 23:40:49 +00002532 (void) SetImageChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002533 }
cristyed231572011-07-14 02:18:59 +00002534 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy8a46d822012-08-28 23:32:39 +00002535 (image->alpha_trait == BlendPixelTrait))
cristyf89cb1d2011-07-07 01:24:37 +00002536 {
cristycf1296e2012-08-26 23:40:49 +00002537 channel_mask=SetImageChannelMask(image,AlphaChannel);
cristy01e9afd2011-08-10 17:38:41 +00002538 status|=LevelImage(image,black_color->alpha,white_color->alpha,1.0,
cristy7c0a0a42011-08-23 17:57:25 +00002539 exception);
cristycf1296e2012-08-26 23:40:49 +00002540 (void) SetImageChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002541 }
cristy3ed852e2009-09-05 21:47:34 +00002542 }
2543 else
2544 {
cristyed231572011-07-14 02:18:59 +00002545 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002546 {
cristycf1296e2012-08-26 23:40:49 +00002547 channel_mask=SetImageChannelMask(image,RedChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002548 status|=LevelizeImage(image,black_color->red,white_color->red,1.0,
2549 exception);
cristycf1296e2012-08-26 23:40:49 +00002550 (void) SetImageChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002551 }
cristyed231572011-07-14 02:18:59 +00002552 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002553 {
cristycf1296e2012-08-26 23:40:49 +00002554 channel_mask=SetImageChannelMask(image,GreenChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002555 status|=LevelizeImage(image,black_color->green,white_color->green,1.0,
2556 exception);
cristycf1296e2012-08-26 23:40:49 +00002557 (void) SetImageChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002558 }
cristyed231572011-07-14 02:18:59 +00002559 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyf89cb1d2011-07-07 01:24:37 +00002560 {
cristycf1296e2012-08-26 23:40:49 +00002561 channel_mask=SetImageChannelMask(image,BlueChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002562 status|=LevelizeImage(image,black_color->blue,white_color->blue,1.0,
2563 exception);
cristycf1296e2012-08-26 23:40:49 +00002564 (void) SetImageChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002565 }
cristyed231572011-07-14 02:18:59 +00002566 if (((GetPixelBlackTraits(image) & UpdatePixelTrait) != 0) &&
cristy4c08aed2011-07-01 19:47:50 +00002567 (image->colorspace == CMYKColorspace))
cristyf89cb1d2011-07-07 01:24:37 +00002568 {
cristycf1296e2012-08-26 23:40:49 +00002569 channel_mask=SetImageChannelMask(image,BlackChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002570 status|=LevelizeImage(image,black_color->black,white_color->black,1.0,
2571 exception);
cristycf1296e2012-08-26 23:40:49 +00002572 (void) SetImageChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002573 }
cristyed231572011-07-14 02:18:59 +00002574 if (((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0) &&
cristy8a46d822012-08-28 23:32:39 +00002575 (image->alpha_trait == BlendPixelTrait))
cristyf89cb1d2011-07-07 01:24:37 +00002576 {
cristycf1296e2012-08-26 23:40:49 +00002577 channel_mask=SetImageChannelMask(image,AlphaChannel);
cristy7c0a0a42011-08-23 17:57:25 +00002578 status|=LevelizeImage(image,black_color->alpha,white_color->alpha,1.0,
2579 exception);
cristycf1296e2012-08-26 23:40:49 +00002580 (void) SetImageChannelMask(image,channel_mask);
cristyf89cb1d2011-07-07 01:24:37 +00002581 }
cristy3ed852e2009-09-05 21:47:34 +00002582 }
2583 return(status == 0 ? MagickFalse : MagickTrue);
2584}
2585
2586/*
2587%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2588% %
2589% %
2590% %
2591% L i n e a r S t r e t c h I m a g e %
2592% %
2593% %
2594% %
2595%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2596%
cristyf1611782011-08-01 15:39:13 +00002597% LinearStretchImage() discards any pixels below the black point and above
2598% the white point and levels the remaining pixels.
cristy3ed852e2009-09-05 21:47:34 +00002599%
2600% The format of the LinearStretchImage method is:
2601%
2602% MagickBooleanType LinearStretchImage(Image *image,
cristy33bd5152011-08-24 01:42:24 +00002603% const double black_point,const double white_point,
2604% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002605%
2606% A description of each parameter follows:
2607%
2608% o image: the image.
2609%
2610% o black_point: the black point.
2611%
2612% o white_point: the white point.
2613%
cristy33bd5152011-08-24 01:42:24 +00002614% o exception: return any errors or warnings in this structure.
2615%
cristy3ed852e2009-09-05 21:47:34 +00002616*/
2617MagickExport MagickBooleanType LinearStretchImage(Image *image,
cristy33bd5152011-08-24 01:42:24 +00002618 const double black_point,const double white_point,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002619{
2620#define LinearStretchImageTag "LinearStretch/Image"
2621
cristy33bd5152011-08-24 01:42:24 +00002622 CacheView
2623 *image_view;
cristy3ed852e2009-09-05 21:47:34 +00002624
cristy3ed852e2009-09-05 21:47:34 +00002625 MagickBooleanType
2626 status;
2627
cristya19f1d72012-08-07 18:24:38 +00002628 double
cristy3ed852e2009-09-05 21:47:34 +00002629 *histogram,
2630 intensity;
2631
cristy8d4629b2010-08-30 17:59:46 +00002632 ssize_t
2633 black,
2634 white,
2635 y;
2636
cristy3ed852e2009-09-05 21:47:34 +00002637 /*
2638 Allocate histogram and linear map.
2639 */
2640 assert(image != (Image *) NULL);
2641 assert(image->signature == MagickSignature);
cristya19f1d72012-08-07 18:24:38 +00002642 histogram=(double *) AcquireQuantumMemory(MaxMap+1UL,
cristy3ed852e2009-09-05 21:47:34 +00002643 sizeof(*histogram));
cristya19f1d72012-08-07 18:24:38 +00002644 if (histogram == (double *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002645 ThrowBinaryException(ResourceLimitError,"MemoryAllocationFailed",
2646 image->filename);
2647 /*
2648 Form histogram.
2649 */
2650 (void) ResetMagickMemory(histogram,0,(MaxMap+1)*sizeof(*histogram));
cristydb070952012-04-20 14:33:00 +00002651 image_view=AcquireVirtualCacheView(image,exception);
cristybb503372010-05-27 20:51:26 +00002652 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002653 {
cristy4c08aed2011-07-01 19:47:50 +00002654 register const Quantum
cristyc47d1f82009-11-26 01:44:43 +00002655 *restrict p;
cristy3ed852e2009-09-05 21:47:34 +00002656
cristybb503372010-05-27 20:51:26 +00002657 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002658 x;
2659
cristy33bd5152011-08-24 01:42:24 +00002660 p=GetCacheViewVirtualPixels(image_view,0,y,image->columns,1,exception);
cristy4c08aed2011-07-01 19:47:50 +00002661 if (p == (const Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002662 break;
cristy33bd5152011-08-24 01:42:24 +00002663 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002664 {
cristyf13c5942012-08-08 23:50:11 +00002665 double
2666 intensity;
2667
2668 intensity=GetPixelIntensity(image,p);
2669 histogram[ScaleQuantumToMap(ClampToQuantum(intensity))]++;
cristyed231572011-07-14 02:18:59 +00002670 p+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002671 }
2672 }
cristy33bd5152011-08-24 01:42:24 +00002673 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00002674 /*
2675 Find the histogram boundaries by locating the black and white point levels.
2676 */
cristy3ed852e2009-09-05 21:47:34 +00002677 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00002678 for (black=0; black < (ssize_t) MaxMap; black++)
cristy3ed852e2009-09-05 21:47:34 +00002679 {
2680 intensity+=histogram[black];
2681 if (intensity >= black_point)
2682 break;
2683 }
2684 intensity=0.0;
cristybb503372010-05-27 20:51:26 +00002685 for (white=(ssize_t) MaxMap; white != 0; white--)
cristy3ed852e2009-09-05 21:47:34 +00002686 {
2687 intensity+=histogram[white];
2688 if (intensity >= white_point)
2689 break;
2690 }
cristya19f1d72012-08-07 18:24:38 +00002691 histogram=(double *) RelinquishMagickMemory(histogram);
cristyc82a27b2011-10-21 01:07:16 +00002692 status=LevelImage(image,(double) black,(double) white,1.0,exception);
cristy3ed852e2009-09-05 21:47:34 +00002693 return(status);
2694}
2695
2696/*
2697%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2698% %
2699% %
2700% %
2701% M o d u l a t e I m a g e %
2702% %
2703% %
2704% %
2705%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2706%
2707% ModulateImage() lets you control the brightness, saturation, and hue
2708% of an image. Modulate represents the brightness, saturation, and hue
2709% as one parameter (e.g. 90,150,100). If the image colorspace is HSL, the
cristye83f3402012-08-17 13:12:15 +00002710% modulation is lightness, saturation, and hue. For HWB, use blackness,
2711% whiteness, and hue. And for HCL, use chrome, luma, and hue.
cristy3ed852e2009-09-05 21:47:34 +00002712%
2713% The format of the ModulateImage method is:
2714%
cristy33bd5152011-08-24 01:42:24 +00002715% MagickBooleanType ModulateImage(Image *image,const char *modulate,
2716% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002717%
2718% A description of each parameter follows:
2719%
2720% o image: the image.
2721%
cristy33bd5152011-08-24 01:42:24 +00002722% o modulate: Define the percent change in brightness, saturation, and hue.
2723%
2724% o exception: return any errors or warnings in this structure.
cristy3ed852e2009-09-05 21:47:34 +00002725%
2726*/
2727
cristye83f3402012-08-17 13:12:15 +00002728static inline void ModulateHCL(const double percent_hue,
2729 const double percent_chroma,const double percent_luma,double *red,
2730 double *green,double *blue)
2731{
2732 double
2733 hue,
2734 luma,
2735 chroma;
2736
2737 /*
2738 Increase or decrease color luma, chroma, or hue.
2739 */
2740 ConvertRGBToHCL(*red,*green,*blue,&hue,&chroma,&luma);
2741 hue+=0.5*(0.01*percent_hue-1.0);
2742 while (hue < 0.0)
2743 hue+=1.0;
2744 while (hue > 1.0)
2745 hue-=1.0;
2746 chroma*=0.01*percent_chroma;
2747 luma*=0.01*percent_luma;
2748 ConvertHCLToRGB(hue,chroma,luma,red,green,blue);
2749}
2750
2751static inline void ModulateHSB(const double percent_hue,
cristy3094b7f2011-10-01 23:18:02 +00002752 const double percent_saturation,const double percent_brightness,double *red,
2753 double *green,double *blue)
cristy3ed852e2009-09-05 21:47:34 +00002754{
2755 double
2756 brightness,
2757 hue,
2758 saturation;
2759
2760 /*
2761 Increase or decrease color brightness, saturation, or hue.
2762 */
cristy0a39a5c2012-06-27 12:51:45 +00002763 ConvertRGBToHSB(*red,*green,*blue,&hue,&saturation,&brightness);
cristy3ed852e2009-09-05 21:47:34 +00002764 hue+=0.5*(0.01*percent_hue-1.0);
2765 while (hue < 0.0)
2766 hue+=1.0;
2767 while (hue > 1.0)
2768 hue-=1.0;
2769 saturation*=0.01*percent_saturation;
2770 brightness*=0.01*percent_brightness;
cristy0a39a5c2012-06-27 12:51:45 +00002771 ConvertHSBToRGB(hue,saturation,brightness,red,green,blue);
cristy3ed852e2009-09-05 21:47:34 +00002772}
2773
cristye83f3402012-08-17 13:12:15 +00002774static inline void ModulateHSL(const double percent_hue,
cristy3094b7f2011-10-01 23:18:02 +00002775 const double percent_saturation,const double percent_lightness,double *red,
2776 double *green,double *blue)
cristy3ed852e2009-09-05 21:47:34 +00002777{
2778 double
2779 hue,
2780 lightness,
2781 saturation;
2782
2783 /*
2784 Increase or decrease color lightness, saturation, or hue.
2785 */
cristy0a39a5c2012-06-27 12:51:45 +00002786 ConvertRGBToHSL(*red,*green,*blue,&hue,&saturation,&lightness);
cristy3ed852e2009-09-05 21:47:34 +00002787 hue+=0.5*(0.01*percent_hue-1.0);
2788 while (hue < 0.0)
2789 hue+=1.0;
2790 while (hue > 1.0)
2791 hue-=1.0;
2792 saturation*=0.01*percent_saturation;
2793 lightness*=0.01*percent_lightness;
cristy0a39a5c2012-06-27 12:51:45 +00002794 ConvertHSLToRGB(hue,saturation,lightness,red,green,blue);
cristy3ed852e2009-09-05 21:47:34 +00002795}
2796
cristye83f3402012-08-17 13:12:15 +00002797static inline void ModulateHWB(const double percent_hue,
2798 const double percent_whiteness,const double percent_blackness,double *red,
2799 double *green,double *blue)
cristy3ed852e2009-09-05 21:47:34 +00002800{
2801 double
2802 blackness,
2803 hue,
2804 whiteness;
2805
2806 /*
2807 Increase or decrease color blackness, whiteness, or hue.
2808 */
cristy0a39a5c2012-06-27 12:51:45 +00002809 ConvertRGBToHWB(*red,*green,*blue,&hue,&whiteness,&blackness);
cristy3ed852e2009-09-05 21:47:34 +00002810 hue+=0.5*(0.01*percent_hue-1.0);
2811 while (hue < 0.0)
2812 hue+=1.0;
2813 while (hue > 1.0)
2814 hue-=1.0;
2815 blackness*=0.01*percent_blackness;
2816 whiteness*=0.01*percent_whiteness;
cristy0a39a5c2012-06-27 12:51:45 +00002817 ConvertHWBToRGB(hue,whiteness,blackness,red,green,blue);
cristy3ed852e2009-09-05 21:47:34 +00002818}
2819
cristy33bd5152011-08-24 01:42:24 +00002820MagickExport MagickBooleanType ModulateImage(Image *image,const char *modulate,
2821 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00002822{
2823#define ModulateImageTag "Modulate/Image"
2824
cristyc4c8d132010-01-07 01:58:38 +00002825 CacheView
2826 *image_view;
2827
cristy3ed852e2009-09-05 21:47:34 +00002828 ColorspaceType
2829 colorspace;
2830
2831 const char
2832 *artifact;
2833
2834 double
2835 percent_brightness,
2836 percent_hue,
cristy4d412212012-06-27 23:14:48 +00002837 percent_saturation;
cristy3ed852e2009-09-05 21:47:34 +00002838
cristy3ed852e2009-09-05 21:47:34 +00002839 GeometryInfo
2840 geometry_info;
2841
cristy3ed852e2009-09-05 21:47:34 +00002842 MagickBooleanType
2843 status;
2844
cristybb503372010-05-27 20:51:26 +00002845 MagickOffsetType
2846 progress;
2847
cristy3ed852e2009-09-05 21:47:34 +00002848 MagickStatusType
2849 flags;
2850
cristybb503372010-05-27 20:51:26 +00002851 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00002852 i;
2853
cristybb503372010-05-27 20:51:26 +00002854 ssize_t
2855 y;
2856
cristy3ed852e2009-09-05 21:47:34 +00002857 /*
cristy2b726bd2010-01-11 01:05:39 +00002858 Initialize modulate table.
cristy3ed852e2009-09-05 21:47:34 +00002859 */
2860 assert(image != (Image *) NULL);
2861 assert(image->signature == MagickSignature);
2862 if (image->debug != MagickFalse)
2863 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
2864 if (modulate == (char *) NULL)
2865 return(MagickFalse);
cristy0a39a5c2012-06-27 12:51:45 +00002866 if (IssRGBCompatibleColorspace(image->colorspace) == MagickFalse)
cristybc7fdcd2012-06-27 01:43:10 +00002867 (void) TransformImageColorspace(image,sRGBColorspace,exception);
cristy3ed852e2009-09-05 21:47:34 +00002868 flags=ParseGeometry(modulate,&geometry_info);
2869 percent_brightness=geometry_info.rho;
2870 percent_saturation=geometry_info.sigma;
2871 if ((flags & SigmaValue) == 0)
2872 percent_saturation=100.0;
2873 percent_hue=geometry_info.xi;
2874 if ((flags & XiValue) == 0)
2875 percent_hue=100.0;
2876 colorspace=UndefinedColorspace;
2877 artifact=GetImageArtifact(image,"modulate:colorspace");
2878 if (artifact != (const char *) NULL)
cristy042ee782011-04-22 18:48:30 +00002879 colorspace=(ColorspaceType) ParseCommandOption(MagickColorspaceOptions,
cristy3ed852e2009-09-05 21:47:34 +00002880 MagickFalse,artifact);
2881 if (image->storage_class == PseudoClass)
nicolase0aa3c32012-08-08 14:57:02 +00002882 for (i=0; i < (ssize_t) image->colors; i++)
nicolasfd74ae32012-08-08 16:00:11 +00002883 {
2884 double
2885 blue,
2886 green,
2887 red;
cristy4d412212012-06-27 23:14:48 +00002888
nicolasfd74ae32012-08-08 16:00:11 +00002889 /*
cristye83f3402012-08-17 13:12:15 +00002890 Modulate colormap.
nicolasfd74ae32012-08-08 16:00:11 +00002891 */
2892 red=image->colormap[i].red;
2893 green=image->colormap[i].green;
2894 blue=image->colormap[i].blue;
2895 switch (colorspace)
2896 {
cristye83f3402012-08-17 13:12:15 +00002897 case HCLColorspace:
2898 {
2899 ModulateHCL(percent_hue,percent_saturation,percent_brightness,
2900 &red,&green,&blue);
2901 break;
2902 }
nicolasfd74ae32012-08-08 16:00:11 +00002903 case HSBColorspace:
cristy3ed852e2009-09-05 21:47:34 +00002904 {
nicolasfd74ae32012-08-08 16:00:11 +00002905 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
2906 &red,&green,&blue);
2907 break;
2908 }
2909 case HSLColorspace:
2910 default:
2911 {
2912 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
2913 &red,&green,&blue);
2914 break;
2915 }
2916 case HWBColorspace:
2917 {
2918 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
2919 &red,&green,&blue);
2920 break;
cristy3ed852e2009-09-05 21:47:34 +00002921 }
cristy0a39a5c2012-06-27 12:51:45 +00002922 }
nicolasfd74ae32012-08-08 16:00:11 +00002923 }
cristy3ed852e2009-09-05 21:47:34 +00002924 /*
2925 Modulate image.
2926 */
2927 status=MagickTrue;
2928 progress=0;
cristydb070952012-04-20 14:33:00 +00002929 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +00002930#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00002931 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy4ee2b0c2012-05-15 00:30:35 +00002932 dynamic_number_threads(image,image->columns,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00002933#endif
cristybb503372010-05-27 20:51:26 +00002934 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00002935 {
cristy4c08aed2011-07-01 19:47:50 +00002936 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00002937 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00002938
cristy8d4629b2010-08-30 17:59:46 +00002939 register ssize_t
2940 x;
2941
cristy3ed852e2009-09-05 21:47:34 +00002942 if (status == MagickFalse)
2943 continue;
2944 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00002945 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00002946 {
2947 status=MagickFalse;
2948 continue;
2949 }
cristybb503372010-05-27 20:51:26 +00002950 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00002951 {
cristy4d412212012-06-27 23:14:48 +00002952 double
2953 blue,
2954 green,
2955 red;
2956
cristyda1f9c12011-10-02 21:39:49 +00002957 red=(double) GetPixelRed(image,q);
2958 green=(double) GetPixelGreen(image,q);
2959 blue=(double) GetPixelBlue(image,q);
cristy3ed852e2009-09-05 21:47:34 +00002960 switch (colorspace)
2961 {
cristyab3c3272012-08-17 17:21:33 +00002962 case HCLColorspace:
2963 {
2964 ModulateHCL(percent_hue,percent_saturation,percent_brightness,
2965 &red,&green,&blue);
2966 break;
2967 }
cristy3ed852e2009-09-05 21:47:34 +00002968 case HSBColorspace:
2969 {
2970 ModulateHSB(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00002971 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00002972 break;
2973 }
2974 case HSLColorspace:
2975 default:
2976 {
2977 ModulateHSL(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00002978 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00002979 break;
2980 }
2981 case HWBColorspace:
2982 {
2983 ModulateHWB(percent_hue,percent_saturation,percent_brightness,
cristy5afeab82011-04-30 01:30:09 +00002984 &red,&green,&blue);
cristy3ed852e2009-09-05 21:47:34 +00002985 break;
2986 }
2987 }
cristy3094b7f2011-10-01 23:18:02 +00002988 SetPixelRed(image,ClampToQuantum(red),q);
2989 SetPixelGreen(image,ClampToQuantum(green),q);
2990 SetPixelBlue(image,ClampToQuantum(blue),q);
cristyed231572011-07-14 02:18:59 +00002991 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00002992 }
2993 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
2994 status=MagickFalse;
2995 if (image->progress_monitor != (MagickProgressMonitor) NULL)
2996 {
2997 MagickBooleanType
2998 proceed;
2999
cristyb5d5f722009-11-04 03:03:49 +00003000#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00003001 #pragma omp critical (MagickCore_ModulateImage)
cristy3ed852e2009-09-05 21:47:34 +00003002#endif
3003 proceed=SetImageProgress(image,ModulateImageTag,progress++,image->rows);
3004 if (proceed == MagickFalse)
3005 status=MagickFalse;
3006 }
3007 }
3008 image_view=DestroyCacheView(image_view);
3009 return(status);
3010}
3011
3012/*
3013%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3014% %
3015% %
3016% %
3017% N e g a t e I m a g e %
3018% %
3019% %
3020% %
3021%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3022%
3023% NegateImage() negates the colors in the reference image. The grayscale
3024% option means that only grayscale values within the image are negated.
3025%
cristy50fbc382011-07-07 02:19:17 +00003026% The format of the NegateImage method is:
cristy3ed852e2009-09-05 21:47:34 +00003027%
3028% MagickBooleanType NegateImage(Image *image,
cristyb3e7c6c2011-07-24 01:43:55 +00003029% const MagickBooleanType grayscale,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003030%
3031% A description of each parameter follows:
3032%
3033% o image: the image.
3034%
cristy3ed852e2009-09-05 21:47:34 +00003035% o grayscale: If MagickTrue, only negate grayscale pixels within the image.
3036%
cristyb3e7c6c2011-07-24 01:43:55 +00003037% o exception: return any errors or warnings in this structure.
3038%
cristy3ed852e2009-09-05 21:47:34 +00003039*/
cristy3ed852e2009-09-05 21:47:34 +00003040MagickExport MagickBooleanType NegateImage(Image *image,
cristyb3e7c6c2011-07-24 01:43:55 +00003041 const MagickBooleanType grayscale,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003042{
cristy3ed852e2009-09-05 21:47:34 +00003043#define NegateImageTag "Negate/Image"
3044
cristyc4c8d132010-01-07 01:58:38 +00003045 CacheView
3046 *image_view;
3047
cristy3ed852e2009-09-05 21:47:34 +00003048 MagickBooleanType
3049 status;
3050
cristybb503372010-05-27 20:51:26 +00003051 MagickOffsetType
3052 progress;
3053
3054 register ssize_t
cristy3ed852e2009-09-05 21:47:34 +00003055 i;
3056
cristybb503372010-05-27 20:51:26 +00003057 ssize_t
3058 y;
3059
cristy3ed852e2009-09-05 21:47:34 +00003060 assert(image != (Image *) NULL);
3061 assert(image->signature == MagickSignature);
3062 if (image->debug != MagickFalse)
3063 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
3064 if (image->storage_class == PseudoClass)
nicolase0aa3c32012-08-08 14:57:02 +00003065 for (i=0; i < (ssize_t) image->colors; i++)
nicolasfd74ae32012-08-08 16:00:11 +00003066 {
3067 /*
cristyaeded782012-09-11 23:39:36 +00003068 Negate colormap.
nicolasfd74ae32012-08-08 16:00:11 +00003069 */
3070 if (grayscale != MagickFalse)
cristyaeded782012-09-11 23:39:36 +00003071 if ((image->colormap[i].red != image->colormap[i].green) ||
3072 (image->colormap[i].green != image->colormap[i].blue))
3073 continue;
nicolasfd74ae32012-08-08 16:00:11 +00003074 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyaeded782012-09-11 23:39:36 +00003075 image->colormap[i].red=QuantumRange-image->colormap[i].red;
nicolasfd74ae32012-08-08 16:00:11 +00003076 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
cristyaeded782012-09-11 23:39:36 +00003077 image->colormap[i].green=QuantumRange-image->colormap[i].green;
nicolasfd74ae32012-08-08 16:00:11 +00003078 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
cristyaeded782012-09-11 23:39:36 +00003079 image->colormap[i].blue=QuantumRange-image->colormap[i].blue;
nicolasfd74ae32012-08-08 16:00:11 +00003080 }
cristy3ed852e2009-09-05 21:47:34 +00003081 /*
3082 Negate image.
3083 */
3084 status=MagickTrue;
3085 progress=0;
cristydb070952012-04-20 14:33:00 +00003086 image_view=AcquireAuthenticCacheView(image,exception);
cristy3ed852e2009-09-05 21:47:34 +00003087 if (grayscale != MagickFalse)
3088 {
cristybb503372010-05-27 20:51:26 +00003089 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003090 {
3091 MagickBooleanType
3092 sync;
3093
cristy4c08aed2011-07-01 19:47:50 +00003094 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003095 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003096
cristy8d4629b2010-08-30 17:59:46 +00003097 register ssize_t
3098 x;
3099
cristy3ed852e2009-09-05 21:47:34 +00003100 if (status == MagickFalse)
3101 continue;
3102 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,
3103 exception);
cristyacd2ed22011-08-30 01:44:23 +00003104 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003105 {
3106 status=MagickFalse;
3107 continue;
3108 }
cristybb503372010-05-27 20:51:26 +00003109 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003110 {
cristy9aa95be2011-07-20 21:56:45 +00003111 register ssize_t
3112 i;
3113
cristy10a6c612012-01-29 21:41:05 +00003114 if ((GetPixelMask(image,q) != 0) ||
3115 (IsPixelGray(image,q) != MagickFalse))
cristy3ed852e2009-09-05 21:47:34 +00003116 {
cristya30d9ba2011-07-23 21:00:48 +00003117 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003118 continue;
3119 }
cristya30d9ba2011-07-23 21:00:48 +00003120 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristy9aa95be2011-07-20 21:56:45 +00003121 {
cristyabace412011-12-11 15:56:53 +00003122 PixelChannel
3123 channel;
3124
cristy1aaa3cd2011-08-21 23:48:17 +00003125 PixelTrait
cristy9aa95be2011-07-20 21:56:45 +00003126 traits;
3127
cristycf1296e2012-08-26 23:40:49 +00003128 channel=GetPixelChannelChannel(image,i);
3129 traits=GetPixelChannelTraits(image,channel);
cristyd09f8802012-02-04 16:44:10 +00003130 if ((traits & UpdatePixelTrait) == 0)
3131 continue;
3132 q[i]=QuantumRange-q[i];
cristy9aa95be2011-07-20 21:56:45 +00003133 }
cristya30d9ba2011-07-23 21:00:48 +00003134 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003135 }
3136 sync=SyncCacheViewAuthenticPixels(image_view,exception);
3137 if (sync == MagickFalse)
3138 status=MagickFalse;
3139 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3140 {
3141 MagickBooleanType
3142 proceed;
3143
cristyb5d5f722009-11-04 03:03:49 +00003144#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00003145 #pragma omp critical (MagickCore_NegateImage)
cristy3ed852e2009-09-05 21:47:34 +00003146#endif
3147 proceed=SetImageProgress(image,NegateImageTag,progress++,
3148 image->rows);
3149 if (proceed == MagickFalse)
3150 status=MagickFalse;
3151 }
3152 }
3153 image_view=DestroyCacheView(image_view);
3154 return(MagickTrue);
3155 }
3156 /*
3157 Negate image.
3158 */
cristyb5d5f722009-11-04 03:03:49 +00003159#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy9a5a52f2012-10-09 14:40:31 +00003160 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy4ee2b0c2012-05-15 00:30:35 +00003161 dynamic_number_threads(image,image->columns,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00003162#endif
cristybb503372010-05-27 20:51:26 +00003163 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003164 {
cristy4c08aed2011-07-01 19:47:50 +00003165 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003166 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003167
cristy8d4629b2010-08-30 17:59:46 +00003168 register ssize_t
3169 x;
3170
cristy3ed852e2009-09-05 21:47:34 +00003171 if (status == MagickFalse)
3172 continue;
3173 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00003174 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003175 {
3176 status=MagickFalse;
3177 continue;
3178 }
cristybb503372010-05-27 20:51:26 +00003179 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003180 {
cristyf7dc44c2011-07-20 14:41:15 +00003181 register ssize_t
3182 i;
3183
cristyd09f8802012-02-04 16:44:10 +00003184 if (GetPixelMask(image,q) != 0)
3185 {
3186 q+=GetPixelChannels(image);
3187 continue;
3188 }
cristya30d9ba2011-07-23 21:00:48 +00003189 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
cristyf7dc44c2011-07-20 14:41:15 +00003190 {
cristyabace412011-12-11 15:56:53 +00003191 PixelChannel
3192 channel;
3193
cristy1aaa3cd2011-08-21 23:48:17 +00003194 PixelTrait
cristyf7dc44c2011-07-20 14:41:15 +00003195 traits;
3196
cristycf1296e2012-08-26 23:40:49 +00003197 channel=GetPixelChannelChannel(image,i);
3198 traits=GetPixelChannelTraits(image,channel);
cristyd09f8802012-02-04 16:44:10 +00003199 if ((traits & UpdatePixelTrait) == 0)
cristyec9e3a62012-02-01 02:09:32 +00003200 continue;
3201 q[i]=QuantumRange-q[i];
cristyf7dc44c2011-07-20 14:41:15 +00003202 }
cristya30d9ba2011-07-23 21:00:48 +00003203 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003204 }
3205 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3206 status=MagickFalse;
3207 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3208 {
3209 MagickBooleanType
3210 proceed;
3211
cristyb5d5f722009-11-04 03:03:49 +00003212#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00003213 #pragma omp critical (MagickCore_NegateImage)
cristy3ed852e2009-09-05 21:47:34 +00003214#endif
3215 proceed=SetImageProgress(image,NegateImageTag,progress++,image->rows);
3216 if (proceed == MagickFalse)
3217 status=MagickFalse;
3218 }
3219 }
3220 image_view=DestroyCacheView(image_view);
3221 return(status);
3222}
3223
3224/*
3225%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3226% %
3227% %
3228% %
3229% N o r m a l i z e I m a g e %
3230% %
3231% %
3232% %
3233%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3234%
anthony207ce472012-04-04 06:21:26 +00003235% The NormalizeImage() method enhances the contrast of a color image by
3236% mapping the darkest 2 percent of all pixel to black and the brightest
3237% 1 percent to white.
cristy3ed852e2009-09-05 21:47:34 +00003238%
3239% The format of the NormalizeImage method is:
3240%
cristye23ec9d2011-08-16 18:15:40 +00003241% MagickBooleanType NormalizeImage(Image *image,ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003242%
3243% A description of each parameter follows:
3244%
3245% o image: the image.
3246%
cristye23ec9d2011-08-16 18:15:40 +00003247% o exception: return any errors or warnings in this structure.
3248%
cristy3ed852e2009-09-05 21:47:34 +00003249*/
cristye23ec9d2011-08-16 18:15:40 +00003250MagickExport MagickBooleanType NormalizeImage(Image *image,
3251 ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003252{
cristy3ed852e2009-09-05 21:47:34 +00003253 double
3254 black_point,
3255 white_point;
3256
cristy530239c2010-07-25 17:34:26 +00003257 black_point=(double) image->columns*image->rows*0.0015;
3258 white_point=(double) image->columns*image->rows*0.9995;
cristye23ec9d2011-08-16 18:15:40 +00003259 return(ContrastStretchImage(image,black_point,white_point,exception));
cristy3ed852e2009-09-05 21:47:34 +00003260}
3261
3262/*
3263%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3264% %
3265% %
3266% %
3267% S i g m o i d a l C o n t r a s t I m a g e %
3268% %
3269% %
3270% %
3271%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3272%
3273% SigmoidalContrastImage() adjusts the contrast of an image with a non-linear
3274% sigmoidal contrast algorithm. Increase the contrast of the image using a
3275% sigmoidal transfer function without saturating highlights or shadows.
3276% Contrast indicates how much to increase the contrast (0 is none; 3 is
anthony207ce472012-04-04 06:21:26 +00003277% typical; 20 is pushing it); mid-point indicates where midtones fall in the
3278% resultant image (0 is white; 50% is middle-gray; 100% is black). Set
3279% sharpen to MagickTrue to increase the image contrast otherwise the contrast
3280% is reduced.
cristy3ed852e2009-09-05 21:47:34 +00003281%
3282% The format of the SigmoidalContrastImage method is:
3283%
3284% MagickBooleanType SigmoidalContrastImage(Image *image,
cristy33bd5152011-08-24 01:42:24 +00003285% const MagickBooleanType sharpen,const char *levels,
3286% ExceptionInfo *exception)
cristy3ed852e2009-09-05 21:47:34 +00003287%
3288% A description of each parameter follows:
3289%
3290% o image: the image.
3291%
cristy3ed852e2009-09-05 21:47:34 +00003292% o sharpen: Increase or decrease image contrast.
3293%
nicolas07299f12012-09-10 17:30:07 +00003294% o contrast: strength of the contrast, the larger the number the more
cristyfa769582010-09-30 23:30:03 +00003295% 'threshold-like' it becomes.
cristy3ed852e2009-09-05 21:47:34 +00003296%
nicolas1de98ba2012-09-10 17:34:35 +00003297% o midpoint: midpoint of the function as a color value 0 to QuantumRange.
cristy3ed852e2009-09-05 21:47:34 +00003298%
cristy33bd5152011-08-24 01:42:24 +00003299% o exception: return any errors or warnings in this structure.
3300%
cristy3ed852e2009-09-05 21:47:34 +00003301*/
nicolas678607b2012-09-10 20:05:40 +00003302
nicolas6457d9f2012-09-12 14:40:01 +00003303/*
nicolasef20d0e2012-09-18 16:05:53 +00003304 ImageMagick 6 has a version of this function which uses LUTs.
3305*/
3306
3307/*
nicolas4d6c5342012-09-12 14:59:55 +00003308 Sigmoidal function Sigmoidal with inflexion point moved to b and "slope
3309 constant" set to a.
3310
nicolas6457d9f2012-09-12 14:40:01 +00003311 The first version, based on the hyperbolic tangent tanh, when combined with
3312 the scaling step, is an exact arithmetic clone of the the sigmoid function
3313 based on the logistic curve. The equivalence is based on the identity
3314
nicolas4d6c5342012-09-12 14:59:55 +00003315 1/(1+exp(-t)) = (1+tanh(t/2))/2
nicolas6457d9f2012-09-12 14:40:01 +00003316
nicolas4d6c5342012-09-12 14:59:55 +00003317 (http://de.wikipedia.org/wiki/Sigmoidfunktion) and the fact that the
3318 scaled sigmoidal derivation is invariant under affine transformations of
3319 the ordinate.
3320
3321 The tanh version is almost certainly more accurate and cheaper. The 0.5
3322 factor in the argument is to clone the legacy ImageMagick behavior. The
3323 reason for making the define depend on atanh even though it only uses tanh
3324 has to do with the construction of the inverse of the scaled sigmoidal.
nicolas6457d9f2012-09-12 14:40:01 +00003325*/
nicolase3466022012-09-04 13:51:46 +00003326#if defined(MAGICKCORE_HAVE_ATANH)
nicolas07299f12012-09-10 17:30:07 +00003327#define Sigmoidal(a,b,x) ( tanh((0.5*(a))*((x)-(b))) )
nicolase3466022012-09-04 13:51:46 +00003328#else
nicolas07299f12012-09-10 17:30:07 +00003329#define Sigmoidal(a,b,x) ( 1.0/(1.0+exp((a)*((b)-(x)))) )
nicolase3466022012-09-04 13:51:46 +00003330#endif
cristy5eab5352012-09-12 13:12:43 +00003331/*
nicolas6457d9f2012-09-12 14:40:01 +00003332 Scaled sigmoidal function:
cristy5eab5352012-09-12 13:12:43 +00003333
nicolas07299f12012-09-10 17:30:07 +00003334 ( Sigmoidal(a,b,x) - Sigmoidal(a,b,0) ) /
3335 ( Sigmoidal(a,b,1) - Sigmoidal(a,b,0) )
cristy5eab5352012-09-12 13:12:43 +00003336
3337 See http://osdir.com/ml/video.image-magick.devel/2005-04/msg00006.html and
3338 http://www.cs.dartmouth.edu/farid/downloads/tutorials/fip.pdf. The limit
3339 of ScaledSigmoidal as a->0 is the identity, but a=0 gives a division by
nicolas1f1584e2012-09-12 15:20:56 +00003340 zero. This is fixed below by exiting immediately when contrast is small,
cristy5eab5352012-09-12 13:12:43 +00003341 leaving the image (or colormap) unmodified. This appears to be safe because
3342 the series expansion of the logistic sigmoidal function around x=b is
nicolas6457d9f2012-09-12 14:40:01 +00003343
3344 1/2-a*(b-x)/4+...
3345
3346 so that the key denominator s(1)-s(0) is about a/4 (a/2 with tanh).
cristy5eab5352012-09-12 13:12:43 +00003347*/
nicolas07299f12012-09-10 17:30:07 +00003348#define ScaledSigmoidal(a,b,x) ( \
nicolasf0158cf2012-09-10 20:11:02 +00003349 (Sigmoidal((a),(b),(x))-Sigmoidal((a),(b),0.0)) / \
nicolas07299f12012-09-10 17:30:07 +00003350 (Sigmoidal((a),(b),1.0)-Sigmoidal((a),(b),0.0)) )
cristy5eab5352012-09-12 13:12:43 +00003351/*
nicolas6457d9f2012-09-12 14:40:01 +00003352 Inverse of ScaledSigmoidal, used for +sigmoidal-contrast. Because b
3353 may be 0 or 1, the argument of the hyperbolic tangent (resp. logistic
3354 sigmoidal) may be outside of the interval (-1,1) (resp. (0,1)), even
3355 when creating a LUT from in gamut values, hence the branching. In
3356 addition, HDRI may have out of gamut values.
nicolas7922b462012-09-12 14:50:49 +00003357 InverseScaledSigmoidal is not a two-sided inverse of ScaledSigmoidal:
cristy5eab5352012-09-12 13:12:43 +00003358 It is only a right inverse. This is unavoidable.
3359*/
cristy5eab5352012-09-12 13:12:43 +00003360static inline double InverseScaledSigmoidal(const double a,const double b,
3361 const double x)
3362{
nicolas6457d9f2012-09-12 14:40:01 +00003363 const double sig0=Sigmoidal(a,b,0.0);
nicolasb45b69a2012-09-15 20:00:31 +00003364 const double sig1=Sigmoidal(a,b,1.0);
3365 const double argument=(sig1-sig0)*x+sig0;
nicolas6457d9f2012-09-12 14:40:01 +00003366 const double clamped=
3367 (
nicolas196d63c2012-09-12 14:46:01 +00003368#if defined(MAGICKCORE_HAVE_ATANH)
nicolas6457d9f2012-09-12 14:40:01 +00003369 argument < -1+MagickEpsilon
3370 ?
3371 -1+MagickEpsilon
3372 :
3373 ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
3374 );
3375 return(b+(2.0/a)*atanh(clamped));
nicolase3466022012-09-04 13:51:46 +00003376#else
nicolas6457d9f2012-09-12 14:40:01 +00003377 argument < MagickEpsilon
3378 ?
3379 MagickEpsilon
3380 :
3381 ( argument > 1-MagickEpsilon ? 1-MagickEpsilon : argument )
3382 );
nicolas02c9a682012-09-15 20:01:56 +00003383 return(b-log(1.0/clamped-1.0)/a);
nicolase3466022012-09-04 13:51:46 +00003384#endif
nicolas196d63c2012-09-12 14:46:01 +00003385}
cristy5eab5352012-09-12 13:12:43 +00003386
3387MagickExport MagickBooleanType SigmoidalContrastImage(Image *image,
3388 const MagickBooleanType sharpen,const double contrast,const double midpoint,
3389 ExceptionInfo *exception)
3390{
3391#define SigmoidalContrastImageTag "SigmoidalContrast/Image"
nicolasf94a4f42012-09-11 09:18:55 +00003392#define ScaledSig(x) ( ClampToQuantum(QuantumRange* \
cristyaeded782012-09-11 23:39:36 +00003393 ScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale*(x))) )
nicolasf94a4f42012-09-11 09:18:55 +00003394#define InverseScaledSig(x) ( ClampToQuantum(QuantumRange* \
cristyaeded782012-09-11 23:39:36 +00003395 InverseScaledSigmoidal(contrast,QuantumScale*midpoint,QuantumScale*(x))) )
nicolas07299f12012-09-10 17:30:07 +00003396
cristy5eab5352012-09-12 13:12:43 +00003397 CacheView
3398 *image_view;
3399
3400 MagickBooleanType
3401 status;
3402
3403 MagickOffsetType
3404 progress;
3405
3406 ssize_t
3407 y;
3408
3409 /*
3410 Convenience macros.
3411 */
nicolas33c76712012-08-08 17:15:05 +00003412 assert(image != (Image *) NULL);
3413 assert(image->signature == MagickSignature);
3414 if (image->debug != MagickFalse)
3415 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
nicolas07299f12012-09-10 17:30:07 +00003416 /*
nicolas6457d9f2012-09-12 14:40:01 +00003417 Side effect: may clamp values unless contrast<MagickEpsilon, in which
cristy5eab5352012-09-12 13:12:43 +00003418 case nothing is done.
3419 */
3420 if (contrast < MagickEpsilon)
3421 return(MagickTrue);
3422 /*
nicolas4ff353c2012-09-10 20:15:40 +00003423 Sigmoidal-contrast enhance colormap.
nicolas07299f12012-09-10 17:30:07 +00003424 */
cristy3ed852e2009-09-05 21:47:34 +00003425 if (image->storage_class == PseudoClass)
nicolasfd74ae32012-08-08 16:00:11 +00003426 {
nicolasb06434e2012-09-10 18:06:15 +00003427 register ssize_t
cristyaeded782012-09-11 23:39:36 +00003428 i;
nicolas07299f12012-09-10 17:30:07 +00003429
nicolasb06434e2012-09-10 18:06:15 +00003430 if (sharpen != MagickFalse)
nicolas80ad4a62012-09-11 15:17:16 +00003431 for (i=0; i < (ssize_t) image->colors; i++)
3432 {
3433 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
cristyaeded782012-09-11 23:39:36 +00003434 image->colormap[i].red=ScaledSig(image->colormap[i].red);
3435 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
3436 image->colormap[i].green=ScaledSig(image->colormap[i].green);
3437 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3438 image->colormap[i].blue=ScaledSig(image->colormap[i].blue);
3439 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
3440 image->colormap[i].alpha=ScaledSig(image->colormap[i].alpha);
3441 }
nicolasb06434e2012-09-10 18:06:15 +00003442 else
nicolas80ad4a62012-09-11 15:17:16 +00003443 for (i=0; i < (ssize_t) image->colors; i++)
cristyaeded782012-09-11 23:39:36 +00003444 {
3445 if ((GetPixelRedTraits(image) & UpdatePixelTrait) != 0)
3446 image->colormap[i].red=InverseScaledSig(image->colormap[i].red);
3447 if ((GetPixelGreenTraits(image) & UpdatePixelTrait) != 0)
nicolas80ad4a62012-09-11 15:17:16 +00003448 image->colormap[i].green=InverseScaledSig(image->colormap[i].green);
cristyaeded782012-09-11 23:39:36 +00003449 if ((GetPixelBlueTraits(image) & UpdatePixelTrait) != 0)
3450 image->colormap[i].blue=InverseScaledSig(image->colormap[i].blue);
nicolas80ad4a62012-09-11 15:17:16 +00003451 if ((GetPixelAlphaTraits(image) & UpdatePixelTrait) != 0)
cristyaeded782012-09-11 23:39:36 +00003452 image->colormap[i].alpha=InverseScaledSig(image->colormap[i].alpha);
3453 }
nicolasfd74ae32012-08-08 16:00:11 +00003454 }
cristy3ed852e2009-09-05 21:47:34 +00003455 /*
nicolas4ff353c2012-09-10 20:15:40 +00003456 Sigmoidal-contrast enhance image.
cristy3ed852e2009-09-05 21:47:34 +00003457 */
3458 status=MagickTrue;
3459 progress=0;
cristydb070952012-04-20 14:33:00 +00003460 image_view=AcquireAuthenticCacheView(image,exception);
cristyb5d5f722009-11-04 03:03:49 +00003461#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristyac245f82012-05-05 17:13:57 +00003462 #pragma omp parallel for schedule(static,4) shared(progress,status) \
cristy4ee2b0c2012-05-15 00:30:35 +00003463 dynamic_number_threads(image,image->columns,image->rows,1)
cristy3ed852e2009-09-05 21:47:34 +00003464#endif
cristybb503372010-05-27 20:51:26 +00003465 for (y=0; y < (ssize_t) image->rows; y++)
cristy3ed852e2009-09-05 21:47:34 +00003466 {
cristy4c08aed2011-07-01 19:47:50 +00003467 register Quantum
cristyc47d1f82009-11-26 01:44:43 +00003468 *restrict q;
cristy3ed852e2009-09-05 21:47:34 +00003469
cristy8d4629b2010-08-30 17:59:46 +00003470 register ssize_t
3471 x;
3472
cristy3ed852e2009-09-05 21:47:34 +00003473 if (status == MagickFalse)
3474 continue;
3475 q=GetCacheViewAuthenticPixels(image_view,0,y,image->columns,1,exception);
cristyacd2ed22011-08-30 01:44:23 +00003476 if (q == (Quantum *) NULL)
cristy3ed852e2009-09-05 21:47:34 +00003477 {
3478 status=MagickFalse;
3479 continue;
3480 }
cristybb503372010-05-27 20:51:26 +00003481 for (x=0; x < (ssize_t) image->columns; x++)
cristy3ed852e2009-09-05 21:47:34 +00003482 {
cristy33bd5152011-08-24 01:42:24 +00003483 register ssize_t
cristyaeded782012-09-11 23:39:36 +00003484 i;
cristy33bd5152011-08-24 01:42:24 +00003485
cristy10a6c612012-01-29 21:41:05 +00003486 if (GetPixelMask(image,q) != 0)
3487 {
3488 q+=GetPixelChannels(image);
3489 continue;
3490 }
cristy33bd5152011-08-24 01:42:24 +00003491 for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
3492 {
cristyabace412011-12-11 15:56:53 +00003493 PixelChannel
3494 channel;
3495
cristy33bd5152011-08-24 01:42:24 +00003496 PixelTrait
3497 traits;
3498
cristycf1296e2012-08-26 23:40:49 +00003499 channel=GetPixelChannelChannel(image,i);
3500 traits=GetPixelChannelTraits(image,channel);
cristyd09f8802012-02-04 16:44:10 +00003501 if ((traits & UpdatePixelTrait) == 0)
3502 continue;
cristyaeded782012-09-11 23:39:36 +00003503 if (sharpen != MagickFalse)
3504 q[i]=ScaledSig(q[i]);
3505 else
3506 q[i]=InverseScaledSig(q[i]);
cristy33bd5152011-08-24 01:42:24 +00003507 }
cristyed231572011-07-14 02:18:59 +00003508 q+=GetPixelChannels(image);
cristy3ed852e2009-09-05 21:47:34 +00003509 }
3510 if (SyncCacheViewAuthenticPixels(image_view,exception) == MagickFalse)
3511 status=MagickFalse;
3512 if (image->progress_monitor != (MagickProgressMonitor) NULL)
3513 {
3514 MagickBooleanType
3515 proceed;
3516
cristyb5d5f722009-11-04 03:03:49 +00003517#if defined(MAGICKCORE_OPENMP_SUPPORT)
cristy564a5692012-01-20 23:56:26 +00003518 #pragma omp critical (MagickCore_SigmoidalContrastImage)
cristy3ed852e2009-09-05 21:47:34 +00003519#endif
3520 proceed=SetImageProgress(image,SigmoidalContrastImageTag,progress++,
3521 image->rows);
3522 if (proceed == MagickFalse)
3523 status=MagickFalse;
3524 }
3525 }
3526 image_view=DestroyCacheView(image_view);
cristy3ed852e2009-09-05 21:47:34 +00003527 return(status);
3528}